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,109 @@
|
||||
add_vanetza_component(security
|
||||
backend.cpp
|
||||
backend_null.cpp
|
||||
cam_ssp.cpp
|
||||
decap_service.cpp
|
||||
delegating_security_entity.cpp
|
||||
ecc_point.cpp
|
||||
ecdsa256.cpp
|
||||
encap_service.cpp
|
||||
hashed_id.cpp
|
||||
hmac.cpp
|
||||
key_type.cpp
|
||||
public_key.cpp
|
||||
persistence.cpp
|
||||
peer_request_tracker.cpp
|
||||
secured_message.cpp
|
||||
sha.cpp
|
||||
signature.cpp
|
||||
straight_verify_service.cpp
|
||||
verify_service.cpp
|
||||
v2/basic_elements.cpp
|
||||
v2/certificate.cpp
|
||||
v2/certificate_cache.cpp
|
||||
v2/default_certificate_validator.cpp
|
||||
v2/ecc_point.cpp
|
||||
v2/encryption_parameter.cpp
|
||||
v2/header_field.cpp
|
||||
v2/int_x.cpp
|
||||
v2/length_coding.cpp
|
||||
v2/naive_certificate_provider.cpp
|
||||
v2/null_certificate_provider.cpp
|
||||
v2/null_certificate_validator.cpp
|
||||
v2/payload.cpp
|
||||
v2/persistence.cpp
|
||||
v2/public_key.cpp
|
||||
v2/recipient_info.cpp
|
||||
v2/region.cpp
|
||||
v2/secured_message.cpp
|
||||
v2/serialization.cpp
|
||||
v2/sign_header_policy.cpp
|
||||
v2/signature.cpp
|
||||
v2/signer_info.cpp
|
||||
v2/sign_service.cpp
|
||||
v2/static_certificate_provider.cpp
|
||||
v2/subject_attribute.cpp
|
||||
v2/subject_info.cpp
|
||||
v2/trailer_field.cpp
|
||||
v2/trust_store.cpp
|
||||
v2/validity_restriction.cpp
|
||||
v2/verification.cpp
|
||||
v3/asn1_conversions.cpp
|
||||
v3/basic_elements.cpp
|
||||
v3/boost_geometry.cpp
|
||||
v3/certificate.cpp
|
||||
v3/certificate_cache.cpp
|
||||
v3/certificate_validator.cpp
|
||||
v3/distance.cpp
|
||||
v3/geometry.cpp
|
||||
v3/hash.cpp
|
||||
v3/issuer_memory_lookup.cpp
|
||||
v3/location_checker.cpp
|
||||
v3/secured_message.cpp
|
||||
v3/sign_service.cpp
|
||||
v3/naive_certificate_provider.cpp
|
||||
v3/sign_header_policy.cpp
|
||||
v3/static_certificate_provider.cpp
|
||||
v3/persistence.cpp
|
||||
v3/revocation_lookup.cpp
|
||||
v3/trust_store.cpp
|
||||
v3/validity_restriction.cpp
|
||||
)
|
||||
target_link_libraries(security PUBLIC Boost::headers asn1 asn1_security common net)
|
||||
target_link_libraries(security PRIVATE geodesy)
|
||||
|
||||
if(VANETZA_WITH_PQC)
|
||||
set_property(TARGET security APPEND PROPERTY SOURCES
|
||||
pqc/fndsa512_oqs.cpp
|
||||
pqc/hybrid_certificate.cpp
|
||||
pqc/hybrid_certificate_validator.cpp)
|
||||
target_link_libraries(security PRIVATE OQS::oqs)
|
||||
endif()
|
||||
|
||||
if(VANETZA_WITH_CRYPTOPP)
|
||||
set_property(TARGET security APPEND PROPERTY
|
||||
SOURCES backend_cryptopp.cpp)
|
||||
target_link_libraries(security PRIVATE CryptoPP::CryptoPP)
|
||||
target_compile_definitions(security PUBLIC "VANETZA_WITH_CRYPTOPP")
|
||||
endif()
|
||||
|
||||
if(VANETZA_WITH_OPENSSL)
|
||||
set_property(TARGET security APPEND PROPERTY
|
||||
SOURCES backend_openssl.cpp openssl_wrapper.cpp)
|
||||
target_link_libraries(security PRIVATE OpenSSL::Crypto)
|
||||
target_compile_definitions(security PUBLIC "VANETZA_WITH_OPENSSL")
|
||||
target_compile_definitions(security PRIVATE "OPENSSL_API_COMPAT=0x10101000L")
|
||||
endif()
|
||||
|
||||
if(NOT VANETZA_WITH_CRYPTOPP AND NOT VANETZA_WITH_OPENSSL)
|
||||
message(WARNING "Vanetza is built without proper security backend")
|
||||
endif()
|
||||
|
||||
# shared test data for all security tests
|
||||
set(SECURITY_TEST_ASSET_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests/assets")
|
||||
add_test_subdirectory(tests)
|
||||
add_test_subdirectory(v2/tests)
|
||||
add_test_subdirectory(v3/tests)
|
||||
if(VANETZA_WITH_PQC)
|
||||
add_test_subdirectory(pqc/tests)
|
||||
endif()
|
||||
@@ -0,0 +1,33 @@
|
||||
# Security
|
||||
|
||||
This is the security module of Vanetza. It implements the ETSI C-ITS security extension of the GeoNetworking protocol based on:
|
||||
- `vanetza::security::v2` namespace: [ETSI TS 103 097 v1.2.1](http://www.etsi.org/deliver/etsi_ts/103000_103099/103097/01.02.01_60/ts_103097v010201p.pdf)
|
||||
- `vanetza::security::v3` namespace: [ETSI TS 103 097 v1.3.1](https://www.etsi.org/deliver/etsi_ts/103000_103099/103097/01.03.01_60/ts_103097v010301p.pdf)
|
||||
|
||||
|
||||
## Implemented Features
|
||||
|
||||
Most features are implemented, including:
|
||||
|
||||
- Security profiles including the CAM and DENM profile
|
||||
- Certificate requests for unknown certificates of other stations
|
||||
- Certificate validation for incoming messages
|
||||
|
||||
## Missing Features
|
||||
|
||||
There are a few missing features, but the overall implementation is in a working state to send and receive secured messages.
|
||||
It has been verified to work correctly by interoperability tests with other implementations.
|
||||
|
||||
- Revocation checks for certificate authorities<br>
|
||||
Certificates of CAs can be revoked via CRLs. There will be a new standard for the corresponding protocol in May 2018. It will be a new version of [ETSI TS 102 941](http://www.etsi.org/deliver/etsi_ts/102900_102999/102941/01.01.01_60/ts_102941v010101p.pdf).
|
||||
|
||||
- v2: region checks for polygonal and identified regions<br>
|
||||
There are `TODO` notes in the code of `region.cpp` within the `is_within()` functions. Implementing these checks is non-trivial.
|
||||
|
||||
- v2: region consistency checks for regions other than circular and none region restrictions<br>
|
||||
There are `TODO` notes in the code of `region.cpp` within the `is_within()` functions. Implementing these checks is non-trivial.
|
||||
|
||||
- v3: region checks for identified regions
|
||||
|
||||
- Certificate requests<br>
|
||||
Currently there's no support to request authorization tickets from an authorization authority or to do an enrolment.
|
||||
@@ -0,0 +1,74 @@
|
||||
#include <vanetza/common/factory.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static vanetza::Factory<vanetza::security::Backend> backend_factory;
|
||||
|
||||
template<typename T>
|
||||
class BackendRegistrar
|
||||
{
|
||||
public:
|
||||
BackendRegistrar(const std::string& name)
|
||||
{
|
||||
auto f = []() { return std::unique_ptr<vanetza::security::Backend> { new T() }; };
|
||||
bool success = backend_factory.add(name, f) && backend_factory.configure_default(name);
|
||||
assert(success);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// order of VANETZA_REGISTER_CRYPTO_BACKEND invocation defines priority: last registered one will be default
|
||||
#define VANETZA_REGISTER_CRYPTO_BACKEND(clazz) \
|
||||
namespace { \
|
||||
using namespace vanetza::security; \
|
||||
static const BackendRegistrar<clazz> clazz##Factory(clazz::backend_name); \
|
||||
}
|
||||
|
||||
#include <vanetza/security/backend_null.hpp>
|
||||
VANETZA_REGISTER_CRYPTO_BACKEND(BackendNull)
|
||||
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
# include <vanetza/security/backend_cryptopp.hpp>
|
||||
VANETZA_REGISTER_CRYPTO_BACKEND(BackendCryptoPP)
|
||||
#endif
|
||||
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
# include <vanetza/security/backend_openssl.hpp>
|
||||
VANETZA_REGISTER_CRYPTO_BACKEND(BackendOpenSsl)
|
||||
#endif
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
const Factory<Backend>& builtin_backends()
|
||||
{
|
||||
return backend_factory;
|
||||
}
|
||||
|
||||
std::unique_ptr<Backend> create_backend(const std::string& name, const Factory<Backend>& factory)
|
||||
{
|
||||
if (name == "default") {
|
||||
return factory.create();
|
||||
} else {
|
||||
return factory.create(name);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Backend> create_backend_or_throw(const std::string& name)
|
||||
{
|
||||
auto backend = create_backend(name);
|
||||
if (!backend) {
|
||||
throw std::runtime_error("security backend unavailable");
|
||||
}
|
||||
return backend;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,126 @@
|
||||
#ifndef BACKEND_HPP_ZMRDTY2O
|
||||
#define BACKEND_HPP_ZMRDTY2O
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/common/factory.hpp>
|
||||
#include <vanetza/security/ecdsa256.hpp>
|
||||
#include <vanetza/security/hash_algorithm.hpp>
|
||||
#include <vanetza/security/private_key.hpp>
|
||||
#include <vanetza/security/public_key.hpp>
|
||||
#include <vanetza/security/signature.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/**
|
||||
* Interface to cryptographic features
|
||||
*/
|
||||
class Backend
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \brief calculate signature for given data and private key
|
||||
*
|
||||
* \param private_key Secret private key
|
||||
* \param data buffer with plaintext data
|
||||
* \return calculated signature
|
||||
*/
|
||||
virtual EcdsaSignature sign_data(const ecdsa256::PrivateKey& private_key, const ByteBuffer& data) = 0;
|
||||
|
||||
/**
|
||||
* \brief calculate signature for given digest and private key
|
||||
*
|
||||
* \param private_key secret private key
|
||||
* \param digest hash value of data
|
||||
* \return calculated signature
|
||||
*/
|
||||
virtual Signature sign_digest(const PrivateKey&, const ByteBuffer& digest) = 0;
|
||||
|
||||
/**
|
||||
* \brief try to verify data using public key and signature
|
||||
*
|
||||
* \param public_key Public key
|
||||
* \param data plaintext
|
||||
* \param sig signature of data
|
||||
* \return true if the data could be verified
|
||||
*/
|
||||
virtual bool verify_data(const ecdsa256::PublicKey& public_key, const ByteBuffer& data, const EcdsaSignature& sig) = 0;
|
||||
|
||||
/**
|
||||
* \brief try to verify digest using public key and signature
|
||||
*
|
||||
* \param public_key public key
|
||||
* \param digest hash value of data
|
||||
* \param sig signature of data
|
||||
* \return true if data could be verified
|
||||
*/
|
||||
virtual bool verify_digest(const PublicKey& public_key, const ByteBuffer& digest, const Signature& sig) = 0;
|
||||
|
||||
/**
|
||||
* \brief decompress a possibly compressed elliptic curve point
|
||||
*
|
||||
* \param ecc_point elliptic curve point
|
||||
* \return uncompressed point
|
||||
*/
|
||||
virtual boost::optional<Uncompressed> decompress_point(const EccPoint& ecc_point) = 0;
|
||||
|
||||
/**
|
||||
* \brief calculate hash value of data
|
||||
*
|
||||
* \param algo hash algorithm
|
||||
* \param data buffer with data
|
||||
* \return buffer containing calculated hash value
|
||||
*/
|
||||
virtual ByteBuffer calculate_hash(HashAlgorithm algo, const ByteBuffer& data) = 0;
|
||||
|
||||
/**
|
||||
* \brief generate a private key and the corresponding public key
|
||||
* \return generated key pair
|
||||
*/
|
||||
virtual ecdsa256::KeyPair generate_key_pair() = 0;
|
||||
|
||||
virtual ~Backend() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief get factory containing builtin backend implementations
|
||||
*
|
||||
* Included set of backends depends on CMake build configuration.
|
||||
* At least the "Null" backend is always included.
|
||||
* \return factory
|
||||
*/
|
||||
const Factory<Backend>& builtin_backends();
|
||||
|
||||
/**
|
||||
* \brief create a backend instance
|
||||
*
|
||||
* A backend named "default" is guaranteed not to return a nullptr.
|
||||
* However, it might be a dummy backend.
|
||||
*
|
||||
* \param name identifying name of backend implementation
|
||||
* \param factory build backend registered by name from this factory
|
||||
* \return backend instance (if available) or nullptr
|
||||
*/
|
||||
std::unique_ptr<Backend> create_backend(const std::string& name, const Factory<Backend>& = builtin_backends());
|
||||
|
||||
/**
|
||||
* \brief create a backend instance
|
||||
*
|
||||
* Will never return a nullptr but throw an exception if backend is unavailable.
|
||||
*
|
||||
* \param name identifying name of backend implementation
|
||||
* \throws runtime_error if backend is unavailable
|
||||
* \return backend instance
|
||||
*/
|
||||
std::unique_ptr<Backend> create_backend_or_throw(const std::string& name);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BACKEND_HPP_ZMRDTY2O */
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
#include <vanetza/security/backend_cryptopp.hpp>
|
||||
#include <vanetza/security/ecc_point.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <cryptopp/oids.h>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Derive Crypto++ OID object from key type
|
||||
* \param key_type key type from our API
|
||||
* \return Crypto++ OID object (possibly empty)
|
||||
*/
|
||||
CryptoPP::OID get_oid(KeyType key_type)
|
||||
{
|
||||
if (key_type == KeyType::NistP256) {
|
||||
return CryptoPP::ASN1::secp256r1();
|
||||
} else if (key_type == KeyType::BrainpoolP256r1) {
|
||||
return CryptoPP::ASN1::brainpoolP256r1();
|
||||
} else if (key_type == KeyType::BrainpoolP384r1) {
|
||||
return CryptoPP::ASN1::brainpoolP384r1();
|
||||
} else {
|
||||
return CryptoPP::OID {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode public key with prefix byte
|
||||
* - 0x02 compressed with Y0
|
||||
* - 0x03 compressed with Y1
|
||||
* - 0x04 uncompressed
|
||||
*
|
||||
* \param pub_key generic public key
|
||||
* \return encoded public key
|
||||
*/
|
||||
ByteBuffer encode_public_key(const PublicKey& pub_key)
|
||||
{
|
||||
ByteBuffer encoded;
|
||||
|
||||
if (pub_key.compression == KeyCompression::NoCompression) {
|
||||
encoded.reserve(1 + pub_key.x.size() + pub_key.y.size());
|
||||
encoded.push_back(0x04);
|
||||
encoded.insert(encoded.end(), pub_key.x.begin(), pub_key.x.end());
|
||||
encoded.insert(encoded.end(), pub_key.y.begin(), pub_key.y.end());
|
||||
} else if (pub_key.compression == KeyCompression::Y0) {
|
||||
encoded.reserve(1 + pub_key.x.size());
|
||||
encoded.push_back(0x02);
|
||||
encoded.insert(encoded.end(), pub_key.x.begin(), pub_key.x.end());
|
||||
} else if (pub_key.compression == KeyCompression::Y1) {
|
||||
encoded.reserve(1 + pub_key.x.size());
|
||||
encoded.push_back(0x03);
|
||||
encoded.insert(encoded.end(), pub_key.x.begin(), pub_key.x.end());
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
using InternalPublicKey = CryptoPP::DL_PublicKey_EC<CryptoPP::ECP>;
|
||||
using InternalPrivateKey = CryptoPP::DL_PrivateKey_EC<CryptoPP::ECP>;
|
||||
|
||||
/**
|
||||
* Convert our PublicKey type to a Crypto++ EC public key
|
||||
* \param pub_key our public key
|
||||
* \return public key as Crypto++ type (if conversion was possible)
|
||||
*/
|
||||
boost::optional<InternalPublicKey> convert_public_key(const PublicKey& pub_key)
|
||||
{
|
||||
InternalPublicKey out;
|
||||
out.AccessGroupParameters().Initialize(get_oid(pub_key.type));
|
||||
auto& curve = out.GetGroupParameters().GetCurve();
|
||||
|
||||
CryptoPP::ECP::Point point;
|
||||
ByteBuffer encoded_pub_key = encode_public_key(pub_key);
|
||||
CryptoPP::StringStore store { encoded_pub_key.data(), encoded_pub_key.size() };
|
||||
if (!curve.DecodePoint(point, store, store.MaxRetrievable())) {
|
||||
return boost::none;
|
||||
}
|
||||
out.SetPublicElement(point);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert our PrivateKey type to a Crypto++ EC private key
|
||||
* \param priv_key our private key
|
||||
* \return private key as Crypto++ type
|
||||
*/
|
||||
InternalPrivateKey convert_private_key(const PrivateKey& priv_key)
|
||||
{
|
||||
InternalPrivateKey out;
|
||||
out.AccessGroupParameters().Initialize(get_oid(priv_key.type));
|
||||
out.SetPrivateExponent(CryptoPP::Integer(priv_key.key.data(), priv_key.key.size()));
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialized Crypto++ Verifier for C-ITS messages
|
||||
*/
|
||||
template<typename ECDSA>
|
||||
class Verifier : public ECDSA::Verifier
|
||||
{
|
||||
public:
|
||||
using BaseVerifier = typename ECDSA::Verifier;
|
||||
|
||||
/**
|
||||
* Construct verifier object for a public key
|
||||
* \param pub public key
|
||||
*/
|
||||
Verifier(const InternalPublicKey& pub)
|
||||
: BaseVerifier(pub)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify digest and signature
|
||||
* \param digest hash of to-be-verified data
|
||||
* \param sig given signature
|
||||
* \return true if digest, signature and public key match
|
||||
*/
|
||||
bool VerifyDigest(const ByteBuffer& digest, const Signature& sig)
|
||||
{
|
||||
using namespace CryptoPP;
|
||||
const auto& alg = this->GetSignatureAlgorithm();
|
||||
const auto& params = this->GetAbstractGroupParameters();
|
||||
const auto& key = this->GetKeyInterface();
|
||||
this->GetMaterial().DoQuickSanityCheck();
|
||||
|
||||
Integer e { digest.data(), digest.size() };
|
||||
Integer r { sig.r.data(), sig.r.size() };
|
||||
Integer s { sig.s.data(), sig.s.size() };
|
||||
return alg.Verify(params, key, e, r, s);
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
class IdentityHash : public CryptoPP::HashTransformation
|
||||
{
|
||||
public:
|
||||
CRYPTOPP_CONSTANT(DIGESTSIZE = N);
|
||||
|
||||
static const char* StaticAlgorithmName()
|
||||
{
|
||||
return "IdentityHash";
|
||||
}
|
||||
|
||||
void Update(const uint8_t* input, size_t length) override
|
||||
{
|
||||
std::copy_n(input, std::min(length, m_hash.size()), m_hash.begin());;
|
||||
}
|
||||
|
||||
void TruncatedFinal(uint8_t* output, size_t len) override
|
||||
{
|
||||
if (output != nullptr) {
|
||||
std::copy_n(m_hash.begin(), std::min(len, m_hash.size()), output);
|
||||
}
|
||||
m_hash.fill(0);
|
||||
}
|
||||
|
||||
unsigned int DigestSize() const override
|
||||
{
|
||||
return m_hash.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, N> m_hash;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
using DigestEcdsa = CryptoPP::ECDSA<CryptoPP::ECP, IdentityHash<N>>;
|
||||
|
||||
} // namespace
|
||||
|
||||
using std::placeholders::_1;
|
||||
|
||||
BackendCryptoPP::BackendCryptoPP()
|
||||
{
|
||||
}
|
||||
|
||||
EcdsaSignature BackendCryptoPP::sign_data(const ecdsa256::PrivateKey& generic_key, const ByteBuffer& data)
|
||||
{
|
||||
return sign_data(internal_private_key(generic_key), data);
|
||||
}
|
||||
|
||||
EcdsaSignature BackendCryptoPP::sign_data(const Ecdsa256::PrivateKey& private_key, const ByteBuffer& data)
|
||||
{
|
||||
// calculate signature
|
||||
Ecdsa256::Signer signer(private_key);
|
||||
ByteBuffer signature(signer.MaxSignatureLength(), 0x00);
|
||||
auto signature_length = signer.SignMessage(m_prng, data.data(), data.size(), signature.data());
|
||||
signature.resize(signature_length);
|
||||
|
||||
auto signature_delimiter = signature.begin();
|
||||
std::advance(signature_delimiter, 32);
|
||||
|
||||
EcdsaSignature ecdsa_signature;
|
||||
// set R
|
||||
X_Coordinate_Only coordinate;
|
||||
coordinate.x = ByteBuffer(signature.begin(), signature_delimiter);
|
||||
ecdsa_signature.R = std::move(coordinate);
|
||||
// set s
|
||||
ByteBuffer trailer_field_buffer(signature_delimiter, signature.end());
|
||||
ecdsa_signature.s = std::move(trailer_field_buffer);
|
||||
|
||||
return ecdsa_signature;
|
||||
}
|
||||
|
||||
Signature BackendCryptoPP::sign_digest(const PrivateKey& private_key, const ByteBuffer& digest)
|
||||
{
|
||||
static const Signature dummy = {};
|
||||
|
||||
CryptoPP::DL_Keys_ECDSA<CryptoPP::ECP>::PrivateKey key;
|
||||
CryptoPP::Integer integer { private_key.key.data(), private_key.key.size() };
|
||||
|
||||
if (private_key.type == KeyType::NistP256) {
|
||||
CryptoPP::Integer integer { private_key.key.data(), private_key.key.size() };
|
||||
key.Initialize(CryptoPP::ASN1::secp256r1(), integer);
|
||||
} else if (private_key.type == KeyType::BrainpoolP256r1) {
|
||||
CryptoPP::Integer integer { private_key.key.data(), private_key.key.size() };
|
||||
key.Initialize(CryptoPP::ASN1::brainpoolP256r1(), integer);
|
||||
} else if (private_key.type == KeyType::BrainpoolP384r1) {
|
||||
CryptoPP::Integer integer { private_key.key.data(), private_key.key.size() };
|
||||
key.Initialize(CryptoPP::ASN1::brainpoolP384r1(), integer);
|
||||
} else {
|
||||
return dummy;
|
||||
}
|
||||
|
||||
auto calculate_signature = [&](CryptoPP::PK_Signer* signer) -> Signature
|
||||
{
|
||||
// calculate signature
|
||||
ByteBuffer signature(signer->MaxSignatureLength(), 0x00);
|
||||
auto signature_length = signer->SignMessage(m_prng, digest.data(), digest.size(), signature.data());
|
||||
signature.resize(signature_length);
|
||||
|
||||
auto signature_delimiter = signature.begin();
|
||||
std::advance(signature_delimiter, signer->MaxSignatureLength() / 2);
|
||||
|
||||
Signature ecdsa_signature;
|
||||
ecdsa_signature.type = private_key.type;
|
||||
ecdsa_signature.r = ByteBuffer(signature.begin(), signature_delimiter);
|
||||
ecdsa_signature.s = ByteBuffer(signature_delimiter, signature.end());
|
||||
return ecdsa_signature;
|
||||
};
|
||||
|
||||
if (digest.size() == 32) {
|
||||
DigestEcdsa<32>::Signer signer(key);
|
||||
return calculate_signature(&signer);
|
||||
} else if (digest.size() == 48) {
|
||||
DigestEcdsa<48>::Signer signer(key);
|
||||
return calculate_signature(&signer);
|
||||
} else {
|
||||
return dummy;
|
||||
}
|
||||
}
|
||||
|
||||
bool BackendCryptoPP::verify_data(const ecdsa256::PublicKey& generic_key, const ByteBuffer& msg, const EcdsaSignature& sig)
|
||||
{
|
||||
const ByteBuffer sigbuf = extract_signature_buffer(sig);
|
||||
return verify_data(internal_public_key(generic_key), msg, sigbuf);
|
||||
}
|
||||
|
||||
bool BackendCryptoPP::verify_digest(const PublicKey& public_key, const ByteBuffer& digest, const Signature& sig)
|
||||
{
|
||||
if (public_key.type != sig.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::optional<InternalPublicKey> internal_pub_key = convert_public_key(public_key);
|
||||
if (!internal_pub_key) {
|
||||
return false;
|
||||
} else if (!internal_pub_key->Validate(m_prng, 3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sig.type == KeyType::NistP256 || sig.type == KeyType::BrainpoolP256r1) {
|
||||
Verifier<Ecdsa256> verifier(*internal_pub_key);
|
||||
return verifier.VerifyDigest(digest, sig);
|
||||
} else if (sig.type == KeyType::BrainpoolP384r1) {
|
||||
Verifier<Ecdsa384> verifier(*internal_pub_key);
|
||||
return verifier.VerifyDigest(digest, sig);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BackendCryptoPP::verify_data(const Ecdsa256::PublicKey& public_key, const ByteBuffer& msg, const ByteBuffer& sig)
|
||||
{
|
||||
Ecdsa256::Verifier verifier(public_key);
|
||||
return verifier.VerifyMessage(msg.data(), msg.size(), sig.data(), sig.size());
|
||||
}
|
||||
|
||||
boost::optional<Uncompressed> BackendCryptoPP::decompress_point(const EccPoint& ecc_point)
|
||||
{
|
||||
struct DecompressionVisitor : public boost::static_visitor<bool>
|
||||
{
|
||||
bool operator()(const X_Coordinate_Only&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const Compressed_Lsb_Y_0& p)
|
||||
{
|
||||
decompress(p.x, 0x02);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(const Compressed_Lsb_Y_1& p)
|
||||
{
|
||||
decompress(p.x, 0x03);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(const Uncompressed& p)
|
||||
{
|
||||
result = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
void decompress(const ByteBuffer& x, ByteBuffer::value_type type)
|
||||
{
|
||||
ByteBuffer compact;
|
||||
compact.reserve(x.size() + 1);
|
||||
compact.push_back(type);
|
||||
std::copy(x.begin(), x.end(), std::back_inserter(compact));
|
||||
|
||||
CryptoPP::ECP::Point point;
|
||||
CryptoPP::DL_GroupParameters_EC<CryptoPP::ECP> group(CryptoPP::ASN1::secp256r1());
|
||||
group.GetCurve().DecodePoint(point, compact.data(), compact.size());
|
||||
|
||||
result.x = x;
|
||||
result.y.resize(result.x.size());
|
||||
point.y.Encode(result.y.data(), result.y.size());
|
||||
}
|
||||
|
||||
Uncompressed result;
|
||||
};
|
||||
|
||||
DecompressionVisitor visitor;
|
||||
if (boost::apply_visitor(visitor, ecc_point)) {
|
||||
return visitor.result;
|
||||
} else {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer BackendCryptoPP::calculate_hash(HashAlgorithm algo_id, const ByteBuffer& buffer)
|
||||
{
|
||||
ByteBuffer hash;
|
||||
if (algo_id == HashAlgorithm::SHA256) {
|
||||
CryptoPP::SHA256 algo;
|
||||
hash.resize(algo.DigestSize());
|
||||
algo.CalculateDigest(hash.data(), buffer.data(), buffer.size());
|
||||
} else if (algo_id == HashAlgorithm::SHA384) {
|
||||
CryptoPP::SHA384 algo;
|
||||
hash.resize(algo.DigestSize());
|
||||
algo.CalculateDigest(hash.data(), buffer.data(), buffer.size());
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
ecdsa256::KeyPair BackendCryptoPP::generate_key_pair()
|
||||
{
|
||||
ecdsa256::KeyPair kp;
|
||||
auto private_key = generate_private_key();
|
||||
auto& private_exponent = private_key.GetPrivateExponent();
|
||||
assert(kp.private_key.key.size() >= private_exponent.ByteCount());
|
||||
private_exponent.Encode(kp.private_key.key.data(), kp.private_key.key.size());
|
||||
|
||||
auto public_key = generate_public_key(private_key);
|
||||
auto& public_element = public_key.GetPublicElement();
|
||||
assert(kp.public_key.x.size() >= public_element.x.ByteCount());
|
||||
assert(kp.public_key.y.size() >= public_element.y.ByteCount());
|
||||
public_element.x.Encode(kp.public_key.x.data(), kp.public_key.x.size());
|
||||
public_element.y.Encode(kp.public_key.y.data(), kp.public_key.y.size());
|
||||
return kp;
|
||||
}
|
||||
|
||||
BackendCryptoPP::Ecdsa256::PrivateKey BackendCryptoPP::generate_private_key()
|
||||
{
|
||||
CryptoPP::OID oid(CryptoPP::ASN1::secp256r1());
|
||||
Ecdsa256::PrivateKey private_key;
|
||||
private_key.Initialize(m_prng, oid);
|
||||
assert(private_key.Validate(m_prng, 3));
|
||||
return private_key;
|
||||
}
|
||||
|
||||
BackendCryptoPP::Ecdsa256::PublicKey BackendCryptoPP::generate_public_key(const Ecdsa256::PrivateKey& private_key)
|
||||
{
|
||||
Ecdsa256::PublicKey public_key;
|
||||
private_key.MakePublicKey(public_key);
|
||||
assert(public_key.Validate(m_prng, 3));
|
||||
return public_key;
|
||||
}
|
||||
|
||||
BackendCryptoPP::Ecdsa256::PublicKey BackendCryptoPP::internal_public_key(const ecdsa256::PublicKey& generic)
|
||||
{
|
||||
CryptoPP::Integer x { generic.x.data(), generic.x.size() };
|
||||
CryptoPP::Integer y { generic.y.data(), generic.y.size() };
|
||||
CryptoPP::ECP::Point q { x, y };
|
||||
|
||||
Ecdsa256::PublicKey pub;
|
||||
pub.Initialize(CryptoPP::ASN1::secp256r1(), q);
|
||||
assert(pub.Validate(m_prng, 3));
|
||||
return pub;
|
||||
}
|
||||
|
||||
BackendCryptoPP::Ecdsa256::PrivateKey BackendCryptoPP::internal_private_key(const ecdsa256::PrivateKey& generic)
|
||||
{
|
||||
Ecdsa256::PrivateKey key;
|
||||
CryptoPP::Integer integer { generic.key.data(), generic.key.size() };
|
||||
key.Initialize(CryptoPP::ASN1::secp256r1(), integer);
|
||||
return key;
|
||||
}
|
||||
|
||||
namespace cryptopp
|
||||
{
|
||||
|
||||
PublicKey derive_public_key(const PrivateKey& private_key)
|
||||
{
|
||||
InternalPrivateKey priv = convert_private_key(private_key);
|
||||
InternalPublicKey pub;
|
||||
priv.MakePublicKey(pub);
|
||||
const auto& element = pub.GetPublicElement();
|
||||
|
||||
PublicKey public_key;
|
||||
public_key.type = private_key.type;
|
||||
public_key.compression = KeyCompression::NoCompression;
|
||||
public_key.x.resize(key_length(private_key.type));
|
||||
public_key.y.resize(key_length(private_key.type));
|
||||
element.x.Encode(public_key.x.data(), public_key.x.size());
|
||||
element.y.Encode(public_key.y.data(), public_key.y.size());
|
||||
return public_key;
|
||||
}
|
||||
|
||||
} // namespace cryptopp
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef BACKEND_CRYPTOPP_HPP_JQWA9MLZ
|
||||
#define BACKEND_CRYPTOPP_HPP_JQWA9MLZ
|
||||
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <cryptopp/eccrypto.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
#include <cryptopp/sha.h>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
class BackendCryptoPP : public Backend
|
||||
{
|
||||
public:
|
||||
using Ecdsa256 = CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>;
|
||||
using Ecdsa384 = CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA384>;
|
||||
|
||||
static constexpr auto backend_name = "CryptoPP";
|
||||
|
||||
BackendCryptoPP();
|
||||
|
||||
/// \see Backend::sign_data
|
||||
EcdsaSignature sign_data(const ecdsa256::PrivateKey& private_key, const ByteBuffer& data_buffer) override;
|
||||
|
||||
/// \see Backend::sign_digest
|
||||
Signature sign_digest(const PrivateKey&, const ByteBuffer& digest) override;
|
||||
|
||||
/// \see Backend::verify_data
|
||||
bool verify_data(const ecdsa256::PublicKey& public_key, const ByteBuffer& data, const EcdsaSignature& sig) override;
|
||||
|
||||
/// \see Backend::verify_digest
|
||||
bool verify_digest(const PublicKey&, const ByteBuffer& digest, const Signature&) override;
|
||||
|
||||
/// \see Backend::decompress_point
|
||||
boost::optional<Uncompressed> decompress_point(const EccPoint& ecc_point) override;
|
||||
|
||||
/// \see Backend::calculate_hash
|
||||
ByteBuffer calculate_hash(HashAlgorithm, const ByteBuffer&) override;
|
||||
|
||||
/// \see Backend::generate_key_pair
|
||||
ecdsa256::KeyPair generate_key_pair() override;
|
||||
|
||||
private:
|
||||
/// internal sign method using crypto++ private key
|
||||
EcdsaSignature sign_data(const Ecdsa256::PrivateKey& key, const ByteBuffer& data);
|
||||
|
||||
/// internal verify method using crypto++ public key
|
||||
bool verify_data(const Ecdsa256::PublicKey& key, const ByteBuffer& data, const ByteBuffer& sig);
|
||||
|
||||
/// create private key
|
||||
Ecdsa256::PrivateKey generate_private_key();
|
||||
|
||||
/// derive public key from private key
|
||||
Ecdsa256::PublicKey generate_public_key(const Ecdsa256::PrivateKey&);
|
||||
|
||||
/// adapt generic public key to internal structure
|
||||
Ecdsa256::PublicKey internal_public_key(const ecdsa256::PublicKey&);
|
||||
|
||||
/// adapt generic private key to internal structure
|
||||
Ecdsa256::PrivateKey internal_private_key(const ecdsa256::PrivateKey&);
|
||||
|
||||
CryptoPP::AutoSeededRandomPool m_prng;
|
||||
};
|
||||
|
||||
namespace cryptopp
|
||||
{
|
||||
/// Derive the public key from a private key (pub = priv * G), on the key's own curve.
|
||||
PublicKey derive_public_key(const PrivateKey& private_key);
|
||||
} // namespace cryptopp
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BACKEND_CRYPTOPP_HPP_JQWA9MLZ */
|
||||
@@ -0,0 +1,78 @@
|
||||
#include <vanetza/common/byte_sequence.hpp>
|
||||
#include <vanetza/security/backend_null.hpp>
|
||||
#include <vanetza/security/public_key.hpp>
|
||||
#include <vanetza/security/signature.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
EcdsaSignature BackendNull::sign_data(const ecdsa256::PrivateKey&, const ByteBuffer&)
|
||||
{
|
||||
static const EcdsaSignature fake = fake_signature();
|
||||
return fake;
|
||||
}
|
||||
|
||||
Signature BackendNull::sign_digest(const PrivateKey&, const ByteBuffer&)
|
||||
{
|
||||
static const Signature empty {};
|
||||
return empty;
|
||||
}
|
||||
|
||||
bool BackendNull::verify_data(const ecdsa256::PublicKey&, const ByteBuffer&, const EcdsaSignature&)
|
||||
{
|
||||
// accept everything
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BackendNull::verify_digest(const PublicKey&, const ByteBuffer&, const Signature&)
|
||||
{
|
||||
// accept everything
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::optional<Uncompressed> BackendNull::decompress_point(const EccPoint&)
|
||||
{
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
EcdsaSignature BackendNull::fake_signature() const
|
||||
{
|
||||
constexpr std::size_t size = 32;
|
||||
EcdsaSignature signature;
|
||||
X_Coordinate_Only coordinate;
|
||||
coordinate.x = random_byte_sequence(size, 0xdead);
|
||||
signature.R = coordinate;
|
||||
signature.s = random_byte_sequence(size, 0xbeef);
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
ByteBuffer BackendNull::calculate_hash(HashAlgorithm algo, const ByteBuffer&)
|
||||
{
|
||||
ByteBuffer hash;
|
||||
switch (algo) {
|
||||
case HashAlgorithm::SHA256:
|
||||
hash.resize(32);
|
||||
break;
|
||||
case HashAlgorithm::SHA384:
|
||||
hash.resize(48);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
ecdsa256::KeyPair BackendNull::generate_key_pair()
|
||||
{
|
||||
ecdsa256::KeyPair key_pair;
|
||||
key_pair.private_key.key.fill(0);
|
||||
key_pair.public_key.x.fill(0);
|
||||
key_pair.public_key.y.fill(0);
|
||||
return key_pair;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef BACKEND_NULL_HPP_3E6GMTPL
|
||||
#define BACKEND_NULL_HPP_3E6GMTPL
|
||||
|
||||
#include <vanetza/security/backend.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/**
|
||||
* \brief A backend doing nothing
|
||||
*
|
||||
* This backend is INSECURE quite obviously!
|
||||
* It will return a faked signature and silently accept all data at verification.
|
||||
*/
|
||||
class BackendNull : public Backend
|
||||
{
|
||||
public:
|
||||
static constexpr auto backend_name = "Null";
|
||||
|
||||
/// \see Backend::sign_data
|
||||
EcdsaSignature sign_data(const ecdsa256::PrivateKey& private_key, const ByteBuffer& data_buffer) override;
|
||||
|
||||
/// \see Backend::sign_digest
|
||||
Signature sign_digest(const PrivateKey&, const ByteBuffer&) override;
|
||||
|
||||
/// \see Backend::verify_data
|
||||
bool verify_data(const ecdsa256::PublicKey& public_key, const ByteBuffer& data, const EcdsaSignature& sig) override;
|
||||
|
||||
/// \see Backend::verify_digest
|
||||
bool verify_digest(const PublicKey&, const ByteBuffer& digest, const Signature&) override;
|
||||
|
||||
/// \see Backend::decompress_point
|
||||
boost::optional<Uncompressed> decompress_point(const EccPoint& ecc_point) override;
|
||||
|
||||
/// \see Backend::calculate_hash
|
||||
ByteBuffer calculate_hash(HashAlgorithm, const ByteBuffer&) override;
|
||||
|
||||
/// \see Backend::generate_key_pair
|
||||
ecdsa256::KeyPair generate_key_pair() override;
|
||||
|
||||
private:
|
||||
EcdsaSignature fake_signature() const;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BACKEND_NULL_HPP_3E6GMTPL */
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
#include <vanetza/security/backend_openssl.hpp>
|
||||
#include <vanetza/security/key_type.hpp>
|
||||
#include <vanetza/security/openssl_wrapper.hpp>
|
||||
#include <vanetza/security/v2/public_key.hpp>
|
||||
#include <vanetza/security/v2/signature.hpp>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/obj_mac.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int openssl_nid(KeyType key)
|
||||
{
|
||||
int nid;
|
||||
switch (key) {
|
||||
case KeyType::NistP256:
|
||||
nid = NID_X9_62_prime256v1;
|
||||
break;
|
||||
case KeyType::BrainpoolP256r1:
|
||||
nid = NID_brainpoolP256r1;
|
||||
break;
|
||||
case KeyType::BrainpoolP384r1:
|
||||
nid = NID_brainpoolP384r1;
|
||||
break;
|
||||
default:
|
||||
nid = NID_undef;
|
||||
break;
|
||||
}
|
||||
return nid;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BackendOpenSsl::BackendOpenSsl()
|
||||
{
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
|
||||
}
|
||||
|
||||
EcdsaSignature BackendOpenSsl::sign_data(const ecdsa256::PrivateKey& key, const ByteBuffer& data)
|
||||
{
|
||||
auto priv_key = internal_private_key(key);
|
||||
auto digest = calculate_sha256_digest(data);
|
||||
|
||||
// sign message data represented by the digest
|
||||
openssl::Signature signature { ECDSA_do_sign(digest.data(), digest.size(), priv_key) };
|
||||
const BIGNUM* sig_r = nullptr;
|
||||
const BIGNUM* sig_s = nullptr;
|
||||
ECDSA_SIG_get0(signature, &sig_r, &sig_s);
|
||||
|
||||
EcdsaSignature ecdsa_signature;
|
||||
X_Coordinate_Only coordinate;
|
||||
|
||||
if (sig_r && sig_s) {
|
||||
const size_t len = field_size(v2::PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256);
|
||||
|
||||
const auto num_bytes_s = BN_num_bytes(sig_s);
|
||||
assert(len >= static_cast<size_t>(num_bytes_s));
|
||||
ecdsa_signature.s.resize(len, 0x00);
|
||||
BN_bn2bin(sig_s, ecdsa_signature.s.data() + len - num_bytes_s);
|
||||
|
||||
const auto num_bytes_r = BN_num_bytes(sig_r);
|
||||
assert(len >= static_cast<size_t>(num_bytes_r));
|
||||
coordinate.x.resize(len, 0x00);
|
||||
BN_bn2bin(sig_r, coordinate.x.data() + len - num_bytes_r);
|
||||
} else {
|
||||
throw openssl::Exception();
|
||||
}
|
||||
|
||||
ecdsa_signature.R = std::move(coordinate);
|
||||
return ecdsa_signature;
|
||||
}
|
||||
|
||||
Signature BackendOpenSsl::sign_digest(const PrivateKey& key, const ByteBuffer& digest)
|
||||
{
|
||||
// sign message data represented by the digest
|
||||
auto priv_key = internal_private_key(key);
|
||||
openssl::Signature signature { ECDSA_do_sign(digest.data(), digest.size(), priv_key) };
|
||||
const BIGNUM* sig_r = nullptr;
|
||||
const BIGNUM* sig_s = nullptr;
|
||||
ECDSA_SIG_get0(signature, &sig_r, &sig_s);
|
||||
|
||||
Signature ecdsa_signature;
|
||||
ecdsa_signature.type = key.type;
|
||||
|
||||
if (sig_r && sig_s) {
|
||||
const size_t len = key_length(key.type);
|
||||
|
||||
const auto num_bytes_s = BN_num_bytes(sig_s);
|
||||
assert(len >= static_cast<size_t>(num_bytes_s));
|
||||
ecdsa_signature.s.resize(len, 0x00);
|
||||
BN_bn2bin(sig_s, ecdsa_signature.s.data() + len - num_bytes_s);
|
||||
|
||||
const auto num_bytes_r = BN_num_bytes(sig_r);
|
||||
assert(len >= static_cast<size_t>(num_bytes_r));
|
||||
ecdsa_signature.r.resize(len, 0x00);
|
||||
BN_bn2bin(sig_r, ecdsa_signature.r.data() + len - num_bytes_r);
|
||||
} else {
|
||||
throw openssl::Exception();
|
||||
}
|
||||
|
||||
return ecdsa_signature;
|
||||
}
|
||||
|
||||
bool BackendOpenSsl::verify_data(const ecdsa256::PublicKey& key, const ByteBuffer& data, const EcdsaSignature& sig)
|
||||
{
|
||||
try {
|
||||
auto digest = calculate_sha256_digest(data);
|
||||
auto pub = internal_public_key(key);
|
||||
openssl::Signature signature(sig);
|
||||
|
||||
return (ECDSA_do_verify(digest.data(), digest.size(), signature, pub) == 1);
|
||||
} catch (const openssl::Exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool BackendOpenSsl::verify_digest(const PublicKey& gpub, const ByteBuffer& digest, const Signature& gsig)
|
||||
{
|
||||
if (gpub.type != gsig.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
openssl::Key pub = internal_public_key(gpub);
|
||||
openssl::Signature sig { gsig };
|
||||
return ECDSA_do_verify(digest.data(), digest.size(), sig, pub) == 1;
|
||||
} catch (const openssl::Exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<Uncompressed> BackendOpenSsl::decompress_point(const EccPoint& ecc_point)
|
||||
{
|
||||
struct DecompressionVisitor : public boost::static_visitor<bool>
|
||||
{
|
||||
bool operator()(const X_Coordinate_Only&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const Compressed_Lsb_Y_0& p)
|
||||
{
|
||||
return decompress(p.x, 0);
|
||||
}
|
||||
|
||||
bool operator()(const Compressed_Lsb_Y_1& p)
|
||||
{
|
||||
return decompress(p.x, 1);
|
||||
}
|
||||
|
||||
bool operator()(const Uncompressed& p)
|
||||
{
|
||||
result = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decompress(const ByteBuffer& x, int y_bit)
|
||||
{
|
||||
try {
|
||||
openssl::BigNumberContext ctx;
|
||||
openssl::BigNumber x_coordinate(x);
|
||||
openssl::Group group(NID_X9_62_prime256v1);
|
||||
openssl::Point point(group);
|
||||
openssl::BigNumber y_coordinate;
|
||||
|
||||
result.x = x;
|
||||
result.y.resize(result.x.size());
|
||||
|
||||
EC_POINT_set_compressed_coordinates(group, point, x_coordinate, y_bit, ctx);
|
||||
EC_POINT_get_affine_coordinates(group, point, nullptr, y_coordinate, ctx);
|
||||
return (BN_bn2binpad(y_coordinate, result.y.data(), result.y.size()) != -1);
|
||||
} catch (const openssl::Exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Uncompressed result;
|
||||
};
|
||||
|
||||
DecompressionVisitor visitor;
|
||||
if (boost::apply_visitor(visitor, ecc_point)) {
|
||||
return visitor.result;
|
||||
} else {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer BackendOpenSsl::calculate_hash(HashAlgorithm algo, const ByteBuffer& data)
|
||||
{
|
||||
ByteBuffer result;
|
||||
if (algo == HashAlgorithm::SHA256) {
|
||||
auto digest = calculate_sha256_digest(data);
|
||||
result.assign(digest.begin(), digest.end());
|
||||
} else if (algo == HashAlgorithm::SHA384) {
|
||||
auto digest = calculate_sha384_digest(data);
|
||||
result.assign(digest.begin(), digest.end());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 32> BackendOpenSsl::calculate_sha256_digest(const ByteBuffer& data) const
|
||||
{
|
||||
static_assert(SHA256_DIGEST_LENGTH == 32, "Unexpected length of SHA256 digest");
|
||||
|
||||
std::array<uint8_t, 32> digest;
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
SHA256_Update(&ctx, data.data(), data.size());
|
||||
SHA256_Final(digest.data(), &ctx);
|
||||
return digest;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 48> BackendOpenSsl::calculate_sha384_digest(const ByteBuffer& data) const
|
||||
{
|
||||
static_assert(SHA384_DIGEST_LENGTH == 48, "Unexpected length of SHA384 digest");
|
||||
|
||||
std::array<uint8_t, 48> digest;
|
||||
SHA384(data.data(), data.size(), digest.data());
|
||||
return digest;
|
||||
}
|
||||
|
||||
openssl::Key BackendOpenSsl::internal_private_key(const ecdsa256::PrivateKey& generic) const
|
||||
{
|
||||
openssl::Key key(NID_X9_62_prime256v1);
|
||||
openssl::BigNumber prv(generic.key);
|
||||
EC_KEY_set_private_key(key, prv);
|
||||
|
||||
// OpenSSL requires public key, so we recreate it from private key
|
||||
openssl::BigNumberContext ctx;
|
||||
const EC_GROUP* group = EC_KEY_get0_group(key);
|
||||
openssl::Point pub(group);
|
||||
openssl::check(EC_POINT_mul(group, pub, prv, nullptr, nullptr, ctx));
|
||||
EC_KEY_set_public_key(key, pub);
|
||||
|
||||
openssl::check(EC_KEY_check_key(key));
|
||||
return key;
|
||||
}
|
||||
|
||||
openssl::Key BackendOpenSsl::internal_private_key(const PrivateKey& generic) const
|
||||
{
|
||||
openssl::Key key(openssl_nid(generic.type));
|
||||
openssl::BigNumber prv(generic.key);
|
||||
EC_KEY_set_private_key(key, prv);
|
||||
|
||||
// OpenSSL requires public key, so we recreate it from private key
|
||||
openssl::BigNumberContext ctx;
|
||||
const EC_GROUP* group = EC_KEY_get0_group(key);
|
||||
openssl::Point pub(group);
|
||||
openssl::check(EC_POINT_mul(group, pub, prv, nullptr, nullptr, ctx));
|
||||
EC_KEY_set_public_key(key, pub);
|
||||
|
||||
openssl::check(EC_KEY_check_key(key));
|
||||
return key;
|
||||
}
|
||||
|
||||
openssl::Key BackendOpenSsl::internal_public_key(const ecdsa256::PublicKey& generic) const
|
||||
{
|
||||
openssl::Key key(NID_X9_62_prime256v1);
|
||||
openssl::BigNumber x(generic.x);
|
||||
openssl::BigNumber y(generic.y);
|
||||
EC_KEY_set_public_key_affine_coordinates(key, x, y);
|
||||
|
||||
openssl::check(EC_KEY_check_key(key));
|
||||
return key;
|
||||
}
|
||||
|
||||
openssl::Key BackendOpenSsl::internal_public_key(const PublicKey& generic) const
|
||||
{
|
||||
openssl::Key key(openssl_nid(generic.type));
|
||||
openssl::Point point = internal_ec_point(generic);
|
||||
EC_KEY_set_public_key(key, point);
|
||||
|
||||
openssl::check(EC_KEY_check_key(key));
|
||||
return key;
|
||||
}
|
||||
|
||||
ecdsa256::KeyPair BackendOpenSsl::generate_key_pair()
|
||||
{
|
||||
ecdsa256::KeyPair key_pair;
|
||||
openssl::Key key(NID_X9_62_prime256v1);
|
||||
openssl::check(EC_KEY_generate_key(key));
|
||||
|
||||
const BIGNUM* priv_bn = EC_KEY_get0_private_key(key);
|
||||
const EC_POINT* pub_point = EC_KEY_get0_public_key(key);
|
||||
const EC_GROUP* group = EC_KEY_get0_group(key);
|
||||
|
||||
// extract private key
|
||||
key_pair.private_key.key.fill(0);
|
||||
auto priv_bytes = BN_num_bytes(priv_bn);
|
||||
BN_bn2bin(priv_bn, key_pair.private_key.key.data() + key_pair.private_key.key.size() - priv_bytes);
|
||||
|
||||
// extract public key coordinates
|
||||
openssl::BigNumber x;
|
||||
openssl::BigNumber y;
|
||||
openssl::BigNumberContext ctx;
|
||||
EC_POINT_get_affine_coordinates(group, pub_point, x, y, ctx);
|
||||
BN_bn2binpad(x, key_pair.public_key.x.data(), key_pair.public_key.x.size());
|
||||
BN_bn2binpad(y, key_pair.public_key.y.data(), key_pair.public_key.y.size());
|
||||
|
||||
return key_pair;
|
||||
}
|
||||
|
||||
openssl::Point BackendOpenSsl::internal_ec_point(const PublicKey& generic) const
|
||||
{
|
||||
openssl::Group group { openssl_nid(generic.type) };
|
||||
openssl::Point point { group };
|
||||
openssl::BigNumberContext bn_ctx;
|
||||
|
||||
switch (generic.compression)
|
||||
{
|
||||
case KeyCompression::NoCompression:
|
||||
EC_POINT_set_affine_coordinates(group, point,
|
||||
openssl::BigNumber { generic.x }, openssl::BigNumber {generic.y },
|
||||
bn_ctx);
|
||||
break;
|
||||
case KeyCompression::Y0:
|
||||
EC_POINT_set_compressed_coordinates(group, point, openssl::BigNumber { generic.x }, 0, bn_ctx);
|
||||
break;
|
||||
case KeyCompression::Y1:
|
||||
EC_POINT_set_compressed_coordinates(group, point, openssl::BigNumber { generic.x }, 1, bn_ctx);
|
||||
break;
|
||||
default:
|
||||
// no-op
|
||||
break;
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
namespace openssl
|
||||
{
|
||||
|
||||
PublicKey derive_public_key(const PrivateKey& private_key)
|
||||
{
|
||||
Key ec_key(openssl_nid(private_key.type));
|
||||
BigNumber prv(private_key.key);
|
||||
EC_KEY_set_private_key(ec_key, prv);
|
||||
|
||||
const EC_GROUP* group = EC_KEY_get0_group(ec_key);
|
||||
Point pub(group);
|
||||
BigNumberContext ctx;
|
||||
check(EC_POINT_mul(group, pub, prv, nullptr, nullptr, ctx));
|
||||
|
||||
BigNumber x;
|
||||
BigNumber y;
|
||||
EC_POINT_get_affine_coordinates(group, pub, x, y, ctx);
|
||||
|
||||
PublicKey public_key;
|
||||
public_key.type = private_key.type;
|
||||
public_key.compression = KeyCompression::NoCompression;
|
||||
public_key.x.resize(key_length(private_key.type));
|
||||
public_key.y.resize(key_length(private_key.type));
|
||||
BN_bn2binpad(x, public_key.x.data(), public_key.x.size());
|
||||
BN_bn2binpad(y, public_key.y.data(), public_key.y.size());
|
||||
return public_key;
|
||||
}
|
||||
|
||||
} // namespace openssl
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef BACKEND_OPENSSL_HPP_CRRV8DCH
|
||||
#define BACKEND_OPENSSL_HPP_CRRV8DCH
|
||||
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
namespace openssl
|
||||
{
|
||||
class Key;
|
||||
class Point;
|
||||
class Signature;
|
||||
} // namespace openssl
|
||||
|
||||
|
||||
/**
|
||||
* \brief Backend implementation based on OpenSSL
|
||||
*/
|
||||
class BackendOpenSsl : public Backend
|
||||
{
|
||||
public:
|
||||
static constexpr auto backend_name = "OpenSSL";
|
||||
|
||||
BackendOpenSsl();
|
||||
|
||||
/// \see Backend::sign_data
|
||||
EcdsaSignature sign_data(const ecdsa256::PrivateKey& private_key, const ByteBuffer& data_buffer) override;
|
||||
|
||||
/// \see Backend::sign_digest
|
||||
Signature sign_digest(const PrivateKey&, const ByteBuffer& digest) override;
|
||||
|
||||
/// \see Backend::verify_data
|
||||
bool verify_data(const ecdsa256::PublicKey& public_key, const ByteBuffer& data, const EcdsaSignature& sig) override;
|
||||
|
||||
/// \see Backend::verify_digest
|
||||
bool verify_digest(const PublicKey&, const ByteBuffer& digest, const Signature&) override;
|
||||
|
||||
ByteBuffer calculate_hash(HashAlgorithm, const ByteBuffer&) override;
|
||||
|
||||
/// \see Backend::decompress_point
|
||||
boost::optional<Uncompressed> decompress_point(const EccPoint& ecc_point) override;
|
||||
|
||||
/// \see Backend::generate_key_pair
|
||||
ecdsa256::KeyPair generate_key_pair() override;
|
||||
|
||||
private:
|
||||
/// calculate SHA256 digest of data buffer
|
||||
std::array<uint8_t, 32> calculate_sha256_digest(const ByteBuffer& data) const;
|
||||
|
||||
/// calculate SHA384 digest of data buffer
|
||||
std::array<uint8_t, 48> calculate_sha384_digest(const ByteBuffer& data) const;
|
||||
|
||||
/// convert to internal format of private key
|
||||
openssl::Key internal_private_key(const ecdsa256::PrivateKey&) const;
|
||||
openssl::Key internal_private_key(const PrivateKey&) const;
|
||||
|
||||
/// convert to internal format of public key
|
||||
openssl::Key internal_public_key(const ecdsa256::PublicKey&) const;
|
||||
openssl::Key internal_public_key(const PublicKey&) const;
|
||||
|
||||
/// convert to internal format of an EC point
|
||||
openssl::Point internal_ec_point(const PublicKey&) const;
|
||||
};
|
||||
|
||||
namespace openssl
|
||||
{
|
||||
/// Derive the public key from a private key (pub = priv * G), on the key's own curve.
|
||||
PublicKey derive_public_key(const PrivateKey& private_key);
|
||||
} // namespace openssl
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BACKEND_OPENSSL_HPP_CRRV8DCH */
|
||||
@@ -0,0 +1,148 @@
|
||||
#include <vanetza/security/cam_ssp.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
CamPermissions::CamPermissions() : m_bits(0)
|
||||
{
|
||||
}
|
||||
|
||||
CamPermissions::CamPermissions(CamPermission cp) : m_bits(static_cast<value_type>(cp))
|
||||
{
|
||||
}
|
||||
|
||||
CamPermissions::CamPermissions(const std::initializer_list<CamPermission>& cps) : m_bits(0)
|
||||
{
|
||||
for (CamPermission cp : cps) {
|
||||
add(cp);
|
||||
}
|
||||
}
|
||||
|
||||
bool CamPermissions::has(CamPermission cp) const
|
||||
{
|
||||
const auto cp_raw = static_cast<value_type>(cp);
|
||||
return (m_bits & cp_raw) == cp_raw;
|
||||
}
|
||||
|
||||
bool CamPermissions::has(const std::initializer_list<CamPermission>& cps) const
|
||||
{
|
||||
for (CamPermission cp : cps) {
|
||||
if (!has(cp)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CamPermissions::has(const CamPermissions& required) const
|
||||
{
|
||||
return (m_bits & required.m_bits) == required.m_bits;
|
||||
}
|
||||
|
||||
bool CamPermissions::none() const
|
||||
{
|
||||
return m_bits == 0;
|
||||
}
|
||||
|
||||
std::set<CamPermission> CamPermissions::permissions() const
|
||||
{
|
||||
std::set<CamPermission> perms;
|
||||
|
||||
std::underlying_type<CamPermission>::type bit = 1;
|
||||
for (unsigned i = 1; i < sizeof(bit) * 8; ++i) {
|
||||
CamPermission permission = static_cast<CamPermission>(bit);
|
||||
if (has(permission)) {
|
||||
perms.insert(permission);
|
||||
}
|
||||
bit <<= 1;
|
||||
}
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
CamPermissions& CamPermissions::add(CamPermission cp)
|
||||
{
|
||||
m_bits |= static_cast<value_type>(cp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CamPermissions& CamPermissions::remove(CamPermission cp)
|
||||
{
|
||||
m_bits &= ~static_cast<value_type>(cp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer CamPermissions::encode() const
|
||||
{
|
||||
return ByteBuffer({1, static_cast<uint8_t>(m_bits), static_cast<uint8_t>(m_bits >> 8) });
|
||||
}
|
||||
|
||||
CamPermissions CamPermissions::decode(const ByteBuffer& buffer)
|
||||
{
|
||||
CamPermissions permissions;
|
||||
if (buffer.size() == 3 && buffer[0] == 1) {
|
||||
permissions.m_bits = buffer[2];
|
||||
permissions.m_bits <<= 8;
|
||||
permissions.m_bits |= buffer[1];
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
||||
std::string stringify(CamPermission permission)
|
||||
{
|
||||
std::string result;
|
||||
switch (permission) {
|
||||
case CamPermission::CEN_DSRC_Tolling_Zone:
|
||||
result = "CEN DSRC Tolling Zone";
|
||||
break;
|
||||
case CamPermission::Public_Transport:
|
||||
result = "Public Transport";
|
||||
break;
|
||||
case CamPermission::Special_Transport:
|
||||
result = "Special Transport";
|
||||
break;
|
||||
case CamPermission::Dangerous_Goods:
|
||||
result = "Dangerous Goods";
|
||||
break;
|
||||
case CamPermission::Roadwork:
|
||||
result = "Roadwork";
|
||||
break;
|
||||
case CamPermission::Rescue:
|
||||
result = "Rescue";
|
||||
break;
|
||||
case CamPermission::Emergency:
|
||||
result = "Emergency";
|
||||
break;
|
||||
case CamPermission::Safety_Car:
|
||||
// everybody should have an AMG GT S Safety Car ;-)
|
||||
result = "Safety Car";
|
||||
break;
|
||||
case CamPermission::Closed_Lanes:
|
||||
result = "Closed Lanes";
|
||||
break;
|
||||
case CamPermission::Request_For_Right_Of_Way:
|
||||
result = "Request for Right of Way";
|
||||
break;
|
||||
case CamPermission::Request_For_Free_Crossing_At_Traffic_Light:
|
||||
result = "Request for Free Crossing at Traffic Light";
|
||||
break;
|
||||
case CamPermission::No_Passing:
|
||||
result = "No Passing";
|
||||
break;
|
||||
case CamPermission::No_Passing_For_Trucks:
|
||||
result = "No Passing for Trucks";
|
||||
break;
|
||||
case CamPermission::Speed_Limit:
|
||||
result = "Speed Limit";
|
||||
break;
|
||||
default:
|
||||
static_assert(sizeof(CamPermission) == 2, "Expected CamPermission to be 2 bytes wide");
|
||||
result = str(boost::format("Reserved (%0#6x)") % static_cast<uint16_t>(permission));
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,92 @@
|
||||
#ifndef CAM_SSP_HPP_GGBE8KES
|
||||
#define CAM_SSP_HPP_GGBE8KES
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/**
|
||||
* CAM permission bits
|
||||
*
|
||||
* These bitmasks describe CA station roles, e.g. emergency vehicles.
|
||||
* Some CAM data containers are linked to these roles. Misuse is opposed by checking CAM SSPs.
|
||||
*
|
||||
* \see EN 302 637-2 V1.3.2, Table 4
|
||||
*/
|
||||
enum class CamPermission : uint16_t
|
||||
{
|
||||
// first octet
|
||||
CEN_DSRC_Tolling_Zone = 0x80,
|
||||
Public_Transport = 0x40,
|
||||
Special_Transport = 0x20,
|
||||
Dangerous_Goods = 0x10,
|
||||
Roadwork = 0x08,
|
||||
Rescue = 0x04,
|
||||
Emergency = 0x02,
|
||||
Safety_Car = 0x01,
|
||||
|
||||
// second octet
|
||||
Closed_Lanes = 0x8000,
|
||||
Request_For_Right_Of_Way = 0x4000,
|
||||
Request_For_Free_Crossing_At_Traffic_Light = 0x2000,
|
||||
No_Passing = 0x1000,
|
||||
No_Passing_For_Trucks = 0x0800,
|
||||
Speed_Limit = 0x0400,
|
||||
// 0x0200 and 0x0100 are reserved for future usage
|
||||
};
|
||||
|
||||
/**
|
||||
* Set of CAM permissions, i.e. Service Specific Permissions
|
||||
*
|
||||
* \see EN 302 637-2 V1.3.2, section 6.2.2.2
|
||||
*/
|
||||
class CamPermissions
|
||||
{
|
||||
public:
|
||||
CamPermissions();
|
||||
CamPermissions(CamPermission);
|
||||
CamPermissions(const std::initializer_list<CamPermission>&);
|
||||
|
||||
// check for permission
|
||||
bool has(CamPermission) const;
|
||||
bool has(const std::initializer_list<CamPermission>&) const;
|
||||
bool has(const CamPermissions&) const;
|
||||
bool none() const;
|
||||
|
||||
/**
|
||||
* Get set of all included permissions
|
||||
* \return permission set
|
||||
*/
|
||||
std::set<CamPermission> permissions() const;
|
||||
|
||||
// permission manipulation (with chaining)
|
||||
CamPermissions& add(CamPermission);
|
||||
CamPermissions& remove(CamPermission);
|
||||
|
||||
// serialization helpers
|
||||
ByteBuffer encode() const;
|
||||
static CamPermissions decode(const ByteBuffer&);
|
||||
|
||||
private:
|
||||
using value_type = std::underlying_type<CamPermission>::type;
|
||||
value_type m_bits;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get string representation of CAM permission flag
|
||||
* \param cp CAM permission flag
|
||||
* \return string representation (empty for unknown flags)
|
||||
*/
|
||||
std::string stringify(CamPermission);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* CAM_SSP_HPP_GGBE8KES */
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef BC8469A6_CC39_4826_A95E_DE639D68863B
|
||||
#define BC8469A6_CC39_4826_A95E_DE639D68863B
|
||||
|
||||
#include <boost/optional/optional.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
enum class CertificateInvalidReason
|
||||
{
|
||||
Broken_Time_Period,
|
||||
Off_Time_Period,
|
||||
Unknown_Signer,
|
||||
Missing_Signature,
|
||||
Missing_Public_Key,
|
||||
Invalid_Signer,
|
||||
Invalid_Name,
|
||||
Excessive_Chain_Length,
|
||||
Off_Region,
|
||||
Inconsistent_With_Signer,
|
||||
Insufficient_ITS_AID,
|
||||
Missing_Subject_Assurance,
|
||||
};
|
||||
|
||||
class CertificateValidity
|
||||
{
|
||||
public:
|
||||
CertificateValidity() = default;
|
||||
|
||||
/**
|
||||
* Create CertificateValidity signalling an invalid certificate
|
||||
* \param reason Reason for invalidity
|
||||
*/
|
||||
CertificateValidity(CertificateInvalidReason reason) : m_reason(reason) {}
|
||||
|
||||
/**
|
||||
* \brief Create CertificateValidity signalling a valid certificate
|
||||
* This method is equivalent to default construction but should be more expressive.
|
||||
* \return validity
|
||||
*/
|
||||
static CertificateValidity valid() { return CertificateValidity(); }
|
||||
|
||||
/**
|
||||
* Check if status corresponds to a valid certificate
|
||||
* \return true if certificate is valid
|
||||
*/
|
||||
operator bool() const { return !m_reason; }
|
||||
|
||||
/**
|
||||
* \brief Get reason for certificate invalidity
|
||||
* This call is only safe if reason is available, i.e. check validity before!
|
||||
*
|
||||
* \return reason
|
||||
*/
|
||||
CertificateInvalidReason reason() const { return *m_reason; }
|
||||
|
||||
private:
|
||||
boost::optional<CertificateInvalidReason> m_reason;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BC8469A6_CC39_4826_A95E_DE639D68863B */
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <vanetza/security/decap_service.hpp>
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
bool is_successful(const DecapReport& report)
|
||||
{
|
||||
return report == VerificationReport::Success;
|
||||
}
|
||||
|
||||
bool operator==(const DecapReport& decap, VerificationReport verification)
|
||||
{
|
||||
struct Visitor : public boost::static_visitor<bool>
|
||||
{
|
||||
Visitor(VerificationReport report) : expected(report) {}
|
||||
|
||||
bool operator()(VerificationReport report) const
|
||||
{
|
||||
return report == expected;
|
||||
}
|
||||
|
||||
bool operator()(boost::blank) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
VerificationReport expected;
|
||||
};
|
||||
|
||||
return boost::apply_visitor(Visitor(verification), decap);
|
||||
}
|
||||
|
||||
bool operator==(VerificationReport verification, const DecapReport& decap)
|
||||
{
|
||||
return (decap == verification);
|
||||
}
|
||||
|
||||
DecapConfirm DecapConfirm::from(VerifyConfirm&& verify, const SecuredMessageView& msg_view)
|
||||
{
|
||||
DecapConfirm decap;
|
||||
decap.plaintext_payload = get_payload_copy(msg_view);
|
||||
decap.report = verify.report;
|
||||
decap.certificate_validity = verify.certificate_validity;
|
||||
decap.certificate_id = verify.certificate_id;
|
||||
decap.its_aid = verify.its_aid;
|
||||
decap.permissions = verify.permissions;
|
||||
return decap;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,91 @@
|
||||
#ifndef F815BB22_3075_4A9D_9385_07876D800765
|
||||
#define F815BB22_3075_4A9D_9385_07876D800765
|
||||
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/net/packet_variant.hpp>
|
||||
#include <vanetza/security/certificate_validity.hpp>
|
||||
#include <vanetza/security/hashed_id.hpp>
|
||||
#include <vanetza/security/secured_message.hpp>
|
||||
#include <vanetza/security/verify_service.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/**
|
||||
* \brief Input data for decapsulating a secured message.
|
||||
*
|
||||
* The structure is equivalent to VerifyRequest, however, decapsulation may
|
||||
* also deal with decryption in future versions.
|
||||
*
|
||||
* \see TS 102 723-8 v1.1.1 SN-DECAP.request
|
||||
*/
|
||||
struct DecapRequest
|
||||
{
|
||||
DecapRequest(SecuredMessageView sec_msg_view) : sec_packet(sec_msg_view) {}
|
||||
SecuredMessageView sec_packet;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief SN-DECAP.confirm report codes
|
||||
* \see TS 102 723-8 v1.1.1 table 27 (report field)
|
||||
*
|
||||
* Instead of duplicating VerificationReport values and the linked burden keeping
|
||||
* these values consistent, DecapReport is a variant incorporating VerificationReport.
|
||||
* When decryption is implemented, a DecryptionReport may be added to the variant.
|
||||
*
|
||||
* The boost::blank entry indicates that the SecuredMessage was neither signed nor encrypted.
|
||||
*/
|
||||
using DecapReport = boost::variant<boost::blank, VerificationReport>;
|
||||
|
||||
/**
|
||||
* \brief Check if report indicates a successful decapsulation.
|
||||
*
|
||||
* Either verification or decryption needs to be successful.
|
||||
* An unsecured message cannot lead to a succesful decapsulation result.
|
||||
*
|
||||
* \param report to check
|
||||
* \return true if either verification or decryption was successful
|
||||
*/
|
||||
bool is_successful(const DecapReport& report);
|
||||
|
||||
/**
|
||||
* \brief Check if decapsulation report matches a particular verification report.
|
||||
*
|
||||
* \param decap decapsulation report
|
||||
* \param verification verification report
|
||||
* \return true if decapsulation matches verification
|
||||
*/
|
||||
bool operator==(const DecapReport& decap, VerificationReport verification);
|
||||
bool operator==(VerificationReport verification, const DecapReport& decap);
|
||||
|
||||
/**
|
||||
* \brief SN-DECAP.confirm
|
||||
* \see TS 102 723-8 v1.1.1 table 27
|
||||
*/
|
||||
struct DecapConfirm
|
||||
{
|
||||
/**
|
||||
* Build DecapConfirm from verify outcome and secured message.
|
||||
*
|
||||
* \param verify_confirm outcome of verification
|
||||
* \param msg_view view of secured message
|
||||
* \return decapsulation confirmation
|
||||
*/
|
||||
static DecapConfirm from(VerifyConfirm&& verify_confirm, const SecuredMessageView& msg_view);
|
||||
|
||||
PacketVariant plaintext_payload; // mandatory (plaintext_packet_length also covered by data type)
|
||||
DecapReport report; // mandatory
|
||||
CertificateValidity certificate_validity; // non-standard extension
|
||||
boost::optional<HashedId8> certificate_id; // optional
|
||||
ItsAid its_aid; // mandatory (its_ait_lenth also covered by data type)
|
||||
ByteBuffer permissions; // mandatory
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* F815BB22_3075_4A9D_9385_07876D800765 */
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/delegating_security_entity.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
DelegatingSecurityEntity::DelegatingSecurityEntity(std::unique_ptr<SignService> sign, std::unique_ptr<VerifyService> verify) :
|
||||
m_sign_service(std::move(sign)),
|
||||
m_verify_service(std::move(verify))
|
||||
{
|
||||
if (!m_sign_service) {
|
||||
throw std::invalid_argument("SN-SIGN service is not callable");
|
||||
} else if (!m_verify_service) {
|
||||
throw std::invalid_argument("SN-VERIFY service is not callable");
|
||||
}
|
||||
}
|
||||
|
||||
EncapConfirm DelegatingSecurityEntity::encapsulate_packet(EncapRequest&& encap_request)
|
||||
{
|
||||
return dispatch(std::move(encap_request), m_sign_service.get());
|
||||
}
|
||||
|
||||
DecapConfirm DelegatingSecurityEntity::decapsulate_packet(DecapRequest&& decap_request)
|
||||
{
|
||||
SecuredMessageView msg_view { decap_request.sec_packet };
|
||||
auto verify_confirm = m_verify_service->verify(VerifyRequest { msg_view });
|
||||
return DecapConfirm::from(std::move(verify_confirm), msg_view);
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#ifndef DELEGATING_SECURITY_ENTITY_HPP_W1MFSVEN
|
||||
#define DELEGATING_SECURITY_ENTITY_HPP_W1MFSVEN
|
||||
|
||||
#include <vanetza/security/security_entity.hpp>
|
||||
#include <vanetza/security/sign_service.hpp>
|
||||
#include <vanetza/security/verify_service.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/**
|
||||
* Implementation of SecurityEntity delegating to SignService and VerifyService
|
||||
*/
|
||||
class DelegatingSecurityEntity : public SecurityEntity
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \brief Create security entity from primitive services.
|
||||
*
|
||||
* A std::invalid_argument exception is thrown at construction
|
||||
* if any given service is not callable.
|
||||
*
|
||||
* \param sign SN-SIGN service
|
||||
* \param verify SN-VERIFY service
|
||||
*/
|
||||
DelegatingSecurityEntity(std::unique_ptr<SignService> sign, std::unique_ptr<VerifyService> verify);
|
||||
|
||||
EncapConfirm encapsulate_packet(EncapRequest&& encap_request) override;
|
||||
DecapConfirm decapsulate_packet(DecapRequest&& decap_request) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<SignService> m_sign_service;
|
||||
std::unique_ptr<VerifyService> m_verify_service;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* DELEGATING_SECURITY_ENTITY_HPP_W1MFSVEN */
|
||||
@@ -0,0 +1,102 @@
|
||||
#include <vanetza/security/ecc_point.hpp>
|
||||
#include <vanetza/security/ecdsa256.hpp>
|
||||
#include <vanetza/security/public_key.hpp>
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
class EccPointVisitor : public boost::static_visitor<ByteBuffer>
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
ByteBuffer operator()(const T& point)
|
||||
{
|
||||
return point.x;
|
||||
}
|
||||
};
|
||||
|
||||
ByteBuffer convert_for_signing(const EccPoint& ecc_point)
|
||||
{
|
||||
EccPointVisitor visit;
|
||||
return boost::apply_visitor(visit, ecc_point);
|
||||
}
|
||||
|
||||
class EccPointLengthVisitor : public boost::static_visitor<std::size_t>
|
||||
{
|
||||
public:
|
||||
std::size_t operator()(const X_Coordinate_Only& x_only) const
|
||||
{
|
||||
return x_only.x.size();
|
||||
}
|
||||
|
||||
std::size_t operator()(const Compressed_Lsb_Y_0& y0) const
|
||||
{
|
||||
return y0.x.size();
|
||||
}
|
||||
|
||||
std::size_t operator()(const Compressed_Lsb_Y_1& y1) const
|
||||
{
|
||||
return y1.x.size();
|
||||
}
|
||||
|
||||
std::size_t operator()(const Uncompressed& unc) const
|
||||
{
|
||||
return unc.x.size() + unc.y.size();
|
||||
}
|
||||
};
|
||||
|
||||
std::size_t get_length(const EccPoint& point)
|
||||
{
|
||||
EccPointLengthVisitor visitor;
|
||||
return boost::apply_visitor(visitor, point);
|
||||
}
|
||||
|
||||
EccPoint compress_public_key(const PublicKey& public_key)
|
||||
{
|
||||
switch (public_key.compression)
|
||||
{
|
||||
case KeyCompression::NoCompression:
|
||||
if (!public_key.y.empty() && public_key.y.back() & 0x01) {
|
||||
return Compressed_Lsb_Y_1 {public_key.x };
|
||||
} else {
|
||||
return Compressed_Lsb_Y_0 {public_key.x };
|
||||
}
|
||||
case KeyCompression::Y0:
|
||||
return Compressed_Lsb_Y_0 {public_key.x };
|
||||
case KeyCompression::Y1:
|
||||
return Compressed_Lsb_Y_1 {public_key.x };
|
||||
default:
|
||||
return Compressed_Lsb_Y_0 {};
|
||||
}
|
||||
}
|
||||
|
||||
EccPoint make_ecc_point(const PublicKey& public_key)
|
||||
{
|
||||
switch (public_key.compression)
|
||||
{
|
||||
case KeyCompression::NoCompression:
|
||||
return Uncompressed { public_key.x, public_key.y };
|
||||
case KeyCompression::Y0:
|
||||
return Compressed_Lsb_Y_0 { public_key.x };
|
||||
case KeyCompression::Y1:
|
||||
return Compressed_Lsb_Y_1 { public_key.x };
|
||||
}
|
||||
throw std::invalid_argument("public key has an unknown point compression");
|
||||
}
|
||||
|
||||
EccPoint compress_public_key(const ecdsa256::PublicKey& public_key)
|
||||
{
|
||||
if (!public_key.y.empty() && public_key.y.back() & 0x01) {
|
||||
return Compressed_Lsb_Y_1 { ByteBuffer { public_key.x.begin(), public_key.x.end() } };
|
||||
} else {
|
||||
return Compressed_Lsb_Y_0 { ByteBuffer { public_key.x.begin(), public_key.x.end() } };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,83 @@
|
||||
#ifndef CFE4DC34_AD31_47E8_9EB2_1B98C6FB0887
|
||||
#define CFE4DC34_AD31_47E8_9EB2_1B98C6FB0887
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
struct PublicKey;
|
||||
namespace ecdsa256 { struct PublicKey; }
|
||||
|
||||
/// X_Coordinate_Only specified in TS 103 097 v1.2.1 in section 4.2.5
|
||||
struct X_Coordinate_Only
|
||||
{
|
||||
ByteBuffer x;
|
||||
};
|
||||
|
||||
/// Compressed_Lsb_Y_0 specified in TS 103 097 v1.2.1 in section 4.2.5
|
||||
struct Compressed_Lsb_Y_0
|
||||
{
|
||||
ByteBuffer x;
|
||||
};
|
||||
|
||||
/// Compressed_Lsb_Y_1 specified in TS 103 097 v1.2.1 in section 4.2.5
|
||||
struct Compressed_Lsb_Y_1
|
||||
{
|
||||
ByteBuffer x;
|
||||
};
|
||||
|
||||
/// Uncompressed specified in TS 103 097 v1.2.1 in section 4.2.5
|
||||
struct Uncompressed
|
||||
{
|
||||
ByteBuffer x;
|
||||
ByteBuffer y;
|
||||
};
|
||||
|
||||
/// EccPoint specified in TS 103 097 v1.2.1 in section 4.2.5
|
||||
using EccPoint = boost::variant<
|
||||
X_Coordinate_Only,
|
||||
Compressed_Lsb_Y_0,
|
||||
Compressed_Lsb_Y_1,
|
||||
Uncompressed
|
||||
>;
|
||||
|
||||
/**
|
||||
* \brief calculate byte length of ECC point structure
|
||||
* \param ecc_point
|
||||
* \return length in bytes
|
||||
*/
|
||||
std::size_t get_length(const EccPoint& ecc_point);
|
||||
|
||||
/**
|
||||
* \brief Convert EccPoint for signature calculation
|
||||
* Uses ecc_point.x as relevant field for signatures.
|
||||
*
|
||||
* \param ecc_point
|
||||
* \return binary representation of ECC point
|
||||
*/
|
||||
ByteBuffer convert_for_signing(const EccPoint& ecc_point);
|
||||
|
||||
/**
|
||||
* \brief Convert a public key into an ECC point, preserving its compression
|
||||
* \param public_key public key (uncompressed or compressed)
|
||||
* \return ECC point matching the key's own point format
|
||||
*/
|
||||
EccPoint make_ecc_point(const PublicKey& public_key);
|
||||
|
||||
/**
|
||||
* \brief Compressed ECC point from public key
|
||||
* \param public_key
|
||||
* \return compressed ECC point
|
||||
*/
|
||||
EccPoint compress_public_key(const PublicKey& public_key);
|
||||
EccPoint compress_public_key(const ecdsa256::PublicKey& public_key);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* CFE4DC34_AD31_47E8_9EB2_1B98C6FB0887 */
|
||||
@@ -0,0 +1,67 @@
|
||||
#include <vanetza/security/ecc_point.hpp>
|
||||
#include <vanetza/security/ecdsa256.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace ecdsa256
|
||||
{
|
||||
|
||||
PublicKey create_public_key(const Uncompressed& unc)
|
||||
{
|
||||
PublicKey pb;
|
||||
assert(unc.x.size() == pb.x.size());
|
||||
assert(unc.y.size() == pb.y.size());
|
||||
std::copy_n(unc.x.begin(), pb.x.size(), pb.x.begin());
|
||||
std::copy_n(unc.y.begin(), pb.y.size(), pb.y.begin());
|
||||
return pb;
|
||||
}
|
||||
|
||||
bool operator==(const PublicKey& lhs, const PublicKey& rhs)
|
||||
{
|
||||
return lhs.x == rhs.x && lhs.y == rhs.y;
|
||||
}
|
||||
|
||||
bool operator!=(const PublicKey& lhs, const PublicKey& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator==(const PrivateKey& lhs, const PrivateKey& rhs)
|
||||
{
|
||||
return lhs.key == rhs.key;
|
||||
}
|
||||
|
||||
bool operator!=(const PrivateKey& lhs, const PrivateKey& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace ecdsa256
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
using PublicKey = vanetza::security::ecdsa256::PublicKey;
|
||||
using PrivateKey = vanetza::security::ecdsa256::PrivateKey;
|
||||
|
||||
size_t hash<PublicKey>::operator()(const PublicKey& k) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
boost::hash_combine(seed, k.x);
|
||||
boost::hash_combine(seed, k.y);
|
||||
return seed;
|
||||
}
|
||||
|
||||
size_t hash<PrivateKey>::operator()(const PrivateKey& k) const
|
||||
{
|
||||
return boost::hash_range(k.key.begin(), k.key.end());
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef ECDSA256_HPP_IOXLJFVZ
|
||||
#define ECDSA256_HPP_IOXLJFVZ
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
struct Uncompressed;
|
||||
|
||||
// do not use ecdsa256 for new v3 code, only for legacy v2 security!
|
||||
namespace ecdsa256
|
||||
{
|
||||
|
||||
constexpr unsigned digest_octets = 32;
|
||||
|
||||
struct PublicKey
|
||||
{
|
||||
std::array<uint8_t, digest_octets> x;
|
||||
std::array<uint8_t, digest_octets> y;
|
||||
};
|
||||
|
||||
bool operator==(const PublicKey& lhs, const PublicKey& rhs);
|
||||
bool operator!=(const PublicKey& lhs, const PublicKey& rhs);
|
||||
|
||||
|
||||
struct PrivateKey
|
||||
{
|
||||
std::array<uint8_t, digest_octets> key;
|
||||
};
|
||||
|
||||
bool operator==(const PrivateKey& lhs, const PrivateKey& rhs);
|
||||
bool operator!=(const PrivateKey& lhs, const PrivateKey& rhs);
|
||||
|
||||
|
||||
struct KeyPair
|
||||
{
|
||||
PrivateKey private_key;
|
||||
PublicKey public_key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create generic public key from uncompressed ECC point
|
||||
* \param unc Uncompressed ECC point
|
||||
* \return public key
|
||||
*/
|
||||
PublicKey create_public_key(const Uncompressed&);
|
||||
|
||||
} // namespace ecdsa256
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template<>
|
||||
struct hash<vanetza::security::ecdsa256::PublicKey>
|
||||
{
|
||||
size_t operator()(const vanetza::security::ecdsa256::PublicKey&) const;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct hash<vanetza::security::ecdsa256::PrivateKey>
|
||||
{
|
||||
size_t operator()(const vanetza::security::ecdsa256::PrivateKey&) const;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif /* ECDSA256_HPP_IOXLJFVZ */
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <vanetza/security/encap_service.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
EncapConfirm EncapConfirm::from(SignConfirm&& sign_confirm)
|
||||
{
|
||||
if (sign_confirm.secured_message) {
|
||||
return EncapConfirm { std::move(*sign_confirm.secured_message) };
|
||||
} else {
|
||||
return EncapConfirm { sign_confirm.error };
|
||||
}
|
||||
}
|
||||
|
||||
const SecuredMessage* EncapConfirm::secured_message() const
|
||||
{
|
||||
return boost::get<SecuredMessage>(this);
|
||||
}
|
||||
|
||||
SecuredMessage* EncapConfirm::secured_message()
|
||||
{
|
||||
return boost::get<SecuredMessage>(this);
|
||||
}
|
||||
|
||||
EncapConfirm dispatch(EncapRequest&& encap_request, SignService* sign_service)
|
||||
{
|
||||
struct Dispatcher : boost::static_visitor<EncapConfirm>
|
||||
{
|
||||
Dispatcher(SignService* sign_service) : m_sign_service(sign_service) {}
|
||||
|
||||
EncapConfirm operator()(SignRequest& request)
|
||||
{
|
||||
if (m_sign_service) {
|
||||
return EncapConfirm::from(m_sign_service->sign(std::move(request)));
|
||||
} else {
|
||||
return EncapConfirm::from(SignConfirm::failure(SignConfirmError::No_Service));
|
||||
}
|
||||
}
|
||||
|
||||
SignService* m_sign_service;
|
||||
};
|
||||
|
||||
Dispatcher dispatcher(sign_service);
|
||||
return boost::apply_visitor(dispatcher, encap_request);
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef FD3D1A69_8B8D_4DC1_8975_9FEB7A791B56
|
||||
#define FD3D1A69_8B8D_4DC1_8975_9FEB7A791B56
|
||||
|
||||
#include <vanetza/security/secured_message.hpp>
|
||||
#include <vanetza/security/sign_service.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/**
|
||||
* Input data for encapsulating a packet in a secured message.
|
||||
*
|
||||
* According to TS 102 723-8 V1.1.1, SN-ENCAP.request offers the very same
|
||||
* functionality as SN-SIGN.request and SN-ENCRYPT.request.
|
||||
*
|
||||
* Since encryption is not yet implemented, so only SignRequest is included.
|
||||
*/
|
||||
using EncapRequest = boost::variant<SignRequest>;
|
||||
|
||||
/**
|
||||
* Confirmation of the encapsulation process (SN-ENCAP.confirm).
|
||||
*
|
||||
* TS 102 723-8 V1.1.1 includes only sec_packet but cannot report any errors.
|
||||
* Instead of throwing exceptions, the variant may convey an error code instead
|
||||
* of a SecuredMessage.
|
||||
*/
|
||||
struct EncapConfirm : public boost::variant<SecuredMessage, SignConfirmError>
|
||||
{
|
||||
public:
|
||||
static EncapConfirm from(SignConfirm&&);
|
||||
|
||||
const SecuredMessage* secured_message() const;
|
||||
SecuredMessage* secured_message();
|
||||
|
||||
private:
|
||||
using variant::variant;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch an encapsulation request to the responsible service.
|
||||
*
|
||||
* \param encap_request SN-ENCAP.request data
|
||||
* \param sign_service service handling signing requests
|
||||
* \return SN-ENCAP.confirm
|
||||
*/
|
||||
EncapConfirm dispatch(EncapRequest&& encap_request, SignService* sign_service);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* FD3D1A69_8B8D_4DC1_8975_9FEB7A791B56 */
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef EXCEPTION_HPP_IY8LEMBQ
|
||||
#define EXCEPTION_HPP_IY8LEMBQ
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/// thrown when a serialization error occurred
|
||||
class serialization_error : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/// thrown when a deserialization error occurred
|
||||
class deserialization_error : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* EXCEPTION_HPP_IY8LEMBQ */
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef AE32B993_652E_4D59_A9A9_AF3C20995F46
|
||||
#define AE32B993_652E_4D59_A9A9_AF3C20995F46
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
enum class HashAlgorithm
|
||||
{
|
||||
Unspecified,
|
||||
SHA256,
|
||||
SHA384,
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* AE32B993_652E_4D59_A9A9_AF3C20995F46 */
|
||||
@@ -0,0 +1,81 @@
|
||||
#include <vanetza/security/hashed_id.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
HashedId3 truncate(const HashedId8& in)
|
||||
{
|
||||
HashedId3 out;
|
||||
assert(out.size() <= in.size());
|
||||
std::copy_n(in.rbegin(), out.size(), out.rbegin());
|
||||
return out;
|
||||
}
|
||||
|
||||
HashedId8 create_hashed_id8(const Sha256Digest& digest)
|
||||
{
|
||||
HashedId8 hashed;
|
||||
std::copy(digest.end() - 8, digest.end(), hashed.data());
|
||||
return hashed;
|
||||
}
|
||||
|
||||
HashedId8 create_hashed_id8(const Sha384Digest& digest)
|
||||
{
|
||||
HashedId8 hashed;
|
||||
std::copy(digest.end() - 8, digest.end(), hashed.data());
|
||||
return hashed;
|
||||
}
|
||||
|
||||
std::string to_string(const vanetza::security::HashedId3& digest)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
for (uint8_t octet : digest)
|
||||
{
|
||||
ss << std::setw(2) << static_cast<unsigned>(octet);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string to_string(const vanetza::security::HashedId8& digest)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
for (uint8_t octet : digest)
|
||||
{
|
||||
ss << std::setw(2) << static_cast<unsigned>(octet);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
size_t hash<vanetza::security::HashedId3>::operator()(const vanetza::security::HashedId3& hid3) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
for (uint8_t octet : hid3) {
|
||||
boost::hash_combine(seed, octet);
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
size_t hash<vanetza::security::HashedId8>::operator()(const vanetza::security::HashedId8& hid8) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
for (uint8_t octet : hid8) {
|
||||
boost::hash_combine(seed, octet);
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef CE45A952_0EE7_4D20_82CB_D42BF87F5B15
|
||||
#define CE45A952_0EE7_4D20_82CB_D42BF87F5B15
|
||||
|
||||
#include <vanetza/security/sha.hpp>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
using HashedId8 = std::array<uint8_t, 8>;
|
||||
using HashedId3 = std::array<uint8_t, 3>;
|
||||
|
||||
HashedId3 truncate(const HashedId8&);
|
||||
|
||||
HashedId8 create_hashed_id8(const Sha256Digest&);
|
||||
HashedId8 create_hashed_id8(const Sha384Digest&);
|
||||
|
||||
std::string to_string(const vanetza::security::HashedId3&);
|
||||
std::string to_string(const vanetza::security::HashedId8&);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
namespace std
|
||||
{
|
||||
// std::hash specialization for HashedId3
|
||||
template<> struct hash<vanetza::security::HashedId3>
|
||||
{
|
||||
size_t operator()(const vanetza::security::HashedId3&) const;
|
||||
};
|
||||
|
||||
/// std::hash specialization for HashedId8
|
||||
template<> struct hash<vanetza::security::HashedId8>
|
||||
{
|
||||
size_t operator()(const vanetza::security::HashedId8&) const;
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
#endif /* CE45A952_0EE7_4D20_82CB_D42BF87F5B15 */
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <vanetza/security/hmac.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL
|
||||
#include <openssl/hmac.h>
|
||||
#endif
|
||||
|
||||
#if defined VANETZA_WITH_CRYPTOPP
|
||||
#include <cryptopp/hmac.h>
|
||||
#include <cryptopp/sha.h>
|
||||
#endif
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL
|
||||
KeyTag create_hmac_tag_openssl(const ByteBuffer& data, const HmacKey& hmacKey)
|
||||
{
|
||||
KeyTag keyTag;
|
||||
std::array<std::uint8_t, EVP_MAX_MD_SIZE> tag;
|
||||
unsigned int len = 0;
|
||||
HMAC(EVP_sha256(), hmacKey.data(), static_cast<int>(hmacKey.size()),
|
||||
data.data(), data.size(), tag.data(), &len);
|
||||
std::copy_n(tag.data(), keyTag.size(), keyTag.data());
|
||||
return keyTag;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined VANETZA_WITH_CRYPTOPP
|
||||
KeyTag create_hmac_tag_cryptopp(const ByteBuffer& data, const HmacKey& hmacKey)
|
||||
{
|
||||
KeyTag keyTag;
|
||||
std::array<std::uint8_t, 32> tag;
|
||||
CryptoPP::HMAC<CryptoPP::SHA256> mac(hmacKey.data(), hmacKey.size());
|
||||
mac.Update(data.data(), data.size());
|
||||
mac.Final(tag.data());
|
||||
std::copy_n(tag.data(), keyTag.size(), keyTag.data());
|
||||
return keyTag;
|
||||
}
|
||||
#endif
|
||||
|
||||
KeyTag create_hmac_tag(const ByteBuffer& data, const HmacKey& hmacKey)
|
||||
{
|
||||
#if defined VANETZA_WITH_OPENSSL
|
||||
return create_hmac_tag_openssl(data, hmacKey);
|
||||
#elif defined VANETZA_WITH_CRYPTOPP
|
||||
return create_hmac_tag_cryptopp(data, hmacKey);
|
||||
#else
|
||||
# warning "no HMAC implementation available"
|
||||
return KeyTag {};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
using HmacKey = std::array<uint8_t, 32>;
|
||||
using KeyTag = std::array<uint8_t, 16>;
|
||||
|
||||
/**
|
||||
* \brief generate HMAC key and create HMAC tag on data
|
||||
* \param data data to be tagged
|
||||
* \param hmacKey generated HMAC key
|
||||
* \return tag of data generated with hmacKey
|
||||
*/
|
||||
KeyTag create_hmac_tag(const vanetza::ByteBuffer& data, const HmacKey& hmacKey);
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL
|
||||
KeyTag create_hmac_tag_openssl(const vanetza::ByteBuffer& data, const HmacKey& hmacKey);
|
||||
#endif
|
||||
|
||||
#if defined VANETZA_WITH_CRYPTOPP
|
||||
KeyTag create_hmac_tag_cryptopp(const vanetza::ByteBuffer& data, const HmacKey& hmacKey);
|
||||
#endif
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <vanetza/security/key_type.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
std::size_t key_length(KeyType key_type)
|
||||
{
|
||||
switch (key_type)
|
||||
{
|
||||
case KeyType::NistP256:
|
||||
case KeyType::BrainpoolP256r1:
|
||||
return 32;
|
||||
case KeyType::BrainpoolP384r1:
|
||||
return 48;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
enum class KeyType
|
||||
{
|
||||
Unspecified,
|
||||
NistP256, // also known as prime256v1
|
||||
BrainpoolP256r1,
|
||||
BrainpoolP384r1
|
||||
};
|
||||
|
||||
/**
|
||||
* Length of private key in bytes
|
||||
* \param type key type
|
||||
* \param length in bytes
|
||||
*/
|
||||
std::size_t key_length(KeyType type);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,242 @@
|
||||
#include <vanetza/security/openssl_wrapper.hpp>
|
||||
#include <vanetza/security/public_key.hpp>
|
||||
#include <vanetza/security/signature.hpp>
|
||||
#include <cassert>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace openssl
|
||||
{
|
||||
|
||||
void check(bool valid)
|
||||
{
|
||||
if (!valid) {
|
||||
throw Exception();
|
||||
}
|
||||
}
|
||||
|
||||
static const char* reason_error_string(Exception::code_type err)
|
||||
{
|
||||
const char* msg = ERR_reason_error_string(err);
|
||||
return msg ? msg : "unknown reason";
|
||||
}
|
||||
|
||||
static const char* library_error_string(Exception::code_type err)
|
||||
{
|
||||
const char* msg = ERR_lib_error_string(err);
|
||||
return msg ? msg : "unknown library";
|
||||
}
|
||||
|
||||
Exception::Exception() : Exception(ERR_get_error())
|
||||
{
|
||||
}
|
||||
|
||||
Exception::Exception(code_type err) :
|
||||
Exception(err, reason_error_string(err))
|
||||
{
|
||||
}
|
||||
|
||||
Exception::Exception(const std::string& msg) :
|
||||
std::runtime_error(msg),
|
||||
m_error(ERR_get_error())
|
||||
{
|
||||
}
|
||||
|
||||
Exception::Exception(code_type err, const std::string& msg) :
|
||||
std::runtime_error(msg),
|
||||
m_error(err)
|
||||
{
|
||||
}
|
||||
|
||||
const char* Exception::reason() const
|
||||
{
|
||||
return reason_error_string(m_error);
|
||||
}
|
||||
|
||||
const char* Exception::library() const
|
||||
{
|
||||
return library_error_string(m_error);
|
||||
}
|
||||
|
||||
BigNumber::BigNumber() : bignum(BN_new())
|
||||
{
|
||||
check(bignum != nullptr);
|
||||
}
|
||||
|
||||
BigNumber::BigNumber(const uint8_t* arr, std::size_t len) : bignum(BN_bin2bn(arr, len, nullptr))
|
||||
{
|
||||
check(bignum != nullptr);
|
||||
}
|
||||
|
||||
BigNumber::~BigNumber()
|
||||
{
|
||||
BN_clear_free(bignum);
|
||||
}
|
||||
|
||||
BigNumberContext::BigNumberContext() : ctx(BN_CTX_new())
|
||||
{
|
||||
check(ctx != nullptr);
|
||||
BN_CTX_start(ctx);
|
||||
}
|
||||
|
||||
BigNumberContext::~BigNumberContext()
|
||||
{
|
||||
BN_CTX_end(ctx);
|
||||
BN_CTX_free(ctx);
|
||||
}
|
||||
|
||||
Point::Point(const EC_GROUP* group) : point(EC_POINT_new(group))
|
||||
{
|
||||
check(point != nullptr);
|
||||
}
|
||||
|
||||
Point::Point(Point&& other) : point(nullptr)
|
||||
{
|
||||
std::swap(point, other.point);
|
||||
}
|
||||
|
||||
Point& Point::operator=(Point&& other)
|
||||
{
|
||||
std::swap(point, other.point);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Point::~Point()
|
||||
{
|
||||
EC_POINT_free(point);
|
||||
}
|
||||
|
||||
Group::Group(int nid) : group(EC_GROUP_new_by_curve_name(nid))
|
||||
{
|
||||
check(group != nullptr);
|
||||
}
|
||||
|
||||
Group::~Group()
|
||||
{
|
||||
EC_GROUP_clear_free(group);
|
||||
}
|
||||
|
||||
Signature::Signature(ECDSA_SIG* sig) : signature(sig)
|
||||
{
|
||||
check(signature);
|
||||
}
|
||||
|
||||
Signature::Signature(const EcdsaSignature& ecdsa) :
|
||||
Signature(convert_for_signing(ecdsa.R), ecdsa.s)
|
||||
{
|
||||
}
|
||||
|
||||
Signature::Signature(const security::Signature& sig) :
|
||||
Signature(sig.r, sig.s)
|
||||
{
|
||||
}
|
||||
|
||||
Signature::Signature(const ByteBuffer& r, const ByteBuffer& s) :
|
||||
signature(ECDSA_SIG_new())
|
||||
{
|
||||
check(signature);
|
||||
// ownership of big numbers is transfered by calling ECDSA_SIG_set0!
|
||||
BIGNUM* bn_r = BN_bin2bn(r.data(), r.size(), nullptr);
|
||||
BIGNUM* bn_s = BN_bin2bn(s.data(), s.size(), nullptr);
|
||||
if (!ECDSA_SIG_set0(signature, bn_r, bn_s)) {
|
||||
BN_clear_free(bn_r);
|
||||
BN_clear_free(bn_s);
|
||||
throw Exception();
|
||||
}
|
||||
}
|
||||
|
||||
Signature::Signature(Signature&& other) : signature(nullptr)
|
||||
{
|
||||
std::swap(signature, other.signature);
|
||||
}
|
||||
|
||||
Signature& Signature::operator=(Signature&& other)
|
||||
{
|
||||
std::swap(signature, other.signature);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Signature::~Signature()
|
||||
{
|
||||
ECDSA_SIG_free(signature);
|
||||
}
|
||||
|
||||
Key::Key() : eckey(EC_KEY_new())
|
||||
{
|
||||
check(eckey);
|
||||
}
|
||||
|
||||
Key::Key(int nid) : eckey(EC_KEY_new_by_curve_name(nid))
|
||||
{
|
||||
check(eckey);
|
||||
}
|
||||
|
||||
Key::Key(EC_KEY* key) : eckey(key)
|
||||
{
|
||||
}
|
||||
|
||||
Key::Key(Key&& other) : eckey(nullptr)
|
||||
{
|
||||
std::swap(eckey, other.eckey);
|
||||
}
|
||||
|
||||
Key& Key::operator=(Key&& other)
|
||||
{
|
||||
std::swap(eckey, other.eckey);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Key::~Key()
|
||||
{
|
||||
EC_KEY_free(eckey);
|
||||
}
|
||||
|
||||
Bio::Bio(BIO* bio) : bio(bio)
|
||||
{
|
||||
}
|
||||
|
||||
Bio::Bio(Bio&& other) : bio(nullptr)
|
||||
{
|
||||
std::swap(bio, other.bio);
|
||||
}
|
||||
|
||||
Bio& Bio::operator=(Bio&& other)
|
||||
{
|
||||
std::swap(bio, other.bio);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bio::~Bio()
|
||||
{
|
||||
BIO_free(bio);
|
||||
}
|
||||
|
||||
EvpKey::EvpKey(EVP_PKEY* pkey) : pkey(pkey)
|
||||
{
|
||||
}
|
||||
|
||||
EvpKey::EvpKey(EvpKey&& other) : pkey(nullptr)
|
||||
{
|
||||
std::swap(pkey, other.pkey);
|
||||
}
|
||||
|
||||
EvpKey& EvpKey::operator=(EvpKey&& other)
|
||||
{
|
||||
std::swap(pkey, other.pkey);
|
||||
return *this;
|
||||
}
|
||||
|
||||
EvpKey::~EvpKey()
|
||||
{
|
||||
EVP_PKEY_free(pkey);
|
||||
}
|
||||
|
||||
} // namespace openssl
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,198 @@
|
||||
#ifndef OPENSSL_WRAPPER_HPP_GUFJGICT
|
||||
#define OPENSSL_WRAPPER_HPP_GUFJGICT
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <boost/core/noncopyable.hpp>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/err.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
struct EcdsaSignature;
|
||||
struct Signature;
|
||||
|
||||
namespace openssl
|
||||
{
|
||||
|
||||
/**
|
||||
* Check valid condition
|
||||
* \param valid If false stored OpenSSL error will be thrown
|
||||
*/
|
||||
void check(bool valid);
|
||||
|
||||
|
||||
class Exception : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using code_type = decltype(ERR_get_error());
|
||||
|
||||
Exception();
|
||||
explicit Exception(code_type err);
|
||||
explicit Exception(const std::string& msg);
|
||||
explicit Exception(code_type, const std::string& msg);
|
||||
~Exception() override = default;
|
||||
|
||||
code_type error_code() const { return m_error; }
|
||||
const char* reason() const;
|
||||
const char* library() const;
|
||||
|
||||
private:
|
||||
code_type m_error = 0;
|
||||
};
|
||||
|
||||
|
||||
class BigNumber : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
BigNumber();
|
||||
~BigNumber();
|
||||
|
||||
BigNumber(const ByteBuffer& buf) :
|
||||
BigNumber(buf.data(), buf.size())
|
||||
{
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
BigNumber(const std::array<uint8_t, N>& bin) :
|
||||
BigNumber(bin.data(), bin.size())
|
||||
{
|
||||
}
|
||||
|
||||
operator BIGNUM*() { return bignum; }
|
||||
|
||||
private:
|
||||
BigNumber(const uint8_t*, std::size_t);
|
||||
|
||||
BIGNUM* bignum;
|
||||
};
|
||||
|
||||
|
||||
class BigNumberContext : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
BigNumberContext();
|
||||
~BigNumberContext();
|
||||
|
||||
operator BN_CTX*() { return ctx; }
|
||||
|
||||
private:
|
||||
BN_CTX* ctx;
|
||||
};
|
||||
|
||||
|
||||
class Group : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
explicit Group(int nid);
|
||||
~Group();
|
||||
|
||||
operator EC_GROUP*() { return group; }
|
||||
|
||||
private:
|
||||
EC_GROUP* group;
|
||||
};
|
||||
|
||||
|
||||
class Point : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
explicit Point(const EC_GROUP* group);
|
||||
Point(Point&&);
|
||||
Point& operator=(Point&&);
|
||||
~Point();
|
||||
|
||||
operator EC_POINT*() { return point; }
|
||||
|
||||
private:
|
||||
EC_POINT* point;
|
||||
};
|
||||
|
||||
|
||||
class Signature : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
explicit Signature(ECDSA_SIG* sig);
|
||||
explicit Signature(const EcdsaSignature& sig);
|
||||
explicit Signature(const security::Signature& sig);
|
||||
Signature(Signature&&);
|
||||
Signature& operator=(Signature&&);
|
||||
~Signature();
|
||||
|
||||
operator const ECDSA_SIG*() { return signature; }
|
||||
const ECDSA_SIG* operator->() const { return signature; }
|
||||
ECDSA_SIG* operator->() { return signature; }
|
||||
operator bool() { return signature != nullptr; }
|
||||
|
||||
private:
|
||||
Signature(const ByteBuffer& r, const ByteBuffer& s);
|
||||
|
||||
ECDSA_SIG* signature;
|
||||
};
|
||||
|
||||
|
||||
class Key
|
||||
{
|
||||
public:
|
||||
Key();
|
||||
explicit Key(int nid);
|
||||
explicit Key(EC_KEY* key);
|
||||
// non-copyable
|
||||
Key(const Key&) = delete;
|
||||
Key& operator=(const Key&) = delete;
|
||||
// but movable
|
||||
Key(Key&&);
|
||||
Key& operator=(Key&&);
|
||||
~Key();
|
||||
|
||||
operator EC_KEY*() { return eckey; }
|
||||
operator bool() const { return eckey != nullptr; }
|
||||
|
||||
private:
|
||||
EC_KEY* eckey;
|
||||
};
|
||||
|
||||
|
||||
class Bio : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
explicit Bio(BIO* bio);
|
||||
Bio(Bio&&);
|
||||
Bio& operator=(Bio&&);
|
||||
~Bio();
|
||||
|
||||
operator BIO*() { return bio; }
|
||||
operator bool() const { return bio != nullptr; }
|
||||
|
||||
private:
|
||||
BIO* bio;
|
||||
};
|
||||
|
||||
|
||||
class EvpKey : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
explicit EvpKey(EVP_PKEY* pkey);
|
||||
EvpKey(EvpKey&&);
|
||||
EvpKey& operator=(EvpKey&&);
|
||||
~EvpKey();
|
||||
|
||||
operator EVP_PKEY*() { return pkey; }
|
||||
operator bool() const { return pkey != nullptr; }
|
||||
|
||||
private:
|
||||
EVP_PKEY* pkey;
|
||||
};
|
||||
|
||||
} // namespace openssl
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* OPENSSL_WRAPPER_HPP_GUFJGICT */
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <vanetza/security/peer_request_tracker.hpp>
|
||||
#include <cassert>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
PeerRequestTracker::PeerRequestTracker(std::size_t limit) : m_limit(limit)
|
||||
{
|
||||
}
|
||||
|
||||
void PeerRequestTracker::add_request(const HashedId3& id)
|
||||
{
|
||||
if (!is_pending(id)) {
|
||||
// drop tail item when queue becomes full
|
||||
if (m_fifo.size() >= m_limit && !m_fifo.empty()) {
|
||||
m_lookup.erase(m_fifo.front());
|
||||
m_fifo.pop_front();
|
||||
}
|
||||
|
||||
auto inserted = m_fifo.insert(m_fifo.end(), id);
|
||||
m_lookup.emplace(id, inserted);
|
||||
}
|
||||
|
||||
assert(m_fifo.size() == m_lookup.size());
|
||||
}
|
||||
|
||||
void PeerRequestTracker::discard_request(const HashedId3& id)
|
||||
{
|
||||
auto found = m_lookup.find(id);
|
||||
if (found != m_lookup.end()) {
|
||||
m_fifo.erase(found->second);
|
||||
m_lookup.erase(found);
|
||||
}
|
||||
|
||||
assert(m_fifo.size() == m_lookup.size());
|
||||
}
|
||||
|
||||
bool PeerRequestTracker::is_pending(const HashedId3& id) const
|
||||
{
|
||||
return m_lookup.find(id) != m_lookup.end();
|
||||
}
|
||||
|
||||
boost::optional<HashedId3> PeerRequestTracker::next_one()
|
||||
{
|
||||
boost::optional<HashedId3> next;
|
||||
if (!m_fifo.empty()) {
|
||||
next = m_fifo.front();
|
||||
m_fifo.pop_front();
|
||||
m_lookup.erase(*next);
|
||||
}
|
||||
|
||||
assert(m_fifo.size() == m_lookup.size());
|
||||
return next;
|
||||
}
|
||||
|
||||
std::list<HashedId3> PeerRequestTracker::next_n(std::size_t max)
|
||||
{
|
||||
if (max >= m_fifo.size()) {
|
||||
return all();
|
||||
} else {
|
||||
std::list<HashedId3> next;
|
||||
auto from = m_fifo.begin();
|
||||
auto to = std::next(from, max);
|
||||
next.splice(next.begin(), m_fifo, from, to);
|
||||
|
||||
for (const HashedId3& id : next) {
|
||||
m_lookup.erase(id);
|
||||
}
|
||||
|
||||
assert(m_fifo.size() == m_lookup.size());
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
std::list<HashedId3> PeerRequestTracker::all()
|
||||
{
|
||||
m_lookup.clear();
|
||||
std::list<HashedId3> all;
|
||||
all.splice(all.begin(), m_fifo, m_fifo.begin(), m_fifo.end());
|
||||
return all;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef A5037FF9_14E0_4DA9_8027_6C0692B1462B
|
||||
#define A5037FF9_14E0_4DA9_8027_6C0692B1462B
|
||||
|
||||
#include <vanetza/security/hashed_id.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
class PeerRequestTracker
|
||||
{
|
||||
public:
|
||||
explicit PeerRequestTracker(std::size_t limit = 16);
|
||||
|
||||
/**
|
||||
* Add a certificate request to the tracker.
|
||||
*
|
||||
* \param id hash of requested certificate
|
||||
*/
|
||||
void add_request(const HashedId3&);
|
||||
|
||||
/**
|
||||
* Discard a pending certificate request.
|
||||
*
|
||||
* \param id hash of certificate
|
||||
*/
|
||||
void discard_request(const HashedId3&);
|
||||
|
||||
/**
|
||||
* Check if a certificate request is pending.
|
||||
*
|
||||
* \param id hash of certificate
|
||||
* \return true if request of this certificate is pending
|
||||
*/
|
||||
bool is_pending(const HashedId3&) const;
|
||||
|
||||
/**
|
||||
* Determine which request shall be handled next and remove it from the queue.
|
||||
*
|
||||
* \return hash of requested certificate if any
|
||||
*/
|
||||
boost::optional<HashedId3> next_one();
|
||||
|
||||
/**
|
||||
* Retrieve next n pending requests from tracker.
|
||||
*
|
||||
* \return list of up to n certificate hashes
|
||||
*/
|
||||
std::list<HashedId3> next_n(std::size_t max);
|
||||
|
||||
/**
|
||||
* Retrieve all pending requests from tracker.
|
||||
*
|
||||
* \return list of all pending certificate hashes
|
||||
*/
|
||||
std::list<HashedId3> all();
|
||||
|
||||
private:
|
||||
using FifoQueue = std::list<HashedId3>;
|
||||
using LookupMap = std::unordered_map<HashedId3, FifoQueue::iterator>;
|
||||
|
||||
std::size_t m_limit;
|
||||
FifoQueue m_fifo;
|
||||
LookupMap m_lookup;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* A5037FF9_14E0_4DA9_8027_6C0692B1462B */
|
||||
@@ -0,0 +1,227 @@
|
||||
#include <vanetza/security/persistence.hpp>
|
||||
#include <vanetza/security/key_type.hpp>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
#include <vanetza/security/openssl_wrapper.hpp>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/objects.h>
|
||||
#include <openssl/pem.h>
|
||||
#endif
|
||||
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
#include <cryptopp/base64.h>
|
||||
#include <cryptopp/eccrypto.h>
|
||||
#include <cryptopp/files.h>
|
||||
#include <cryptopp/integer.h>
|
||||
#include <cryptopp/oids.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
#include <cryptopp/queue.h>
|
||||
#include <cryptopp/sha.h>
|
||||
#endif
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
namespace
|
||||
{
|
||||
|
||||
KeyType key_type_from_nid(int nid)
|
||||
{
|
||||
switch (nid) {
|
||||
case NID_X9_62_prime256v1:
|
||||
return KeyType::NistP256;
|
||||
case NID_brainpoolP256r1:
|
||||
return KeyType::BrainpoolP256r1;
|
||||
case NID_brainpoolP384r1:
|
||||
return KeyType::BrainpoolP384r1;
|
||||
default:
|
||||
return KeyType::Unspecified;
|
||||
}
|
||||
}
|
||||
|
||||
PrivateKey extract_private_key(openssl::EvpKey& pkey)
|
||||
{
|
||||
openssl::Key ec_key(EVP_PKEY_get1_EC_KEY(pkey));
|
||||
if (!ec_key) {
|
||||
throw std::runtime_error("Key is not an EC key");
|
||||
}
|
||||
|
||||
const EC_GROUP* group = EC_KEY_get0_group(ec_key);
|
||||
const KeyType type = key_type_from_nid(group ? EC_GROUP_get_curve_name(group) : NID_undef);
|
||||
if (type == KeyType::Unspecified) {
|
||||
throw std::runtime_error("Unsupported EC curve in private key");
|
||||
}
|
||||
|
||||
const BIGNUM* priv_bn = EC_KEY_get0_private_key(ec_key);
|
||||
if (!priv_bn) {
|
||||
throw std::runtime_error("EC key has no private component");
|
||||
}
|
||||
|
||||
PrivateKey key;
|
||||
key.type = type;
|
||||
key.key.resize(key_length(type));
|
||||
if (BN_bn2binpad(priv_bn, key.key.data(), key.key.size()) < 0) {
|
||||
throw std::runtime_error("private key does not fit the curve length");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PrivateKey load_private_key_from_pem_file_openssl(const std::string& key_path)
|
||||
{
|
||||
openssl::Bio bio(BIO_new_file(key_path.c_str(), "rb"));
|
||||
if (!bio) {
|
||||
throw std::runtime_error("Cannot open key file: " + key_path);
|
||||
}
|
||||
openssl::EvpKey pkey(PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr));
|
||||
if (!pkey) {
|
||||
throw std::runtime_error("Failed to load PEM private key from: " + key_path);
|
||||
}
|
||||
return extract_private_key(pkey);
|
||||
}
|
||||
|
||||
PrivateKey load_private_key_from_der_file_openssl(const std::string& key_path)
|
||||
{
|
||||
openssl::Bio bio(BIO_new_file(key_path.c_str(), "rb"));
|
||||
if (!bio) {
|
||||
throw std::runtime_error("Cannot open key file: " + key_path);
|
||||
}
|
||||
openssl::EvpKey pkey(d2i_PrivateKey_bio(bio, nullptr));
|
||||
if (!pkey) {
|
||||
throw std::runtime_error("Failed to load DER private key from: " + key_path);
|
||||
}
|
||||
return extract_private_key(pkey);
|
||||
}
|
||||
#endif /* VANETZA_WITH_OPENSSL */
|
||||
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
namespace
|
||||
{
|
||||
|
||||
using EcPrivateKey = CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>::PrivateKey;
|
||||
using EcGroupParameters = CryptoPP::DL_GroupParameters_EC<CryptoPP::ECP>;
|
||||
|
||||
KeyType detect_curve(const EcGroupParameters& gp)
|
||||
{
|
||||
KeyType key_type = KeyType::Unspecified;
|
||||
|
||||
if (EcGroupParameters(CryptoPP::ASN1::secp256r1()) == gp) {
|
||||
key_type = KeyType::NistP256;
|
||||
} else if (EcGroupParameters(CryptoPP::ASN1::brainpoolP256r1()) == gp) {
|
||||
key_type = KeyType::BrainpoolP256r1;
|
||||
} else if (EcGroupParameters(CryptoPP::ASN1::brainpoolP384r1()) == gp) {
|
||||
key_type = KeyType::BrainpoolP384r1;
|
||||
}
|
||||
|
||||
return key_type;
|
||||
}
|
||||
|
||||
PrivateKey extract_private_key(CryptoPP::BufferedTransformation& der)
|
||||
{
|
||||
CryptoPP::AutoSeededRandomPool rng;
|
||||
EcPrivateKey private_key;
|
||||
private_key.Load(der);
|
||||
if (!private_key.Validate(rng, 3)) {
|
||||
throw std::runtime_error("Private key validation failed");
|
||||
}
|
||||
|
||||
const KeyType type = detect_curve(private_key.GetGroupParameters());
|
||||
if (type == KeyType::Unspecified) {
|
||||
throw std::runtime_error("Unsupported EC curve in private key");
|
||||
}
|
||||
|
||||
PrivateKey key;
|
||||
key.type = type;
|
||||
key.key.resize(key_length(type));
|
||||
private_key.GetPrivateExponent().Encode(key.key.data(), key.key.size());
|
||||
return key;
|
||||
}
|
||||
|
||||
void pem_decode(std::istream& in, CryptoPP::BufferedTransformation& dest)
|
||||
{
|
||||
static const std::string HEADER = "-----BEGIN PRIVATE KEY-----";
|
||||
static const std::string FOOTER = "-----END PRIVATE KEY-----";
|
||||
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
if (line.find(HEADER) != std::string::npos) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!in) {
|
||||
throw std::runtime_error("PEM header not found");
|
||||
}
|
||||
|
||||
CryptoPP::Base64Decoder decoder;
|
||||
decoder.Attach(new CryptoPP::Redirector(dest));
|
||||
|
||||
while (std::getline(in, line)) {
|
||||
if (line.find(FOOTER) != std::string::npos) {
|
||||
break;
|
||||
}
|
||||
decoder.Put(reinterpret_cast<const uint8_t*>(line.data()), line.length());
|
||||
}
|
||||
if (!in) {
|
||||
throw std::runtime_error("PEM footer not found");
|
||||
}
|
||||
|
||||
decoder.MessageEnd();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PrivateKey load_private_key_from_pem_file_cryptopp(const std::string& key_path)
|
||||
{
|
||||
std::ifstream file(key_path);
|
||||
if (!file) {
|
||||
throw std::runtime_error("Cannot open key file: " + key_path);
|
||||
}
|
||||
CryptoPP::ByteQueue der;
|
||||
pem_decode(file, der);
|
||||
return extract_private_key(der);
|
||||
}
|
||||
|
||||
PrivateKey load_private_key_from_der_file_cryptopp(const std::string& key_path)
|
||||
{
|
||||
try {
|
||||
CryptoPP::FileSource source(key_path.c_str(), true);
|
||||
return extract_private_key(source);
|
||||
} catch (const CryptoPP::FileStore::OpenErr&) {
|
||||
throw std::runtime_error("Cannot open key file: " + key_path);
|
||||
}
|
||||
}
|
||||
#endif /* VANETZA_WITH_CRYPTOPP */
|
||||
|
||||
PrivateKey load_private_key_from_pem_file(const std::string& key_path)
|
||||
{
|
||||
#if defined(VANETZA_WITH_OPENSSL)
|
||||
return load_private_key_from_pem_file_openssl(key_path);
|
||||
#elif defined(VANETZA_WITH_CRYPTOPP)
|
||||
return load_private_key_from_pem_file_cryptopp(key_path);
|
||||
#else
|
||||
# warning "no crypto backend available for persistence"
|
||||
return PrivateKey {};
|
||||
#endif
|
||||
}
|
||||
|
||||
PrivateKey load_private_key_from_der_file(const std::string& key_path)
|
||||
{
|
||||
#if defined(VANETZA_WITH_OPENSSL)
|
||||
return load_private_key_from_der_file_openssl(key_path);
|
||||
#elif defined(VANETZA_WITH_CRYPTOPP)
|
||||
return load_private_key_from_der_file_cryptopp(key_path);
|
||||
#else
|
||||
# warning "no crypto backend available for persistence"
|
||||
return PrivateKey {};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <vanetza/security/private_key.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/**
|
||||
* Load a private key from a PEM file, deriving its type from the stored curve.
|
||||
* \param key_path PEM file to load the key from
|
||||
* \return loaded private key
|
||||
*/
|
||||
PrivateKey load_private_key_from_pem_file(const std::string& key_path);
|
||||
|
||||
/**
|
||||
* Load a private key from a DER file, deriving its type from the stored curve.
|
||||
* \param key_path DER file to load the key from
|
||||
* \return loaded private key
|
||||
*/
|
||||
PrivateKey load_private_key_from_der_file(const std::string& key_path);
|
||||
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
PrivateKey load_private_key_from_pem_file_openssl(const std::string& key_path);
|
||||
PrivateKey load_private_key_from_der_file_openssl(const std::string& key_path);
|
||||
#endif
|
||||
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
PrivateKey load_private_key_from_pem_file_cryptopp(const std::string& key_path);
|
||||
PrivateKey load_private_key_from_der_file_cryptopp(const std::string& key_path);
|
||||
#endif
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace pqc
|
||||
{
|
||||
|
||||
constexpr std::size_t fndsa512_public_key_size = 897;
|
||||
constexpr std::size_t fndsa512_private_key_size = 1281;
|
||||
constexpr std::size_t fndsa512_signature_size = 666;
|
||||
|
||||
struct PublicKey
|
||||
{
|
||||
ByteBuffer bytes;
|
||||
};
|
||||
|
||||
struct PrivateKey
|
||||
{
|
||||
ByteBuffer bytes;
|
||||
};
|
||||
|
||||
struct Signature
|
||||
{
|
||||
ByteBuffer bytes;
|
||||
};
|
||||
|
||||
struct KeyPair
|
||||
{
|
||||
PublicKey public_key;
|
||||
PrivateKey private_key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cryptographic operations for the experimental FN-DSA-512 profile.
|
||||
*
|
||||
* The interface is intentionally separate from security::Backend. Enabling
|
||||
* the experimental profile therefore does not alter the established ECC
|
||||
* backend contract or its key types.
|
||||
*/
|
||||
class Backend
|
||||
{
|
||||
public:
|
||||
virtual KeyPair generate_key_pair() = 0;
|
||||
virtual Signature sign(const PrivateKey&, const ByteBuffer& data) = 0;
|
||||
virtual bool verify(const PublicKey&, const ByteBuffer& data, const Signature&) = 0;
|
||||
virtual ~Backend() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the liboqs-backed implementation used by the experimental profile.
|
||||
*/
|
||||
std::unique_ptr<Backend> create_fndsa512_backend();
|
||||
|
||||
} // namespace pqc
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,99 @@
|
||||
#include <vanetza/security/pqc/fndsa512.hpp>
|
||||
#include <oqs/oqs.h>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace pqc
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using OqsSignature = std::unique_ptr<OQS_SIG, decltype(&OQS_SIG_free)>;
|
||||
|
||||
OqsSignature create_signature_context()
|
||||
{
|
||||
OqsSignature context { OQS_SIG_new(OQS_SIG_alg_falcon_padded_512), OQS_SIG_free };
|
||||
if (!context) {
|
||||
throw std::runtime_error("liboqs does not provide Falcon-padded-512");
|
||||
}
|
||||
|
||||
if (context->length_public_key != fndsa512_public_key_size ||
|
||||
context->length_secret_key != fndsa512_private_key_size ||
|
||||
context->length_signature != fndsa512_signature_size) {
|
||||
throw std::runtime_error("liboqs Falcon-padded-512 parameters do not match the ASN.1 profile");
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
class OqsBackend final : public Backend
|
||||
{
|
||||
public:
|
||||
KeyPair generate_key_pair() override
|
||||
{
|
||||
auto context = create_signature_context();
|
||||
KeyPair key_pair;
|
||||
key_pair.public_key.bytes.resize(fndsa512_public_key_size);
|
||||
key_pair.private_key.bytes.resize(fndsa512_private_key_size);
|
||||
|
||||
const auto result = OQS_SIG_keypair(
|
||||
context.get(), key_pair.public_key.bytes.data(), key_pair.private_key.bytes.data());
|
||||
if (result != OQS_SUCCESS) {
|
||||
throw std::runtime_error("liboqs failed to generate an FN-DSA-512 key pair");
|
||||
}
|
||||
|
||||
return key_pair;
|
||||
}
|
||||
|
||||
Signature sign(const PrivateKey& private_key, const ByteBuffer& data) override
|
||||
{
|
||||
if (private_key.bytes.size() != fndsa512_private_key_size) {
|
||||
throw std::invalid_argument("FN-DSA-512 private key has an invalid size");
|
||||
}
|
||||
|
||||
auto context = create_signature_context();
|
||||
Signature signature;
|
||||
signature.bytes.resize(fndsa512_signature_size);
|
||||
std::size_t signature_size = 0;
|
||||
const auto result = OQS_SIG_sign(
|
||||
context.get(), signature.bytes.data(), &signature_size,
|
||||
data.data(), data.size(), private_key.bytes.data());
|
||||
if (result != OQS_SUCCESS) {
|
||||
throw std::runtime_error("liboqs failed to create an FN-DSA-512 signature");
|
||||
}
|
||||
if (signature_size != fndsa512_signature_size) {
|
||||
throw std::runtime_error("liboqs returned a non-padded FN-DSA-512 signature");
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
bool verify(const PublicKey& public_key, const ByteBuffer& data, const Signature& signature) override
|
||||
{
|
||||
if (public_key.bytes.size() != fndsa512_public_key_size ||
|
||||
signature.bytes.size() != fndsa512_signature_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto context = create_signature_context();
|
||||
return OQS_SIG_verify(
|
||||
context.get(), data.data(), data.size(), signature.bytes.data(),
|
||||
signature.bytes.size(), public_key.bytes.data()) == OQS_SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Backend> create_fndsa512_backend()
|
||||
{
|
||||
return std::unique_ptr<Backend> { new OqsBackend() };
|
||||
}
|
||||
|
||||
} // namespace pqc
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
#include <vanetza/security/pqc/hybrid_certificate.hpp>
|
||||
#include <vanetza/asn1/asn1c_wrapper.hpp>
|
||||
#include <vanetza/asn1/security_profile.hpp>
|
||||
#include VANETZA_ASN1_SECURITY_HEADER(PublicVerificationKey.h)
|
||||
#include VANETZA_ASN1_SECURITY_HEADER(Signature.h)
|
||||
#include VANETZA_ASN1_SECURITY_HEADER(ToBeSignedCertificate.h)
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/private_key.hpp>
|
||||
#include <vanetza/security/v3/asn1_conversions.hpp>
|
||||
#include <vanetza/security/v3/certificate.hpp>
|
||||
#include <vanetza/security/v3/hash.hpp>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace pqc
|
||||
{
|
||||
|
||||
using v3::Certificate;
|
||||
using v3::CertificateView;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
boost::optional<Certificate> copy_certificate(const CertificateView& view)
|
||||
{
|
||||
try {
|
||||
Certificate certificate;
|
||||
if (certificate.decode(view.encode())) {
|
||||
return certificate;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
// A malformed or empty view cannot expose hybrid certificate data.
|
||||
}
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
Certificate copy_certificate_or_throw(const CertificateView& view)
|
||||
{
|
||||
auto certificate = copy_certificate(view);
|
||||
if (!certificate) {
|
||||
throw std::invalid_argument("certificate cannot be encoded and decoded");
|
||||
}
|
||||
return std::move(*certificate);
|
||||
}
|
||||
|
||||
ByteBuffer copy_octets(const OCTET_STRING_t& value)
|
||||
{
|
||||
if (!value.buf || value.size == 0) {
|
||||
return {};
|
||||
}
|
||||
return ByteBuffer(value.buf, value.buf + value.size);
|
||||
}
|
||||
|
||||
boost::optional<PublicKey> extract_alternative_public_key(const Certificate& certificate)
|
||||
{
|
||||
const auto* key = certificate->toBeSigned.altVerificationKey;
|
||||
if (!key || key->present != Vanetza_Security_PublicVerificationKey_PR_fnDsa512) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
PublicKey result { copy_octets(key->choice.fnDsa512) };
|
||||
return result.bytes.size() == fndsa512_public_key_size ?
|
||||
boost::optional<PublicKey> { std::move(result) } : boost::none;
|
||||
}
|
||||
|
||||
boost::optional<Signature> extract_alternative_signature(const Certificate& certificate)
|
||||
{
|
||||
const auto* signature = certificate->toBeSigned.altSignatureValue;
|
||||
if (!signature || signature->present != Vanetza_Security_Signature_PR_fnDsa512Signature) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
Signature result { copy_octets(signature->choice.fnDsa512Signature) };
|
||||
return result.bytes.size() == fndsa512_signature_size ?
|
||||
boost::optional<Signature> { std::move(result) } : boost::none;
|
||||
}
|
||||
|
||||
void assign_octets(OCTET_STRING_t& destination, const ByteBuffer& source)
|
||||
{
|
||||
if (source.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()) ||
|
||||
OCTET_STRING_fromBuf(&destination,
|
||||
reinterpret_cast<const char*>(source.data()),
|
||||
static_cast<int>(source.size())) != 0) {
|
||||
throw std::runtime_error("cannot allocate ASN.1 octet string");
|
||||
}
|
||||
}
|
||||
|
||||
void set_primary_signature(
|
||||
Certificate& certificate, const ::vanetza::security::Signature& signature)
|
||||
{
|
||||
if (signature.r.size() != key_length(signature.type) ||
|
||||
signature.s.size() != key_length(signature.type)) {
|
||||
throw std::invalid_argument("ECC certificate signature has an invalid size");
|
||||
}
|
||||
|
||||
if (certificate->signature) {
|
||||
vanetza::asn1::free(asn_DEF_Vanetza_Security_Signature, certificate->signature);
|
||||
}
|
||||
certificate->signature = vanetza::asn1::allocate<v3::asn1::Signature>();
|
||||
|
||||
v3::asn1::EccP256CurvePoint* r256 = nullptr;
|
||||
v3::asn1::EccP384CurvePoint* r384 = nullptr;
|
||||
OCTET_STRING_t* s = nullptr;
|
||||
switch (signature.type) {
|
||||
case KeyType::NistP256:
|
||||
certificate->signature->present = Vanetza_Security_Signature_PR_ecdsaNistP256Signature;
|
||||
r256 = &certificate->signature->choice.ecdsaNistP256Signature.rSig;
|
||||
s = &certificate->signature->choice.ecdsaNistP256Signature.sSig;
|
||||
break;
|
||||
case KeyType::BrainpoolP256r1:
|
||||
certificate->signature->present = Vanetza_Security_Signature_PR_ecdsaBrainpoolP256r1Signature;
|
||||
r256 = &certificate->signature->choice.ecdsaBrainpoolP256r1Signature.rSig;
|
||||
s = &certificate->signature->choice.ecdsaBrainpoolP256r1Signature.sSig;
|
||||
break;
|
||||
case KeyType::BrainpoolP384r1:
|
||||
certificate->signature->present = Vanetza_Security_Signature_PR_ecdsaBrainpoolP384r1Signature;
|
||||
r384 = &certificate->signature->choice.ecdsaBrainpoolP384r1Signature.rSig;
|
||||
s = &certificate->signature->choice.ecdsaBrainpoolP384r1Signature.sSig;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("unsupported ECC certificate signature type");
|
||||
}
|
||||
|
||||
if (r256) {
|
||||
r256->present = Vanetza_Security_EccP256CurvePoint_PR_x_only;
|
||||
assign_octets(r256->choice.x_only, signature.r);
|
||||
} else {
|
||||
r384->present = Vanetza_Security_EccP384CurvePoint_PR_x_only;
|
||||
assign_octets(r384->choice.x_only, signature.r);
|
||||
}
|
||||
assign_octets(*s, signature.s);
|
||||
}
|
||||
|
||||
const CertificateView& signing_certificate(
|
||||
const CertificateView& subject, const CertificateView* issuer)
|
||||
{
|
||||
if (subject.issuer_is_self()) {
|
||||
return subject;
|
||||
}
|
||||
if (!issuer) {
|
||||
throw std::invalid_argument("issuer certificate is required for a non-self-signed certificate");
|
||||
}
|
||||
return *issuer;
|
||||
}
|
||||
|
||||
ByteBuffer canonical_tbs(const CertificateView& subject, SignatureLayer layer)
|
||||
{
|
||||
Certificate certificate = copy_certificate_or_throw(subject);
|
||||
if (layer == SignatureLayer::Alternative) {
|
||||
clear_alternative_signature(certificate);
|
||||
}
|
||||
|
||||
auto canonical = certificate.canonicalize();
|
||||
if (!canonical) {
|
||||
throw std::invalid_argument("certificate cannot be canonicalized");
|
||||
}
|
||||
|
||||
return vanetza::asn1::encode_oer(
|
||||
asn_DEF_Vanetza_Security_ToBeSignedCertificate, &canonical->content()->toBeSigned);
|
||||
}
|
||||
|
||||
ByteBuffer canonical_issuer(const CertificateView& subject, const CertificateView* issuer)
|
||||
{
|
||||
if (subject.issuer_is_self()) {
|
||||
return {};
|
||||
}
|
||||
if (!issuer) {
|
||||
throw std::invalid_argument("issuer certificate is required for a non-self-signed certificate");
|
||||
}
|
||||
|
||||
const auto expected_digest = subject.issuer_digest();
|
||||
const auto actual_digest = issuer->calculate_digest();
|
||||
if (!expected_digest || !actual_digest || *expected_digest != *actual_digest) {
|
||||
throw std::invalid_argument("issuer certificate does not match the subject issuer identifier");
|
||||
}
|
||||
|
||||
auto canonical = issuer->canonicalize();
|
||||
if (!canonical) {
|
||||
throw std::invalid_argument("issuer certificate cannot be canonicalized");
|
||||
}
|
||||
return canonical->encode();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
boost::optional<PublicKey> get_alternative_public_key(const CertificateView& view)
|
||||
{
|
||||
auto certificate = copy_certificate(view);
|
||||
return certificate ? extract_alternative_public_key(*certificate) : boost::none;
|
||||
}
|
||||
|
||||
boost::optional<Signature> get_alternative_signature(const CertificateView& view)
|
||||
{
|
||||
auto certificate = copy_certificate(view);
|
||||
return certificate ? extract_alternative_signature(*certificate) : boost::none;
|
||||
}
|
||||
|
||||
MaterialState alternative_material_state(const CertificateView& view)
|
||||
{
|
||||
auto certificate = copy_certificate(view);
|
||||
if (!certificate) {
|
||||
return MaterialState::Inconsistent;
|
||||
}
|
||||
|
||||
const bool key_present = certificate->content()->toBeSigned.altVerificationKey;
|
||||
const bool signature_present = certificate->content()->toBeSigned.altSignatureValue;
|
||||
const bool has_key = static_cast<bool>(extract_alternative_public_key(*certificate));
|
||||
const bool has_signature = static_cast<bool>(extract_alternative_signature(*certificate));
|
||||
if (!key_present && !signature_present) {
|
||||
return MaterialState::None;
|
||||
}
|
||||
if (key_present != has_key || signature_present != has_signature) {
|
||||
return MaterialState::Inconsistent;
|
||||
}
|
||||
if (view.is_at_certificate()) {
|
||||
return !has_key && has_signature ? MaterialState::EndEntity : MaterialState::Inconsistent;
|
||||
}
|
||||
if (view.issuer_is_self() || view.is_ca_certificate()) {
|
||||
return has_key && has_signature ? MaterialState::Authority : MaterialState::Inconsistent;
|
||||
}
|
||||
return MaterialState::Inconsistent;
|
||||
}
|
||||
|
||||
void set_alternative_public_key(Certificate& certificate, const PublicKey& key)
|
||||
{
|
||||
if (key.bytes.size() != fndsa512_public_key_size) {
|
||||
throw std::invalid_argument("FN-DSA-512 public key has an invalid size");
|
||||
}
|
||||
clear_alternative_public_key(certificate);
|
||||
certificate->toBeSigned.altVerificationKey =
|
||||
vanetza::asn1::allocate<v3::asn1::PublicVerificationKey>();
|
||||
certificate->toBeSigned.altVerificationKey->present =
|
||||
Vanetza_Security_PublicVerificationKey_PR_fnDsa512;
|
||||
assign_octets(certificate->toBeSigned.altVerificationKey->choice.fnDsa512, key.bytes);
|
||||
}
|
||||
|
||||
void set_alternative_signature(Certificate& certificate, const Signature& signature)
|
||||
{
|
||||
if (signature.bytes.size() != fndsa512_signature_size) {
|
||||
throw std::invalid_argument("FN-DSA-512 signature has an invalid size");
|
||||
}
|
||||
clear_alternative_signature(certificate);
|
||||
certificate->toBeSigned.altSignatureValue =
|
||||
vanetza::asn1::allocate<v3::asn1::Signature>();
|
||||
certificate->toBeSigned.altSignatureValue->present =
|
||||
Vanetza_Security_Signature_PR_fnDsa512Signature;
|
||||
assign_octets(
|
||||
certificate->toBeSigned.altSignatureValue->choice.fnDsa512Signature,
|
||||
signature.bytes);
|
||||
}
|
||||
|
||||
void clear_alternative_public_key(Certificate& certificate)
|
||||
{
|
||||
if (certificate->toBeSigned.altVerificationKey) {
|
||||
vanetza::asn1::free(
|
||||
asn_DEF_Vanetza_Security_PublicVerificationKey,
|
||||
certificate->toBeSigned.altVerificationKey);
|
||||
certificate->toBeSigned.altVerificationKey = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_alternative_signature(Certificate& certificate)
|
||||
{
|
||||
if (certificate->toBeSigned.altSignatureValue) {
|
||||
vanetza::asn1::free(
|
||||
asn_DEF_Vanetza_Security_Signature,
|
||||
certificate->toBeSigned.altSignatureValue);
|
||||
certificate->toBeSigned.altSignatureValue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer calculate_certificate_hash(
|
||||
::vanetza::security::Backend& backend, HashAlgorithm algorithm,
|
||||
const CertificateView& subject,
|
||||
const CertificateView* issuer, SignatureLayer layer)
|
||||
{
|
||||
if (algorithm == HashAlgorithm::Unspecified) {
|
||||
throw std::invalid_argument("certificate hash algorithm is unspecified");
|
||||
}
|
||||
|
||||
const ByteBuffer data_input = canonical_tbs(subject, layer);
|
||||
const ByteBuffer signer_input = canonical_issuer(subject, issuer);
|
||||
const ByteBuffer data_hash = backend.calculate_hash(algorithm, data_input);
|
||||
const ByteBuffer signer_hash = backend.calculate_hash(algorithm, signer_input);
|
||||
|
||||
ByteBuffer concatenated;
|
||||
concatenated.reserve(data_hash.size() + signer_hash.size());
|
||||
concatenated.insert(concatenated.end(), data_hash.begin(), data_hash.end());
|
||||
concatenated.insert(concatenated.end(), signer_hash.begin(), signer_hash.end());
|
||||
return backend.calculate_hash(algorithm, concatenated);
|
||||
}
|
||||
|
||||
void sign_primary_certificate(
|
||||
Certificate& subject, const CertificateView* issuer,
|
||||
::vanetza::security::Backend& backend,
|
||||
const ::vanetza::security::PrivateKey& issuer_key)
|
||||
{
|
||||
const CertificateView& signer = signing_certificate(subject, issuer);
|
||||
const auto public_key = v3::get_public_key(*copy_certificate_or_throw(signer).content());
|
||||
if (!public_key || public_key->type != issuer_key.type) {
|
||||
throw std::invalid_argument("ECC private key does not match the issuer certificate key type");
|
||||
}
|
||||
|
||||
const HashAlgorithm algorithm = v3::specified_hash_algorithm(issuer_key.type);
|
||||
const ByteBuffer digest = calculate_certificate_hash(
|
||||
backend, algorithm, subject, issuer, SignatureLayer::Primary);
|
||||
const auto signature = backend.sign_digest(issuer_key, digest);
|
||||
if (!backend.verify_digest(*public_key, digest, signature)) {
|
||||
throw std::invalid_argument(
|
||||
"ECC private key does not match the issuer certificate public key");
|
||||
}
|
||||
set_primary_signature(subject, signature);
|
||||
}
|
||||
|
||||
bool verify_primary_certificate(
|
||||
const CertificateView& subject, const CertificateView* issuer,
|
||||
::vanetza::security::Backend& backend)
|
||||
{
|
||||
try {
|
||||
const CertificateView& signer = signing_certificate(subject, issuer);
|
||||
Certificate signer_copy = copy_certificate_or_throw(signer);
|
||||
Certificate subject_copy = copy_certificate_or_throw(subject);
|
||||
const auto public_key = v3::get_public_key(*signer_copy.content());
|
||||
const auto signature = v3::get_signature(*subject_copy.content());
|
||||
if (!public_key || !signature || public_key->type != signature->type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HashAlgorithm algorithm = v3::specified_hash_algorithm(public_key->type);
|
||||
const ByteBuffer digest = calculate_certificate_hash(
|
||||
backend, algorithm, subject, issuer, SignatureLayer::Primary);
|
||||
return backend.verify_digest(*public_key, digest, *signature);
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void sign_alternative_certificate(
|
||||
Certificate& subject, const CertificateView* issuer,
|
||||
::vanetza::security::Backend& hash_backend, Backend& backend,
|
||||
const PrivateKey& issuer_key)
|
||||
{
|
||||
const CertificateView& signer = signing_certificate(subject, issuer);
|
||||
const auto public_key = get_alternative_public_key(signer);
|
||||
if (!public_key) {
|
||||
throw std::invalid_argument("issuer certificate has no FN-DSA-512 alternative key");
|
||||
}
|
||||
|
||||
const ByteBuffer digest = calculate_certificate_hash(
|
||||
hash_backend, HashAlgorithm::SHA256, subject, issuer, SignatureLayer::Alternative);
|
||||
const auto signature = backend.sign(issuer_key, digest);
|
||||
if (!backend.verify(*public_key, digest, signature)) {
|
||||
throw std::invalid_argument(
|
||||
"FN-DSA-512 private key does not match the issuer certificate public key");
|
||||
}
|
||||
set_alternative_signature(subject, signature);
|
||||
}
|
||||
|
||||
bool verify_alternative_certificate(
|
||||
const CertificateView& subject, const CertificateView* issuer,
|
||||
::vanetza::security::Backend& hash_backend, Backend& backend)
|
||||
{
|
||||
try {
|
||||
const CertificateView& signer = signing_certificate(subject, issuer);
|
||||
const auto public_key = get_alternative_public_key(signer);
|
||||
const auto signature = get_alternative_signature(subject);
|
||||
if (!public_key || !signature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ByteBuffer digest = calculate_certificate_hash(
|
||||
hash_backend, HashAlgorithm::SHA256, subject, issuer, SignatureLayer::Alternative);
|
||||
return backend.verify(*public_key, digest, *signature);
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace pqc
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/security/hash_algorithm.hpp>
|
||||
#include <vanetza/security/pqc/fndsa512.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
class Backend;
|
||||
struct PrivateKey;
|
||||
|
||||
namespace v3
|
||||
{
|
||||
class Certificate;
|
||||
class CertificateView;
|
||||
} // namespace v3
|
||||
|
||||
namespace pqc
|
||||
{
|
||||
|
||||
/** The two nested certificate signatures used by the experimental profile. */
|
||||
enum class SignatureLayer
|
||||
{
|
||||
Alternative,
|
||||
Primary,
|
||||
};
|
||||
|
||||
/** Shape of the optional alternative material carried by a certificate. */
|
||||
enum class MaterialState
|
||||
{
|
||||
None,
|
||||
Authority,
|
||||
EndEntity,
|
||||
Inconsistent,
|
||||
};
|
||||
|
||||
boost::optional<PublicKey> get_alternative_public_key(const v3::CertificateView&);
|
||||
boost::optional<Signature> get_alternative_signature(const v3::CertificateView&);
|
||||
MaterialState alternative_material_state(const v3::CertificateView&);
|
||||
|
||||
void set_alternative_public_key(v3::Certificate&, const PublicKey&);
|
||||
void set_alternative_signature(v3::Certificate&, const Signature&);
|
||||
void clear_alternative_public_key(v3::Certificate&);
|
||||
void clear_alternative_signature(v3::Certificate&);
|
||||
|
||||
/**
|
||||
* Calculate the certificate signature hash.
|
||||
*
|
||||
* The construction follows the IEEE 1609.2 certificate signature input:
|
||||
* H(H(COER(toBeSigned)) || H(COER(canonical issuer certificate))). For a
|
||||
* self-signed certificate the issuer input is empty. The alternative layer
|
||||
* omits altSignatureValue from toBeSigned; the primary layer includes it.
|
||||
*/
|
||||
ByteBuffer calculate_certificate_hash(
|
||||
::vanetza::security::Backend&, HashAlgorithm, const v3::CertificateView& subject,
|
||||
const v3::CertificateView* issuer, SignatureLayer);
|
||||
|
||||
/** Sign or verify the outer, classical certificate signature. */
|
||||
void sign_primary_certificate(
|
||||
v3::Certificate&, const v3::CertificateView* issuer,
|
||||
::vanetza::security::Backend&, const ::vanetza::security::PrivateKey& issuer_key);
|
||||
bool verify_primary_certificate(
|
||||
const v3::CertificateView&, const v3::CertificateView* issuer,
|
||||
::vanetza::security::Backend&);
|
||||
|
||||
/** Sign or verify the inner FN-DSA-512 certificate signature. */
|
||||
void sign_alternative_certificate(
|
||||
v3::Certificate&, const v3::CertificateView* issuer,
|
||||
::vanetza::security::Backend& hash_backend, Backend&,
|
||||
const PrivateKey& issuer_key);
|
||||
bool verify_alternative_certificate(
|
||||
const v3::CertificateView&, const v3::CertificateView* issuer,
|
||||
::vanetza::security::Backend&, Backend&);
|
||||
|
||||
} // namespace pqc
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#include <vanetza/security/pqc/hybrid_certificate_validator.hpp>
|
||||
#include <vanetza/security/pqc/hybrid_certificate.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/v3/certificate.hpp>
|
||||
#include <vanetza/security/v3/issuer_lookup.hpp>
|
||||
#include <vanetza/security/v3/trust_store.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace pqc
|
||||
{
|
||||
|
||||
auto HybridCertificateValidator::valid_for_signing(
|
||||
const v3::CertificateView& certificate, ItsAid aid) -> Verdict
|
||||
{
|
||||
const Verdict policy_verdict = m_policy_validator.valid_for_signing(certificate, aid);
|
||||
if (policy_verdict != Verdict::Valid) {
|
||||
return policy_verdict;
|
||||
}
|
||||
if (!m_issuer_lookup || !m_trust_store || !m_ecc_backend ||
|
||||
(m_verification_policy != VerificationPolicy::ClassicalOnly && !m_pqc_backend)) {
|
||||
return Verdict::Misconfiguration;
|
||||
}
|
||||
return chain_is_authentic(certificate) ? Verdict::Valid : Verdict::Untrusted;
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::use_runtime(const Runtime* runtime)
|
||||
{
|
||||
m_policy_validator.use_runtime(runtime);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::use_position_provider(PositionProvider* provider)
|
||||
{
|
||||
m_policy_validator.use_position_provider(provider);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::use_issuer_lookup(const v3::IssuerLookup* lookup)
|
||||
{
|
||||
m_issuer_lookup = lookup;
|
||||
m_policy_validator.use_issuer_lookup(lookup);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::use_location_checker(const v3::LocationChecker* checker)
|
||||
{
|
||||
m_policy_validator.use_location_checker(checker);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::use_revocation_lookup(const v3::RevocationLookup* lookup)
|
||||
{
|
||||
m_policy_validator.use_revocation_lookup(lookup);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::use_trust_store(const v3::TrustStore* store)
|
||||
{
|
||||
m_trust_store = store;
|
||||
m_policy_validator.use_trust_store(store);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::use_backends(
|
||||
::vanetza::security::Backend* ecc, Backend* pqc_backend)
|
||||
{
|
||||
m_ecc_backend = ecc;
|
||||
m_pqc_backend = pqc_backend;
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::use_verification_policy(VerificationPolicy policy)
|
||||
{
|
||||
m_verification_policy = policy;
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::disable_time_checks(bool disable)
|
||||
{
|
||||
m_policy_validator.disable_time_checks(disable);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::disable_location_checks(bool disable)
|
||||
{
|
||||
m_policy_validator.disable_location_checks(disable);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::disable_chain_consistency_checks(bool disable)
|
||||
{
|
||||
m_policy_validator.disable_chain_consistency_checks(disable);
|
||||
}
|
||||
|
||||
void HybridCertificateValidator::disable_region_consistency_checks(bool disable)
|
||||
{
|
||||
m_policy_validator.disable_region_consistency_checks(disable);
|
||||
}
|
||||
|
||||
bool HybridCertificateValidator::alternative_signature_is_accepted(
|
||||
const v3::CertificateView& subject, const v3::CertificateView* issuer) const
|
||||
{
|
||||
if (m_verification_policy == VerificationPolicy::ClassicalOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto state = alternative_material_state(subject);
|
||||
if (state == MaterialState::Inconsistent) {
|
||||
return false;
|
||||
}
|
||||
if (state == MaterialState::None) {
|
||||
return m_verification_policy == VerificationPolicy::HybridIfPresent;
|
||||
}
|
||||
|
||||
return m_pqc_backend && verify_alternative_certificate(
|
||||
subject, issuer, *m_ecc_backend, *m_pqc_backend);
|
||||
}
|
||||
|
||||
bool HybridCertificateValidator::chain_is_authentic(
|
||||
const v3::CertificateView& signing_certificate) const
|
||||
{
|
||||
constexpr int maximum_chain_depth = 8;
|
||||
const v3::CertificateView* subject = &signing_certificate;
|
||||
|
||||
for (int depth = 0; depth < maximum_chain_depth; ++depth) {
|
||||
if (subject->issuer_is_self()) {
|
||||
const auto root_digest = subject->calculate_digest();
|
||||
if (!root_digest || m_trust_store->lookup(*root_digest).empty()) {
|
||||
return false;
|
||||
}
|
||||
return verify_primary_certificate(*subject, nullptr, *m_ecc_backend) &&
|
||||
alternative_signature_is_accepted(*subject, nullptr);
|
||||
}
|
||||
|
||||
const auto expected_issuer_digest = subject->issuer_digest();
|
||||
if (!expected_issuer_digest) {
|
||||
return false;
|
||||
}
|
||||
const v3::Certificate* issuer = m_issuer_lookup->find_issuer(*expected_issuer_digest);
|
||||
if (!issuer) {
|
||||
return false;
|
||||
}
|
||||
const auto actual_issuer_digest = issuer->calculate_digest();
|
||||
if (!actual_issuer_digest || *actual_issuer_digest != *expected_issuer_digest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!verify_primary_certificate(*subject, issuer, *m_ecc_backend) ||
|
||||
!alternative_signature_is_accepted(*subject, issuer)) {
|
||||
return false;
|
||||
}
|
||||
subject = issuer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace pqc
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <vanetza/security/v3/certificate_validator.hpp>
|
||||
#include <vanetza/security/pqc/fndsa512.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
class PositionProvider;
|
||||
class Runtime;
|
||||
|
||||
namespace security
|
||||
{
|
||||
|
||||
class Backend;
|
||||
|
||||
namespace v3
|
||||
{
|
||||
class CertificateView;
|
||||
class IssuerLookup;
|
||||
class LocationChecker;
|
||||
class RevocationLookup;
|
||||
class TrustStore;
|
||||
} // namespace v3
|
||||
|
||||
namespace pqc
|
||||
{
|
||||
|
||||
/**
|
||||
* Default V3 policy checks plus cryptographic certificate-chain validation.
|
||||
*
|
||||
* This class is compiled only for the experimental profile. Message
|
||||
* signatures remain classical ECC; this validator concerns the nested
|
||||
* signatures on certificates in the Root -> AA -> AT chain.
|
||||
*/
|
||||
class HybridCertificateValidator : public v3::CertificateValidator
|
||||
{
|
||||
public:
|
||||
enum class VerificationPolicy
|
||||
{
|
||||
ClassicalOnly,
|
||||
HybridIfPresent,
|
||||
HybridRequired,
|
||||
};
|
||||
|
||||
Verdict valid_for_signing(const v3::CertificateView&, ItsAid) override;
|
||||
|
||||
void use_runtime(const Runtime*);
|
||||
void use_position_provider(PositionProvider*);
|
||||
void use_issuer_lookup(const v3::IssuerLookup*);
|
||||
void use_location_checker(const v3::LocationChecker*);
|
||||
void use_revocation_lookup(const v3::RevocationLookup*);
|
||||
void use_trust_store(const v3::TrustStore*);
|
||||
void use_backends(::vanetza::security::Backend*, Backend*);
|
||||
void use_verification_policy(VerificationPolicy);
|
||||
|
||||
void disable_time_checks(bool);
|
||||
void disable_location_checks(bool);
|
||||
void disable_chain_consistency_checks(bool);
|
||||
void disable_region_consistency_checks(bool);
|
||||
|
||||
private:
|
||||
bool chain_is_authentic(const v3::CertificateView&) const;
|
||||
bool alternative_signature_is_accepted(
|
||||
const v3::CertificateView& subject, const v3::CertificateView* issuer) const;
|
||||
|
||||
v3::DefaultCertificateValidator m_policy_validator;
|
||||
const v3::IssuerLookup* m_issuer_lookup = nullptr;
|
||||
const v3::TrustStore* m_trust_store = nullptr;
|
||||
::vanetza::security::Backend* m_ecc_backend = nullptr;
|
||||
Backend* m_pqc_backend = nullptr;
|
||||
VerificationPolicy m_verification_policy = VerificationPolicy::HybridRequired;
|
||||
};
|
||||
|
||||
} // namespace pqc
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,4 @@
|
||||
include(UseGTest)
|
||||
configure_gtest_directory(LINK_LIBRARIES geodesy security
|
||||
COMPILE_DEFINITIONS ASSET_DIR="${SECURITY_TEST_ASSET_DIR}")
|
||||
add_gtest(HybridPqcCertificate hybrid_certificate.cpp)
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
#include <vanetza/asn1/security_profile.hpp>
|
||||
#include VANETZA_ASN1_SECURITY_HEADER(Certificate.h)
|
||||
#include VANETZA_ASN1_SECURITY_HEADER(PsidGroupPermissions.h)
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/pqc/fndsa512.hpp>
|
||||
#include <vanetza/security/pqc/hybrid_certificate.hpp>
|
||||
#include <vanetza/security/pqc/hybrid_certificate_validator.hpp>
|
||||
#include <vanetza/security/v3/asn1_conversions.hpp>
|
||||
#include <vanetza/security/v3/certificate.hpp>
|
||||
#include <vanetza/security/v3/issuer_memory_lookup.hpp>
|
||||
#include <vanetza/security/v3/trust_store.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace vanetza;
|
||||
using namespace vanetza::security;
|
||||
using namespace vanetza::security::v3;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
PrivateKey to_private_key(const ecdsa256::PrivateKey& input)
|
||||
{
|
||||
PrivateKey output;
|
||||
output.type = KeyType::NistP256;
|
||||
output.key.assign(input.key.begin(), input.key.end());
|
||||
return output;
|
||||
}
|
||||
|
||||
void set_verification_key(Certificate& certificate, const ecdsa256::PublicKey& key)
|
||||
{
|
||||
auto& indicator = certificate->toBeSigned.verifyKeyIndicator;
|
||||
indicator.present = Vanetza_Security_VerificationKeyIndicator_PR_verificationKey;
|
||||
auto& verification_key = indicator.choice.verificationKey;
|
||||
verification_key.present = Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256;
|
||||
|
||||
auto& point = verification_key.choice.ecdsaNistP256;
|
||||
point.present = Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256;
|
||||
OCTET_STRING_fromBuf(
|
||||
&point.choice.uncompressedP256.x,
|
||||
reinterpret_cast<const char*>(key.x.data()), key.x.size());
|
||||
OCTET_STRING_fromBuf(
|
||||
&point.choice.uncompressedP256.y,
|
||||
reinterpret_cast<const char*>(key.y.data()), key.y.size());
|
||||
}
|
||||
|
||||
void set_issuer(Certificate& certificate, const Certificate* issuer)
|
||||
{
|
||||
if (!issuer) {
|
||||
certificate->issuer.present = Vanetza_Security_IssuerIdentifier_PR_self;
|
||||
certificate->issuer.choice.self = Vanetza_Security_HashAlgorithm_sha256;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto digest = issuer->calculate_digest();
|
||||
if (!digest) {
|
||||
throw std::runtime_error("issuer certificate has no digest");
|
||||
}
|
||||
certificate->issuer.present = Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest;
|
||||
OCTET_STRING_fromBuf(
|
||||
&certificate->issuer.choice.sha256AndDigest,
|
||||
reinterpret_cast<const char*>(digest->data()), digest->size());
|
||||
}
|
||||
|
||||
void add_all_issue_permissions(Certificate& certificate)
|
||||
{
|
||||
auto* permissions = vanetza::asn1::allocate<v3::asn1::PsidGroupPermissions>();
|
||||
permissions->subjectPermissions.present = Vanetza_Security_SubjectPermissions_PR_all;
|
||||
permissions->subjectPermissions.choice.all = 0;
|
||||
certificate.add_cert_issue_permission(permissions);
|
||||
}
|
||||
|
||||
Certificate make_certificate(
|
||||
const ecdsa256::PublicKey& subject_key, const Certificate* issuer, bool authority)
|
||||
{
|
||||
Certificate certificate;
|
||||
certificate->version = 3;
|
||||
certificate->type = Vanetza_Security_CertificateType_explicit;
|
||||
set_issuer(certificate, issuer);
|
||||
|
||||
if (authority) {
|
||||
static const char name[] = "Hybrid test CA";
|
||||
certificate->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_name;
|
||||
OCTET_STRING_fromBuf(
|
||||
&certificate->toBeSigned.id.choice.name, name, sizeof(name) - 1);
|
||||
add_all_issue_permissions(certificate);
|
||||
} else {
|
||||
certificate->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_none;
|
||||
certificate.add_app_permission(aid::CA, ByteBuffer { 1, 0, 0 });
|
||||
}
|
||||
|
||||
static const std::array<char, 3> craca_id {{ 0, 0, 0 }};
|
||||
OCTET_STRING_fromBuf(
|
||||
&certificate->toBeSigned.cracaId, craca_id.data(), craca_id.size());
|
||||
certificate->toBeSigned.crlSeries = 0;
|
||||
certificate->toBeSigned.validityPeriod.start = 0;
|
||||
certificate->toBeSigned.validityPeriod.duration.present =
|
||||
Vanetza_Security_Duration_PR_years;
|
||||
certificate->toBeSigned.validityPeriod.duration.choice.years = 10;
|
||||
set_verification_key(certificate, subject_key);
|
||||
return certificate;
|
||||
}
|
||||
|
||||
void sign_hybrid(
|
||||
Certificate& subject, const Certificate* issuer,
|
||||
Backend& ecc_backend, pqc::Backend& pqc_backend,
|
||||
const PrivateKey& ecc_key, const pqc::PrivateKey& pqc_key)
|
||||
{
|
||||
pqc::sign_alternative_certificate(
|
||||
subject, issuer, ecc_backend, pqc_backend, pqc_key);
|
||||
pqc::sign_primary_certificate(subject, issuer, ecc_backend, ecc_key);
|
||||
}
|
||||
|
||||
void set_unsupported_alternative_signature(Certificate& certificate)
|
||||
{
|
||||
pqc::clear_alternative_signature(certificate);
|
||||
auto* signature = vanetza::asn1::allocate<v3::asn1::Signature>();
|
||||
signature->present = Vanetza_Security_Signature_PR_ecdsaNistP256Signature;
|
||||
auto& r = signature->choice.ecdsaNistP256Signature.rSig;
|
||||
r.present = Vanetza_Security_EccP256CurvePoint_PR_x_only;
|
||||
const std::array<char, 32> zeroes {{}};
|
||||
OCTET_STRING_fromBuf(&r.choice.x_only, zeroes.data(), zeroes.size());
|
||||
OCTET_STRING_fromBuf(
|
||||
&signature->choice.ecdsaNistP256Signature.sSig,
|
||||
zeroes.data(), zeroes.size());
|
||||
certificate->toBeSigned.altSignatureValue = signature;
|
||||
}
|
||||
|
||||
struct Chain
|
||||
{
|
||||
explicit Chain(bool hybrid_material = true) :
|
||||
ecc_backend(create_backend_or_throw("default")),
|
||||
pqc_backend(pqc::create_fndsa512_backend()),
|
||||
root_ecc_pair(ecc_backend->generate_key_pair()),
|
||||
aa_ecc_pair(ecc_backend->generate_key_pair()),
|
||||
at_ecc_pair(ecc_backend->generate_key_pair()),
|
||||
root_ecc_key(to_private_key(root_ecc_pair.private_key)),
|
||||
aa_ecc_key(to_private_key(aa_ecc_pair.private_key)),
|
||||
root_pqc_pair(pqc_backend->generate_key_pair()),
|
||||
aa_pqc_pair(pqc_backend->generate_key_pair())
|
||||
{
|
||||
root = make_certificate(root_ecc_pair.public_key, nullptr, true);
|
||||
if (hybrid_material) {
|
||||
pqc::set_alternative_public_key(root, root_pqc_pair.public_key);
|
||||
sign_hybrid(root, nullptr, *ecc_backend, *pqc_backend, root_ecc_key, root_pqc_pair.private_key);
|
||||
} else {
|
||||
pqc::sign_primary_certificate(root, nullptr, *ecc_backend, root_ecc_key);
|
||||
}
|
||||
|
||||
aa = make_certificate(aa_ecc_pair.public_key, &root, true);
|
||||
if (hybrid_material) {
|
||||
pqc::set_alternative_public_key(aa, aa_pqc_pair.public_key);
|
||||
sign_hybrid(aa, &root, *ecc_backend, *pqc_backend, root_ecc_key, root_pqc_pair.private_key);
|
||||
} else {
|
||||
pqc::sign_primary_certificate(aa, &root, *ecc_backend, root_ecc_key);
|
||||
}
|
||||
|
||||
at = make_certificate(at_ecc_pair.public_key, &aa, false);
|
||||
if (hybrid_material) {
|
||||
sign_hybrid(at, &aa, *ecc_backend, *pqc_backend, aa_ecc_key, aa_pqc_pair.private_key);
|
||||
} else {
|
||||
pqc::sign_primary_certificate(at, &aa, *ecc_backend, aa_ecc_key);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Backend> ecc_backend;
|
||||
std::unique_ptr<pqc::Backend> pqc_backend;
|
||||
ecdsa256::KeyPair root_ecc_pair;
|
||||
ecdsa256::KeyPair aa_ecc_pair;
|
||||
ecdsa256::KeyPair at_ecc_pair;
|
||||
PrivateKey root_ecc_key;
|
||||
PrivateKey aa_ecc_key;
|
||||
pqc::KeyPair root_pqc_pair;
|
||||
pqc::KeyPair aa_pqc_pair;
|
||||
Certificate root;
|
||||
Certificate aa;
|
||||
Certificate at;
|
||||
};
|
||||
|
||||
v3::CertificateValidator::Verdict validate(
|
||||
Chain& chain, pqc::HybridCertificateValidator::VerificationPolicy policy)
|
||||
{
|
||||
TrustStore trust_store;
|
||||
trust_store.insert(chain.root);
|
||||
|
||||
IssuerMemoryLookup issuer_lookup;
|
||||
if (!issuer_lookup.insert(chain.root) || !issuer_lookup.insert(chain.aa)) {
|
||||
throw std::runtime_error("cannot populate issuer lookup");
|
||||
}
|
||||
|
||||
pqc::HybridCertificateValidator validator;
|
||||
validator.use_issuer_lookup(&issuer_lookup);
|
||||
validator.use_trust_store(&trust_store);
|
||||
validator.use_backends(chain.ecc_backend.get(), chain.pqc_backend.get());
|
||||
validator.use_verification_policy(policy);
|
||||
validator.disable_time_checks(true);
|
||||
validator.disable_location_checks(true);
|
||||
return validator.valid_for_signing(chain.at, aid::CA);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(HybridCertificate, fndsa512_backend_rejects_invalid_material_sizes)
|
||||
{
|
||||
auto backend = pqc::create_fndsa512_backend();
|
||||
const auto key_pair = backend->generate_key_pair();
|
||||
const ByteBuffer message { 1, 2, 3, 4 };
|
||||
const auto signature = backend->sign(key_pair.private_key, message);
|
||||
|
||||
EXPECT_EQ(pqc::fndsa512_public_key_size, key_pair.public_key.bytes.size());
|
||||
EXPECT_EQ(pqc::fndsa512_private_key_size, key_pair.private_key.bytes.size());
|
||||
EXPECT_EQ(pqc::fndsa512_signature_size, signature.bytes.size());
|
||||
EXPECT_TRUE(backend->verify(key_pair.public_key, message, signature));
|
||||
|
||||
pqc::PublicKey short_key { ByteBuffer(1, 0) };
|
||||
pqc::PrivateKey short_private_key { ByteBuffer(1, 0) };
|
||||
pqc::Signature short_signature { ByteBuffer(1, 0) };
|
||||
EXPECT_THROW(backend->sign(short_private_key, message), std::invalid_argument);
|
||||
EXPECT_FALSE(backend->verify(short_key, message, signature));
|
||||
EXPECT_FALSE(backend->verify(key_pair.public_key, message, short_signature));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, material_roundtrips_through_oer)
|
||||
{
|
||||
Chain chain;
|
||||
Certificate decoded;
|
||||
ASSERT_TRUE(decoded.decode(chain.at.encode()));
|
||||
|
||||
EXPECT_EQ(pqc::MaterialState::EndEntity, pqc::alternative_material_state(decoded));
|
||||
EXPECT_FALSE(pqc::get_alternative_public_key(decoded));
|
||||
const auto signature = pqc::get_alternative_signature(decoded);
|
||||
ASSERT_TRUE(signature);
|
||||
EXPECT_EQ(pqc::fndsa512_signature_size, signature->bytes.size());
|
||||
|
||||
Certificate decoded_aa;
|
||||
ASSERT_TRUE(decoded_aa.decode(chain.aa.encode()));
|
||||
EXPECT_EQ(pqc::MaterialState::Authority, pqc::alternative_material_state(decoded_aa));
|
||||
ASSERT_TRUE(pqc::get_alternative_public_key(decoded_aa));
|
||||
ASSERT_TRUE(pqc::get_alternative_signature(decoded_aa));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, verifies_both_nested_signature_layers)
|
||||
{
|
||||
Chain chain;
|
||||
EXPECT_TRUE(pqc::verify_alternative_certificate(
|
||||
chain.root, nullptr, *chain.ecc_backend, *chain.pqc_backend));
|
||||
EXPECT_TRUE(pqc::verify_primary_certificate(chain.root, nullptr, *chain.ecc_backend));
|
||||
EXPECT_TRUE(pqc::verify_alternative_certificate(
|
||||
chain.aa, &chain.root, *chain.ecc_backend, *chain.pqc_backend));
|
||||
EXPECT_TRUE(pqc::verify_primary_certificate(chain.aa, &chain.root, *chain.ecc_backend));
|
||||
EXPECT_TRUE(pqc::verify_alternative_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, *chain.pqc_backend));
|
||||
EXPECT_TRUE(pqc::verify_primary_certificate(chain.at, &chain.aa, *chain.ecc_backend));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, certificate_hash_rejects_a_non_matching_issuer)
|
||||
{
|
||||
Chain chain;
|
||||
|
||||
EXPECT_THROW(
|
||||
pqc::calculate_certificate_hash(
|
||||
*chain.ecc_backend, HashAlgorithm::SHA256, chain.at, &chain.root,
|
||||
pqc::SignatureLayer::Primary),
|
||||
std::invalid_argument);
|
||||
EXPECT_FALSE(pqc::verify_primary_certificate(
|
||||
chain.at, &chain.root, *chain.ecc_backend));
|
||||
EXPECT_FALSE(pqc::verify_alternative_certificate(
|
||||
chain.at, &chain.root, *chain.ecc_backend, *chain.pqc_backend));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, signing_rejects_mismatched_alternative_private_key)
|
||||
{
|
||||
Chain chain;
|
||||
const auto unrelated_key_pair = chain.pqc_backend->generate_key_pair();
|
||||
pqc::clear_alternative_signature(chain.at);
|
||||
|
||||
EXPECT_THROW(
|
||||
pqc::sign_alternative_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, *chain.pqc_backend,
|
||||
unrelated_key_pair.private_key),
|
||||
std::invalid_argument);
|
||||
EXPECT_FALSE(pqc::get_alternative_signature(chain.at));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, signing_rejects_mismatched_primary_private_key)
|
||||
{
|
||||
Chain chain;
|
||||
const auto unrelated_key_pair = chain.ecc_backend->generate_key_pair();
|
||||
|
||||
EXPECT_THROW(
|
||||
pqc::sign_primary_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend,
|
||||
to_private_key(unrelated_key_pair.private_key)),
|
||||
std::invalid_argument);
|
||||
EXPECT_TRUE(pqc::verify_primary_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, primary_signature_covers_alternative_signature)
|
||||
{
|
||||
Chain chain;
|
||||
auto signature = pqc::get_alternative_signature(chain.at);
|
||||
ASSERT_TRUE(signature);
|
||||
signature->bytes.front() ^= 0x01;
|
||||
pqc::set_alternative_signature(chain.at, *signature);
|
||||
|
||||
EXPECT_FALSE(pqc::verify_alternative_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, *chain.pqc_backend));
|
||||
EXPECT_FALSE(pqc::verify_primary_certificate(chain.at, &chain.aa, *chain.ecc_backend));
|
||||
|
||||
pqc::sign_primary_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, chain.aa_ecc_key);
|
||||
EXPECT_TRUE(pqc::verify_primary_certificate(chain.at, &chain.aa, *chain.ecc_backend));
|
||||
EXPECT_FALSE(pqc::verify_alternative_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, *chain.pqc_backend));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, validator_accepts_authentic_hybrid_chain)
|
||||
{
|
||||
Chain chain;
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Valid,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridRequired));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, validator_verifies_authority_certificates_in_the_chain)
|
||||
{
|
||||
Chain chain;
|
||||
ASSERT_TRUE(chain.aa->signature);
|
||||
ASSERT_EQ(Vanetza_Security_Signature_PR_ecdsaNistP256Signature,
|
||||
chain.aa->signature->present);
|
||||
|
||||
auto& encoded_s = chain.aa->signature->choice.ecdsaNistP256Signature.sSig;
|
||||
ASSERT_TRUE(encoded_s.buf);
|
||||
ASSERT_GT(encoded_s.size, 0u);
|
||||
encoded_s.buf[0] ^= 0x01;
|
||||
|
||||
// Reissue the AT for the modified AA so leaf verification still succeeds.
|
||||
chain.at = make_certificate(chain.at_ecc_pair.public_key, &chain.aa, false);
|
||||
sign_hybrid(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, *chain.pqc_backend,
|
||||
chain.aa_ecc_key, chain.aa_pqc_pair.private_key);
|
||||
|
||||
EXPECT_TRUE(pqc::verify_primary_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend));
|
||||
EXPECT_FALSE(pqc::verify_primary_certificate(
|
||||
chain.aa, &chain.root, *chain.ecc_backend));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridRequired));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, validator_policy_is_explicit_for_classical_chains)
|
||||
{
|
||||
Chain chain(false);
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridRequired));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Valid,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridIfPresent));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Valid,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::ClassicalOnly));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, hybrid_policy_rejects_incomplete_authority_material)
|
||||
{
|
||||
Chain chain;
|
||||
pqc::clear_alternative_signature(chain.aa);
|
||||
pqc::sign_primary_certificate(
|
||||
chain.aa, &chain.root, *chain.ecc_backend, chain.root_ecc_key);
|
||||
|
||||
chain.at = make_certificate(chain.at_ecc_pair.public_key, &chain.aa, false);
|
||||
sign_hybrid(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, *chain.pqc_backend,
|
||||
chain.aa_ecc_key, chain.aa_pqc_pair.private_key);
|
||||
|
||||
EXPECT_EQ(pqc::MaterialState::Inconsistent,
|
||||
pqc::alternative_material_state(chain.aa));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridIfPresent));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Valid,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::ClassicalOnly));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, classical_policy_still_verifies_outer_signature)
|
||||
{
|
||||
Chain chain;
|
||||
auto signature = pqc::get_alternative_signature(chain.at);
|
||||
ASSERT_TRUE(signature);
|
||||
signature->bytes.front() ^= 0x01;
|
||||
pqc::set_alternative_signature(chain.at, *signature);
|
||||
pqc::sign_primary_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, chain.aa_ecc_key);
|
||||
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridRequired));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridIfPresent));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Valid,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::ClassicalOnly));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, every_policy_rejects_a_broken_primary_signature)
|
||||
{
|
||||
Chain chain;
|
||||
ASSERT_TRUE(chain.at->signature);
|
||||
ASSERT_EQ(Vanetza_Security_Signature_PR_ecdsaNistP256Signature,
|
||||
chain.at->signature->present);
|
||||
|
||||
auto& encoded_s = chain.at->signature->choice.ecdsaNistP256Signature.sSig;
|
||||
ASSERT_TRUE(encoded_s.buf);
|
||||
ASSERT_GT(encoded_s.size, 0u);
|
||||
encoded_s.buf[0] ^= 0x01;
|
||||
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridRequired));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridIfPresent));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::ClassicalOnly));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, authorization_ticket_rejects_alternative_public_key)
|
||||
{
|
||||
Chain chain;
|
||||
pqc::set_alternative_public_key(chain.at, chain.aa_pqc_pair.public_key);
|
||||
EXPECT_EQ(pqc::MaterialState::Inconsistent,
|
||||
pqc::alternative_material_state(chain.at));
|
||||
}
|
||||
|
||||
TEST(HybridCertificate, unsupported_alternative_choice_is_not_treated_as_absent)
|
||||
{
|
||||
Chain chain;
|
||||
set_unsupported_alternative_signature(chain.at);
|
||||
pqc::sign_primary_certificate(
|
||||
chain.at, &chain.aa, *chain.ecc_backend, chain.aa_ecc_key);
|
||||
|
||||
EXPECT_EQ(pqc::MaterialState::Inconsistent,
|
||||
pqc::alternative_material_state(chain.at));
|
||||
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
|
||||
validate(chain, pqc::HybridCertificateValidator::VerificationPolicy::HybridIfPresent));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/security/key_type.hpp>
|
||||
#include <vanetza/security/public_key.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
struct PrivateKey
|
||||
{
|
||||
KeyType type;
|
||||
ByteBuffer key;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,132 @@
|
||||
#include <vanetza/security/public_key.hpp>
|
||||
#include <boost/algorithm/hex.hpp>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
std::string canonical_hexstring(const PublicKey& key)
|
||||
{
|
||||
const std::size_t expected_length = key_length(key.type);
|
||||
if (expected_length == 0 || key.x.size() != expected_length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string input;
|
||||
switch (key.compression) {
|
||||
case KeyCompression::NoCompression:
|
||||
if (key.y.size() != expected_length) {
|
||||
return {};
|
||||
}
|
||||
input.push_back(*key.y.rbegin() % 2 == 0 ? 0x02 : 0x03);
|
||||
break;
|
||||
case KeyCompression::Y0:
|
||||
input.push_back(0x02);
|
||||
break;
|
||||
case KeyCompression::Y1:
|
||||
input.push_back(0x03);
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
std::copy(key.x.begin(), key.x.end(), std::back_inserter(input));
|
||||
return boost::algorithm::hex(input);
|
||||
}
|
||||
|
||||
ByteBuffer encode_subject_public_key_info(const PublicKey& key)
|
||||
{
|
||||
const std::size_t expected_length = key_length(key.type);
|
||||
if (expected_length == 0 || key.x.size() != expected_length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// RFC 5480: id-ecPublicKey OBJECT IDENTIFIER ::= { 1.2.840.10045.2.1 }
|
||||
static const uint8_t oid_ec_public_key[] = {
|
||||
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01
|
||||
};
|
||||
// RFC 5480: secp256r1 (NIST P-256) OID 1.2.840.10045.3.1.7
|
||||
static const uint8_t oid_secp256r1[] = {
|
||||
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07
|
||||
};
|
||||
// RFC 5639: brainpoolP256r1 OID 1.3.36.3.3.2.8.1.1.7
|
||||
static const uint8_t oid_brainpoolP256r1[] = {
|
||||
0x06, 0x09, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07
|
||||
};
|
||||
// RFC 5639: brainpoolP384r1 OID 1.3.36.3.3.2.8.1.1.11
|
||||
static const uint8_t oid_brainpoolP384r1[] = {
|
||||
0x06, 0x09, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B
|
||||
};
|
||||
|
||||
const uint8_t* named_curve_oid = nullptr;
|
||||
std::size_t named_curve_oid_size = 0;
|
||||
switch (key.type) {
|
||||
case KeyType::NistP256:
|
||||
named_curve_oid = oid_secp256r1;
|
||||
named_curve_oid_size = sizeof(oid_secp256r1);
|
||||
break;
|
||||
case KeyType::BrainpoolP256r1:
|
||||
named_curve_oid = oid_brainpoolP256r1;
|
||||
named_curve_oid_size = sizeof(oid_brainpoolP256r1);
|
||||
break;
|
||||
case KeyType::BrainpoolP384r1:
|
||||
named_curve_oid = oid_brainpoolP384r1;
|
||||
named_curve_oid_size = sizeof(oid_brainpoolP384r1);
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
uint8_t ec_point_prefix = 0;
|
||||
std::size_t ec_point_size = 0;
|
||||
switch (key.compression) {
|
||||
case KeyCompression::NoCompression:
|
||||
if (key.y.size() != expected_length) {
|
||||
return {};
|
||||
}
|
||||
ec_point_prefix = 0x04;
|
||||
ec_point_size = 1 + 2 * expected_length;
|
||||
break;
|
||||
case KeyCompression::Y0:
|
||||
ec_point_prefix = 0x02;
|
||||
ec_point_size = 1 + expected_length;
|
||||
break;
|
||||
case KeyCompression::Y1:
|
||||
ec_point_prefix = 0x03;
|
||||
ec_point_size = 1 + expected_length;
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::size_t algo_content_size = sizeof(oid_ec_public_key) + named_curve_oid_size;
|
||||
const std::size_t bit_string_content_size = 1 + ec_point_size; // leading "unused bits" byte
|
||||
const std::size_t spki_content_size = 2 + algo_content_size + 2 + bit_string_content_size;
|
||||
|
||||
ByteBuffer result;
|
||||
result.reserve(2 + spki_content_size);
|
||||
|
||||
result.push_back(0x30); // SEQUENCE (SubjectPublicKeyInfo)
|
||||
result.push_back(static_cast<uint8_t>(spki_content_size));
|
||||
|
||||
result.push_back(0x30); // SEQUENCE (AlgorithmIdentifier)
|
||||
result.push_back(static_cast<uint8_t>(algo_content_size));
|
||||
result.insert(result.end(), oid_ec_public_key, oid_ec_public_key + sizeof(oid_ec_public_key));
|
||||
result.insert(result.end(), named_curve_oid, named_curve_oid + named_curve_oid_size);
|
||||
|
||||
result.push_back(0x03); // BIT STRING (subjectPublicKey)
|
||||
result.push_back(static_cast<uint8_t>(bit_string_content_size));
|
||||
result.push_back(0x00); // unused bits
|
||||
result.push_back(ec_point_prefix);
|
||||
result.insert(result.end(), key.x.begin(), key.x.end());
|
||||
if (key.compression == KeyCompression::NoCompression) {
|
||||
result.insert(result.end(), key.y.begin(), key.y.end());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/security/key_type.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
enum class KeyCompression
|
||||
{
|
||||
NoCompression,
|
||||
Y0,
|
||||
Y1
|
||||
};
|
||||
|
||||
struct PublicKey
|
||||
{
|
||||
ByteBuffer x;
|
||||
ByteBuffer y;
|
||||
KeyType type;
|
||||
KeyCompression compression;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute canonical hex string of a compressed public key.
|
||||
* \return hex string, or empty string if the key is malformed
|
||||
*/
|
||||
std::string canonical_hexstring(const PublicKey&);
|
||||
|
||||
/**
|
||||
* Encode public key as SubjectPublicKeyInfo according to RFC 5480.
|
||||
* \return DER-encoded SubjectPublicKeyInfo, or empty buffer if the key is malformed
|
||||
*/
|
||||
ByteBuffer encode_subject_public_key_info(const PublicKey&);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,130 @@
|
||||
#include <vanetza/security/secured_message.hpp>
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
SecuredMessageView::SecuredMessageView(const SecuredMessage& msg) :
|
||||
m_variant(msg)
|
||||
{
|
||||
}
|
||||
|
||||
struct ItsAidVisitor : boost::static_visitor<ItsAid>
|
||||
{
|
||||
ItsAid operator()(const v2::SecuredMessage& msg) const
|
||||
{
|
||||
return get_its_aid(msg);
|
||||
}
|
||||
|
||||
ItsAid operator()(const v3::SecuredMessage& msg) const
|
||||
{
|
||||
return msg.its_aid();
|
||||
}
|
||||
};
|
||||
|
||||
ItsAid get_its_aid(const SecuredMessage& msg)
|
||||
{
|
||||
return boost::apply_visitor(ItsAidVisitor(), msg);
|
||||
}
|
||||
|
||||
ItsAid get_its_aid(const SecuredMessageView& msg)
|
||||
{
|
||||
return boost::apply_visitor(ItsAidVisitor(), msg);
|
||||
}
|
||||
|
||||
std::size_t get_size(const SecuredMessage& msg)
|
||||
{
|
||||
struct Visitor : boost::static_visitor<std::size_t>
|
||||
{
|
||||
std::size_t operator()(const v2::SecuredMessage& msg) const
|
||||
{
|
||||
return get_size(msg);
|
||||
}
|
||||
|
||||
std::size_t operator()(const v3::SecuredMessage& msg) const
|
||||
{
|
||||
return msg.size();
|
||||
}
|
||||
};
|
||||
|
||||
return boost::apply_visitor(Visitor(), msg);
|
||||
}
|
||||
|
||||
struct SerializeVisitor : boost::static_visitor<void>
|
||||
{
|
||||
OutputArchive& m_archive;
|
||||
SerializeVisitor(OutputArchive& ar) : m_archive(ar) {}
|
||||
|
||||
void operator()(const v2::SecuredMessage& msg)
|
||||
{
|
||||
serialize(m_archive, msg);
|
||||
}
|
||||
|
||||
void operator()(const v3::SecuredMessage& msg)
|
||||
{
|
||||
serialize(m_archive, msg);
|
||||
}
|
||||
};
|
||||
|
||||
void serialize(OutputArchive& ar, const SecuredMessage& msg)
|
||||
{
|
||||
SerializeVisitor visitor { ar };
|
||||
boost::apply_visitor(visitor, msg);
|
||||
}
|
||||
|
||||
void serialize(OutputArchive& ar, const SecuredMessageView& msg)
|
||||
{
|
||||
SerializeVisitor visitor { ar };
|
||||
boost::apply_visitor(visitor, msg);
|
||||
}
|
||||
|
||||
std::size_t deserialize(InputArchive& ar, SecuredMessage& msg)
|
||||
{
|
||||
struct Visitor : boost::static_visitor<std::size_t>
|
||||
{
|
||||
InputArchive& m_archive;
|
||||
Visitor(InputArchive& ar) : m_archive(ar) {}
|
||||
|
||||
std::size_t operator()(v2::SecuredMessage& msg)
|
||||
{
|
||||
return deserialize(m_archive, msg);
|
||||
}
|
||||
|
||||
std::size_t operator()(v3::SecuredMessage& msg)
|
||||
{
|
||||
return deserialize(m_archive, msg);
|
||||
}
|
||||
};
|
||||
|
||||
Visitor visitor(ar);
|
||||
return boost::apply_visitor(visitor, msg);
|
||||
}
|
||||
|
||||
struct PayloadCopyVisitor : boost::static_visitor<PacketVariant>
|
||||
{
|
||||
PacketVariant operator()(const v2::SecuredMessage& msg) const
|
||||
{
|
||||
return msg.payload.data;
|
||||
}
|
||||
|
||||
PacketVariant operator()(const v3::SecuredMessage& msg) const
|
||||
{
|
||||
return msg.payload();
|
||||
}
|
||||
};
|
||||
|
||||
PacketVariant get_payload_copy(const SecuredMessage& msg)
|
||||
{
|
||||
return boost::apply_visitor(PayloadCopyVisitor(), msg);
|
||||
}
|
||||
|
||||
PacketVariant get_payload_copy(const SecuredMessageView& msg)
|
||||
{
|
||||
return boost::apply_visitor(PayloadCopyVisitor(), msg);
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef BAAED6CC_75E1_4851_B84B_7B90FD87FBAC
|
||||
#define BAAED6CC_75E1_4851_B84B_7B90FD87FBAC
|
||||
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/common/serialization.hpp>
|
||||
#include <vanetza/net/packet_variant.hpp>
|
||||
#include <vanetza/security/v2/secured_message.hpp>
|
||||
#include <vanetza/security/v3/secured_message.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
using SecuredMessage = boost::variant<v2::SecuredMessage, v3::SecuredMessage>;
|
||||
|
||||
class SecuredMessageView
|
||||
{
|
||||
public:
|
||||
explicit SecuredMessageView(const SecuredMessage& msg);
|
||||
|
||||
template<typename Visitor>
|
||||
typename Visitor::result_type apply_visitor(Visitor& visitor) const
|
||||
{
|
||||
return m_variant.apply_visitor(visitor);
|
||||
}
|
||||
|
||||
private:
|
||||
boost::variant<const v2::SecuredMessage&, const v3::SecuredMessage&> m_variant;
|
||||
};
|
||||
|
||||
ItsAid get_its_aid(const SecuredMessage&);
|
||||
ItsAid get_its_aid(const SecuredMessageView&);
|
||||
|
||||
std::size_t get_size(const SecuredMessage& msg);
|
||||
std::size_t get_size(const SecuredMessageView& msg);
|
||||
|
||||
void serialize(OutputArchive& ar, const SecuredMessage& msg);
|
||||
void serialize(OutputArchive& ar, const SecuredMessageView& msg);
|
||||
|
||||
std::size_t deserialize(InputArchive& ar, SecuredMessage&);
|
||||
|
||||
PacketVariant get_payload_copy(const SecuredMessage&);
|
||||
PacketVariant get_payload_copy(const SecuredMessageView&);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BAAED6CC_75E1_4851_B84B_7B90FD87FBAC */
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef SECURITY_ENTITY_HPP
|
||||
#define SECURITY_ENTITY_HPP
|
||||
|
||||
#include <vanetza/security/decap_service.hpp>
|
||||
#include <vanetza/security/encap_service.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
class SecurityEntity
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \brief Creates a security envelope covering the given payload.
|
||||
*
|
||||
* The payload consists of the CommonHeader, ExtendedHeader and the payload of
|
||||
* the layers above the network layer. The entire security envelope is used
|
||||
* to calculate a signature which gets added to the resulting SecuredMessage.
|
||||
*
|
||||
* \param request containing payload to sign
|
||||
* \return confirmation containing signed SecuredMessage
|
||||
*/
|
||||
virtual EncapConfirm encapsulate_packet(EncapRequest&& request) = 0;
|
||||
|
||||
/**
|
||||
* \brief Decapsulates the payload within a SecuredMessage
|
||||
*
|
||||
* Verifies the Signature and SignerInfo of a SecuredMessage.
|
||||
*
|
||||
* \param request containing a SecuredMessage
|
||||
* \return decapsulation confirmation including plaintext payload
|
||||
*/
|
||||
virtual DecapConfirm decapsulate_packet(DecapRequest&& request) = 0;
|
||||
|
||||
virtual ~SecurityEntity() = default;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif // SECURITY_ENTITY_HPP
|
||||
@@ -0,0 +1,49 @@
|
||||
#include <vanetza/security/sha.hpp>
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
#include <cryptopp/sha.h>
|
||||
#endif
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
#include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
Sha256Digest calculate_sha256_digest(const uint8_t* data, std::size_t len)
|
||||
{
|
||||
Sha256Digest digest;
|
||||
#if defined VANETZA_WITH_OPENSSL
|
||||
static_assert(SHA256_DIGEST_LENGTH == digest.size(), "size of OpenSSL SHA256_DIGEST_LENGTH does not match");
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
SHA256_Update(&ctx, data, len);
|
||||
SHA256_Final(digest.data(), &ctx);
|
||||
#elif defined VANETZA_WITH_CRYPTOPP
|
||||
static_assert(CryptoPP::SHA256::DIGESTSIZE == digest.size(), "size of CryptoPP::SHA256 does not match digest");
|
||||
CryptoPP::SHA256 hash;
|
||||
hash.CalculateDigest(digest.data(), data, len);
|
||||
#else
|
||||
# error "no SHA256 implementation available"
|
||||
#endif
|
||||
return digest;
|
||||
}
|
||||
|
||||
Sha384Digest calculate_sha384_digest(const uint8_t* data, std::size_t len)
|
||||
{
|
||||
Sha384Digest digest;
|
||||
#if defined VANETZA_WITH_OPENSSL
|
||||
SHA384(data, len, digest.data());
|
||||
#elif defined VANETZA_WITH_CRYPTOPP
|
||||
static_assert(CryptoPP::SHA384::DIGESTSIZE == digest.size(), "size of CryptoPP::SHA384 does not match digest");
|
||||
CryptoPP::SHA384 hash;
|
||||
hash.CalculateDigest(digest.data(), data, len);
|
||||
#else
|
||||
# error "no SHA384 implementation available"
|
||||
#endif
|
||||
return digest;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef SHA_HPP_ENSVKDXU
|
||||
#define SHA_HPP_ENSVKDXU
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
using Sha256Digest = std::array<uint8_t, 32>;
|
||||
using Sha384Digest = std::array<uint8_t, 48>;
|
||||
|
||||
Sha256Digest calculate_sha256_digest(const uint8_t* data, std::size_t len);
|
||||
Sha384Digest calculate_sha384_digest(const uint8_t* data, std::size_t len);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* SHA_HPP_ENSVKDXU */
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef SIGN_SERVICE_HPP_4MDQBSEF
|
||||
#define SIGN_SERVICE_HPP_4MDQBSEF
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/common/position_provider.hpp>
|
||||
#include <vanetza/net/packet.hpp>
|
||||
#include <vanetza/security/hash_algorithm.hpp>
|
||||
#include <vanetza/security/key_type.hpp>
|
||||
#include <vanetza/security/secured_message.hpp>
|
||||
#include <vanetza/security/signing_policy.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <functional>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
class Runtime;
|
||||
|
||||
namespace security
|
||||
{
|
||||
|
||||
// mandatory SN-SIGN.request parameters
|
||||
struct SignRequest
|
||||
{
|
||||
DownPacket plain_message;
|
||||
ItsAid its_aid;
|
||||
ByteBuffer permissions;
|
||||
// optional SN-ENCAP.request context_information (TS 102 723-8 V1.1.1 Table 24):
|
||||
// opaque octets for selecting properties of the security protocol
|
||||
ByteBuffer context_information;
|
||||
bool external_payload = false;
|
||||
bool self_signed = false;
|
||||
};
|
||||
|
||||
enum class SignConfirmError
|
||||
{
|
||||
Unspecified,
|
||||
No_Certificate,
|
||||
No_Service,
|
||||
};
|
||||
|
||||
// mandatory SN-SIGN.confirm parameters
|
||||
struct SignConfirm
|
||||
{
|
||||
SignConfirm(SignConfirmError error, boost::optional<SecuredMessage> message)
|
||||
: error(error), secured_message(std::move(message))
|
||||
{
|
||||
}
|
||||
|
||||
static SignConfirm success(SecuredMessage&& message)
|
||||
{
|
||||
return { SignConfirmError::Unspecified, std::move(message) };
|
||||
}
|
||||
|
||||
static SignConfirm failure(SignConfirmError error)
|
||||
{
|
||||
return { error, boost::none };
|
||||
}
|
||||
|
||||
SignConfirmError error;
|
||||
boost::optional<SecuredMessage> secured_message;
|
||||
};
|
||||
|
||||
/**
|
||||
* Equivalant of SN-SIGN service in TS 102 723-8 v1.1.1
|
||||
*/
|
||||
class SignService
|
||||
{
|
||||
public:
|
||||
virtual ~SignService() = default;
|
||||
virtual SignConfirm sign(SignRequest&&) = 0;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* SIGN_SERVICE_HPP_4MDQBSEF */
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifndef FA909143_0CE7_4397_A42B_5CDA56AB4716
|
||||
#define FA909143_0CE7_4397_A42B_5CDA56AB4716
|
||||
|
||||
#include <vanetza/security/ecc_point.hpp>
|
||||
#include <vanetza/security/signature.hpp>
|
||||
#include <cassert>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
EcdsaSignatureFuture::EcdsaSignatureFuture(std::shared_future<EcdsaSignature> future,
|
||||
EcdsaSignature placeholder) :
|
||||
m_future(future), m_placeholder(std::move(placeholder))
|
||||
{
|
||||
if (!m_future.valid()) {
|
||||
throw std::invalid_argument("EcdsaSignature future has to be valid");
|
||||
}
|
||||
}
|
||||
|
||||
const EcdsaSignature& EcdsaSignatureFuture::get() const
|
||||
{
|
||||
assert(m_future.valid());
|
||||
const EcdsaSignature& signature = m_future.get();
|
||||
assert(signature.s.size() == m_placeholder.s.size());
|
||||
assert(signature.R.which() == m_placeholder.R.which());
|
||||
assert(get_length(signature.R) == get_length(m_placeholder.R));
|
||||
return signature;
|
||||
}
|
||||
|
||||
std::size_t EcdsaSignatureFuture::size() const
|
||||
{
|
||||
return get_length(m_placeholder.R) + m_placeholder.s.size();
|
||||
}
|
||||
|
||||
ByteBuffer extract_signature_buffer(const SomeEcdsaSignature& sig)
|
||||
{
|
||||
struct extraction_visitor : public boost::static_visitor<>
|
||||
{
|
||||
void operator()(const EcdsaSignature& sig)
|
||||
{
|
||||
m_buffer = convert_for_signing(sig.R);
|
||||
m_buffer.insert(m_buffer.end(), sig.s.begin(), sig.s.end());
|
||||
}
|
||||
|
||||
void operator()(const EcdsaSignatureFuture& sig_future)
|
||||
{
|
||||
const EcdsaSignature& sig = sig_future.get();
|
||||
(*this)(sig);
|
||||
}
|
||||
|
||||
ByteBuffer m_buffer;
|
||||
};
|
||||
|
||||
extraction_visitor visitor;
|
||||
boost::apply_visitor(visitor, sig);
|
||||
|
||||
return visitor.m_buffer;
|
||||
}
|
||||
|
||||
ByteBuffer extract_signature_buffer(const Signature& sig)
|
||||
{
|
||||
ByteBuffer buffer = sig.r;
|
||||
buffer.insert(buffer.end(), sig.s.begin(), sig.s.end());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* FA909143_0CE7_4397_A42B_5CDA56AB4716 */
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef CF4C0740_EE9F_493A_A3F5_95DA691E8989
|
||||
#define CF4C0740_EE9F_493A_A3F5_95DA691E8989
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/security/ecc_point.hpp>
|
||||
#include <vanetza/security/key_type.hpp>
|
||||
#include <cstddef>
|
||||
#include <future>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/// EcdsaSignature specified in TS 103 097 v1.2.1, section 4.2.9
|
||||
struct EcdsaSignature
|
||||
{
|
||||
EccPoint R;
|
||||
ByteBuffer s;
|
||||
};
|
||||
|
||||
class EcdsaSignatureFuture
|
||||
{
|
||||
public:
|
||||
EcdsaSignatureFuture(std::shared_future<EcdsaSignature>, EcdsaSignature placholder);
|
||||
|
||||
const EcdsaSignature& get() const;
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
mutable std::shared_future<EcdsaSignature> m_future;
|
||||
EcdsaSignature m_placeholder;
|
||||
};
|
||||
|
||||
using SomeEcdsaSignature = boost::variant<EcdsaSignature, EcdsaSignatureFuture>;
|
||||
|
||||
struct Signature
|
||||
{
|
||||
ByteBuffer r;
|
||||
ByteBuffer s;
|
||||
KeyType type;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Extracts binary signature
|
||||
* \param signature source for binary signature
|
||||
* \return signature as binary
|
||||
*/
|
||||
ByteBuffer extract_signature_buffer(const SomeEcdsaSignature& sig);
|
||||
ByteBuffer extract_signature_buffer(const Signature& sig);
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* CF4C0740_EE9F_493A_A3F5_95DA691E8989 */
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef C234BFC2_5BE1_49B6_90C3_0DFD7690BAB5
|
||||
#define C234BFC2_5BE1_49B6_90C3_0DFD7690BAB5
|
||||
|
||||
#include <vanetza/common/position_provider.hpp>
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
class SigningPolicy
|
||||
{
|
||||
public:
|
||||
virtual ~SigningPolicy() = default;
|
||||
};
|
||||
|
||||
class DefaultSigningPolicy : public SigningPolicy
|
||||
{
|
||||
public:
|
||||
DefaultSigningPolicy(const Runtime&, PositionProvider&);
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* C234BFC2_5BE1_49B6_90C3_0DFD7690BAB5 */
|
||||
+571
@@ -0,0 +1,571 @@
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/common/position_provider.hpp>
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/public_key.hpp>
|
||||
#include <vanetza/security/straight_verify_service.hpp>
|
||||
#include <vanetza/security/v2/basic_elements.hpp>
|
||||
#include <vanetza/security/v2/certificate_cache.hpp>
|
||||
#include <vanetza/security/v2/certificate_provider.hpp>
|
||||
#include <vanetza/security/v2/certificate_validator.hpp>
|
||||
#include <vanetza/security/v2/sign_header_policy.hpp>
|
||||
#include <vanetza/security/v2/verification.hpp>
|
||||
#include <vanetza/security/v3/asn1_conversions.hpp>
|
||||
#include <vanetza/security/v3/asn1_types.hpp>
|
||||
#include <vanetza/security/v3/certificate_cache.hpp>
|
||||
#include <vanetza/security/v3/certificate_provider.hpp>
|
||||
#include <vanetza/security/v3/certificate_validator.hpp>
|
||||
#include <vanetza/security/v3/hash.hpp>
|
||||
#include <vanetza/security/v3/sign_header_policy.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
// asn1c quirk
|
||||
struct Vanetza_Security_Certificate : public Vanetza_Security_CertificateBase {};
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool assign_permissions(const v2::Certificate& certificate, VerifyConfirm& confirm)
|
||||
{
|
||||
for (auto& subject_attribute : certificate.subject_attributes) {
|
||||
if (get_type(subject_attribute) != v2::SubjectAttributeType::ITS_AID_SSP_List) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& permissions = boost::get<std::list<v2::ItsAidSsp> >(subject_attribute);
|
||||
for (auto& permission : permissions) {
|
||||
if (permission.its_aid == confirm.its_aid) {
|
||||
confirm.permissions = permission.service_specific_permissions;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StraightVerifyService::StraightVerifyService(const Runtime& runtime, Backend& backend) :
|
||||
m_runtime(runtime), m_backend(backend)
|
||||
{
|
||||
}
|
||||
|
||||
StraightVerifyService::StraightVerifyService(const Runtime& runtime, Backend& backend, PositionProvider& position) :
|
||||
m_runtime(runtime), m_backend(backend), m_position_provider(&position)
|
||||
{
|
||||
}
|
||||
|
||||
void StraightVerifyService::use_certificate_cache(v2::CertificateCache* cache)
|
||||
{
|
||||
m_context_v2.m_cert_cache = cache;
|
||||
}
|
||||
|
||||
void StraightVerifyService::use_certificate_provider(v2::CertificateProvider* provider)
|
||||
{
|
||||
m_context_v2.m_cert_provider = provider;
|
||||
}
|
||||
|
||||
void StraightVerifyService::use_certificate_validator(v2::CertificateValidator* validator)
|
||||
{
|
||||
m_context_v2.m_cert_validator = validator;
|
||||
}
|
||||
|
||||
void StraightVerifyService::use_sign_header_policy(v2::SignHeaderPolicy* policy)
|
||||
{
|
||||
m_context_v2.m_sign_policy = policy;
|
||||
}
|
||||
|
||||
void StraightVerifyService::use_certificate_provider(v3::CertificateProvider* provider)
|
||||
{
|
||||
if (provider)
|
||||
{
|
||||
use_certificate_cache(&provider->cache());
|
||||
}
|
||||
}
|
||||
|
||||
void StraightVerifyService::use_certificate_cache(v3::CertificateCache* cache)
|
||||
{
|
||||
m_context_v3.m_cert_cache = cache;
|
||||
}
|
||||
|
||||
void StraightVerifyService::use_certificate_validator(v3::CertificateValidator* validator)
|
||||
{
|
||||
m_context_v3.m_cert_validator = validator;
|
||||
}
|
||||
|
||||
void StraightVerifyService::use_sign_header_policy(v3::SignHeaderPolicy* policy)
|
||||
{
|
||||
m_context_v3.m_sign_policy = policy;
|
||||
}
|
||||
|
||||
VerifyConfirm StraightVerifyService::verify(const VerifyRequest& request)
|
||||
{
|
||||
struct visitor : public boost::static_visitor<VerifyConfirm>
|
||||
{
|
||||
visitor(StraightVerifyService* service) : m_service(service)
|
||||
{
|
||||
}
|
||||
|
||||
VerifyConfirm operator()(const v2::SecuredMessage& msg)
|
||||
{
|
||||
return m_service->verify(msg);
|
||||
}
|
||||
|
||||
VerifyConfirm operator()(const v3::SecuredMessage& msg)
|
||||
{
|
||||
return m_service->verify(msg);
|
||||
}
|
||||
|
||||
StraightVerifyService* m_service = nullptr;
|
||||
} visitor(this);
|
||||
|
||||
return boost::apply_visitor(visitor, request.secured_message);
|
||||
}
|
||||
|
||||
VerifyConfirm StraightVerifyService::verify(const v2::SecuredMessage& secured_message)
|
||||
{
|
||||
// TODO check if certificates in chain have been revoked for all CA certificates, ATs are never revoked
|
||||
VerifyConfirm confirm;
|
||||
using namespace v2;
|
||||
|
||||
if (PayloadType::Signed != secured_message.payload.type) {
|
||||
confirm.report = VerificationReport::Unsigned_Message;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
if (2 != secured_message.protocol_version()) {
|
||||
confirm.report = VerificationReport::Incompatible_Protocol;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
if (!m_context_v2.complete() || !m_position_provider) {
|
||||
confirm.report = VerificationReport::Configuration_Problem;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
v2::CertificateProvider& cert_provider = *m_context_v2.m_cert_provider;
|
||||
v2::CertificateCache& cert_cache = *m_context_v2.m_cert_cache;
|
||||
v2::CertificateValidator& cert_validator = *m_context_v2.m_cert_validator;
|
||||
v2::SignHeaderPolicy& sign_policy = *m_context_v2.m_sign_policy;
|
||||
|
||||
const std::list<HashedId3>* requested_certs = secured_message.header_field<HeaderFieldType::Request_Unrecognized_Certificate>();
|
||||
if (requested_certs) {
|
||||
for (auto& requested_cert : *requested_certs) {
|
||||
if (truncate(calculate_hash(cert_provider.own_certificate())) == requested_cert) {
|
||||
sign_policy.request_certificate();
|
||||
}
|
||||
|
||||
for (auto& cert : cert_provider.own_chain()) {
|
||||
if (truncate(calculate_hash(cert)) == requested_cert) {
|
||||
sign_policy.request_certificate_chain();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const IntX* its_aid = secured_message.header_field<HeaderFieldType::Its_Aid>();
|
||||
if (!its_aid) {
|
||||
// ITS-AID is required to be present, report as incompatible protocol, as that's the closest match
|
||||
confirm.report = VerificationReport::Incompatible_Protocol;
|
||||
return confirm;
|
||||
}
|
||||
confirm.its_aid = its_aid->get();
|
||||
|
||||
const SignerInfo* signer_info = secured_message.header_field<HeaderFieldType::Signer_Info>();
|
||||
std::list<v2::Certificate> possible_certificates;
|
||||
bool possible_certificates_from_cache = false;
|
||||
|
||||
// use a dummy hash for initialization
|
||||
HashedId8 signer_hash;
|
||||
signer_hash.fill(0x00);
|
||||
|
||||
if (signer_info) {
|
||||
switch (get_type(*signer_info)) {
|
||||
case SignerInfoType::Certificate:
|
||||
possible_certificates.push_back(boost::get<v2::Certificate>(*signer_info));
|
||||
signer_hash = calculate_hash(boost::get<v2::Certificate>(*signer_info));
|
||||
|
||||
if (confirm.its_aid == aid::CA && cert_cache.lookup(signer_hash, SubjectType::Authorization_Ticket).size() == 0) {
|
||||
// Previously unknown certificate, send own certificate in next CAM
|
||||
// See TS 103 097 v1.2.1, section 7.1, 1st bullet, 3rd dash
|
||||
sign_policy.request_certificate();
|
||||
}
|
||||
|
||||
break;
|
||||
case SignerInfoType::Certificate_Digest_With_SHA256:
|
||||
signer_hash = boost::get<HashedId8>(*signer_info);
|
||||
possible_certificates.splice(possible_certificates.end(), cert_cache.lookup(signer_hash, SubjectType::Authorization_Ticket));
|
||||
possible_certificates_from_cache = true;
|
||||
break;
|
||||
case SignerInfoType::Certificate_Chain:
|
||||
{
|
||||
std::list<v2::Certificate> chain = boost::get<std::list<v2::Certificate>>(*signer_info);
|
||||
if (chain.size() == 0) {
|
||||
confirm.report = VerificationReport::Signer_Certificate_Not_Found;
|
||||
return confirm;
|
||||
} else if (chain.size() > 3) {
|
||||
// prevent DoS by sending very long chains, maximum length is three certificates, because:
|
||||
// AT → AA → Root and no other signatures are allowed, sending the Root is optional
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
return confirm;
|
||||
}
|
||||
// pre-check chain certificates, otherwise they're not available for the ticket check
|
||||
for (auto& cert : chain) {
|
||||
// root certificates must already be known, otherwise the validation will fail anyway
|
||||
if (cert.subject_info.subject_type == SubjectType::Authorization_Authority) {
|
||||
// there's no need to report unknown signers at this point, see comment above
|
||||
CertificateValidity validity = cert_validator.check_certificate(cert);
|
||||
|
||||
// we can abort early if there are invalid AA certificates in the chain
|
||||
if (!validity) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
confirm.certificate_validity = validity;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
// We won't cache outdated or premature certificates in the cache and abort early.
|
||||
// This check isn't required as it would just fail below or in the consistency checks,
|
||||
// but it's an optimization and saves us from polluting the cache with such certificates.
|
||||
if (!check_certificate_time(cert, m_runtime.now()) || !check_certificate_region(cert, m_position_provider->position_fix())) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
cert_cache.insert(cert);
|
||||
}
|
||||
}
|
||||
// last certificate must be the authorization ticket
|
||||
signer_hash = calculate_hash(chain.back());
|
||||
possible_certificates.push_back(chain.back());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
confirm.report = VerificationReport::Unsupported_Signer_Identifier_Type;
|
||||
return confirm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (possible_certificates.size() == 0) {
|
||||
confirm.report = VerificationReport::Signer_Certificate_Not_Found;
|
||||
confirm.certificate_id = signer_hash;
|
||||
sign_policy.request_unrecognized_certificate(signer_hash);
|
||||
return confirm;
|
||||
}
|
||||
|
||||
if (!check_generation_time(secured_message, m_runtime.now())) {
|
||||
confirm.report = VerificationReport::Invalid_Timestamp;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
// TODO check Duplicate_Message, Invalid_Mobility_Data, Unencrypted_Message, Decryption_Error
|
||||
|
||||
// check signature
|
||||
const TrailerField* signature_field = secured_message.trailer_field(TrailerFieldType::Signature);
|
||||
const v2::Signature* signature = boost::get<v2::Signature>(signature_field);
|
||||
|
||||
if (!signature) {
|
||||
confirm.report = VerificationReport::Unsigned_Message;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
if (PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256 != get_type(*signature)) {
|
||||
confirm.report = VerificationReport::False_Signature;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
// check the size of signature.R and siganture.s
|
||||
auto ecdsa = extract_ecdsa_signature(*signature);
|
||||
const auto field_len = field_size(PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256);
|
||||
if (!ecdsa || ecdsa->s.size() != field_len) {
|
||||
confirm.report = VerificationReport::False_Signature;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
// verify payload signature with given signature
|
||||
ByteBuffer payload = convert_for_signing(secured_message, secured_message.trailer_fields);
|
||||
boost::optional<v2::Certificate> signer;
|
||||
|
||||
for (const auto& cert : possible_certificates) {
|
||||
SubjectType subject_type = cert.subject_info.subject_type;
|
||||
if (subject_type != SubjectType::Authorization_Ticket) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
confirm.certificate_validity = CertificateInvalidReason::Invalid_Signer;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
boost::optional<ecdsa256::PublicKey> public_key = get_public_key(cert, m_backend);
|
||||
|
||||
// public key could not be extracted
|
||||
if (!public_key) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
confirm.certificate_validity = CertificateInvalidReason::Missing_Public_Key;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
if (m_backend.verify_data(public_key.get(), payload, *ecdsa)) {
|
||||
signer = cert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!signer) {
|
||||
// HashedId8 of authorization tickets is not guaranteed to be globally unique.
|
||||
// The collision probability is rather low, but it might happen.
|
||||
if (signer_info && get_type(*signer_info) == SignerInfoType::Certificate_Digest_With_SHA256) {
|
||||
// assume a hash collision since we got only a digest with message
|
||||
confirm.report = VerificationReport::Signer_Certificate_Not_Found;
|
||||
} else {
|
||||
// signature does not match the certificate received with this message
|
||||
confirm.report = VerificationReport::False_Signature;
|
||||
}
|
||||
|
||||
confirm.certificate_id = signer_hash;
|
||||
sign_policy.request_unrecognized_certificate(signer_hash);
|
||||
return confirm;
|
||||
}
|
||||
|
||||
// we can only check the generation location after we have identified the correct certificate
|
||||
if (!check_generation_location(secured_message, *signer)) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
confirm.certificate_validity = CertificateInvalidReason::Off_Region;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
CertificateValidity cert_validity = CertificateValidity::valid();
|
||||
if (!possible_certificates_from_cache) { // certificates from cache are already verified as trusted
|
||||
cert_validity = cert_validator.check_certificate(*signer);
|
||||
}
|
||||
|
||||
confirm.certificate_validity = cert_validity;
|
||||
|
||||
// if certificate could not be verified return correct DecapReport
|
||||
if (!cert_validity) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
|
||||
if (cert_validity.reason() == CertificateInvalidReason::Unknown_Signer) {
|
||||
if (get_type(signer->signer_info) == SignerInfoType::Certificate_Digest_With_SHA256) {
|
||||
auto signer_hash = boost::get<HashedId8>(signer->signer_info);
|
||||
confirm.certificate_id = signer_hash;
|
||||
sign_policy.request_unrecognized_certificate(signer_hash);
|
||||
}
|
||||
}
|
||||
|
||||
return confirm;
|
||||
}
|
||||
|
||||
if (!check_certificate_time(*signer, m_runtime.now())) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
confirm.certificate_validity = CertificateInvalidReason::Off_Time_Period;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
if (!check_certificate_region(*signer, m_position_provider->position_fix())) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
confirm.certificate_validity = CertificateInvalidReason::Off_Region;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
// Assign permissions from the certificate based on the message AID already present in the confirm
|
||||
// and reject the certificate if no permissions are present for the claimed AID.
|
||||
if (!assign_permissions(*signer, confirm)) {
|
||||
// This might seem weird, because the certificate itself is valid, but not for the received message.
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
confirm.certificate_validity = CertificateInvalidReason::Insufficient_ITS_AID;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
// cache only certificates that are useful, one that mismatches its restrictions isn't
|
||||
cert_cache.insert(*signer);
|
||||
|
||||
confirm.report = VerificationReport::Success;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
VerifyConfirm StraightVerifyService::verify(const v3::SecuredMessage& msg)
|
||||
{
|
||||
/*
|
||||
* TS 103 097 v1.3.1 demands to assess the validity of signed data
|
||||
* according to IEEE 1609.2 clause 5.2.
|
||||
*/
|
||||
VerifyConfirm confirm;
|
||||
confirm.report = VerificationReport::Incompatible_Protocol; /*< fallback error code */
|
||||
|
||||
if (!msg.is_signed()) {
|
||||
confirm.report = VerificationReport::Unsigned_Message;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
if (msg.protocol_version() != 3) {
|
||||
confirm.report = VerificationReport::Incompatible_Protocol;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
auto gen_time = msg.generation_time();
|
||||
if (!gen_time) {
|
||||
// TS 103 097 v1.3.1 demands generation time to be always present
|
||||
confirm.report = VerificationReport::Invalid_Timestamp;
|
||||
return confirm;
|
||||
}
|
||||
// TODO further generation time checks depending on application profile
|
||||
|
||||
auto signature = msg.signature();
|
||||
if (!signature) {
|
||||
confirm.report = VerificationReport::Unsigned_Message;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
struct certificate_lookup_visitor : public boost::static_visitor<const v3::asn1::Certificate*> {
|
||||
certificate_lookup_visitor(v3::CertificateCache* cache) :
|
||||
m_cache(cache)
|
||||
{
|
||||
}
|
||||
|
||||
const v3::asn1::Certificate* operator()(const v3::asn1::HashedId8* digest)
|
||||
{
|
||||
// look up certificate matching digest in local storage
|
||||
if (m_cache && digest) {
|
||||
const v3::Certificate* found = m_cache->lookup(v3::convert(*digest));
|
||||
return found ? found->content() : nullptr;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const v3::asn1::Certificate* operator()(const v3::asn1::Certificate* cert)
|
||||
{
|
||||
return cert;
|
||||
}
|
||||
|
||||
v3::CertificateCache* m_cache;
|
||||
} certificate_lookup_visitor(m_context_v3.m_cert_cache);
|
||||
auto signer_identifier = msg.signer_identifier();
|
||||
|
||||
// track "known" stations
|
||||
auto maybe_digest = v3::get_certificate_id(signer_identifier);
|
||||
if (m_context_v3.m_cert_cache && maybe_digest) {
|
||||
bool was_unknown_station = m_context_v3.m_cert_cache->announce(*maybe_digest);
|
||||
if (was_unknown_station && msg.its_aid() == aid::CA && m_context_v3.m_sign_policy) {
|
||||
// CAM from unknown station received, add our certificate to next outgoing message
|
||||
m_context_v3.m_sign_policy->request_certificate();
|
||||
}
|
||||
}
|
||||
|
||||
// check if a ITS-AID 36 (CAM) with inline cert request shall be handled by us
|
||||
if (msg.its_aid() == aid::CA && m_context_v3.m_sign_policy) {
|
||||
Vanetza_Security_SequenceOfHashedId3* requests = msg->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest;
|
||||
if (requests) {
|
||||
for (int i = 0; i < requests->list.count; ++i)
|
||||
{
|
||||
const Vanetza_Security_HashedId3_t* req_digest = requests->list.array[i];
|
||||
const HashedId3 hid3 = create_hashed_id3(*req_digest);
|
||||
m_context_v3.m_sign_policy->enqueue_p2p_request(hid3);
|
||||
}
|
||||
}
|
||||
|
||||
Vanetza_Security_Certificate* included_cert = msg->content->choice.signedData->tbsData->headerInfo.requestedCertificate;
|
||||
if (included_cert) {
|
||||
auto maybe_digest = v3::calculate_digest(*included_cert);
|
||||
if (maybe_digest) {
|
||||
m_context_v3.m_sign_policy->discard_p2p_request(truncate(*maybe_digest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const v3::asn1::Certificate* certificate = boost::apply_visitor(certificate_lookup_visitor, signer_identifier);
|
||||
if (!certificate) {
|
||||
if (msg.its_aid() == aid::CA && m_context_v3.m_sign_policy && maybe_digest) {
|
||||
// for received CAMs (having digest as signer identifier) with unknown AT we request the full AT certificate
|
||||
m_context_v3.m_sign_policy->request_unrecognized_certificate(*maybe_digest);
|
||||
}
|
||||
confirm.report = VerificationReport::Signer_Certificate_Not_Found;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
// code below can safely dereference certificate
|
||||
assert(certificate != nullptr);
|
||||
|
||||
// check AT certificate's validity
|
||||
if (m_context_v3.m_cert_validator) {
|
||||
auto verdict = m_context_v3.m_cert_validator->valid_for_signing(v3::CertificateView { certificate }, msg.its_aid());
|
||||
if (verdict != v3::CertificateValidator::Verdict::Valid) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
return confirm;
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<HashedId8> aa_digest;
|
||||
switch (certificate->issuer.present)
|
||||
{
|
||||
case Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest:
|
||||
aa_digest = v3::convert(certificate->issuer.choice.sha256AndDigest);
|
||||
break;
|
||||
case Vanetza_Security_IssuerIdentifier_PR_sha384AndDigest:
|
||||
aa_digest = v3::convert(certificate->issuer.choice.sha384AndDigest);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (aa_digest && m_context_v3.m_cert_cache) {
|
||||
if (!m_context_v3.m_cert_cache->is_known(*aa_digest)) {
|
||||
if (m_context_v3.m_sign_policy) {
|
||||
// request unknown AA certificate for any received message signed with AT issued by this AA
|
||||
m_context_v3.m_sign_policy->request_unrecognized_certificate(*aa_digest);
|
||||
}
|
||||
}
|
||||
// TODO: if AA is validated and fails, announce it in cache
|
||||
}
|
||||
|
||||
auto public_key = v3::get_public_key(*certificate);
|
||||
if (!public_key) {
|
||||
confirm.report = VerificationReport::Invalid_Certificate;
|
||||
confirm.certificate_validity = CertificateInvalidReason::Missing_Public_Key;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
try {
|
||||
ByteBuffer msg_hash = v3::calculate_message_hash(m_backend, msg.hash_id(),
|
||||
msg.signing_payload(), v3::CertificateView { certificate });
|
||||
if (!m_backend.verify_digest(*public_key, msg_hash, *signature)) {
|
||||
confirm.report = VerificationReport::False_Signature;
|
||||
return confirm;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
// malformed input data, e.g. failing certificate encoding
|
||||
confirm.report = VerificationReport::Incompatible_Protocol;
|
||||
return confirm;
|
||||
}
|
||||
|
||||
confirm.its_aid = msg.its_aid();
|
||||
confirm.permissions = v3::get_app_permissions(*certificate, confirm.its_aid);
|
||||
confirm.certificate_id = maybe_digest;
|
||||
confirm.report = VerificationReport::Success;
|
||||
|
||||
if (m_context_v3.m_cert_cache && confirm.certificate_id) {
|
||||
v3::CertificateCache& cache = *m_context_v3.m_cert_cache;
|
||||
bool already_cached = cache.lookup(*confirm.certificate_id) != nullptr;
|
||||
if (!already_cached && confirm.its_aid == aid::CA && m_context_v3.m_sign_policy) {
|
||||
// CAM from unknown station received, add our certificate to next outgoing message
|
||||
m_context_v3.m_sign_policy->request_certificate();
|
||||
}
|
||||
|
||||
// update certificate cache with received certificate
|
||||
if (v3::contains_certificate(signer_identifier)) {
|
||||
cache.store(v3::Certificate { *certificate });
|
||||
}
|
||||
}
|
||||
|
||||
return confirm;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include <vanetza/security/verify_service.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
class PositionProvider;
|
||||
class Runtime;
|
||||
|
||||
namespace security
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class Backend;
|
||||
|
||||
namespace v2
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class CertificateCache;
|
||||
class CertificateProvider;
|
||||
class CertificateValidator;
|
||||
class SignHeaderPolicy;
|
||||
|
||||
} // namespace v2
|
||||
|
||||
namespace v3
|
||||
{
|
||||
|
||||
// forward declarations
|
||||
class CertificateCache;
|
||||
class CertificateProvider;
|
||||
class CertificateValidator;
|
||||
class SignHeaderPolicy;
|
||||
|
||||
} // namespace v3
|
||||
|
||||
/**
|
||||
* Verify service with basic certificate and signature checks
|
||||
*/
|
||||
class StraightVerifyService : public VerifyService
|
||||
{
|
||||
public:
|
||||
StraightVerifyService(const Runtime&, Backend&);
|
||||
StraightVerifyService(const Runtime&, Backend&, PositionProvider&);
|
||||
|
||||
void use_certificate_cache(v2::CertificateCache*);
|
||||
void use_certificate_provider(v2::CertificateProvider*);
|
||||
void use_certificate_validator(v2::CertificateValidator*);
|
||||
void use_sign_header_policy(v2::SignHeaderPolicy*);
|
||||
|
||||
void use_certificate_provider(v3::CertificateProvider*);
|
||||
void use_certificate_cache(v3::CertificateCache*);
|
||||
void use_certificate_validator(v3::CertificateValidator*);
|
||||
void use_sign_header_policy(v3::SignHeaderPolicy*);
|
||||
|
||||
VerifyConfirm verify(const VerifyRequest&) override;
|
||||
VerifyConfirm verify(const v2::SecuredMessage&);
|
||||
VerifyConfirm verify(const v3::SecuredMessage&);
|
||||
|
||||
private:
|
||||
const Runtime& m_runtime;
|
||||
Backend& m_backend;
|
||||
PositionProvider* m_position_provider = nullptr;
|
||||
|
||||
struct {
|
||||
v2::CertificateCache* m_cert_cache = nullptr;
|
||||
v2::CertificateProvider* m_cert_provider = nullptr;
|
||||
v2::CertificateValidator* m_cert_validator = nullptr;
|
||||
v2::SignHeaderPolicy* m_sign_policy = nullptr;
|
||||
|
||||
constexpr bool complete() const
|
||||
{
|
||||
return m_cert_cache && m_cert_provider && m_cert_validator && m_sign_policy;
|
||||
}
|
||||
} m_context_v2;
|
||||
|
||||
struct {
|
||||
v3::CertificateCache* m_cert_cache = nullptr;
|
||||
v3::CertificateValidator* m_cert_validator = nullptr;
|
||||
v3::SignHeaderPolicy* m_sign_policy = nullptr;
|
||||
} m_context_v3;
|
||||
};
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,21 @@
|
||||
include(UseGTest)
|
||||
add_library(security_test STATIC
|
||||
serialization.cpp
|
||||
)
|
||||
target_include_directories(security_test PUBLIC $<TARGET_PROPERTY:security,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
target_link_libraries(security_test PUBLIC ${GTest_LIBRARY})
|
||||
configure_gtest_directory(LINK_LIBRARIES Boost::boost security security_test
|
||||
COMPILE_DEFINITIONS ASSET_DIR="${SECURITY_TEST_ASSET_DIR}")
|
||||
|
||||
add_gtest(Backend backend.cpp)
|
||||
add_gtest(CamServiceSpecificPermissions cam_ssp.cpp)
|
||||
add_gtest(CertificateV3 certificate_v3.cpp)
|
||||
add_gtest(DecapService decap_service.cpp)
|
||||
add_gtest(DummyVerifyService dummy_verify_service.cpp)
|
||||
add_gtest(EccPointDecompression ecc_point_decompression.cpp)
|
||||
add_gtest(Geometry geometry.cpp)
|
||||
add_gtest(Hmac hmac.cpp)
|
||||
add_gtest(PeerRequestTracker peer_request_tracker.cpp)
|
||||
add_gtest(Persistence persistence.cpp)
|
||||
add_gtest(PublicKey public_key.cpp)
|
||||
add_gtest(SecuredMessageV3 secured_message_v3.cpp)
|
||||
Vendored
+394
@@ -0,0 +1,394 @@
|
||||
# FOKUS WebValidator for TS 103 097
|
||||
|
||||
Vanetza unit tests for SecuredMessage serialization use some third-party test data available online: [FOKUS WebValidator](https://werkzeug.dcaiti.tu-berlin.de/etsi/ts103097/)
|
||||
Unfortunately, the provided SecuredMessages are for protocol version 1 whereas Vanetza currently follows version 2. The original messages from FOKUS WebValidator are pasted into this document for reference because changes were necessary to adapt these for the recent protocol version.
|
||||
|
||||
## SecuredMessage/v1 (1)
|
||||
|
||||
``
|
||||
010181038002010901A8ED6DF65B0E6D6A0100809400000418929DB6A9E452223062C52028E956BF9874E0A40D21D5F9F56564F39C5DD187C922F2E5F0630373879A43393373B9F6205BF01FBD9C1F113165C291C376F535010004EABA91A915D81807E910FD292D99DF8B401EED88CF7F031412D5ED9905F9996469798C412FC8F7237A3AB3469795E2DEF5E1B783EA4F6B6A2359D21772B2EA9D0200210AC040800101C0408101010F01099EB20109B1270003040100960000004B2E6D0D0EE9BC4AD9CD087B601E9AF06031995443D652763455FBB794B33982889260740EF64CFA8C6808A58F98E06CE42A1E9C22A0785D7242647F7895ABFC0000009373931CD7580500021E011C983E690E5F6D755BD4871578A9427E7BC383903DC7DA3B560384013643010000FE8566BEA87B39E6411F80226E792D6E01E77B598F2BB1FCE7F2DD441185C07CEF0573FBFB9876B99FE811486F6F5D499E6114FC0724A67F8D71D2A897A7EB34
|
||||
``
|
||||
|
||||
struct SecuredMessage {
|
||||
uint8 protocol_version: 1
|
||||
uint8 security_profile: 1
|
||||
HeaderField<259> header_fields {
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: signer_info (128)
|
||||
struct SignerInfo signer {
|
||||
SignerInfoType type: certificate (2)
|
||||
struct Certificate certificate {
|
||||
uint8 version: 1
|
||||
SignerInfo<9> signer_info_v1 {
|
||||
struct SignerInfo {
|
||||
SignerInfoType type: certificate_digest_with_sha256 (1)
|
||||
HashedId8 digest: A8ED6DF65B0E6D6A
|
||||
}
|
||||
}
|
||||
struct SubjectInfo subject_info {
|
||||
SubjectType subject_type: authorization_ticket (1)
|
||||
opaque<0> subject_name:
|
||||
}
|
||||
SubjectAttribute<148> subject_attributes {
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: verification_key (0)
|
||||
struct PublicKey key {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EccPoint public_key {
|
||||
EccPointType type: uncompressed (4)
|
||||
opaque[32] x: 18929DB6A9E452223062C52028E956BF9874E0A40D21D5F9F56564F39C5DD187
|
||||
opaque[32] y: C922F2E5F0630373879A43393373B9F6205BF01FBD9C1F113165C291C376F535
|
||||
}
|
||||
}
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: encryption_key (1)
|
||||
struct PublicKey key {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EccPoint public_key {
|
||||
EccPointType type: uncompressed (4)
|
||||
opaque[32] x: EABA91A915D81807E910FD292D99DF8B401EED88CF7F031412D5ED9905F99964
|
||||
opaque[32] y: 69798C412FC8F7237A3AB3469795E2DEF5E1B783EA4F6B6A2359D21772B2EA9D
|
||||
}
|
||||
}
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: assurance_level (2)
|
||||
SubjectAssurance assurance_level: assurance level = 0, confidence = 0 (bitmask = 0)
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: its_aid_ssp_list (33)
|
||||
ItsAidSsp<10> its_aid_ssp_list {
|
||||
struct ItsAidSsp {
|
||||
IntX its_aid: 16512
|
||||
opaque<1> service_specific_permissions: 01
|
||||
}
|
||||
struct ItsAidSsp {
|
||||
IntX its_aid: 16513
|
||||
opaque<1> service_specific_permissions: 01
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ValidityRestriction<15> validity_restrictions {
|
||||
struct ValidityRestriction {
|
||||
ValidityRestrictionType type: time_start_and_end (1)
|
||||
Time32 start_validity: 2015-02-12 00:00:00 UTC
|
||||
Time32 end_validity: 2015-02-25 23:59:59 UTC
|
||||
}
|
||||
struct ValidityRestriction {
|
||||
ValidityRestrictionType type: region (3)
|
||||
struct GeographicRegion region {
|
||||
RegionType region_type: id (4)
|
||||
struct IdentifiedRegion id_region {
|
||||
RegionDictionary region_dictionary: un_stats (1)
|
||||
uint16 region_identifier: 150
|
||||
IntX local_region: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct Signature {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EcdsaSignature ecdsa_signature {
|
||||
struct EccPoint R {
|
||||
EccPointType type: x_coordinate_only (0)
|
||||
opaque[32] x: 4B2E6D0D0EE9BC4AD9CD087B601E9AF06031995443D652763455FBB794B33982
|
||||
}
|
||||
opaque[32] s: 889260740EF64CFA8C6808A58F98E06CE42A1E9C22A0785D7242647F7895ABFC
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: generation_time (0)
|
||||
Time64 generation_time: 2015-02-20 10:36:37.663 UTC
|
||||
}
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: message_type (5)
|
||||
uint16 message_type: 2
|
||||
}
|
||||
}
|
||||
Payload<30> payload_fields {
|
||||
struct Payload {
|
||||
PayloadType type: signed (1)
|
||||
opaque<28> data: 983E690E5F6D755BD4871578A9427E7BC383903DC7DA3B5603840136
|
||||
}
|
||||
}
|
||||
TrailerField<67> trailer_fields {
|
||||
struct TrailerField {
|
||||
TrailerFieldType type: signature (1)
|
||||
struct Signature signature {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EcdsaSignature ecdsa_signature {
|
||||
struct EccPoint R {
|
||||
EccPointType type: x_coordinate_only (0)
|
||||
opaque[32] x: FE8566BEA87B39E6411F80226E792D6E01E77B598F2BB1FCE7F2DD441185C07C
|
||||
}
|
||||
opaque[32] s: EF0573FBFB9876B99FE811486F6F5D499E6114FC0724A67F8D71D2A897A7EB34
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## SecuredMessage/v1 (2)
|
||||
|
||||
``
|
||||
010181038002010901A8ED6DF65B0E6D6A01008094000004C4EC137145DD4F450145DE530CCA36E73AB3D87FC8275847CDAD8248C1CD20879BD6A8CB54EA9E05D3B41376CE2F24789AEF82836CA818D568ADF4A140E96E48010004D6C268EE68B5B8B387B2312B7E1D21CE0C366D251A32431508B96EB6A3479CCF96A8738F30ED451F00DA8DDE84367C7EB16727D14FF14F5DD8F9791FE0A12A640200210AC040800101C0408101010F01099EB20109B1270003040100960000001EB035FE8E51DCDD8558DE0BE9B87895B36B420583A5C6B2B8B2EAB7F3D3C99163638FA025A0033D4BD80BBA02B8E3DE1B55766459D494677AF24917E51B80AC0000009373CC5F22C8050002220120F29384759027349075829034707ABABABABABAABAB98437985739845783974954301000081E7CDB6D2C741C1700822305C39E8E809622AF9FCA1C0786F762D08E80580C42F1FCC1D5499577210834C390BB4613E102DECB14F575A2820743DC9A66BBD7A
|
||||
``
|
||||
|
||||
struct SecuredMessage {
|
||||
uint8 protocol_version: 1
|
||||
uint8 security_profile: 1
|
||||
HeaderField<259> header_fields {
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: signer_info (128)
|
||||
struct SignerInfo signer {
|
||||
SignerInfoType type: certificate (2)
|
||||
struct Certificate certificate {
|
||||
uint8 version: 1
|
||||
SignerInfo<9> signer_info_v1 {
|
||||
struct SignerInfo {
|
||||
SignerInfoType type: certificate_digest_with_sha256 (1)
|
||||
HashedId8 digest: A8ED6DF65B0E6D6A
|
||||
}
|
||||
}
|
||||
struct SubjectInfo subject_info {
|
||||
SubjectType subject_type: authorization_ticket (1)
|
||||
opaque<0> subject_name:
|
||||
}
|
||||
SubjectAttribute<148> subject_attributes {
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: verification_key (0)
|
||||
struct PublicKey key {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EccPoint public_key {
|
||||
EccPointType type: uncompressed (4)
|
||||
opaque[32] x: C4EC137145DD4F450145DE530CCA36E73AB3D87FC8275847CDAD8248C1CD2087
|
||||
opaque[32] y: 9BD6A8CB54EA9E05D3B41376CE2F24789AEF82836CA818D568ADF4A140E96E48
|
||||
}
|
||||
}
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: encryption_key (1)
|
||||
struct PublicKey key {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EccPoint public_key {
|
||||
EccPointType type: uncompressed (4)
|
||||
opaque[32] x: D6C268EE68B5B8B387B2312B7E1D21CE0C366D251A32431508B96EB6A3479CCF
|
||||
opaque[32] y: 96A8738F30ED451F00DA8DDE84367C7EB16727D14FF14F5DD8F9791FE0A12A64
|
||||
}
|
||||
}
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: assurance_level (2)
|
||||
SubjectAssurance assurance_level: assurance level = 0, confidence = 0 (bitmask = 0)
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: its_aid_ssp_list (33)
|
||||
ItsAidSsp<10> its_aid_ssp_list {
|
||||
struct ItsAidSsp {
|
||||
IntX its_aid: 16512
|
||||
opaque<1> service_specific_permissions: 01
|
||||
}
|
||||
struct ItsAidSsp {
|
||||
IntX its_aid: 16513
|
||||
opaque<1> service_specific_permissions: 01
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ValidityRestriction<15> validity_restrictions {
|
||||
struct ValidityRestriction {
|
||||
ValidityRestrictionType type: time_start_and_end (1)
|
||||
Time32 start_validity: 2015-02-12 00:00:00 UTC
|
||||
Time32 end_validity: 2015-02-25 23:59:59 UTC
|
||||
}
|
||||
struct ValidityRestriction {
|
||||
ValidityRestrictionType type: region (3)
|
||||
struct GeographicRegion region {
|
||||
RegionType region_type: id (4)
|
||||
struct IdentifiedRegion id_region {
|
||||
RegionDictionary region_dictionary: un_stats (1)
|
||||
uint16 region_identifier: 150
|
||||
IntX local_region: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct Signature {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EcdsaSignature ecdsa_signature {
|
||||
struct EccPoint R {
|
||||
EccPointType type: x_coordinate_only (0)
|
||||
opaque[32] x: 1EB035FE8E51DCDD8558DE0BE9B87895B36B420583A5C6B2B8B2EAB7F3D3C991
|
||||
}
|
||||
opaque[32] s: 63638FA025A0033D4BD80BBA02B8E3DE1B55766459D494677AF24917E51B80AC
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: generation_time (0)
|
||||
Time64 generation_time: 2015-02-20 10:52:38.309 UTC
|
||||
}
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: message_type (5)
|
||||
uint16 message_type: 2
|
||||
}
|
||||
}
|
||||
Payload<34> payload_fields {
|
||||
struct Payload {
|
||||
PayloadType type: signed (1)
|
||||
opaque<32> data: F29384759027349075829034707ABABABABABAABAB9843798573984578397495
|
||||
}
|
||||
}
|
||||
TrailerField<67> trailer_fields {
|
||||
struct TrailerField {
|
||||
TrailerFieldType type: signature (1)
|
||||
struct Signature signature {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EcdsaSignature ecdsa_signature {
|
||||
struct EccPoint R {
|
||||
EccPointType type: x_coordinate_only (0)
|
||||
opaque[32] x: 81E7CDB6D2C741C1700822305C39E8E809622AF9FCA1C0786F762D08E80580C4
|
||||
}
|
||||
opaque[32] s: 2F1FCC1D5499577210834C390BB4613E102DECB14F575A2820743DC9A66BBD7A
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## SecuredMessage/v1 (3)
|
||||
|
||||
``
|
||||
010181038002010901A8ED6DF65B0E6D6A010080940000040209B0434163CCBAFDD34A45333E418FB96C05BBE0E7E1D755D40D0B4BBE8DA508EC2F2723B7ADF0F27C39F3AECFF0783C196F9961F8821E6294375D9294CD6A01000452113CE698DB081491675DF8FFE81C23EA5D0071B2D2BF0E0DA4ADA0CDA58259CA5D999200B6565E194EDAB8BD3DCA863F2DDF39C13E7A0375ECE2566C5EB8C60200210AC040800101C0408101010F01099EB20109B1270003040100960000008DA1F3F9F35E04C3DE77D7438988A8D57EBE44DAA021A4269E297C177C9CFE458E128EC290785D6631961625020943B6D87DAA54919A98F7865709929A7C6E480000009373CF482D400500020A01080123456789ABCDEF43010000371423BBA0902D8AF2FB2226D73A7781D4D6B6772650A8BEE5A1AF198CEDABA2C9BF57540C629E6A1E629B8812AEBDDDBCAF472F6586F16C14B3DEFBE9B6ADB2
|
||||
``
|
||||
|
||||
struct SecuredMessage {
|
||||
uint8 protocol_version: 1
|
||||
uint8 security_profile: 1
|
||||
HeaderField<259> header_fields {
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: signer_info (128)
|
||||
struct SignerInfo signer {
|
||||
SignerInfoType type: certificate (2)
|
||||
struct Certificate certificate {
|
||||
uint8 version: 1
|
||||
SignerInfo<9> signer_info_v1 {
|
||||
struct SignerInfo {
|
||||
SignerInfoType type: certificate_digest_with_sha256 (1)
|
||||
HashedId8 digest: A8ED6DF65B0E6D6A
|
||||
}
|
||||
}
|
||||
struct SubjectInfo subject_info {
|
||||
SubjectType subject_type: authorization_ticket (1)
|
||||
opaque<0> subject_name:
|
||||
}
|
||||
SubjectAttribute<148> subject_attributes {
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: verification_key (0)
|
||||
struct PublicKey key {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EccPoint public_key {
|
||||
EccPointType type: uncompressed (4)
|
||||
opaque[32] x: 0209B0434163CCBAFDD34A45333E418FB96C05BBE0E7E1D755D40D0B4BBE8DA5
|
||||
opaque[32] y: 08EC2F2723B7ADF0F27C39F3AECFF0783C196F9961F8821E6294375D9294CD6A
|
||||
}
|
||||
}
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: encryption_key (1)
|
||||
struct PublicKey key {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EccPoint public_key {
|
||||
EccPointType type: uncompressed (4)
|
||||
opaque[32] x: 52113CE698DB081491675DF8FFE81C23EA5D0071B2D2BF0E0DA4ADA0CDA58259
|
||||
opaque[32] y: CA5D999200B6565E194EDAB8BD3DCA863F2DDF39C13E7A0375ECE2566C5EB8C6
|
||||
}
|
||||
}
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: assurance_level (2)
|
||||
SubjectAssurance assurance_level: assurance level = 0, confidence = 0 (bitmask = 0)
|
||||
}
|
||||
struct SubjectAttribute {
|
||||
SubjectAttributeType type: its_aid_ssp_list (33)
|
||||
ItsAidSsp<10> its_aid_ssp_list {
|
||||
struct ItsAidSsp {
|
||||
IntX its_aid: 16512
|
||||
opaque<1> service_specific_permissions: 01
|
||||
}
|
||||
struct ItsAidSsp {
|
||||
IntX its_aid: 16513
|
||||
opaque<1> service_specific_permissions: 01
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ValidityRestriction<15> validity_restrictions {
|
||||
struct ValidityRestriction {
|
||||
ValidityRestrictionType type: time_start_and_end (1)
|
||||
Time32 start_validity: 2015-02-12 00:00:00 UTC
|
||||
Time32 end_validity: 2015-02-25 23:59:59 UTC
|
||||
}
|
||||
struct ValidityRestriction {
|
||||
ValidityRestrictionType type: region (3)
|
||||
struct GeographicRegion region {
|
||||
RegionType region_type: id (4)
|
||||
struct IdentifiedRegion id_region {
|
||||
RegionDictionary region_dictionary: un_stats (1)
|
||||
uint16 region_identifier: 150
|
||||
IntX local_region: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct Signature {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EcdsaSignature ecdsa_signature {
|
||||
struct EccPoint R {
|
||||
EccPointType type: x_coordinate_only (0)
|
||||
opaque[32] x: 8DA1F3F9F35E04C3DE77D7438988A8D57EBE44DAA021A4269E297C177C9CFE45
|
||||
}
|
||||
opaque[32] s: 8E128EC290785D6631961625020943B6D87DAA54919A98F7865709929A7C6E48
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: generation_time (0)
|
||||
Time64 generation_time: 2015-02-20 10:53:27.136 UTC
|
||||
}
|
||||
struct HeaderField {
|
||||
HeaderFieldType type: message_type (5)
|
||||
uint16 message_type: 2
|
||||
}
|
||||
}
|
||||
Payload<10> payload_fields {
|
||||
struct Payload {
|
||||
PayloadType type: signed (1)
|
||||
opaque<8> data: 0123456789ABCDEF
|
||||
}
|
||||
}
|
||||
TrailerField<67> trailer_fields {
|
||||
struct TrailerField {
|
||||
TrailerFieldType type: signature (1)
|
||||
struct Signature signature {
|
||||
PublicKeyAlgorithm algorithm: ecdsa_nistp256_with_sha256 (0)
|
||||
struct EcdsaSignature ecdsa_signature {
|
||||
struct EccPoint R {
|
||||
EccPointType type: x_coordinate_only (0)
|
||||
opaque[32] x: 371423BBA0902D8AF2FB2226D73A7781D4D6B6772650A8BEE5A1AF198CEDABA2
|
||||
}
|
||||
opaque[32] s: C9BF57540C629E6A1E629B8812AEBDDDBCAF472F6586F16C14B3DEFBE9B6ADB2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgU7d+uEguPBrTNnDA
|
||||
xsRrwDaQ1ABZ08u1gbM2r4qYk/ShRANCAAQchQ3HRWMpPLDz5V7aexDstOl0b4Nv
|
||||
hHaWwx7oaE43dijxZ/tkznt5pgIGKqwRf1lrrHfHHNb0yqcIeszNq5Gr
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,212 @@
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
#include <vanetza/security/backend_openssl.hpp>
|
||||
#endif
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
#include <vanetza/security/backend_cryptopp.hpp>
|
||||
#endif
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
|
||||
#include "serialization.hpp"
|
||||
|
||||
using namespace vanetza;
|
||||
using namespace vanetza::security;
|
||||
|
||||
class BackendTest : public ::testing::TestWithParam<std::string>
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
backend = create_backend(GetParam());
|
||||
ASSERT_NE(backend.get(), nullptr);
|
||||
}
|
||||
|
||||
ByteBuffer buffer_from_string(const std::string& s)
|
||||
{
|
||||
ByteBuffer b;
|
||||
std::copy(s.begin(), s.end(), std::back_inserter(b));
|
||||
return b;
|
||||
}
|
||||
|
||||
std::unique_ptr<Backend> backend;
|
||||
};
|
||||
|
||||
TEST_P(BackendTest, sha256sum)
|
||||
{
|
||||
const ByteBuffer input = buffer_from_string("All your ITS stations are belong to us");
|
||||
const ByteBuffer expected = buffer_from_hexstring("dcb61edffc5f536aeb80c7e61fc943239a28b16edad52f4a0bc6e83f2df0fdc8");
|
||||
EXPECT_EQ(backend->calculate_hash(HashAlgorithm::SHA256, input), expected);
|
||||
}
|
||||
|
||||
TEST_P(BackendTest, sha384sum)
|
||||
{
|
||||
const ByteBuffer input = buffer_from_string("All your ITS stations are belong to us");
|
||||
const ByteBuffer expected = buffer_from_hexstring("4dfdccefa8612f3285aa4e909c644eb841e0347132465a733cbc99b46437d9ea0886c96a25fd9d51389585431b069651");
|
||||
EXPECT_EQ(backend->calculate_hash(HashAlgorithm::SHA384, input), expected);
|
||||
}
|
||||
|
||||
TEST_P(BackendTest, sign_and_verify_nistp256)
|
||||
{
|
||||
const ByteBuffer digest = buffer_from_hexstring("33e57499804cebc409407d6bf4ade70b49b58d0d4bae9ca57d507b260a676685");
|
||||
PrivateKey private_key;
|
||||
private_key.type = KeyType::NistP256;
|
||||
private_key.key = buffer_from_hexstring("f4ce0e4b48829aae85abd2124a2574dba44388eea94ebd373f9203ad39719a30");
|
||||
PublicKey public_key;
|
||||
public_key.type = KeyType::NistP256;
|
||||
public_key.compression = KeyCompression::NoCompression;
|
||||
public_key.x = buffer_from_hexstring("8e65e0ab7d4cd66860be693e29bf747fe796ebfe3942416b1f9c7ecf7fdb5797");
|
||||
public_key.y = buffer_from_hexstring("49ce27c4acfc53c34867420a35b999e7deb3aeabec388f0d08d7fe0edf54ba62");
|
||||
Signature sig = backend->sign_digest(private_key, digest);
|
||||
EXPECT_TRUE(backend->verify_digest(public_key, digest, sig));
|
||||
}
|
||||
|
||||
TEST_P(BackendTest, sign_and_verify_brainpoolp256r1)
|
||||
{
|
||||
const ByteBuffer digest = buffer_from_hexstring("33e57499804cebc409407d6bf4ade70b49b58d0d4bae9ca57d507b260a676685");
|
||||
PrivateKey private_key;
|
||||
private_key.type = KeyType::BrainpoolP256r1;
|
||||
private_key.key = buffer_from_hexstring("38f72493269d99c77b6e6488de05aea60bc707a35b464b0286665463ecc2883d");
|
||||
PublicKey public_key;
|
||||
public_key.type = KeyType::BrainpoolP256r1;
|
||||
public_key.compression = KeyCompression::NoCompression;
|
||||
public_key.x = buffer_from_hexstring("2cdb98f1053bb69fb4879101946d0a49aba965c8c4ffad5ef64356b0cff0bcf8");
|
||||
public_key.y = buffer_from_hexstring("17828cc0e33f68cbf9cb1d5419597464aef62efea8503676403177a9074cf86e");
|
||||
Signature sig = backend->sign_digest(private_key, digest);
|
||||
EXPECT_TRUE(backend->verify_digest(public_key, digest, sig));
|
||||
}
|
||||
|
||||
TEST_P(BackendTest, sign_and_verify_brainpoolp384r1)
|
||||
{
|
||||
const ByteBuffer digest = buffer_from_hexstring("33e57499804cebc409407d6bf4ade70b49b58d0d4bae9ca57d507b260a676685");
|
||||
PrivateKey private_key;
|
||||
private_key.type = KeyType::BrainpoolP384r1;
|
||||
private_key.key = buffer_from_hexstring("29267526c4511103f094f4d9ef1e8a57e2e6429642188939b756fe6738db49744363ea4081601e5acebe091d258bcedd");
|
||||
PublicKey public_key;
|
||||
public_key.type = KeyType::BrainpoolP384r1;
|
||||
public_key.compression = KeyCompression::NoCompression;
|
||||
public_key.x = buffer_from_hexstring("271dd92e47814e1f6b39c29488a80a720ae18153597380f41b2079cca4d92373b058850af280e920b10993bf925bdc4a");
|
||||
public_key.y = buffer_from_hexstring("388b971cb0ad878842de6825e5f1a6e359c1c4cddd65593781af6179fa743c21a056577de9cca2631e107edc28dc2ef7");
|
||||
Signature sig = backend->sign_digest(private_key, digest);
|
||||
EXPECT_TRUE(backend->verify_digest(public_key, digest, sig));
|
||||
}
|
||||
|
||||
TEST_P(BackendTest, verify_nistp256)
|
||||
{
|
||||
Signature sig;
|
||||
sig.type = KeyType::NistP256;
|
||||
sig.r = buffer_from_hexstring("de99edf601b3682d90e6458ab0e5588fcdef54d679852e38fc85e7e16ad02074");
|
||||
sig.s = buffer_from_hexstring("552962f3572898a05dd99b179124d6e1d1a672a3f407083b59a1fe2dc62e8161");
|
||||
|
||||
PublicKey public_key;
|
||||
public_key.type = KeyType::NistP256;
|
||||
public_key.compression = KeyCompression::NoCompression;
|
||||
public_key.x = buffer_from_hexstring("8e65e0ab7d4cd66860be693e29bf747fe796ebfe3942416b1f9c7ecf7fdb5797");
|
||||
public_key.y = buffer_from_hexstring("49ce27c4acfc53c34867420a35b999e7deb3aeabec388f0d08d7fe0edf54ba62");
|
||||
|
||||
ByteBuffer digest = buffer_from_hexstring("33e57499804cebc409407d6bf4ade70b49b58d0d4bae9ca57d507b260a676685");
|
||||
EXPECT_TRUE(backend->verify_digest(public_key, digest, sig));
|
||||
|
||||
ByteBuffer false_digest = digest;
|
||||
false_digest[0] ^= 0x01;
|
||||
EXPECT_FALSE(backend->verify_digest(public_key, false_digest, sig));
|
||||
}
|
||||
|
||||
TEST_P(BackendTest, verify_brainpoolp256r1)
|
||||
{
|
||||
Signature sig;
|
||||
sig.type = KeyType::BrainpoolP256r1;
|
||||
sig.r = buffer_from_hexstring("28c9b30e37b05388c2f04be921dbc79480d972f1c782ff8c5ade76c40e136d5a");
|
||||
sig.s = buffer_from_hexstring("77cf1e91d0204d2a58847b543df055605ff968cd6120c4ac9c751be08d782518");
|
||||
|
||||
PublicKey public_key;
|
||||
public_key.type = KeyType::BrainpoolP256r1;
|
||||
public_key.compression = KeyCompression::NoCompression;
|
||||
public_key.x = buffer_from_hexstring("2cdb98f1053bb69fb4879101946d0a49aba965c8c4ffad5ef64356b0cff0bcf8");
|
||||
public_key.y = buffer_from_hexstring("17828cc0e33f68cbf9cb1d5419597464aef62efea8503676403177a9074cf86e");
|
||||
|
||||
ByteBuffer digest = buffer_from_hexstring("33e57499804cebc409407d6bf4ade70b49b58d0d4bae9ca57d507b260a676685");
|
||||
EXPECT_TRUE(backend->verify_digest(public_key, digest, sig));
|
||||
|
||||
ByteBuffer false_digest = digest;
|
||||
false_digest[0] ^= 0x01;
|
||||
EXPECT_FALSE(backend->verify_digest(public_key, false_digest, sig));
|
||||
}
|
||||
|
||||
TEST_P(BackendTest, verify_brainpoolp384r1)
|
||||
{
|
||||
Signature sig;
|
||||
sig.type = KeyType::BrainpoolP384r1;
|
||||
sig.r = buffer_from_hexstring("2b51c205ac9598ff245cbed8df5c8f3e1d9d3c7d6901a08d45e790784457900a84c903167f5d6b124ee193881ff93950");
|
||||
sig.s = buffer_from_hexstring("81c06d7f5fdf4e825b3cd8545956e6b1b587be4dcc66ce3007ca8b6966f90bc94efc8d88c70b5bd3bd44739ec34da54f");
|
||||
|
||||
PublicKey public_key;
|
||||
public_key.type = KeyType::BrainpoolP384r1;
|
||||
public_key.compression = KeyCompression::NoCompression;
|
||||
public_key.x = buffer_from_hexstring("271dd92e47814e1f6b39c29488a80a720ae18153597380f41b2079cca4d92373b058850af280e920b10993bf925bdc4a");
|
||||
public_key.y = buffer_from_hexstring("388b971cb0ad878842de6825e5f1a6e359c1c4cddd65593781af6179fa743c21a056577de9cca2631e107edc28dc2ef7");
|
||||
|
||||
ByteBuffer digest = buffer_from_hexstring("33e57499804cebc409407d6bf4ade70b49b58d0d4bae9ca57d507b260a676685");
|
||||
EXPECT_TRUE(backend->verify_digest(public_key, digest, sig));
|
||||
|
||||
ByteBuffer false_digest = digest;
|
||||
false_digest[0] ^= 0x01;
|
||||
EXPECT_FALSE(backend->verify_digest(public_key, false_digest, sig));
|
||||
}
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL || defined VANETZA_WITH_CRYPTOPP
|
||||
namespace
|
||||
{
|
||||
|
||||
// Check that every built backend derives the known public key from the private key.
|
||||
void check_derive_public_key(KeyType type, const char* priv_hex, const char* x_hex, const char* y_hex)
|
||||
{
|
||||
PrivateKey priv;
|
||||
priv.type = type;
|
||||
priv.key = buffer_from_hexstring(priv_hex);
|
||||
const ByteBuffer x = buffer_from_hexstring(x_hex);
|
||||
const ByteBuffer y = buffer_from_hexstring(y_hex);
|
||||
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
EXPECT_EQ(openssl::derive_public_key(priv).x, x);
|
||||
EXPECT_EQ(openssl::derive_public_key(priv).y, y);
|
||||
#endif
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
EXPECT_EQ(cryptopp::derive_public_key(priv).x, x);
|
||||
EXPECT_EQ(cryptopp::derive_public_key(priv).y, y);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Backend, derive_public_key_nistp256)
|
||||
{
|
||||
check_derive_public_key(KeyType::NistP256,
|
||||
"f4ce0e4b48829aae85abd2124a2574dba44388eea94ebd373f9203ad39719a30",
|
||||
"8e65e0ab7d4cd66860be693e29bf747fe796ebfe3942416b1f9c7ecf7fdb5797",
|
||||
"49ce27c4acfc53c34867420a35b999e7deb3aeabec388f0d08d7fe0edf54ba62");
|
||||
}
|
||||
|
||||
TEST(Backend, derive_public_key_brainpoolp384r1)
|
||||
{
|
||||
check_derive_public_key(KeyType::BrainpoolP384r1,
|
||||
"29267526c4511103f094f4d9ef1e8a57e2e6429642188939b756fe6738db49744363ea4081601e5acebe091d258bcedd",
|
||||
"271dd92e47814e1f6b39c29488a80a720ae18153597380f41b2079cca4d92373b058850af280e920b10993bf925bdc4a",
|
||||
"388b971cb0ad878842de6825e5f1a6e359c1c4cddd65593781af6179fa743c21a056577de9cca2631e107edc28dc2ef7");
|
||||
}
|
||||
#endif /* VANETZA_WITH_OPENSSL || VANETZA_WITH_CRYPTOPP */
|
||||
|
||||
std::list<std::string> available_backends()
|
||||
{
|
||||
std::list<std::string> backends;
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
backends.emplace_back("OpenSSL");
|
||||
#endif
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
backends.emplace_back("CryptoPP");
|
||||
#endif
|
||||
return backends;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(BackendImplementations, BackendTest, ::testing::ValuesIn(available_backends()));
|
||||
@@ -0,0 +1,133 @@
|
||||
#include <vanetza/security/cam_ssp.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
using vanetza::ByteBuffer;
|
||||
using namespace vanetza::security;
|
||||
|
||||
static const std::set<CamPermission> all {{
|
||||
CamPermission::CEN_DSRC_Tolling_Zone,
|
||||
CamPermission::Public_Transport,
|
||||
CamPermission::Special_Transport,
|
||||
CamPermission::Dangerous_Goods,
|
||||
CamPermission::Roadwork,
|
||||
CamPermission::Rescue,
|
||||
CamPermission::Emergency,
|
||||
CamPermission::Safety_Car,
|
||||
CamPermission::Closed_Lanes,
|
||||
CamPermission::Request_For_Right_Of_Way,
|
||||
CamPermission::Request_For_Free_Crossing_At_Traffic_Light,
|
||||
CamPermission::No_Passing,
|
||||
CamPermission::No_Passing_For_Trucks,
|
||||
CamPermission::Speed_Limit,
|
||||
}};
|
||||
|
||||
TEST(CamSsp, empty)
|
||||
{
|
||||
CamPermissions empty;
|
||||
EXPECT_TRUE(std::none_of(all.begin(), all.end(), [empty](CamPermission cp) { return empty.has(cp); }));
|
||||
EXPECT_TRUE(empty.none());
|
||||
}
|
||||
|
||||
TEST(CamSsp, single)
|
||||
{
|
||||
CamPermissions single(CamPermission::Safety_Car);
|
||||
EXPECT_FALSE(single.none());
|
||||
EXPECT_TRUE(single.has(CamPermission::Safety_Car));
|
||||
|
||||
std::set<CamPermission> rest = all;
|
||||
rest.erase(CamPermission::Safety_Car);
|
||||
EXPECT_TRUE(std::none_of(rest.begin(), rest.end(), [single](CamPermission cp) { return single.has(cp); }));
|
||||
}
|
||||
|
||||
TEST(CamSsp, multiple)
|
||||
{
|
||||
CamPermissions multiple({CamPermission::Roadwork, CamPermission::Public_Transport, CamPermission::Speed_Limit});
|
||||
EXPECT_TRUE(multiple.has(CamPermission::Roadwork));
|
||||
EXPECT_TRUE(multiple.has(CamPermission::Public_Transport));
|
||||
EXPECT_TRUE(multiple.has(CamPermission::Speed_Limit));
|
||||
EXPECT_TRUE(multiple.has({CamPermission::Roadwork, CamPermission::Public_Transport, CamPermission::Speed_Limit}));
|
||||
}
|
||||
|
||||
TEST(CamSsp, manipulation)
|
||||
{
|
||||
CamPermissions ssp({CamPermission::No_Passing, CamPermission::Rescue});
|
||||
EXPECT_TRUE(ssp.has({CamPermission::Rescue, CamPermission::No_Passing}));
|
||||
ssp.remove(CamPermission::Rescue);
|
||||
EXPECT_FALSE(ssp.has({CamPermission::Rescue, CamPermission::No_Passing}));
|
||||
EXPECT_TRUE(ssp.has(CamPermission::No_Passing));
|
||||
|
||||
ssp.remove(CamPermission::Speed_Limit);
|
||||
EXPECT_FALSE(ssp.has(CamPermission::Speed_Limit));
|
||||
EXPECT_TRUE(ssp.has(CamPermission::No_Passing));
|
||||
|
||||
ssp.add(CamPermission::Closed_Lanes);
|
||||
EXPECT_TRUE(ssp.has({CamPermission::Closed_Lanes, CamPermission::No_Passing}));
|
||||
ssp.remove(CamPermission::No_Passing).remove(CamPermission::Closed_Lanes);
|
||||
EXPECT_TRUE(ssp.none());
|
||||
}
|
||||
|
||||
TEST(CamSsp, serialization)
|
||||
{
|
||||
CamPermissions ssp;
|
||||
const auto empty_ssp_buffer = ssp.encode();
|
||||
EXPECT_EQ(ByteBuffer({0x01, 0x00, 0x00}), empty_ssp_buffer);
|
||||
|
||||
ssp.add(CamPermission::CEN_DSRC_Tolling_Zone);
|
||||
const auto dsrc_ssp_buffer = ssp.encode();
|
||||
EXPECT_EQ(ByteBuffer({0x01, 0x80, 0x00}), dsrc_ssp_buffer);
|
||||
|
||||
ssp.add(CamPermission::Speed_Limit);
|
||||
const auto extremes_ssp_buffer = ssp.encode();
|
||||
EXPECT_EQ(ByteBuffer({0x01, 0x80, 0x04}), extremes_ssp_buffer);
|
||||
|
||||
CamPermissions decoded = CamPermissions::decode(extremes_ssp_buffer);
|
||||
EXPECT_EQ(extremes_ssp_buffer, decoded.encode());
|
||||
EXPECT_TRUE(decoded.has({CamPermission::Speed_Limit, CamPermission::CEN_DSRC_Tolling_Zone}));
|
||||
decoded.remove(CamPermission::Speed_Limit).remove(CamPermission::CEN_DSRC_Tolling_Zone);
|
||||
EXPECT_TRUE(decoded.none());
|
||||
|
||||
CamPermissions decoded_empty = CamPermissions::decode(ByteBuffer {});
|
||||
EXPECT_TRUE(decoded_empty.none());
|
||||
|
||||
CamPermissions decoded_testing = CamPermissions::decode(ByteBuffer {0x00});
|
||||
EXPECT_TRUE(decoded_testing.none());
|
||||
|
||||
CamPermissions decoded_version = CamPermissions::decode(ByteBuffer {0xff, 0x80, 0x30});
|
||||
EXPECT_TRUE(decoded_version.none());
|
||||
|
||||
CamPermissions decoded_short = CamPermissions::decode(ByteBuffer {0x01, 0x40});
|
||||
EXPECT_TRUE(decoded_short.none());
|
||||
|
||||
CamPermissions decoded_long = CamPermissions::decode(ByteBuffer {0x01, 0x80, 0x04, 0x00});
|
||||
EXPECT_TRUE(decoded_long.none());
|
||||
|
||||
CamPermissions decoded_reserved = CamPermissions::decode(ByteBuffer {0x01, 0x02, 0x07});
|
||||
EXPECT_FALSE(decoded_reserved.none());
|
||||
EXPECT_TRUE(decoded_reserved.has(CamPermission::Speed_Limit));
|
||||
decoded_reserved.remove(CamPermission::Speed_Limit).remove(CamPermission::Emergency);
|
||||
EXPECT_TRUE(std::none_of(all.begin(), all.end(),
|
||||
[decoded_reserved](CamPermission cp) { return decoded_reserved.has(cp); }));
|
||||
EXPECT_FALSE(decoded_reserved.none());
|
||||
}
|
||||
|
||||
TEST(CamSsp, permissions)
|
||||
{
|
||||
CamPermissions ssp { CamPermission::No_Passing, CamPermission::Speed_Limit };
|
||||
std::set<CamPermission> expected { CamPermission::No_Passing, CamPermission::Speed_Limit };
|
||||
EXPECT_EQ(expected, ssp.permissions());
|
||||
|
||||
// works also for reserved bits
|
||||
const CamPermission reserved = static_cast<CamPermission>(0x0100);
|
||||
ssp.add(reserved);
|
||||
expected.insert(reserved);
|
||||
ASSERT_EQ(3, expected.size());
|
||||
EXPECT_EQ(expected, ssp.permissions());
|
||||
}
|
||||
|
||||
TEST(CamSsp, stringify)
|
||||
{
|
||||
EXPECT_EQ("Safety Car", stringify(CamPermission::Safety_Car));
|
||||
EXPECT_EQ("Reserved (0x0200)", stringify(static_cast<CamPermission>(0x0200)));
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vanetza/security/v3/certificate.hpp>
|
||||
#include <vanetza/security/v3/certificate_cache.hpp>
|
||||
#include <vanetza/security/sha.hpp>
|
||||
|
||||
using namespace vanetza;
|
||||
using namespace vanetza::security;
|
||||
|
||||
// implicit certificate (IEEE 1609.2 clause 6.4.5) with a dummy reconstruction value
|
||||
inline v3::Certificate fake_implicit_certificate()
|
||||
{
|
||||
v3::Certificate cert;
|
||||
cert->version = 3;
|
||||
cert->type = Vanetza_Security_CertificateType_implicit;
|
||||
cert->issuer.present = Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest;
|
||||
std::array<char, 8> issuer_digest = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
||||
OCTET_STRING_fromBuf(&cert->issuer.choice.sha256AndDigest, issuer_digest.data(), issuer_digest.size());
|
||||
cert->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_none;
|
||||
std::array<char, 3> craca_id = { 0, 0, 0 };
|
||||
OCTET_STRING_fromBuf(&cert->toBeSigned.cracaId, craca_id.data(), craca_id.size());
|
||||
cert->toBeSigned.crlSeries = 0;
|
||||
cert->toBeSigned.validityPeriod.start = 0;
|
||||
cert->toBeSigned.validityPeriod.duration.present = Vanetza_Security_Duration_PR_minutes;
|
||||
cert->toBeSigned.validityPeriod.duration.choice.minutes = 10080;
|
||||
cert->toBeSigned.verifyKeyIndicator.present = Vanetza_Security_VerificationKeyIndicator_PR_reconstructionValue;
|
||||
cert->toBeSigned.verifyKeyIndicator.choice.reconstructionValue.present = Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0;
|
||||
std::array<char, 32> dummy_point {};
|
||||
OCTET_STRING_fromBuf(
|
||||
&cert->toBeSigned.verifyKeyIndicator.choice.reconstructionValue.choice.compressed_y_0,
|
||||
dummy_point.data(), dummy_point.size()
|
||||
);
|
||||
cert.add_app_permission(aid::CA, ByteBuffer({ 1, 0, 0 }));
|
||||
return cert;
|
||||
}
|
||||
|
||||
TEST(CertificateV3, cache)
|
||||
{
|
||||
v3::CertificateCache cache;
|
||||
cache.store(v3::fake_certificate());
|
||||
}
|
||||
|
||||
TEST(CertificateV3, implicit_certificate_digest)
|
||||
{
|
||||
v3::Certificate cert = fake_implicit_certificate();
|
||||
ASSERT_TRUE(v3::is_canonical(*cert));
|
||||
|
||||
// curve of a reconstructed key is unknown without the issuer certificate
|
||||
EXPECT_EQ(KeyType::Unspecified, cert.get_verification_key_type());
|
||||
// ECQV public key reconstruction is not supported
|
||||
EXPECT_FALSE(v3::get_public_key(*cert));
|
||||
|
||||
// IEEE 1609.2 clause 5.3.2: SHA-256 over the canonical encoding
|
||||
const ByteBuffer encoded = cert.encode();
|
||||
const HashedId8 expected = create_hashed_id8(calculate_sha256_digest(encoded.data(), encoded.size()));
|
||||
auto digest = cert.calculate_digest();
|
||||
ASSERT_TRUE(digest);
|
||||
EXPECT_EQ(expected, *digest);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/decap_service.hpp>
|
||||
#include <vanetza/security/v3/secured_message.hpp>
|
||||
|
||||
#include "printer.hpp"
|
||||
|
||||
using namespace vanetza;
|
||||
using namespace vanetza::security;
|
||||
|
||||
TEST(DecapService, confirm_from_verify_confirm)
|
||||
{
|
||||
v3::SecuredMessage v3_msg = v3::SecuredMessage::with_signed_data();
|
||||
v3_msg.set_payload(ByteBuffer { 0xca, 0xfe });
|
||||
SecuredMessage msg { std::move(v3_msg) };
|
||||
|
||||
const HashedId8 signer { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
|
||||
VerifyConfirm verify;
|
||||
verify.report = VerificationReport::Success;
|
||||
verify.its_aid = aid::CA;
|
||||
verify.permissions = ByteBuffer { 0x01, 0x02 };
|
||||
verify.certificate_validity = CertificateValidity::valid();
|
||||
verify.certificate_id = signer;
|
||||
|
||||
DecapConfirm decap = DecapConfirm::from(std::move(verify), SecuredMessageView { msg });
|
||||
EXPECT_EQ(VerificationReport::Success, decap.report);
|
||||
EXPECT_EQ(aid::CA, decap.its_aid);
|
||||
EXPECT_EQ(ByteBuffer({ 0x01, 0x02 }), decap.permissions);
|
||||
EXPECT_TRUE(decap.certificate_validity);
|
||||
ASSERT_TRUE(decap.certificate_id);
|
||||
EXPECT_EQ(signer, *decap.certificate_id);
|
||||
EXPECT_EQ(2, boost::apply_visitor([](const auto& packet) { return packet.size(); }, decap.plaintext_payload));
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/verify_service.hpp>
|
||||
#include <vanetza/security/v3/certificate.hpp>
|
||||
#include <vanetza/security/v3/certificate_cache.hpp>
|
||||
#include <vanetza/security/v3/secured_message.hpp>
|
||||
#include <memory>
|
||||
|
||||
using namespace vanetza;
|
||||
using namespace vanetza::security;
|
||||
|
||||
TEST(DummyVerifyServiceTest, lookup)
|
||||
{
|
||||
std::unique_ptr<VerifyService> dummy { new DummyVerifyService {
|
||||
VerificationReport::Invalid_Timestamp, CertificateValidity::valid() }};
|
||||
|
||||
SecuredMessage message;
|
||||
VerifyRequest req(SecuredMessageView { message });
|
||||
|
||||
auto confirm = dummy->verify(std::move(req));
|
||||
|
||||
EXPECT_EQ(VerificationReport::Invalid_Timestamp, confirm.report);
|
||||
EXPECT_TRUE(confirm.certificate_validity);
|
||||
EXPECT_EQ(0, confirm.its_aid);
|
||||
EXPECT_EQ(vanetza::ByteBuffer({}), confirm.permissions);
|
||||
EXPECT_FALSE(confirm.certificate_id);
|
||||
}
|
||||
|
||||
TEST(DummyVerifyServiceTest, signer_with_full_certificate)
|
||||
{
|
||||
DummyVerifyService dummy { VerificationReport::Success, CertificateValidity::valid() };
|
||||
|
||||
v3::Certificate cert = v3::fake_certificate();
|
||||
auto digest = cert.calculate_digest();
|
||||
ASSERT_TRUE(digest);
|
||||
|
||||
v3::SecuredMessage msg = v3::SecuredMessage::with_signed_data();
|
||||
msg.set_its_aid(aid::CA);
|
||||
msg.set_signer_identifier(cert);
|
||||
|
||||
auto confirm = dummy.verify(VerifyRequest { SecuredMessageView { std::move(msg) } });
|
||||
|
||||
EXPECT_EQ(aid::CA, confirm.its_aid);
|
||||
ASSERT_TRUE(confirm.certificate_id);
|
||||
EXPECT_EQ(*digest, *confirm.certificate_id);
|
||||
EXPECT_EQ(ByteBuffer({ 1, 0, 0 }), confirm.permissions);
|
||||
}
|
||||
|
||||
TEST(DummyVerifyServiceTest, signer_with_digest_no_cache)
|
||||
{
|
||||
DummyVerifyService dummy { VerificationReport::Success, CertificateValidity::valid() };
|
||||
|
||||
HashedId8 digest = {{ 1, 2, 3, 4, 5, 6, 7, 8 }};
|
||||
v3::SecuredMessage msg = v3::SecuredMessage::with_signed_data();
|
||||
msg.set_its_aid(aid::CA);
|
||||
msg.set_signer_identifier(digest);
|
||||
|
||||
auto confirm = dummy.verify(VerifyRequest { SecuredMessageView { std::move(msg) } });
|
||||
|
||||
ASSERT_TRUE(confirm.certificate_id);
|
||||
EXPECT_EQ(digest, *confirm.certificate_id);
|
||||
EXPECT_TRUE(confirm.permissions.empty());
|
||||
}
|
||||
|
||||
TEST(DummyVerifyServiceTest, signer_with_digest_cache_hit)
|
||||
{
|
||||
DummyVerifyService dummy { VerificationReport::Success, CertificateValidity::valid() };
|
||||
|
||||
v3::Certificate cert = v3::fake_certificate();
|
||||
auto digest = cert.calculate_digest();
|
||||
ASSERT_TRUE(digest);
|
||||
|
||||
v3::CertificateCache cache;
|
||||
cache.store(std::move(cert));
|
||||
dummy.use_certificate_cache(&cache);
|
||||
|
||||
v3::SecuredMessage msg = v3::SecuredMessage::with_signed_data();
|
||||
msg.set_its_aid(aid::CA);
|
||||
msg.set_signer_identifier(*digest);
|
||||
|
||||
auto confirm = dummy.verify(VerifyRequest { SecuredMessageView { std::move(msg) } });
|
||||
|
||||
ASSERT_TRUE(confirm.certificate_id);
|
||||
EXPECT_EQ(*digest, *confirm.certificate_id);
|
||||
EXPECT_EQ(ByteBuffer({ 1, 0, 0 }), confirm.permissions);
|
||||
}
|
||||
|
||||
TEST(DummyVerifyServiceTest, signer_with_digest_cache_miss)
|
||||
{
|
||||
DummyVerifyService dummy { VerificationReport::Success, CertificateValidity::valid() };
|
||||
|
||||
v3::Certificate cert = v3::fake_certificate();
|
||||
auto digest = cert.calculate_digest();
|
||||
ASSERT_TRUE(digest);
|
||||
|
||||
v3::CertificateCache cache;
|
||||
// certificate is not added
|
||||
dummy.use_certificate_cache(&cache);
|
||||
|
||||
v3::SecuredMessage msg = v3::SecuredMessage::with_signed_data();
|
||||
msg.set_its_aid(aid::CA);
|
||||
msg.set_signer_identifier(*digest);
|
||||
|
||||
auto confirm = dummy.verify(VerifyRequest { SecuredMessageView { std::move(msg) } });
|
||||
|
||||
ASSERT_TRUE(confirm.certificate_id);
|
||||
EXPECT_EQ(*digest, *confirm.certificate_id);
|
||||
EXPECT_TRUE(confirm.permissions.empty());
|
||||
}
|
||||
|
||||
TEST(DummyVerifyServiceTest, full_certificate_populates_cache)
|
||||
{
|
||||
DummyVerifyService dummy { VerificationReport::Success, CertificateValidity::valid() };
|
||||
|
||||
v3::CertificateCache cache;
|
||||
dummy.use_certificate_cache(&cache);
|
||||
|
||||
v3::Certificate cert = v3::fake_certificate();
|
||||
auto digest = cert.calculate_digest();
|
||||
ASSERT_TRUE(digest);
|
||||
|
||||
v3::SecuredMessage msg = v3::SecuredMessage::with_signed_data();
|
||||
msg.set_its_aid(aid::CA);
|
||||
msg.set_signer_identifier(cert);
|
||||
|
||||
EXPECT_EQ(nullptr, cache.lookup(*digest));
|
||||
auto confirm = dummy.verify(VerifyRequest { SecuredMessageView { std::move(msg) } });
|
||||
|
||||
EXPECT_EQ(ByteBuffer({ 1, 0, 0 }), confirm.permissions);
|
||||
EXPECT_EQ(1u, cache.size());
|
||||
EXPECT_NE(nullptr, cache.lookup(*digest));
|
||||
}
|
||||
|
||||
TEST(DummyVerifyServiceTest, permissions_empty_for_unrelated_aid)
|
||||
{
|
||||
DummyVerifyService dummy { VerificationReport::Success, CertificateValidity::valid() };
|
||||
|
||||
v3::Certificate cert = v3::fake_certificate();
|
||||
auto digest = cert.calculate_digest();
|
||||
ASSERT_TRUE(digest);
|
||||
|
||||
v3::SecuredMessage msg = v3::SecuredMessage::with_signed_data();
|
||||
msg.set_its_aid(aid::DEN);
|
||||
msg.set_signer_identifier(cert);
|
||||
|
||||
auto confirm = dummy.verify(VerifyRequest { SecuredMessageView { std::move(msg) } });
|
||||
|
||||
EXPECT_EQ(aid::DEN, confirm.its_aid);
|
||||
ASSERT_TRUE(confirm.certificate_id);
|
||||
EXPECT_EQ(*digest, *confirm.certificate_id);
|
||||
EXPECT_TRUE(confirm.permissions.empty());
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/ecc_point.hpp>
|
||||
#include <memory>
|
||||
|
||||
using namespace vanetza::security;
|
||||
using namespace vanetza;
|
||||
|
||||
class EccPointDecompressionTest: public ::testing::TestWithParam<const char*>
|
||||
{
|
||||
protected:
|
||||
std::unique_ptr<Backend> backend;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
backend = create_backend(GetParam());
|
||||
ASSERT_TRUE(backend);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST_P(EccPointDecompressionTest, LSB_Y_0)
|
||||
{
|
||||
const ByteBuffer x = {
|
||||
0x21, 0x31, 0xB5, 0x19, 0x4C, 0xE6, 0xEE, 0x9F,
|
||||
0x47, 0xB6, 0xB4, 0x5F, 0xF8, 0x46, 0xC1, 0x79,
|
||||
0x65, 0x1A, 0x4A, 0x15, 0x63, 0x5A, 0x09, 0x87,
|
||||
0xCC, 0xC3, 0x8F, 0x3E, 0x34, 0x4D, 0xCD, 0x77 };
|
||||
const ByteBuffer y_expected = {
|
||||
0xD0, 0x12, 0x39, 0xBF, 0x92, 0x7F, 0x16, 0xA3,
|
||||
0xAD, 0xDB, 0x58, 0x2F, 0x94, 0x9B, 0xA7, 0x25,
|
||||
0x8F, 0xAC, 0x53, 0xFE, 0xD2, 0xE7, 0x26, 0xDB,
|
||||
0x9C, 0x62, 0xC3, 0x2C, 0xAC, 0x8D, 0xBA, 0x5A };
|
||||
const EccPoint point = Compressed_Lsb_Y_0 { x };
|
||||
|
||||
auto decompressed = backend->decompress_point(point);
|
||||
ASSERT_TRUE(decompressed);
|
||||
EXPECT_EQ(x, decompressed->x);
|
||||
EXPECT_EQ(y_expected, decompressed->y);
|
||||
}
|
||||
|
||||
TEST_P(EccPointDecompressionTest, LSB_Y_1)
|
||||
{
|
||||
const ByteBuffer x = {
|
||||
0x7F, 0x92, 0xF5, 0xBA, 0x25, 0xE0, 0x2C, 0x7F,
|
||||
0x7C, 0xF9, 0x82, 0x2D, 0x74, 0x6F, 0x28, 0x05,
|
||||
0x65, 0xD6, 0xA1, 0x4A, 0x64, 0x38, 0x40, 0x61,
|
||||
0x2E, 0x08, 0x63, 0x4D, 0x6E, 0x85, 0x7F, 0x13 };
|
||||
const ByteBuffer y_expected = {
|
||||
0x32, 0xD9, 0x85, 0xB6, 0x1D, 0x0E, 0x70, 0x51,
|
||||
0x51, 0x03, 0x88, 0xF9, 0x08, 0x2E, 0x24, 0x05,
|
||||
0x0B, 0x1A, 0xFE, 0xB6, 0x56, 0x49, 0x2D, 0x17,
|
||||
0x29, 0xB2, 0x8F, 0x9B, 0x58, 0xCE, 0xCB, 0xAF };
|
||||
const EccPoint point = Compressed_Lsb_Y_1 { x };
|
||||
|
||||
auto decompressed = backend->decompress_point(point);
|
||||
ASSERT_TRUE(decompressed);
|
||||
EXPECT_EQ(x, decompressed->x);
|
||||
EXPECT_EQ(y_expected, decompressed->y);
|
||||
}
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL && defined VANETZA_WITH_CRYPTOPP
|
||||
static auto backends = ::testing::Values("OpenSSL", "CryptoPP");
|
||||
#elif defined VANETZA_WITH_OPENSSL
|
||||
static auto backends = ::testing::Values("OpenSSL");
|
||||
#elif defined VANETZA_WITH_CRYPTOPP
|
||||
static auto backends = ::testing::Values("CryptoPP");
|
||||
#endif
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EccPointDecompression, EccPointDecompressionTest, backends);
|
||||
@@ -0,0 +1,104 @@
|
||||
#include <gtest/gtest.h>
|
||||
#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/common/position_fix.hpp>
|
||||
#include <vanetza/security/v3/geometry.hpp>
|
||||
#include <vanetza/units/angle.hpp>
|
||||
#include <array>
|
||||
|
||||
using namespace vanetza::security::v3;
|
||||
namespace units = vanetza::units;
|
||||
|
||||
TEST(Geometry, location_is_valid)
|
||||
{
|
||||
asn1::TwoDLocation location;
|
||||
location.latitude = Vanetza_Security_NinetyDegreeInt_unknown;
|
||||
location.longitude = Vanetza_Security_OneEightyDegreeInt_unknown;
|
||||
EXPECT_FALSE(is_valid(location));
|
||||
|
||||
location.latitude = 0;
|
||||
EXPECT_FALSE(is_valid(location));
|
||||
|
||||
location.longitude = 0;
|
||||
EXPECT_TRUE(is_valid(location));
|
||||
}
|
||||
|
||||
TEST(Geometry, is_inside_circular)
|
||||
{
|
||||
vanetza::PositionFix fix;
|
||||
fix.latitude = 48.0 * units::degree;
|
||||
fix.longitude = 11.0 * units::degree;
|
||||
|
||||
asn1::CircularRegion region;
|
||||
region.center.latitude = 480010000; /*< 1 degree equals 111km */
|
||||
region.center.longitude = 110000000;
|
||||
|
||||
region.radius = 100; /*< 100 is not enough */
|
||||
EXPECT_FALSE(is_inside(fix, region));
|
||||
|
||||
region.radius = 120; /*< 120 exceeds 111m */
|
||||
EXPECT_TRUE(is_inside(fix, region));
|
||||
}
|
||||
|
||||
TEST(Geometry, is_inside_rectangular)
|
||||
{
|
||||
asn1::RectangularRegion region;
|
||||
region.northWest.latitude = 485000000;
|
||||
region.northWest.longitude = 110000000;
|
||||
region.southEast.latitude = 480000000;
|
||||
region.southEast.longitude = 115000000;
|
||||
|
||||
auto build_posfix = [](double lat, double lon) {
|
||||
vanetza::PositionFix fix;
|
||||
fix.latitude = lat * units::degree;
|
||||
fix.longitude = lon * units::degree;
|
||||
return fix;
|
||||
};
|
||||
|
||||
EXPECT_TRUE(is_inside(build_posfix(48.001, 11.001), region));
|
||||
EXPECT_FALSE(is_inside(build_posfix(47.999, 11.001), region));
|
||||
EXPECT_FALSE(is_inside(build_posfix(48.501, 11.001), region));
|
||||
EXPECT_FALSE(is_inside(build_posfix(48.001, 10.999), region));
|
||||
EXPECT_FALSE(is_inside(build_posfix(48.001, 11.501), region));
|
||||
|
||||
std::swap(region.northWest, region.southEast);
|
||||
EXPECT_FALSE(is_inside(build_posfix(48.001, 11.001), region));
|
||||
}
|
||||
|
||||
TEST(Geometry, is_inside_polygon)
|
||||
{
|
||||
asn1::PolygonalRegion region;
|
||||
std::array<asn1::TwoDLocation, 4> locations = {{
|
||||
{ 100000000, 0 },
|
||||
{ 100000000, 100000000 },
|
||||
{ 0, 100000000 },
|
||||
{ 0, 0}
|
||||
}};
|
||||
std::array<asn1::TwoDLocation*, 4> location_ptrs = {
|
||||
&locations[0], &locations[1], &locations[2], &locations[3]
|
||||
};
|
||||
region.list.array = location_ptrs.data();
|
||||
region.list.count = location_ptrs.size();
|
||||
region.list.size = location_ptrs.size();
|
||||
|
||||
asn1::TwoDLocation point_b = { -10000000, -10000000 };
|
||||
EXPECT_FALSE(is_inside(&point_b, ®ion));
|
||||
|
||||
asn1::TwoDLocation point_a = { 50000000, 50000000 };
|
||||
EXPECT_TRUE(is_inside(&point_a, ®ion));
|
||||
|
||||
// also reversed order of region points
|
||||
location_ptrs = { &locations[3], &locations[2], &locations[1], &locations[0] };
|
||||
EXPECT_TRUE(is_inside(&point_a, ®ion));
|
||||
|
||||
vanetza::PositionFix fix;
|
||||
fix.latitude = -1 * units::degree;
|
||||
fix.longitude = -1 * units::degree;
|
||||
EXPECT_FALSE(is_inside(fix, region));
|
||||
|
||||
fix.latitude = 5 * units::degree;
|
||||
fix.longitude = 5 * units::degree;
|
||||
EXPECT_TRUE(is_inside(fix, region));
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/security/hmac.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace vanetza::security;
|
||||
using vanetza::ByteBuffer;
|
||||
|
||||
class HmacTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
ByteBuffer data = {0x01, 0x02, 0x03, 0x04, 0x05};
|
||||
HmacKey key = {{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
|
||||
}};
|
||||
};
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL || defined VANETZA_WITH_CRYPTOPP
|
||||
TEST_F(HmacTest, create_hmac_tag)
|
||||
{
|
||||
KeyTag tag = create_hmac_tag(data, key);
|
||||
KeyTag zero = {};
|
||||
EXPECT_NE(tag, zero);
|
||||
}
|
||||
|
||||
TEST_F(HmacTest, deterministic)
|
||||
{
|
||||
KeyTag tag1 = create_hmac_tag(data, key);
|
||||
KeyTag tag2 = create_hmac_tag(data, key);
|
||||
EXPECT_EQ(tag1, tag2);
|
||||
}
|
||||
|
||||
TEST_F(HmacTest, different_data_different_tag)
|
||||
{
|
||||
ByteBuffer other_data = {0x05, 0x04, 0x03, 0x02, 0x01};
|
||||
KeyTag tag1 = create_hmac_tag(data, key);
|
||||
KeyTag tag2 = create_hmac_tag(other_data, key);
|
||||
EXPECT_NE(tag1, tag2);
|
||||
}
|
||||
|
||||
TEST_F(HmacTest, different_key_different_tag)
|
||||
{
|
||||
HmacKey other_key = {};
|
||||
KeyTag tag1 = create_hmac_tag(data, key);
|
||||
KeyTag tag2 = create_hmac_tag(data, other_key);
|
||||
EXPECT_NE(tag1, tag2);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL
|
||||
TEST_F(HmacTest, openssl)
|
||||
{
|
||||
KeyTag tag = create_hmac_tag_openssl(data, key);
|
||||
KeyTag zero = {};
|
||||
EXPECT_NE(tag, zero);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined VANETZA_WITH_CRYPTOPP
|
||||
TEST_F(HmacTest, cryptopp)
|
||||
{
|
||||
KeyTag tag = create_hmac_tag_cryptopp(data, key);
|
||||
KeyTag zero = {};
|
||||
EXPECT_NE(tag, zero);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL && defined VANETZA_WITH_CRYPTOPP
|
||||
TEST_F(HmacTest, openssl_and_cryptopp_match)
|
||||
{
|
||||
KeyTag tag_openssl = create_hmac_tag_openssl(data, key);
|
||||
KeyTag tag_cryptopp = create_hmac_tag_cryptopp(data, key);
|
||||
EXPECT_EQ(tag_openssl, tag_cryptopp);
|
||||
}
|
||||
#endif
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vanetza/security/peer_request_tracker.hpp>
|
||||
|
||||
#include "printer.hpp"
|
||||
|
||||
using namespace vanetza::security;
|
||||
|
||||
HashedId3 create_id(uint32_t id)
|
||||
{
|
||||
HashedId3 hid;
|
||||
hid[0] = id & 0xFF;
|
||||
hid[1] = (id >> 8) & 0xFF;
|
||||
hid[2] = (id >> 16) & 0xFF;
|
||||
return hid;
|
||||
}
|
||||
|
||||
TEST(PeerRequestTracker, is_pending)
|
||||
{
|
||||
PeerRequestTracker tracker;
|
||||
EXPECT_FALSE(tracker.is_pending(create_id(42)));
|
||||
tracker.add_request(create_id(42));
|
||||
EXPECT_TRUE(tracker.is_pending(create_id(42)));
|
||||
tracker.discard_request(create_id(42));
|
||||
EXPECT_FALSE(tracker.is_pending(create_id(42)));
|
||||
}
|
||||
|
||||
TEST(PeerRequestTracker, bounded_capacity)
|
||||
{
|
||||
PeerRequestTracker tracker(2);
|
||||
tracker.add_request(create_id(1));
|
||||
tracker.add_request(create_id(2));
|
||||
EXPECT_TRUE(tracker.is_pending(create_id(1)));
|
||||
EXPECT_TRUE(tracker.is_pending(create_id(2)));
|
||||
|
||||
tracker.add_request(create_id(3));
|
||||
EXPECT_TRUE(tracker.is_pending(create_id(3)));
|
||||
EXPECT_TRUE(tracker.is_pending(create_id(2)));
|
||||
// dropped oldest pending request
|
||||
EXPECT_FALSE(tracker.is_pending(create_id(1)));
|
||||
}
|
||||
|
||||
TEST(PeerRequestTracker, keep_order)
|
||||
{
|
||||
PeerRequestTracker tracker(4);
|
||||
tracker.add_request(create_id(1));
|
||||
tracker.add_request(create_id(2));
|
||||
tracker.add_request(create_id(3));
|
||||
tracker.add_request(create_id(4));
|
||||
tracker.add_request(create_id(3));
|
||||
tracker.add_request(create_id(2));
|
||||
|
||||
std::list<HashedId3> expected = { create_id(1), create_id(2), create_id(3), create_id(4) };
|
||||
EXPECT_EQ(expected, tracker.all());
|
||||
|
||||
// nothing left in tracker
|
||||
EXPECT_FALSE(tracker.next_one());
|
||||
}
|
||||
|
||||
TEST(PeerRequestTracker, next_one)
|
||||
{
|
||||
PeerRequestTracker tracker(3);
|
||||
tracker.add_request(create_id(0xc0));
|
||||
tracker.add_request(create_id(0xff));
|
||||
tracker.add_request(create_id(0xee));
|
||||
tracker.add_request(create_id(0x42));
|
||||
|
||||
EXPECT_EQ(tracker.next_one(), create_id(0xff));
|
||||
EXPECT_EQ(tracker.next_one(), create_id(0xee));
|
||||
EXPECT_EQ(tracker.next_one(), create_id(0x42));
|
||||
EXPECT_FALSE(tracker.next_one());
|
||||
}
|
||||
|
||||
TEST(PeerRequestTracker, next_n)
|
||||
{
|
||||
PeerRequestTracker tracker(6);
|
||||
tracker.add_request(create_id(0x01));
|
||||
tracker.add_request(create_id(0x02));
|
||||
tracker.add_request(create_id(0x03));
|
||||
tracker.add_request(create_id(0x04));
|
||||
tracker.add_request(create_id(0x05));
|
||||
|
||||
std::list<HashedId3> expected = { create_id(0x01), create_id(0x02), create_id(0x03) };
|
||||
EXPECT_EQ(expected, tracker.next_n(3));
|
||||
|
||||
expected = { create_id(0x04), create_id(0x05) };
|
||||
EXPECT_EQ(expected, tracker.next_n(8));
|
||||
|
||||
expected = { };
|
||||
EXPECT_EQ(expected, tracker.next_n(2));
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#include <vanetza/security/persistence.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
using namespace vanetza::security;
|
||||
|
||||
#define ASSET(path) ASSET_DIR "/" path
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const std::array<uint8_t, 32> expected_private_key = {
|
||||
0x53, 0xb7, 0x7e, 0xb8, 0x48, 0x2e, 0x3c, 0x1a,
|
||||
0xd3, 0x36, 0x70, 0xc0, 0xc6, 0xc4, 0x6b, 0xc0,
|
||||
0x36, 0x90, 0xd4, 0x00, 0x59, 0xd3, 0xcb, 0xb5,
|
||||
0x81, 0xb3, 0x36, 0xaf, 0x8a, 0x98, 0x93, 0xf4
|
||||
};
|
||||
|
||||
void check_private_key(const PrivateKey& key)
|
||||
{
|
||||
EXPECT_EQ(key.type, KeyType::NistP256);
|
||||
ASSERT_EQ(key.key.size(), expected_private_key.size());
|
||||
EXPECT_TRUE(std::equal(key.key.begin(), key.key.end(), expected_private_key.begin()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined VANETZA_WITH_OPENSSL || defined VANETZA_WITH_CRYPTOPP
|
||||
|
||||
TEST(Persistence, load_pem)
|
||||
{
|
||||
check_private_key(load_private_key_from_pem_file(ASSET("test_key.pem")));
|
||||
}
|
||||
|
||||
TEST(Persistence, load_der)
|
||||
{
|
||||
check_private_key(load_private_key_from_der_file(ASSET("test_key.der")));
|
||||
}
|
||||
|
||||
TEST(Persistence, pem_and_der_load_same_key)
|
||||
{
|
||||
auto pem = load_private_key_from_pem_file(ASSET("test_key.pem"));
|
||||
auto der = load_private_key_from_der_file(ASSET("test_key.der"));
|
||||
EXPECT_EQ(pem.type, der.type);
|
||||
EXPECT_EQ(pem.key, der.key);
|
||||
}
|
||||
|
||||
TEST(Persistence, load_pem_nonexistent_file)
|
||||
{
|
||||
EXPECT_THROW(load_private_key_from_pem_file("nonexistent.pem"), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(Persistence, load_der_nonexistent_file)
|
||||
{
|
||||
EXPECT_THROW(load_private_key_from_der_file("nonexistent.der"), std::runtime_error);
|
||||
}
|
||||
|
||||
#endif /* VANETZA_WITH_OPENSSL || VANETZA_WITH_CRYPTOPP */
|
||||
|
||||
#ifdef VANETZA_WITH_OPENSSL
|
||||
TEST(Persistence, openssl_load_pem)
|
||||
{
|
||||
check_private_key(load_private_key_from_pem_file_openssl(ASSET("test_key.pem")));
|
||||
}
|
||||
|
||||
TEST(Persistence, openssl_load_der)
|
||||
{
|
||||
check_private_key(load_private_key_from_der_file_openssl(ASSET("test_key.der")));
|
||||
}
|
||||
#endif /* VANETZA_WITH_OPENSSL */
|
||||
|
||||
#ifdef VANETZA_WITH_CRYPTOPP
|
||||
TEST(Persistence, cryptopp_load_pem)
|
||||
{
|
||||
check_private_key(load_private_key_from_pem_file_cryptopp(ASSET("test_key.pem")));
|
||||
}
|
||||
|
||||
TEST(Persistence, cryptopp_load_der)
|
||||
{
|
||||
check_private_key(load_private_key_from_der_file_cryptopp(ASSET("test_key.der")));
|
||||
}
|
||||
#endif /* VANETZA_WITH_CRYPTOPP */
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <boost/optional/optional_io.hpp>
|
||||
#include <vanetza/security/decap_service.hpp>
|
||||
#include <vanetza/security/hashed_id.hpp>
|
||||
#include <ostream>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
void PrintTo(const DecapReport& report, std::ostream* out)
|
||||
{
|
||||
struct Printer : boost::static_visitor<>
|
||||
{
|
||||
Printer(std::ostream* out) : out(out) {}
|
||||
|
||||
void operator()(const VerificationReport& report) const
|
||||
{
|
||||
*out << "DecapReport(VerificationReport(" << static_cast<int>(report) << "))";
|
||||
}
|
||||
|
||||
void operator()(const boost::blank&) const
|
||||
{
|
||||
*out << "DecapReport(None)";
|
||||
}
|
||||
|
||||
std::ostream* out;
|
||||
};
|
||||
boost::apply_visitor(Printer(out), report);
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
namespace std {
|
||||
|
||||
/* HashedId3 and HashedId8 are mere type aliases of std::array<> */
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const vanetza::security::HashedId3& id)
|
||||
{
|
||||
os << vanetza::security::to_string(id);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const vanetza::security::HashedId8& id)
|
||||
{
|
||||
os << vanetza::security::to_string(id);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vanetza/security/public_key.hpp>
|
||||
|
||||
using namespace vanetza;
|
||||
using namespace vanetza::security;
|
||||
using namespace std;
|
||||
|
||||
TEST(PublicKey, canonical_hexstring_y0)
|
||||
{
|
||||
PublicKey key;
|
||||
key.type = KeyType::NistP256;
|
||||
key.compression = KeyCompression::Y0;
|
||||
key.x = ByteBuffer(32, 0x00);
|
||||
EXPECT_EQ("02" + std::string(64, '0'), canonical_hexstring(key));
|
||||
}
|
||||
|
||||
TEST(PublicKey, canonical_hexstring_y1)
|
||||
{
|
||||
PublicKey key;
|
||||
key.type = KeyType::BrainpoolP384r1;
|
||||
key.compression = KeyCompression::Y1;
|
||||
key.x = ByteBuffer(48, 0x00);
|
||||
EXPECT_EQ("03" + std::string(96, '0'), canonical_hexstring(key));
|
||||
}
|
||||
|
||||
TEST(PublicKey, canonical_hexstring_encoding)
|
||||
{
|
||||
PublicKey key;
|
||||
key.type = KeyType::NistP256;
|
||||
key.compression = KeyCompression::Y0;
|
||||
key.x = {
|
||||
0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd,
|
||||
0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32,
|
||||
0x10, 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba,
|
||||
0xbe, 0x00, 0xff, 0x11, 0xee, 0x22, 0xdd, 0x33
|
||||
};
|
||||
auto expected = "02"
|
||||
"000123456789ABCD"
|
||||
"EFFEDCBA98765432"
|
||||
"10DEADBEEFCAFEBA"
|
||||
"BE00FF11EE22DD33";
|
||||
EXPECT_EQ(expected, canonical_hexstring(key));
|
||||
}
|
||||
|
||||
TEST(PublicKey, canonical_hexstring_uncompressed_parity)
|
||||
{
|
||||
PublicKey key;
|
||||
key.type = KeyType::NistP256;
|
||||
key.compression = KeyCompression::NoCompression;
|
||||
key.x = ByteBuffer(32, 0xab);
|
||||
key.y = ByteBuffer(32, 0x04);
|
||||
EXPECT_EQ("02", canonical_hexstring(key).substr(0, 2));
|
||||
|
||||
key.y = ByteBuffer(32, 0x05);
|
||||
EXPECT_EQ("03", canonical_hexstring(key).substr(0, 2));
|
||||
}
|
||||
|
||||
TEST(PublicKey, canonical_hexstring_rejects_malformed)
|
||||
{
|
||||
PublicKey key;
|
||||
key.compression = KeyCompression::Y0;
|
||||
|
||||
key.type = KeyType::Unspecified;
|
||||
key.x = ByteBuffer(32, 0xab);
|
||||
EXPECT_TRUE(canonical_hexstring(key).empty());
|
||||
|
||||
key.type = KeyType::NistP256;
|
||||
key.x.clear();
|
||||
EXPECT_TRUE(canonical_hexstring(key).empty());
|
||||
|
||||
key.x = ByteBuffer(31, 0xab);
|
||||
EXPECT_TRUE(canonical_hexstring(key).empty());
|
||||
|
||||
key.x = ByteBuffer(32, 0xab);
|
||||
key.compression = KeyCompression::NoCompression;
|
||||
EXPECT_TRUE(canonical_hexstring(key).empty());
|
||||
|
||||
key.y = ByteBuffer(31, 0x04);
|
||||
EXPECT_TRUE(canonical_hexstring(key).empty());
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vanetza/common/manual_runtime.hpp>
|
||||
#include <vanetza/common/stored_position_provider.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/straight_verify_service.hpp>
|
||||
#include <vanetza/security/v3/naive_certificate_provider.hpp>
|
||||
#include <vanetza/security/v3/secured_message.hpp>
|
||||
#include <vanetza/security/v3/sign_service.hpp>
|
||||
|
||||
#include "printer.hpp"
|
||||
|
||||
using namespace vanetza;
|
||||
using namespace vanetza::security;
|
||||
|
||||
TEST(SecuredMessageV3, deserialize)
|
||||
{
|
||||
const ByteBuffer encoded_secured_msg = {
|
||||
0x03, 0x81, 0x00, 0x40, 0x03, 0x80, 0x56, 0x20, 0x50, 0x02, 0x80, 0x00,
|
||||
0x32, 0x01, 0x00, 0x14, 0x00, 0xd2, 0xfb, 0x6a, 0x0c, 0x62, 0xd2, 0xbf,
|
||||
0x6c, 0x54, 0x53, 0x1f, 0x44, 0xef, 0xf5, 0x06, 0x66, 0x36, 0x09, 0x84,
|
||||
0x6a, 0x06, 0xc8, 0x00, 0x00, 0xa0, 0x00, 0x07, 0xd1, 0x00, 0x00, 0x02,
|
||||
0x02, 0x6a, 0x0c, 0x62, 0xd2, 0x57, 0x41, 0x00, 0x5a, 0x9d, 0x3a, 0xbf,
|
||||
0x6e, 0x36, 0x01, 0x20, 0x22, 0x34, 0x23, 0x00, 0xfc, 0x35, 0x96, 0xd4,
|
||||
0x58, 0x69, 0x40, 0xe2, 0x4e, 0x03, 0x02, 0x96, 0x8a, 0x9b, 0x34, 0x3d,
|
||||
0x82, 0x09, 0x9e, 0x10, 0x41, 0xc0, 0x14, 0xb9, 0x80, 0x40, 0x01, 0x24,
|
||||
0x00, 0x01, 0xc8, 0x0b, 0xbf, 0x35, 0x4b, 0x4f, 0x80, 0x0b, 0xa2, 0xd2,
|
||||
0xfb, 0x6a, 0x0c, 0x62, 0xd2, 0x80, 0x82, 0xea, 0x71, 0xef, 0xf9, 0xc6,
|
||||
0xbb, 0x15, 0x7b, 0x8e, 0x16, 0x66, 0x44, 0x12, 0x0d, 0x7f, 0x98, 0xf2,
|
||||
0x52, 0x5e, 0x73, 0x8f, 0x6f, 0x41, 0xd7, 0xfd, 0xfa, 0xc7, 0xd0, 0xd8,
|
||||
0x8a, 0xa3, 0x9b, 0x10, 0x96, 0xfa, 0xb3, 0xfb, 0x2a, 0x0a, 0x92, 0x2a,
|
||||
0x3b, 0x5f, 0xeb, 0x91, 0xf9, 0xf5, 0x22, 0xd0, 0x06, 0x1f, 0x3b, 0x9c,
|
||||
0xa7, 0xa4, 0x6f, 0x7c, 0x7b, 0xd3, 0xef, 0x9d, 0x3a, 0x84, 0xbc
|
||||
};
|
||||
|
||||
v3::SecuredMessage msg;
|
||||
EXPECT_TRUE(msg.decode(encoded_secured_msg));
|
||||
EXPECT_EQ(3, msg.protocol_version());
|
||||
}
|
||||
|
||||
TEST(SecuredMessageV3, verify)
|
||||
{
|
||||
// a CAM signed by a VW Golf 8 including a full signer certificate
|
||||
const ByteBuffer encoded_secured_msg = {
|
||||
0x03, 0x81, 0x00, 0x40, 0x03, 0x80, 0x56, 0x20, 0x50, 0x02, 0x80, 0x00,
|
||||
0x32, 0x01, 0x00, 0x14, 0x00, 0xfe, 0x38, 0x4c, 0xe0, 0xb8, 0x90, 0xbf,
|
||||
0x6b, 0x2f, 0x5b, 0x1f, 0x45, 0x28, 0x58, 0x06, 0x64, 0x0a, 0x6d, 0x80,
|
||||
0xe8, 0x03, 0xbe, 0x00, 0x00, 0xa0, 0x00, 0x07, 0xd1, 0x00, 0x00, 0x02,
|
||||
0x02, 0x4c, 0xe0, 0xb8, 0x90, 0x30, 0xbc, 0x00, 0x5a, 0x9d, 0x42, 0x2a,
|
||||
0x0e, 0x35, 0xbb, 0xa0, 0x22, 0x44, 0x23, 0x86, 0xda, 0x35, 0x96, 0xd4,
|
||||
0x58, 0x3b, 0xe1, 0x20, 0xa1, 0x03, 0x02, 0x96, 0x8a, 0xcf, 0x33, 0xe7,
|
||||
0x81, 0xff, 0x82, 0x10, 0x3f, 0xe0, 0x14, 0x19, 0x80, 0x40, 0x01, 0x24,
|
||||
0x00, 0x01, 0xc8, 0x0b, 0xba, 0xb6, 0xc8, 0x15, 0x81, 0x01, 0x01, 0x80,
|
||||
0x03, 0x00, 0x80, 0x56, 0xdf, 0xd6, 0xd6, 0x27, 0xa3, 0x62, 0xdc, 0x10,
|
||||
0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xdf, 0xf7, 0xb5, 0x84, 0x00,
|
||||
0xa8, 0x01, 0x02, 0x80, 0x01, 0x24, 0x81, 0x04, 0x03, 0x01, 0x00, 0x00,
|
||||
0x80, 0x01, 0x25, 0x81, 0x05, 0x04, 0x01, 0x90, 0x1a, 0x25, 0x80, 0x80,
|
||||
0x82, 0x04, 0x27, 0xbb, 0x27, 0xc9, 0x98, 0xc1, 0xec, 0xa2, 0xb1, 0x0e,
|
||||
0x71, 0x07, 0x98, 0x02, 0x44, 0x51, 0x8b, 0x3c, 0x50, 0xa3, 0xa3, 0x27,
|
||||
0xb5, 0xb1, 0x90, 0xd0, 0x90, 0xf1, 0x45, 0x1f, 0x3d, 0x80, 0x80, 0x83,
|
||||
0xc2, 0xf3, 0xca, 0xeb, 0xc7, 0xfa, 0x35, 0x94, 0x5c, 0x03, 0x0a, 0x5a,
|
||||
0xe0, 0x1a, 0x41, 0x7a, 0xdf, 0x6d, 0xff, 0xd5, 0x41, 0xcc, 0xd2, 0xd9,
|
||||
0x2b, 0xfe, 0xb6, 0x3d, 0xc1, 0x56, 0x89, 0xcb, 0xd6, 0xb8, 0xe3, 0x2b,
|
||||
0xd5, 0xe8, 0x66, 0xd9, 0xfa, 0xa2, 0xfe, 0x55, 0x95, 0xe2, 0xdb, 0xb9,
|
||||
0xbe, 0x3e, 0x96, 0x5a, 0x70, 0x94, 0x25, 0x8b, 0x4a, 0x24, 0x9d, 0xfb,
|
||||
0x75, 0x8a, 0x07, 0x80, 0x82, 0x73, 0x7a, 0x94, 0x51, 0x6c, 0x56, 0xf8,
|
||||
0x85, 0x26, 0x2f, 0xd4, 0xd2, 0xac, 0x77, 0x5e, 0xba, 0xa1, 0x46, 0x84,
|
||||
0xeb, 0xf6, 0x59, 0x39, 0x66, 0xef, 0x7d, 0x30, 0x84, 0x07, 0x8e, 0xdd,
|
||||
0xd0, 0xf4, 0xfe, 0x94, 0x06, 0x04, 0x2b, 0x1d, 0x1a, 0x92, 0xb7, 0x0a,
|
||||
0x0c, 0xce, 0x8d, 0x7d, 0xe7, 0xe9, 0xb6, 0xfe, 0x13, 0xfb, 0x26, 0x9a,
|
||||
0x5a, 0x67, 0x57, 0x31, 0x61, 0x58, 0x9e, 0x2a, 0x79
|
||||
};
|
||||
|
||||
v3::SecuredMessage msg;
|
||||
EXPECT_TRUE(msg.decode(encoded_secured_msg));
|
||||
EXPECT_EQ(3, msg.protocol_version());
|
||||
|
||||
StoredPositionProvider position_provider;
|
||||
ManualRuntime runtime { Clock::at("2019-11-21 11:30") };
|
||||
auto backend = create_backend("default");
|
||||
|
||||
StraightVerifyService verify_service { runtime, *backend, position_provider };
|
||||
VerifyConfirm confirm = verify_service.verify(msg);
|
||||
EXPECT_EQ(confirm.report, VerificationReport::Success);
|
||||
EXPECT_EQ(confirm.its_aid, aid::CA);
|
||||
ByteBuffer ca_ssp {0x01, 0x00, 0x00};
|
||||
EXPECT_EQ(confirm.permissions, ca_ssp);
|
||||
HashedId8 digest { 0x12, 0x7c, 0xff, 0x38, 0x4c, 0xe0, 0xb8, 0x90};
|
||||
EXPECT_EQ(confirm.certificate_id, digest);
|
||||
// AT issuer digest = 56dfd6d627a362dc
|
||||
}
|
||||
|
||||
TEST(SecuredMessageV3, sign_and_verify)
|
||||
{
|
||||
StoredPositionProvider position_provider;
|
||||
ManualRuntime runtime { Clock::at("2024-08-12 11:30") };
|
||||
auto backend = create_backend("default");
|
||||
|
||||
v3::NaiveCertificateProvider cert_provider { runtime };
|
||||
v3::DefaultSignHeaderPolicy sign_header_policy { runtime, position_provider, cert_provider };
|
||||
v3::NullCertificateValidator cert_validator;
|
||||
v3::StraightSignService sign_service { cert_provider, *backend, sign_header_policy, cert_validator };
|
||||
|
||||
SignRequest request;
|
||||
ChunkPacket packet;
|
||||
packet[OsiLayer::Application] = ByteBuffer {0x01, 0x02, 0x03};
|
||||
request.plain_message = std::move(packet);
|
||||
request.its_aid = aid::DEN;
|
||||
SignConfirm sign_confirm = sign_service.sign(std::move(request));
|
||||
ASSERT_TRUE(sign_confirm.secured_message);
|
||||
|
||||
StraightVerifyService verify_service { runtime, *backend, position_provider };
|
||||
VerifyRequest verify_request { SecuredMessageView { *sign_confirm.secured_message } };
|
||||
VerifyConfirm verify_confirm = verify_service.verify(verify_request);
|
||||
EXPECT_EQ(verify_confirm.report, VerificationReport::Success);
|
||||
EXPECT_TRUE(verify_confirm.certificate_validity.valid());
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <vanetza/security/tests/serialization.hpp>
|
||||
#include <boost/algorithm/hex.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
ByteBuffer buffer_from_hexstring(const char* string)
|
||||
{
|
||||
ByteBuffer buf;
|
||||
boost::algorithm::unhex(string, back_inserter(buf));
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef SERIALIZATION_HPP_ZWGI3RCG
|
||||
#define SERIALIZATION_HPP_ZWGI3RCG
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/common/byte_buffer_source.hpp>
|
||||
#include <vanetza/security/v2/serialization.hpp>
|
||||
#include <boost/iostreams/stream_buffer.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
|
||||
/**
|
||||
* \brief create a ByteBuffer from griven hex string
|
||||
* \param string hex string
|
||||
* \return equivalent byte buffer
|
||||
*/
|
||||
ByteBuffer buffer_from_hexstring(const char*);
|
||||
|
||||
/**
|
||||
* \brief Deserialize any class from given hex string
|
||||
* \tparam T the target type of deserialization
|
||||
* \tparam ARGS additional arguments passed to deserialize function
|
||||
* \param string hex string
|
||||
* \param result deserialize into this object
|
||||
* \param args additional arguments for deserialization
|
||||
*/
|
||||
template<typename T, typename... ARGS>
|
||||
size_t deserialize_from_hexstring(const char* string, T& result, ARGS&&... args)
|
||||
{
|
||||
auto buffer = buffer_from_hexstring(string);
|
||||
byte_buffer_source source(buffer);
|
||||
boost::iostreams::stream_buffer<byte_buffer_source> stream(source);
|
||||
InputArchive ar(stream);
|
||||
return deserialize(ar, result, std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/**
|
||||
* \brief Serialize and deserialize an object
|
||||
*
|
||||
* Source object is serialized and deserialized
|
||||
* object form this binary representation is returned.
|
||||
*
|
||||
* \tparam T the type of the result
|
||||
* \tparam ARGS additional arguments passed to underlying functions
|
||||
* \param source serialize from this object
|
||||
* \param args additional arguments
|
||||
* \return deserialized object (should be equal to source)
|
||||
*/
|
||||
template<typename T, typename... ARGS>
|
||||
T serialize_roundtrip(const T& source, ARGS&&... args)
|
||||
{
|
||||
std::stringstream stream;
|
||||
OutputArchive oa(stream);
|
||||
serialize(oa, source, std::forward<ARGS>(args)...);
|
||||
|
||||
T result;
|
||||
InputArchive ia(stream);
|
||||
deserialize(ia, result, std::forward<ARGS>(args)...);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* SERIALIZATION_HPP_ZWGI3RCG */
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <vanetza/security/v2/basic_elements.hpp>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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 v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef BASIC_ELEMENTS_HPP_RALCTYHI
|
||||
#define BASIC_ELEMENTS_HPP_RALCTYHI
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/security/hashed_id.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
using Time64 = uint64_t;
|
||||
using Time32 = uint32_t;
|
||||
|
||||
/// Time64WithStandardDeviation specified in TS 103 097 v1.2.1, section 4.2.16
|
||||
struct Time64WithStandardDeviation
|
||||
{
|
||||
Time64 time64;
|
||||
uint8_t log_std_dev;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 point to time stamp
|
||||
* \param tp time point
|
||||
* \return time stamp with microsecond accuracy
|
||||
*/
|
||||
Time64 convert_time64(const Clock::time_point& tp);
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* BASIC_ELEMENTS_HPP_RALCTYHI */
|
||||
@@ -0,0 +1,259 @@
|
||||
#include <vanetza/common/byte_buffer_sink.hpp>
|
||||
#include <vanetza/common/serialization_buffer.hpp>
|
||||
#include <vanetza/security/exception.hpp>
|
||||
#include <vanetza/security/sha.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/v2/length_coding.hpp>
|
||||
#include <vanetza/security/v2/signer_info.hpp>
|
||||
#include <vanetza/security/v2/validity_restriction.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <boost/variant/get.hpp>
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
size_t get_size(const Certificate& cert)
|
||||
{
|
||||
size_t size = sizeof(cert.version());
|
||||
size += get_size(cert.signer_info);
|
||||
size += get_size(cert.subject_info);
|
||||
size += get_size(cert.subject_attributes);
|
||||
size += length_coding_size(get_size(cert.subject_attributes));
|
||||
size += get_size(cert.validity_restriction);
|
||||
size += length_coding_size(get_size(cert.validity_restriction));
|
||||
size += get_size(cert.signature);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
void serialize(OutputArchive& ar, const Certificate& cert)
|
||||
{
|
||||
serialize(ar, host_cast(cert.version()));
|
||||
serialize(ar, cert.signer_info);
|
||||
serialize(ar, cert.subject_info);
|
||||
serialize(ar, cert.subject_attributes);
|
||||
serialize(ar, cert.validity_restriction);
|
||||
serialize(ar, cert.signature);
|
||||
}
|
||||
|
||||
size_t deserialize(InputArchive& ar, Certificate& cert)
|
||||
{
|
||||
uint8_t version = 0;
|
||||
deserialize(ar, version);
|
||||
size_t size = sizeof(cert.version());
|
||||
if (2 == version) {
|
||||
size += deserialize_certificate_signer(ar, cert.signer_info);
|
||||
size += deserialize(ar, cert.subject_info);
|
||||
size += deserialize(ar, cert.subject_attributes);
|
||||
size += length_coding_size(get_size(cert.subject_attributes));
|
||||
size += deserialize(ar, cert.validity_restriction);
|
||||
size += length_coding_size(get_size(cert.validity_restriction));
|
||||
size += deserialize(ar, cert.signature);
|
||||
} else {
|
||||
throw deserialization_error("Unsupported Certificate version");
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
ByteBuffer convert_for_signing(const Certificate& cert)
|
||||
{
|
||||
ByteBuffer buf;
|
||||
byte_buffer_sink sink(buf);
|
||||
|
||||
boost::iostreams::stream_buffer<byte_buffer_sink> stream(sink);
|
||||
OutputArchive ar(stream);
|
||||
|
||||
const uint8_t version = cert.version();
|
||||
ar << version;
|
||||
serialize(ar, cert.signer_info);
|
||||
serialize(ar, cert.subject_info);
|
||||
serialize(ar, cert.subject_attributes);
|
||||
serialize(ar, cert.validity_restriction);
|
||||
|
||||
stream.close();
|
||||
return buf;
|
||||
}
|
||||
|
||||
void sort(Certificate& cert)
|
||||
{
|
||||
cert.subject_attributes.sort([](const SubjectAttribute& a, const SubjectAttribute& b) {
|
||||
const SubjectAttributeType type_a = get_type(a);
|
||||
const SubjectAttributeType type_b = get_type(b);
|
||||
|
||||
// all fields must be encoded in ascending order
|
||||
using enum_int = std::underlying_type<SubjectAttributeType>::type;
|
||||
return static_cast<enum_int>(type_a) < static_cast<enum_int>(type_b);
|
||||
});
|
||||
|
||||
cert.validity_restriction.sort([](const ValidityRestriction& a, const ValidityRestriction& b) {
|
||||
const ValidityRestrictionType type_a = get_type(a);
|
||||
const ValidityRestrictionType type_b = get_type(b);
|
||||
|
||||
// all fields must be encoded in ascending order
|
||||
using enum_int = std::underlying_type<ValidityRestrictionType>::type;
|
||||
return static_cast<enum_int>(type_a) < static_cast<enum_int>(type_b);
|
||||
});
|
||||
}
|
||||
|
||||
boost::optional<Uncompressed> get_uncompressed_public_key(const Certificate& cert, Backend& backend)
|
||||
{
|
||||
boost::optional<Uncompressed> public_key_coordinates;
|
||||
for (auto& attribute : cert.subject_attributes) {
|
||||
if (get_type(attribute) == SubjectAttributeType::Verification_Key) {
|
||||
const VerificationKey& verification_key = boost::get<VerificationKey>(attribute);
|
||||
const EccPoint& ecc_point = boost::get<ecdsa_nistp256_with_sha256>(verification_key.key).public_key;
|
||||
public_key_coordinates = backend.decompress_point(ecc_point);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return public_key_coordinates;
|
||||
}
|
||||
|
||||
boost::optional<ecdsa256::PublicKey> get_public_key(const Certificate& cert, Backend& backend)
|
||||
{
|
||||
auto unc = get_uncompressed_public_key(cert, backend);
|
||||
boost::optional<ecdsa256::PublicKey> result;
|
||||
ecdsa256::PublicKey pub;
|
||||
if (unc && unc->x.size() == pub.x.size() && unc->y.size() == pub.y.size()) {
|
||||
std::copy_n(unc->x.begin(), pub.x.size(), pub.x.data());
|
||||
std::copy_n(unc->y.begin(), pub.y.size(), pub.y.data());
|
||||
result = std::move(pub);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HashedId8 calculate_hash(const Certificate& cert)
|
||||
{
|
||||
Certificate canonical_cert = cert;
|
||||
|
||||
// canonical encoding according to TS 103 097 V1.2.1, section 4.2.12
|
||||
boost::optional<EcdsaSignature> signature = extract_ecdsa_signature(cert.signature);
|
||||
if (signature) {
|
||||
struct canonical_visitor : public boost::static_visitor<EccPoint>
|
||||
{
|
||||
EccPoint operator()(const X_Coordinate_Only& x_only) const
|
||||
{
|
||||
return x_only;
|
||||
}
|
||||
|
||||
EccPoint operator()(const Compressed_Lsb_Y_0& y0) const
|
||||
{
|
||||
return X_Coordinate_Only { y0.x };
|
||||
}
|
||||
|
||||
EccPoint operator()(const Compressed_Lsb_Y_1& y1) const
|
||||
{
|
||||
return X_Coordinate_Only { y1.x };
|
||||
}
|
||||
|
||||
EccPoint operator()(const Uncompressed& unc) const
|
||||
{
|
||||
return X_Coordinate_Only { unc.x };
|
||||
}
|
||||
};
|
||||
|
||||
EcdsaSignature canonical_sig;
|
||||
canonical_sig.s = signature->s;
|
||||
canonical_sig.R = boost::apply_visitor(canonical_visitor(), signature->R);
|
||||
assert(get_type(canonical_sig.R) == EccPointType::X_Coordinate_Only);
|
||||
canonical_cert.signature = canonical_sig;
|
||||
}
|
||||
|
||||
ByteBuffer bytes;
|
||||
serialize_into_buffer(canonical_cert, bytes);
|
||||
|
||||
HashedId8 id;
|
||||
Sha256Digest digest = calculate_sha256_digest(bytes.data(), bytes.size());
|
||||
assert(digest.size() >= id.size());
|
||||
std::copy(digest.end() - id.size(), digest.end(), id.begin());
|
||||
return id;
|
||||
}
|
||||
|
||||
const SubjectAttribute* Certificate::get_attribute(SubjectAttributeType sat) const
|
||||
{
|
||||
const SubjectAttribute* match = nullptr;
|
||||
for (auto& attribute : subject_attributes) {
|
||||
if (get_type(attribute) == sat) {
|
||||
match = &attribute;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
const ValidityRestriction* Certificate::get_restriction(ValidityRestrictionType vrt) const
|
||||
{
|
||||
const ValidityRestriction* match = nullptr;
|
||||
for (auto& restriction : validity_restriction) {
|
||||
if (get_type(restriction) == vrt) {
|
||||
match = &restriction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
void Certificate::remove_attribute(SubjectAttributeType type)
|
||||
{
|
||||
for (auto it = subject_attributes.begin(); it != subject_attributes.end(); /* noop */) {
|
||||
if (get_type(*it) == type) {
|
||||
it = subject_attributes.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Certificate::remove_restriction(ValidityRestrictionType type)
|
||||
{
|
||||
for (auto it = validity_restriction.begin(); it != validity_restriction.end(); /* noop */) {
|
||||
if (get_type(*it) == type) {
|
||||
it = validity_restriction.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Certificate::add_permission(ItsAid aid)
|
||||
{
|
||||
for (auto& item : subject_attributes) {
|
||||
if (get_type(item) == SubjectAttributeType::ITS_AID_List) {
|
||||
auto& aid_list = boost::get<std::list<IntX>>(item);
|
||||
aid_list.push_back(IntX(aid));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
subject_attributes.push_back(std::list<IntX>({ IntX(aid) }));
|
||||
}
|
||||
|
||||
void Certificate::add_permission(ItsAid aid, const ByteBuffer& ssp)
|
||||
{
|
||||
ItsAidSsp permission({ IntX(aid), ssp });
|
||||
|
||||
for (auto& item : subject_attributes) {
|
||||
if (get_type(item) == SubjectAttributeType::ITS_AID_SSP_List) {
|
||||
auto& aid_ssp_list = boost::get<std::list<ItsAidSsp> >(item);
|
||||
aid_ssp_list.push_back(permission);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
subject_attributes.push_back(std::list<ItsAidSsp>({ permission }));
|
||||
}
|
||||
|
||||
} // ns v2
|
||||
} // ns security
|
||||
} // ns vanetza
|
||||
@@ -0,0 +1,171 @@
|
||||
#ifndef CERTIFICATE_HPP_LWBWIAVL
|
||||
#define CERTIFICATE_HPP_LWBWIAVL
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/ecdsa256.hpp>
|
||||
#include <vanetza/security/v2/basic_elements.hpp>
|
||||
#include <vanetza/security/v2/ecc_point.hpp>
|
||||
#include <vanetza/security/v2/serialization.hpp>
|
||||
#include <vanetza/security/v2/signature.hpp>
|
||||
#include <vanetza/security/v2/signer_info.hpp>
|
||||
#include <vanetza/security/v2/subject_attribute.hpp>
|
||||
#include <vanetza/security/v2/subject_info.hpp>
|
||||
#include <vanetza/security/v2/validity_restriction.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/variant/get.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/// described in TS 103 097 v1.2.1 (2015-06), section 6.1
|
||||
struct Certificate
|
||||
{
|
||||
SignerInfo signer_info;
|
||||
SubjectInfo subject_info;
|
||||
std::list<SubjectAttribute> subject_attributes;
|
||||
std::list<ValidityRestriction> validity_restriction;
|
||||
Signature signature;
|
||||
// certificate version is two, for conformance with the present standard
|
||||
uint8_t version() const { return 2; }
|
||||
|
||||
/**
|
||||
* Get subject attribute of a certain type (if present)
|
||||
* \param type of subject attribute
|
||||
*/
|
||||
const SubjectAttribute* get_attribute(SubjectAttributeType type) const;
|
||||
|
||||
/**
|
||||
* Get validity restriction of a certain type (if present)
|
||||
* \param type of validity restriction
|
||||
*/
|
||||
const ValidityRestriction* get_restriction(ValidityRestrictionType type) const;
|
||||
|
||||
/**
|
||||
* Remove subject attribute of a certain type (if present)
|
||||
* \param type of subject attribute
|
||||
*/
|
||||
void remove_attribute(SubjectAttributeType type);
|
||||
|
||||
/**
|
||||
* Remove validity restriction of a certain type (if present)
|
||||
* \param type of validity restriction
|
||||
*/
|
||||
void remove_restriction(ValidityRestrictionType type);
|
||||
|
||||
/**
|
||||
* Add ITS-AID to certificate's subject attributes
|
||||
* \param aid ITS-AID
|
||||
*/
|
||||
void add_permission(ItsAid aid);
|
||||
|
||||
/**
|
||||
* Add ITS-AID along with SSP to certificate's subject attributes
|
||||
* \param aid ITS-AID
|
||||
* \param ssp Service Specific Permissions
|
||||
*/
|
||||
void add_permission(ItsAid aid, const ByteBuffer& ssp);
|
||||
|
||||
/**
|
||||
* Get subject attribute by type
|
||||
* \tparam T subject attribute type
|
||||
* \return subject attribute, nullptr if not found
|
||||
*/
|
||||
template<SubjectAttributeType T>
|
||||
const subject_attribute_type<T>* get_attribute() const
|
||||
{
|
||||
using type = subject_attribute_type<T>;
|
||||
const SubjectAttribute* field = get_attribute(T);
|
||||
return boost::get<type>(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validity restriction by type
|
||||
* \tparam T validity restriction type
|
||||
* \return validity restriction, nullptr if not found
|
||||
*/
|
||||
template<ValidityRestrictionType T>
|
||||
const validity_restriction_type<T>* get_restriction() const
|
||||
{
|
||||
using type = validity_restriction_type<T>;
|
||||
const ValidityRestriction* field = get_restriction(T);
|
||||
return boost::get<type>(field);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Calculates size of an certificate object
|
||||
*
|
||||
* \param cert
|
||||
* \return number of octets needed to serialize the object
|
||||
*/
|
||||
size_t get_size(const Certificate&);
|
||||
|
||||
/**
|
||||
* \brief Serializes an object into a binary archive
|
||||
*
|
||||
* \param ar archive to serialize in
|
||||
* \param cert to serialize
|
||||
*/
|
||||
void serialize(OutputArchive&, const Certificate&);
|
||||
|
||||
/**
|
||||
* \brief Deserializes an object from a binary archive
|
||||
*
|
||||
* \param ar archive with a serialized object at the beginning
|
||||
* \param cert to deserialize
|
||||
* \return size of the deserialized object
|
||||
*/
|
||||
size_t deserialize(InputArchive&, Certificate&);
|
||||
|
||||
/**
|
||||
* \brief Serialize parts of a Certificate for signature calculation
|
||||
*
|
||||
* Uses version, signer_field, subject_info, subject_attributes (+ length),
|
||||
* validity_restriction (+ length).
|
||||
*
|
||||
* \param cert certificate to be converted
|
||||
* \return binary representation
|
||||
*/
|
||||
ByteBuffer convert_for_signing(const Certificate&);
|
||||
|
||||
/**
|
||||
* \brief Sort lists in the certificate to be in the correct order for serialization
|
||||
*
|
||||
* \param cert certificate to sort
|
||||
*/
|
||||
void sort(Certificate& certificate);
|
||||
|
||||
/**
|
||||
* \brief Extract public key from certificate
|
||||
* \param cert Certificate
|
||||
* \param backend Backend
|
||||
* \return Uncompressed public key (if available)
|
||||
*/
|
||||
boost::optional<Uncompressed> get_uncompressed_public_key(const Certificate&, Backend& backend);
|
||||
|
||||
/**
|
||||
* \brief Extract public ECDSA256 key from certificate
|
||||
* \param cert Certificate
|
||||
* \param backend Backend
|
||||
* \return public key (if available)
|
||||
*/
|
||||
boost::optional<ecdsa256::PublicKey> get_public_key(const Certificate&, Backend& backend);
|
||||
|
||||
/**
|
||||
* Calculate hash id of certificate
|
||||
* \param cert Certificate
|
||||
* \return hash
|
||||
*/
|
||||
HashedId8 calculate_hash(const Certificate&);
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* CERTIFICATE_HPP_LWBWIAVL */
|
||||
@@ -0,0 +1,110 @@
|
||||
#include <vanetza/security/v2/certificate_cache.hpp>
|
||||
#include <chrono>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
CertificateCache::CertificateCache(const Runtime& rt) : m_runtime(rt)
|
||||
{
|
||||
}
|
||||
|
||||
void CertificateCache::insert(const Certificate& certificate)
|
||||
{
|
||||
const HashedId8 id = calculate_hash(certificate);
|
||||
|
||||
// this may drop expired entries and extend some's lifetime
|
||||
std::list<Certificate> certs = lookup(id, certificate.subject_info.subject_type);
|
||||
|
||||
// TODO: implement equality comparison for Certificate
|
||||
if (certs.size()) {
|
||||
const auto binary_insert = convert_for_signing(certificate);
|
||||
for (auto& cert : certs) {
|
||||
const auto binary_found = convert_for_signing(cert);
|
||||
if (binary_insert == binary_found) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Clock::duration lifetime = Clock::duration::zero();
|
||||
if (certificate.subject_info.subject_type == SubjectType::Authorization_Ticket) {
|
||||
// section 7.1 in ETSI TS 103 097 v1.2.1
|
||||
// there must be a CAM with the authorization ticket every one second
|
||||
// we choose two seconds here to account for one missed message
|
||||
lifetime = std::chrono::seconds(2);
|
||||
} else if (certificate.subject_info.subject_type == SubjectType::Authorization_Authority) {
|
||||
// section 7.1 in ETSI TS 103 097 v1.2.1
|
||||
// chains are only sent upon request, there will probably only be a few authoritation authorities in use
|
||||
// one hour is an arbitrarily choosen cache period for now
|
||||
lifetime = std::chrono::seconds(3600);
|
||||
}
|
||||
|
||||
if (lifetime > Clock::duration::zero()) {
|
||||
CachedCertificate entry;
|
||||
entry.certificate = certificate;
|
||||
map_type::iterator stored = m_certificates.emplace(id, entry);
|
||||
heap_type::handle_type& handle = stored->second.handle;
|
||||
handle = m_expiries.push(Expiry { m_runtime.now() + lifetime, stored });
|
||||
}
|
||||
}
|
||||
|
||||
std::list<Certificate> CertificateCache::lookup(const HashedId8& id, SubjectType type)
|
||||
{
|
||||
drop_expired();
|
||||
|
||||
using iterator = std::multimap<HashedId8, CachedCertificate>::iterator;
|
||||
std::pair<iterator, iterator> range = m_certificates.equal_range(id);
|
||||
|
||||
std::list<Certificate> matches;
|
||||
for (auto item = range.first; item != range.second; ++item) {
|
||||
const Certificate& cert = item->second.certificate;
|
||||
|
||||
auto subject_type = cert.subject_info.subject_type;
|
||||
if (subject_type != type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
matches.push_back(cert);
|
||||
|
||||
// renew cached certificate
|
||||
if (subject_type == SubjectType::Authorization_Ticket) {
|
||||
refresh(item->second.handle, std::chrono::seconds(2));
|
||||
} else if (subject_type == SubjectType::Authorization_Authority) {
|
||||
refresh(item->second.handle, std::chrono::seconds(3600));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
void CertificateCache::drop_expired()
|
||||
{
|
||||
while (!m_expiries.empty() && is_expired(m_expiries.top())) {
|
||||
m_certificates.erase(m_expiries.top().certificate);
|
||||
m_expiries.pop();
|
||||
}
|
||||
}
|
||||
|
||||
bool CertificateCache::is_expired(const Expiry& expiry) const
|
||||
{
|
||||
return m_runtime.now() > expiry;
|
||||
}
|
||||
|
||||
void CertificateCache::refresh(heap_type::handle_type& handle, Clock::duration lifetime)
|
||||
{
|
||||
static_cast<Clock::time_point&>(*handle) = m_runtime.now() + lifetime;
|
||||
m_expiries.update(handle);
|
||||
}
|
||||
|
||||
CertificateCache::Expiry::Expiry(Clock::time_point expiry, map_type::iterator it) :
|
||||
Clock::time_point(expiry), certificate(it)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef VANETZA_CERTIFICATE_CACHE_HPP
|
||||
#define VANETZA_CERTIFICATE_CACHE_HPP
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <boost/heap/binomial_heap.hpp>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/**
|
||||
* CertificateCache remembers validated certificates for some time.
|
||||
* This is necessary for certificate lookup when only its digest is known.
|
||||
*/
|
||||
class CertificateCache
|
||||
{
|
||||
public:
|
||||
CertificateCache(const Runtime& rt);
|
||||
|
||||
/**
|
||||
* Puts a (validated) certificate into the cache.
|
||||
*
|
||||
* \param certificate certificate to add to the cache
|
||||
*/
|
||||
void insert(const Certificate& certificate);
|
||||
|
||||
/**
|
||||
* Lookup certificates based on the passed HashedId8.
|
||||
*
|
||||
* \param id hash identifier of the certificate
|
||||
* \param type type of certificate to lookup
|
||||
* \return all stored certificates matching the passed identifier and type
|
||||
*/
|
||||
std::list<Certificate> lookup(const HashedId8& id, SubjectType type);
|
||||
|
||||
/**
|
||||
* Number of currently stored certificates
|
||||
* \return cache size
|
||||
*/
|
||||
std::size_t size() const { return m_certificates.size(); }
|
||||
|
||||
private:
|
||||
struct CachedCertificate;
|
||||
using map_type = std::multimap<HashedId8, CachedCertificate>;
|
||||
|
||||
struct Expiry : public Clock::time_point
|
||||
{
|
||||
Expiry(Clock::time_point, map_type::iterator);
|
||||
const map_type::iterator certificate;
|
||||
};
|
||||
|
||||
using heap_type = boost::heap::binomial_heap<Expiry, boost::heap::compare<std::greater<Expiry>>>;
|
||||
|
||||
struct CachedCertificate
|
||||
{
|
||||
Certificate certificate;
|
||||
heap_type::handle_type handle;
|
||||
};
|
||||
|
||||
const Runtime& m_runtime;
|
||||
heap_type m_expiries;
|
||||
map_type m_certificates;
|
||||
|
||||
void drop_expired();
|
||||
bool is_expired(const Expiry&) const;
|
||||
void refresh(heap_type::handle_type&, Clock::duration);
|
||||
};
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* VANETZA_CERTIFICATE_CACHE_HPP */
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#ifndef A137DCCA_FFB9_4D91_8441_D559E6F17C14
|
||||
#define A137DCCA_FFB9_4D91_8441_D559E6F17C14
|
||||
|
||||
#include <vanetza/security/ecdsa256.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <list>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
class CertificateProvider
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Get own certificate to use for signing
|
||||
* \return own certificate
|
||||
*/
|
||||
virtual const Certificate& own_certificate() = 0;
|
||||
|
||||
/**
|
||||
* Get own certificate chain in root CA → AA → AT order, excluding the AT and root certificate
|
||||
* \return own certificate chain
|
||||
*/
|
||||
virtual std::list<Certificate> own_chain() = 0;
|
||||
|
||||
/**
|
||||
* Get private key associated with own certificate
|
||||
* \return private key
|
||||
*/
|
||||
virtual const ecdsa256::PrivateKey& own_private_key() = 0;
|
||||
|
||||
virtual ~CertificateProvider() = default;
|
||||
};
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* A137DCCA_FFB9_4D91_8441_D559E6F17C14 */
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
#ifndef CERTIFICATE_VALIDATOR_HPP
|
||||
#define CERTIFICATE_VALIDATOR_HPP
|
||||
|
||||
//#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/certificate_validity.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
struct Certificate;
|
||||
|
||||
class CertificateValidator
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Check validity of given certificate and consistency with parent certificates.
|
||||
* \param certificate given certificate
|
||||
* \return validity result
|
||||
*/
|
||||
virtual CertificateValidity check_certificate(const Certificate& certificate) = 0;
|
||||
|
||||
virtual ~CertificateValidator() = default;
|
||||
};
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif // CERTIFICATE_VALIDATOR_HPP
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/common/position_fix.hpp>
|
||||
#include <vanetza/security/hashed_id.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/v2/certificate_cache.hpp>
|
||||
#include <vanetza/security/v2/default_certificate_validator.hpp>
|
||||
#include <vanetza/security/v2/signature.hpp>
|
||||
#include <vanetza/security/v2/trust_store.hpp>
|
||||
#include <vanetza/security/v2/validity_restriction.hpp>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
boost::optional<StartAndEndValidity> extract_validity_time(const Certificate& certificate)
|
||||
{
|
||||
boost::optional<StartAndEndValidity> restriction;
|
||||
|
||||
for (auto& validity_restriction : certificate.validity_restriction) {
|
||||
ValidityRestrictionType type = get_type(validity_restriction);
|
||||
|
||||
if (type == ValidityRestrictionType::Time_Start_And_End) {
|
||||
// reject more than one restriction
|
||||
if (restriction) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
restriction = boost::get<StartAndEndValidity>(validity_restriction);
|
||||
|
||||
// check if certificate validity restriction timestamps are logically correct
|
||||
if (restriction->start_validity >= restriction->end_validity) {
|
||||
return boost::none;
|
||||
}
|
||||
} else if (type == ValidityRestrictionType::Time_End) {
|
||||
// must not be used, no certificate profile allows it
|
||||
return boost::none;
|
||||
} else if (type == ValidityRestrictionType::Time_Start_And_Duration) {
|
||||
// must not be used, no certificate profile allows it
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
return restriction;
|
||||
}
|
||||
|
||||
bool check_time_consistency(const Certificate& certificate, const Certificate& signer)
|
||||
{
|
||||
boost::optional<StartAndEndValidity> certificate_time = extract_validity_time(certificate);
|
||||
boost::optional<StartAndEndValidity> signer_time = extract_validity_time(signer);
|
||||
|
||||
if (!certificate_time || !signer_time) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signer_time->start_validity > certificate_time->start_validity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signer_time->end_validity < certificate_time->end_validity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::list<ItsAid> extract_application_identifiers(const Certificate& certificate)
|
||||
{
|
||||
std::list<ItsAid> aids;
|
||||
|
||||
auto certificate_type = certificate.subject_info.subject_type;
|
||||
if (certificate_type == SubjectType::Authorization_Ticket) {
|
||||
auto list = certificate.get_attribute<SubjectAttributeType::ITS_AID_SSP_List>();
|
||||
if (list) {
|
||||
for (auto& item : *list) {
|
||||
aids.push_back(item.its_aid.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto list = certificate.get_attribute<SubjectAttributeType::ITS_AID_List>();
|
||||
if (list) {
|
||||
for (auto& item : *list) {
|
||||
aids.push_back(item.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aids;
|
||||
}
|
||||
|
||||
bool check_permission_consistency(const Certificate& certificate, const Certificate& signer)
|
||||
{
|
||||
auto certificate_aids = extract_application_identifiers(certificate);
|
||||
auto signer_aids = extract_application_identifiers(signer);
|
||||
auto compare = [](ItsAid a, ItsAid b) { return a < b; };
|
||||
|
||||
certificate_aids.sort(compare);
|
||||
signer_aids.sort(compare);
|
||||
|
||||
return std::includes(signer_aids.begin(), signer_aids.end(), certificate_aids.begin(), certificate_aids.end());
|
||||
}
|
||||
|
||||
bool check_subject_assurance_consistency(const Certificate& certificate, const Certificate& signer)
|
||||
{
|
||||
auto certificate_assurance = certificate.get_attribute<SubjectAttributeType::Assurance_Level>();
|
||||
auto signer_assurance = signer.get_attribute<SubjectAttributeType::Assurance_Level>();
|
||||
|
||||
if (!certificate_assurance || !signer_assurance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// See TS 103 096-2 v1.3.1, section 5.2.7.11 + 5.3.5.17 and following
|
||||
if (certificate_assurance->assurance() > signer_assurance->assurance()) {
|
||||
return false;
|
||||
} else if (certificate_assurance->assurance() == signer_assurance->assurance()) {
|
||||
if (certificate_assurance->confidence() > signer_assurance->confidence()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_region_consistency(const Certificate& certificate, const Certificate& signer)
|
||||
{
|
||||
auto certificate_region = certificate.get_restriction<ValidityRestrictionType::Region>();
|
||||
auto signer_region = signer.get_restriction<ValidityRestrictionType::Region>();
|
||||
|
||||
if (!signer_region) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!certificate_region) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_within(*certificate_region, *signer_region);
|
||||
}
|
||||
|
||||
bool check_consistency(const Certificate& certificate, const Certificate& signer)
|
||||
{
|
||||
if (!check_time_consistency(certificate, signer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!check_permission_consistency(certificate, signer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!check_subject_assurance_consistency(certificate, signer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!check_region_consistency(certificate, signer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DefaultCertificateValidator::DefaultCertificateValidator(Backend& backend, CertificateCache& cert_cache, const TrustStore& trust_store) :
|
||||
m_crypto_backend(backend),
|
||||
m_cert_cache(cert_cache),
|
||||
m_trust_store(trust_store)
|
||||
{
|
||||
}
|
||||
|
||||
CertificateValidity DefaultCertificateValidator::check_certificate(const Certificate& certificate)
|
||||
{
|
||||
if (!extract_validity_time(certificate)) {
|
||||
return CertificateInvalidReason::Broken_Time_Period;
|
||||
}
|
||||
|
||||
if (!certificate.get_attribute<SubjectAttributeType::Assurance_Level>()) {
|
||||
return CertificateInvalidReason::Missing_Subject_Assurance;
|
||||
}
|
||||
|
||||
SubjectType subject_type = certificate.subject_info.subject_type;
|
||||
|
||||
// check if subject_name is empty if certificate is authorization ticket
|
||||
if (subject_type == SubjectType::Authorization_Ticket && 0 != certificate.subject_info.subject_name.size()) {
|
||||
return CertificateInvalidReason::Invalid_Name;
|
||||
}
|
||||
|
||||
if (get_type(certificate.signer_info) != SignerInfoType::Certificate_Digest_With_SHA256) {
|
||||
return CertificateInvalidReason::Invalid_Signer;
|
||||
}
|
||||
|
||||
HashedId8 signer_hash = boost::get<HashedId8>(certificate.signer_info);
|
||||
|
||||
// try to extract ECDSA signature
|
||||
boost::optional<EcdsaSignature> sig = extract_ecdsa_signature(certificate.signature);
|
||||
if (!sig) {
|
||||
return CertificateInvalidReason::Missing_Signature;
|
||||
}
|
||||
|
||||
// create buffer of certificate
|
||||
ByteBuffer binary_cert = convert_for_signing(certificate);
|
||||
|
||||
// authorization tickets may only be signed by authorization authorities
|
||||
if (subject_type == SubjectType::Authorization_Ticket) {
|
||||
for (auto& possible_signer : m_cert_cache.lookup(signer_hash, SubjectType::Authorization_Authority)) {
|
||||
auto verification_key = get_public_key(possible_signer, m_crypto_backend);
|
||||
if (!verification_key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_crypto_backend.verify_data(verification_key.get(), binary_cert, sig.get())) {
|
||||
if (!check_consistency(certificate, possible_signer)) {
|
||||
return CertificateInvalidReason::Inconsistent_With_Signer;
|
||||
}
|
||||
|
||||
return CertificateValidity::valid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// authorization authorities may only be signed by root CAs
|
||||
// Note: There's no clear specification about this, but there's a test for it in 5.2.7.12.4 of TS 103 096-2 V1.3.1
|
||||
if (subject_type == SubjectType::Authorization_Authority) {
|
||||
for (auto& possible_signer : m_trust_store.lookup(signer_hash)) {
|
||||
auto verification_key = get_public_key(possible_signer, m_crypto_backend);
|
||||
if (!verification_key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_crypto_backend.verify_data(verification_key.get(), binary_cert, sig.get())) {
|
||||
if (!check_consistency(certificate, possible_signer)) {
|
||||
return CertificateInvalidReason::Inconsistent_With_Signer;
|
||||
}
|
||||
|
||||
return CertificateValidity::valid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CertificateInvalidReason::Unknown_Signer;
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
#ifndef DEFAULT_CERTIFICATE_VALIDATOR_HPP_MTULFLKX
|
||||
#define DEFAULT_CERTIFICATE_VALIDATOR_HPP_MTULFLKX
|
||||
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/common/position_provider.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/v2/certificate_validator.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
// forward declaration
|
||||
class TrustStore;
|
||||
class CertificateCache;
|
||||
|
||||
/**
|
||||
* \brief The default certificate validator
|
||||
*
|
||||
* This certificate validator is reasonably secure! It just doesn't implement revocation checks for CA certificates.
|
||||
*/
|
||||
class DefaultCertificateValidator : public CertificateValidator
|
||||
{
|
||||
public:
|
||||
DefaultCertificateValidator(Backend&, CertificateCache&, const TrustStore&);
|
||||
|
||||
/**
|
||||
* \brief check certificate
|
||||
* \param certificate to verify
|
||||
* \return certificate status
|
||||
*/
|
||||
CertificateValidity check_certificate(const Certificate& certificate) override;
|
||||
|
||||
private:
|
||||
Backend& m_crypto_backend;
|
||||
CertificateCache& m_cert_cache;
|
||||
const TrustStore& m_trust_store;
|
||||
};
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* DEFAULT_CERTIFICATE_VALIDATOR_HPP_MTULFLKX */
|
||||
@@ -0,0 +1,186 @@
|
||||
#include <vanetza/security/exception.hpp>
|
||||
#include <vanetza/security/v2/ecc_point.hpp>
|
||||
#include <vanetza/security/v2/public_key.hpp>
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
#include <cassert>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
size_t get_size(const EccPoint& point)
|
||||
{
|
||||
size_t size = sizeof(EccPointType);
|
||||
struct ecc_point_visitor : public boost::static_visitor<size_t>
|
||||
{
|
||||
size_t operator()(X_Coordinate_Only coord)
|
||||
{
|
||||
return coord.x.size();
|
||||
}
|
||||
|
||||
size_t operator()(Compressed_Lsb_Y_0 coord)
|
||||
{
|
||||
return coord.x.size();
|
||||
}
|
||||
|
||||
size_t operator()(Compressed_Lsb_Y_1 coord)
|
||||
{
|
||||
return coord.x.size();
|
||||
}
|
||||
|
||||
size_t operator()(Uncompressed coord)
|
||||
{
|
||||
return coord.x.size() + coord.y.size();
|
||||
}
|
||||
};
|
||||
|
||||
ecc_point_visitor visit;
|
||||
boost::apply_visitor(visit, point);
|
||||
|
||||
size += boost::apply_visitor(visit, point);
|
||||
return size;
|
||||
}
|
||||
|
||||
EccPointType get_type(const EccPoint& point)
|
||||
{
|
||||
struct ecc_point_visitor : public boost::static_visitor<EccPointType>
|
||||
{
|
||||
EccPointType operator()(const X_Coordinate_Only&)
|
||||
{
|
||||
return EccPointType::X_Coordinate_Only;
|
||||
}
|
||||
|
||||
EccPointType operator()(const Compressed_Lsb_Y_0&)
|
||||
{
|
||||
return EccPointType::Compressed_Lsb_Y_0;
|
||||
}
|
||||
|
||||
EccPointType operator()(const Compressed_Lsb_Y_1&)
|
||||
{
|
||||
return EccPointType::Compressed_Lsb_Y_1;
|
||||
}
|
||||
|
||||
EccPointType operator()(const Uncompressed&)
|
||||
{
|
||||
return EccPointType::Uncompressed;
|
||||
}
|
||||
};
|
||||
|
||||
ecc_point_visitor visit;
|
||||
return boost::apply_visitor(visit, point);
|
||||
}
|
||||
|
||||
void serialize(OutputArchive& ar, const EccPoint& point, PublicKeyAlgorithm algo)
|
||||
{
|
||||
struct ecc_point_visitor : public boost::static_visitor<>
|
||||
{
|
||||
ecc_point_visitor(OutputArchive& ar, PublicKeyAlgorithm algo) :
|
||||
m_archive(ar), m_algo(algo)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(X_Coordinate_Only coord)
|
||||
{
|
||||
assert(coord.x.size() == field_size(m_algo));
|
||||
for (auto byte : coord.x) {
|
||||
m_archive << byte;
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(Compressed_Lsb_Y_0 coord)
|
||||
{
|
||||
assert(coord.x.size() == field_size(m_algo));
|
||||
for (auto byte : coord.x) {
|
||||
m_archive << byte;
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(Compressed_Lsb_Y_1 coord)
|
||||
{
|
||||
assert(coord.x.size() == field_size(m_algo));
|
||||
for (auto byte : coord.x) {
|
||||
m_archive << byte;
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(Uncompressed coord)
|
||||
{
|
||||
assert(coord.x.size() == field_size(m_algo));
|
||||
assert(coord.y.size() == field_size(m_algo));
|
||||
for (auto byte : coord.x) {
|
||||
m_archive << byte;
|
||||
}
|
||||
for (auto byte : coord.y) {
|
||||
m_archive << byte;
|
||||
}
|
||||
}
|
||||
|
||||
OutputArchive& m_archive;
|
||||
PublicKeyAlgorithm m_algo;
|
||||
};
|
||||
|
||||
EccPointType type = get_type(point);
|
||||
serialize(ar, type);
|
||||
ecc_point_visitor visit(ar, algo);
|
||||
boost::apply_visitor(visit, point);
|
||||
}
|
||||
|
||||
void deserialize(InputArchive& ar, EccPoint& point, PublicKeyAlgorithm algo)
|
||||
{
|
||||
size_t size = field_size(algo);
|
||||
uint8_t elem;
|
||||
EccPointType type;
|
||||
deserialize(ar, type);
|
||||
switch (type) {
|
||||
case EccPointType::X_Coordinate_Only: {
|
||||
X_Coordinate_Only coord;
|
||||
for (size_t c = 0; c < size; c++) {
|
||||
ar >> elem;
|
||||
coord.x.push_back(elem);
|
||||
}
|
||||
point = coord;
|
||||
break;
|
||||
}
|
||||
case EccPointType::Compressed_Lsb_Y_0: {
|
||||
Compressed_Lsb_Y_0 coord;
|
||||
for (size_t c = 0; c < size; c++) {
|
||||
ar >> elem;
|
||||
coord.x.push_back(elem);
|
||||
}
|
||||
point = coord;
|
||||
break;
|
||||
}
|
||||
case EccPointType::Compressed_Lsb_Y_1: {
|
||||
Compressed_Lsb_Y_1 coord;
|
||||
for (size_t c = 0; c < size; c++) {
|
||||
ar >> elem;
|
||||
coord.x.push_back(elem);
|
||||
}
|
||||
point = coord;
|
||||
break;
|
||||
}
|
||||
case EccPointType::Uncompressed: {
|
||||
Uncompressed coord;
|
||||
for (size_t c = 0; c < size; c++) {
|
||||
ar >> elem;
|
||||
coord.x.push_back(elem);
|
||||
}
|
||||
for (size_t c = 0; c < size; c++) {
|
||||
ar >> elem;
|
||||
coord.y.push_back(elem);
|
||||
}
|
||||
point = coord;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw deserialization_error("Unknown EccPointType");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef ECC_POINT_HPP_XCESTUEB
|
||||
#define ECC_POINT_HPP_XCESTUEB
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/security/ecc_point.hpp>
|
||||
#include <vanetza/security/v2/serialization.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/// forward declaration, see public_key.hpp
|
||||
enum class PublicKeyAlgorithm: uint8_t;
|
||||
|
||||
/// EccPointType specified in TS 103 097 v1.2.1 in section 4.2.6
|
||||
enum class EccPointType : uint8_t
|
||||
{
|
||||
X_Coordinate_Only = 0,
|
||||
Compressed_Lsb_Y_0 = 2,
|
||||
Compressed_Lsb_Y_1 = 3,
|
||||
Uncompressed = 4
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Determines EccPointType to a given EccPoint
|
||||
* \param ecc_point
|
||||
* \return type
|
||||
*/
|
||||
EccPointType get_type(const EccPoint&);
|
||||
|
||||
/**
|
||||
* \brief Serializes an EccPoint into a binary archive
|
||||
* \param ar to serialize in
|
||||
* \param ecc_point to serialize
|
||||
* \param pka Public key algorithm used for EccPoint
|
||||
*/
|
||||
void serialize(OutputArchive&, const EccPoint&, PublicKeyAlgorithm);
|
||||
|
||||
/**
|
||||
* \brief Deserializes an EccPoint from a binary archive
|
||||
* \param ar with a serialized EccPoint at the beginning,
|
||||
* \param ecc_point to deserialize
|
||||
* \param pka to get field size of the encoded coordinates
|
||||
*/
|
||||
void deserialize(InputArchive&, EccPoint&, PublicKeyAlgorithm);
|
||||
|
||||
/**
|
||||
* \brief Calculates size of an EccPoint
|
||||
* \param ecc_point
|
||||
* \return size_t containing the number of octets needed to serialize the EccPoint
|
||||
*/
|
||||
size_t get_size(const EccPoint&);
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* ECC_POINT_HPP_XCESTUEB */
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
#include <vanetza/security/exception.hpp>
|
||||
#include <vanetza/security/v2/encryption_parameter.hpp>
|
||||
#include <vanetza/security/v2/public_key.hpp>
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
SymmetricAlgorithm get_type(const EncryptionParameter& param)
|
||||
{
|
||||
struct Encryption_visitor : public boost::static_visitor<SymmetricAlgorithm>
|
||||
{
|
||||
SymmetricAlgorithm operator()(const Nonce&)
|
||||
{
|
||||
return SymmetricAlgorithm::AES128_CCM;
|
||||
}
|
||||
};
|
||||
|
||||
Encryption_visitor visit;
|
||||
return boost::apply_visitor(visit, param);
|
||||
}
|
||||
|
||||
void serialize(OutputArchive& ar, const EncryptionParameter& param)
|
||||
{
|
||||
struct Encryption_visitor : public boost::static_visitor<>
|
||||
{
|
||||
Encryption_visitor(OutputArchive& ar) :
|
||||
m_archive(ar)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(const Nonce& nonce)
|
||||
{
|
||||
for (auto& byte : nonce) {
|
||||
m_archive << byte;
|
||||
}
|
||||
}
|
||||
|
||||
OutputArchive& m_archive;
|
||||
};
|
||||
|
||||
SymmetricAlgorithm algo = get_type(param);
|
||||
serialize(ar, algo);
|
||||
Encryption_visitor visit(ar);
|
||||
boost::apply_visitor(visit, param);
|
||||
}
|
||||
|
||||
size_t get_size(const EncryptionParameter& param)
|
||||
{
|
||||
size_t size = sizeof(SymmetricAlgorithm);
|
||||
struct Encryption_visitor : public boost::static_visitor<size_t>
|
||||
{
|
||||
size_t operator()(const Nonce& nonce)
|
||||
{
|
||||
return nonce.size();
|
||||
}
|
||||
};
|
||||
|
||||
Encryption_visitor visit;
|
||||
size += boost::apply_visitor(visit, param);
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t deserialize(InputArchive& ar, EncryptionParameter& param)
|
||||
{
|
||||
SymmetricAlgorithm algo;
|
||||
deserialize(ar, algo);
|
||||
switch (algo) {
|
||||
case SymmetricAlgorithm::AES128_CCM: {
|
||||
Nonce nonce;
|
||||
for (size_t s = 0; s < nonce.size(); s++) {
|
||||
ar >> nonce[s];
|
||||
}
|
||||
param = nonce;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw deserialization_error("Unknown Symmetric Algorithm");
|
||||
break;
|
||||
}
|
||||
return get_size(param);
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
#ifndef ENCRYPTION_PARAMETER_HPP_EIAWNAWY
|
||||
#define ENCRYPTION_PARAMETER_HPP_EIAWNAWY
|
||||
|
||||
#include <vanetza/security/v2/serialization.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/// forward declaration, see public_key.hpp
|
||||
enum class SymmetricAlgorithm : uint8_t;
|
||||
|
||||
/// Nonce specified in TS 103 097 v1.2.1, section 4.2.7
|
||||
using Nonce = std::array<uint8_t, 12>;
|
||||
|
||||
/// EncryptionParameter specified in TS 103 097 v1.2.1, section 4.2.7
|
||||
using EncryptionParameter = boost::variant<Nonce>;
|
||||
|
||||
/**
|
||||
* \brief Determines SymmetricAlgorithm for an EncryptionParameter
|
||||
* \param param
|
||||
* \return SymmetricAlgorithm
|
||||
*/
|
||||
SymmetricAlgorithm get_type(const EncryptionParameter&);
|
||||
|
||||
/**
|
||||
* \brief Serializes an EncryptionParameter into a binary archive
|
||||
* \param ar to serialize in
|
||||
* \param param to serialize
|
||||
*/
|
||||
void serialize(OutputArchive&, const EncryptionParameter&);
|
||||
|
||||
/**
|
||||
* \brief Calculates size of an EncryptionParameter
|
||||
* \param param
|
||||
* \return number of octets needed to serialize the EncryptionParameter
|
||||
*/
|
||||
size_t get_size(const EncryptionParameter&);
|
||||
|
||||
/**
|
||||
* \brief Deserializes an EncryptionParameter from a binary archive
|
||||
* \param ar Input expected to start with an EncryptionParameter
|
||||
* \param enc Deserialized encryption parameter
|
||||
* \return size of deserialized EncryptionParameter
|
||||
*/
|
||||
size_t deserialize(InputArchive&, EncryptionParameter&);
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetzta
|
||||
|
||||
#endif /* ENCRYPTION_PARAMETER_HPP_EIAWNAWY */
|
||||
@@ -0,0 +1,290 @@
|
||||
#include <vanetza/security/exception.hpp>
|
||||
#include <vanetza/security/v2/header_field.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
HeaderFieldType get_type(const HeaderField& field)
|
||||
{
|
||||
struct HeaderFieldVisitor : public boost::static_visitor<HeaderFieldType>
|
||||
{
|
||||
HeaderFieldType operator()(const Time64&)
|
||||
{
|
||||
return HeaderFieldType::Generation_Time;
|
||||
}
|
||||
HeaderFieldType operator()(const Time64WithStandardDeviation&)
|
||||
{
|
||||
return HeaderFieldType::Generation_Time_Confidence;
|
||||
}
|
||||
HeaderFieldType operator()(const Time32&)
|
||||
{
|
||||
return HeaderFieldType::Expiration;
|
||||
}
|
||||
HeaderFieldType operator()(const ThreeDLocation&)
|
||||
{
|
||||
return HeaderFieldType::Generation_Location;
|
||||
}
|
||||
HeaderFieldType operator()(const std::list<HashedId3>&)
|
||||
{
|
||||
return HeaderFieldType::Request_Unrecognized_Certificate;
|
||||
}
|
||||
HeaderFieldType operator()(const IntX&)
|
||||
{
|
||||
return HeaderFieldType::Its_Aid;
|
||||
}
|
||||
HeaderFieldType operator()(const SignerInfo&)
|
||||
{
|
||||
return HeaderFieldType::Signer_Info;
|
||||
}
|
||||
HeaderFieldType operator()(const std::list<RecipientInfo>&)
|
||||
{
|
||||
return HeaderFieldType::Recipient_Info;
|
||||
}
|
||||
HeaderFieldType operator()(const EncryptionParameter&)
|
||||
{
|
||||
return HeaderFieldType::Encryption_Parameters;
|
||||
}
|
||||
};
|
||||
|
||||
HeaderFieldVisitor visit;
|
||||
return boost::apply_visitor(visit, field);
|
||||
}
|
||||
|
||||
size_t get_size(const HeaderField& field)
|
||||
{
|
||||
size_t size = sizeof(HeaderFieldType);
|
||||
struct HeaderFieldVisitor : public boost::static_visitor<>
|
||||
{
|
||||
void operator()(const Time64&)
|
||||
{
|
||||
m_size = sizeof(Time64);
|
||||
}
|
||||
void operator()(const Time64WithStandardDeviation& time)
|
||||
{
|
||||
m_size = sizeof(time.time64);
|
||||
m_size += sizeof(time.log_std_dev);
|
||||
}
|
||||
void operator()(const Time32&)
|
||||
{
|
||||
m_size = sizeof(Time32);
|
||||
}
|
||||
void operator()(const ThreeDLocation& loc)
|
||||
{
|
||||
m_size = get_size(loc);
|
||||
}
|
||||
void operator()(const std::list<HashedId3>& list)
|
||||
{
|
||||
m_size = 0;
|
||||
for (auto& elem : list) {
|
||||
m_size += elem.size();
|
||||
}
|
||||
m_size += length_coding_size(m_size);
|
||||
}
|
||||
void operator()(const IntX& itsAid)
|
||||
{
|
||||
m_size = get_size(itsAid);
|
||||
}
|
||||
void operator()(const SignerInfo& info)
|
||||
{
|
||||
m_size = get_size(info);
|
||||
}
|
||||
void operator()(const std::list<RecipientInfo>& list)
|
||||
{
|
||||
m_size = get_size(list);
|
||||
m_size += length_coding_size(m_size);
|
||||
}
|
||||
void operator()(const EncryptionParameter& enc)
|
||||
{
|
||||
m_size = get_size(enc);
|
||||
}
|
||||
size_t m_size;
|
||||
};
|
||||
|
||||
HeaderFieldVisitor visit;
|
||||
boost::apply_visitor(visit, field);
|
||||
size += visit.m_size;
|
||||
return size;
|
||||
}
|
||||
|
||||
void serialize(OutputArchive& ar, const HeaderField& field)
|
||||
{
|
||||
struct HeaderFieldVisitor : public boost::static_visitor<>
|
||||
{
|
||||
HeaderFieldVisitor(OutputArchive& ar) :
|
||||
m_archive(ar)
|
||||
{
|
||||
}
|
||||
void operator()(const Time64& time)
|
||||
{
|
||||
serialize(m_archive, host_cast(time));
|
||||
}
|
||||
void operator()(const Time64WithStandardDeviation& time)
|
||||
{
|
||||
serialize(m_archive, host_cast(time.time64));
|
||||
serialize(m_archive, host_cast(time.log_std_dev));
|
||||
}
|
||||
void operator()(const Time32& time)
|
||||
{
|
||||
serialize(m_archive, host_cast(time));
|
||||
}
|
||||
void operator()(const ThreeDLocation& loc)
|
||||
{
|
||||
serialize(m_archive, loc);
|
||||
}
|
||||
void operator()(const std::list<HashedId3>& list)
|
||||
{
|
||||
size_t size = 0;
|
||||
for (auto& elem : list) {
|
||||
size += elem.size();
|
||||
}
|
||||
serialize_length(m_archive, size);
|
||||
for (auto& elem : list) {
|
||||
m_archive << elem[0];
|
||||
m_archive << elem[1];
|
||||
m_archive << elem[2];
|
||||
}
|
||||
}
|
||||
void operator()(const IntX& itsAid)
|
||||
{
|
||||
serialize(m_archive, itsAid);
|
||||
}
|
||||
void operator()(const SignerInfo& info)
|
||||
{
|
||||
serialize(m_archive, info);
|
||||
}
|
||||
void operator()(const std::list<RecipientInfo>& list)
|
||||
{
|
||||
// TODO: only works until further symmetric algorithms are introduced
|
||||
serialize(m_archive, list, SymmetricAlgorithm::AES128_CCM);
|
||||
}
|
||||
void operator()(const EncryptionParameter& param)
|
||||
{
|
||||
serialize(m_archive, param);
|
||||
}
|
||||
|
||||
OutputArchive& m_archive;
|
||||
};
|
||||
HeaderFieldType type = get_type(field);
|
||||
serialize(ar, type);
|
||||
HeaderFieldVisitor visit(ar);
|
||||
boost::apply_visitor(visit, field);
|
||||
}
|
||||
|
||||
std::size_t deserialize(InputArchive& ar, std::list<HeaderField>& list)
|
||||
{
|
||||
static const std::size_t size_limit = 1024;
|
||||
const std::size_t size = trim_size(deserialize_length(ar));
|
||||
if (size > size_limit) {
|
||||
ar.fail(InputArchive::ErrorCode::ExcessiveLength);
|
||||
}
|
||||
|
||||
std::size_t read = 0;
|
||||
boost::optional<SymmetricAlgorithm> sym_algo;
|
||||
while (read < size && ar.is_good()) {
|
||||
HeaderField field;
|
||||
HeaderFieldType type;
|
||||
deserialize(ar, type);
|
||||
read += sizeof(HeaderFieldType);
|
||||
switch (type) {
|
||||
case HeaderFieldType::Generation_Time: {
|
||||
Time64 time;
|
||||
deserialize(ar, time);
|
||||
field = time;
|
||||
list.push_back(field);
|
||||
read += sizeof(Time64);
|
||||
break;
|
||||
}
|
||||
case HeaderFieldType::Generation_Time_Confidence: {
|
||||
Time64WithStandardDeviation time;
|
||||
deserialize(ar, time.time64);
|
||||
deserialize(ar, time.log_std_dev);
|
||||
field = time;
|
||||
list.push_back(field);
|
||||
read += sizeof(Time64);
|
||||
read += sizeof(uint8_t);
|
||||
break;
|
||||
}
|
||||
case HeaderFieldType::Expiration: {
|
||||
Time32 time;
|
||||
deserialize(ar, time);
|
||||
field = time;
|
||||
list.push_back(field);
|
||||
read += sizeof(Time32);
|
||||
break;
|
||||
}
|
||||
case HeaderFieldType::Generation_Location: {
|
||||
ThreeDLocation loc;
|
||||
read += deserialize(ar, loc);
|
||||
field = loc;
|
||||
list.push_back(field);
|
||||
break;
|
||||
}
|
||||
case HeaderFieldType::Request_Unrecognized_Certificate: {
|
||||
const std::size_t tmp_size = trim_size(deserialize_length(ar));
|
||||
read += tmp_size;
|
||||
std::list<HashedId3> hashedList;
|
||||
for (std::size_t c = 0; c < tmp_size; c += 3) {
|
||||
HashedId3 id;
|
||||
ar >> id[0];
|
||||
ar >> id[1];
|
||||
ar >> id[2];
|
||||
hashedList.push_back(id);
|
||||
}
|
||||
field = hashedList;
|
||||
read += length_coding_size(tmp_size);
|
||||
list.push_back(field);
|
||||
break;
|
||||
}
|
||||
case HeaderFieldType::Its_Aid: {
|
||||
IntX its_aid;
|
||||
read += deserialize(ar, its_aid);
|
||||
field = its_aid;
|
||||
list.push_back(field);
|
||||
break;
|
||||
}
|
||||
case HeaderFieldType::Signer_Info: {
|
||||
SignerInfo info;
|
||||
read += deserialize(ar, info);
|
||||
field = info;
|
||||
list.push_back(field);
|
||||
break;
|
||||
}
|
||||
case HeaderFieldType::Recipient_Info: {
|
||||
std::list<RecipientInfo> recipientList;
|
||||
if (sym_algo) {
|
||||
const size_t tmp_size = deserialize(ar, recipientList, sym_algo.get());
|
||||
read += tmp_size;
|
||||
read += length_coding_size(tmp_size);
|
||||
field = recipientList;
|
||||
list.push_back(field);
|
||||
} else {
|
||||
throw deserialization_error("HeaderFields: RecipientInfo read before EncryptionParameters: SymmetricAlgorithm still unknown");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HeaderFieldType::Encryption_Parameters: {
|
||||
EncryptionParameter param;
|
||||
read += deserialize(ar, param);
|
||||
field = param;
|
||||
sym_algo = get_type(param);
|
||||
list.push_back(field);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw deserialization_error("Unknown HeaderFieldType");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,144 @@
|
||||
#ifndef HEADER_FIELD_HPP_IHIAKD4K
|
||||
#define HEADER_FIELD_HPP_IHIAKD4K
|
||||
|
||||
#include <vanetza/security/v2/basic_elements.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/v2/encryption_parameter.hpp>
|
||||
#include <vanetza/security/v2/int_x.hpp>
|
||||
#include <vanetza/security/v2/recipient_info.hpp>
|
||||
#include <vanetza/security/v2/region.hpp>
|
||||
#include <vanetza/security/v2/signer_info.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
#include <list>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/// HeaderFieldType specified in TS 103 097 v1.2.1, section 5.5
|
||||
enum class HeaderFieldType : uint8_t
|
||||
{
|
||||
Generation_Time = 0, // Time64
|
||||
Generation_Time_Confidence = 1, // Time64WithStandardDeviation
|
||||
Expiration = 2, // Time32
|
||||
Generation_Location = 3, // TreeDLocation
|
||||
Request_Unrecognized_Certificate = 4, // std::list<HashedId3>
|
||||
Its_Aid = 5, // IntX
|
||||
Signer_Info = 128, // SignerInfo
|
||||
Encryption_Parameters = 129, // EncryptionParameters
|
||||
Recipient_Info = 130, // std::list<RecipientInfo>
|
||||
};
|
||||
|
||||
/// HeaderField specified in TS 103 097 v1.2.1, section 5.4
|
||||
using HeaderField = boost::variant<
|
||||
Time64,
|
||||
Time64WithStandardDeviation,
|
||||
Time32,
|
||||
ThreeDLocation,
|
||||
std::list<HashedId3>,
|
||||
IntX,
|
||||
SignerInfo,
|
||||
EncryptionParameter,
|
||||
std::list<RecipientInfo>
|
||||
>;
|
||||
|
||||
/**
|
||||
* \brief Determines HeaderFieldType to a given HeaderField
|
||||
* \param field
|
||||
* \return type
|
||||
*/
|
||||
HeaderFieldType get_type(const HeaderField& field);
|
||||
|
||||
/**
|
||||
* \brief Calculates size of a HeaderField
|
||||
* \param field
|
||||
* \return number of octets needed to serialize the HeaderField
|
||||
*/
|
||||
std::size_t get_size(const HeaderField& field);
|
||||
|
||||
/**
|
||||
* \brief Serializes a HeaderField into a binary archive
|
||||
* \note Serialization of HeaderField lists is provided by template
|
||||
* \param ar to serialize in
|
||||
* \param field to serialize
|
||||
*/
|
||||
void serialize(OutputArchive& ar, const HeaderField& field);
|
||||
|
||||
/**
|
||||
* \brief Deserializes a list of HeaderFields from a binary archive
|
||||
* \param ar with a serialized list of HeaderFields at the beginning
|
||||
* \param list of HeaderFields to deserialize
|
||||
* \return size of the deserialized list
|
||||
*/
|
||||
size_t deserialize(InputArchive& ar, std::list<HeaderField>& list);
|
||||
|
||||
/**
|
||||
* \brief resolve type for matching HeaderFieldType
|
||||
*
|
||||
* This is kind of the reverse function of get_type(const HeaderField&)
|
||||
*/
|
||||
template<HeaderFieldType>
|
||||
struct header_field_type;
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Generation_Time>
|
||||
{
|
||||
using type = Time64;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Generation_Time_Confidence>
|
||||
{
|
||||
using type = Time64WithStandardDeviation;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Expiration>
|
||||
{
|
||||
using type = Time32;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Generation_Location>
|
||||
{
|
||||
using type = ThreeDLocation;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Request_Unrecognized_Certificate>
|
||||
{
|
||||
using type = std::list<HashedId3>;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Its_Aid>
|
||||
{
|
||||
using type = IntX;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Signer_Info>
|
||||
{
|
||||
using type = SignerInfo;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Encryption_Parameters>
|
||||
{
|
||||
using type = EncryptionParameter;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct header_field_type<HeaderFieldType::Recipient_Info>
|
||||
{
|
||||
using type = std::list<RecipientInfo>;
|
||||
};
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* HEADER_FIELD_HPP_IHIAKD4K */
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <vanetza/security/v2/int_x.hpp>
|
||||
#include <vanetza/security/v2/length_coding.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
void IntX::set(integer_type x)
|
||||
{
|
||||
m_value = x;
|
||||
}
|
||||
|
||||
ByteBuffer IntX::encode() const
|
||||
{
|
||||
return encode_length(m_value);
|
||||
}
|
||||
|
||||
boost::optional<IntX> IntX::decode(const ByteBuffer& buffer)
|
||||
{
|
||||
std::tuple<ByteBuffer::const_iterator, std::uintmax_t> decoded = decode_length(buffer);
|
||||
if (std::get<0>(decoded) != buffer.begin()) {
|
||||
IntX result;
|
||||
result.set(std::get<1>(decoded));
|
||||
return result;
|
||||
}
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
size_t get_size(IntX intx)
|
||||
{
|
||||
return length_coding_size(intx.get());
|
||||
}
|
||||
|
||||
void serialize(OutputArchive& ar, const IntX& intx)
|
||||
{
|
||||
serialize_length(ar, intx.get());
|
||||
}
|
||||
|
||||
size_t deserialize(InputArchive& ar, IntX& intx)
|
||||
{
|
||||
const auto size = deserialize_length(ar);
|
||||
intx.set(size);
|
||||
return get_size(intx);
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef INT_X_HPP_RW3TJBBI
|
||||
#define INT_X_HPP_RW3TJBBI
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <vanetza/security/v2/serialization.hpp>
|
||||
#include <boost/operators.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/// IntX specified in TS 103 097 v1.2.1, section 4.2.1
|
||||
class IntX :
|
||||
public boost::equality_comparable<IntX>,
|
||||
public boost::equality_comparable<IntX, std::uintmax_t>
|
||||
{
|
||||
public:
|
||||
using integer_type = std::uintmax_t;
|
||||
constexpr IntX() : m_value(0) {}
|
||||
constexpr explicit IntX(integer_type x) : m_value(x) {}
|
||||
|
||||
void set(integer_type x);
|
||||
constexpr integer_type get() const { return m_value; }
|
||||
|
||||
constexpr bool operator==(const IntX& other) const
|
||||
{
|
||||
return m_value == other.m_value;
|
||||
}
|
||||
|
||||
constexpr bool operator==(integer_type other) const
|
||||
{
|
||||
return m_value == other;
|
||||
}
|
||||
|
||||
ByteBuffer encode() const;
|
||||
static boost::optional<IntX> decode(const ByteBuffer&);
|
||||
|
||||
private:
|
||||
integer_type m_value;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Serializes an IntX into a binary archive
|
||||
* \param ar to serialize in
|
||||
* \param intx to serialize
|
||||
*/
|
||||
void serialize(OutputArchive&, const IntX&);
|
||||
|
||||
/**
|
||||
* \brief Deserializes an IntX from a binary archive
|
||||
* \param ar with a serialized IntX at the beginning
|
||||
* \param intx to deserialize
|
||||
* \return size of the deserialized IntX
|
||||
*/
|
||||
size_t deserialize(InputArchive&, IntX&);
|
||||
|
||||
/**
|
||||
* \brief Calculates size of an IntX
|
||||
* \param intx
|
||||
* \return number of octets needed to serialize the IntX
|
||||
*/
|
||||
size_t get_size(IntX);
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* INT_X_HPP_RW3TJBBI */
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <vanetza/security/v2/length_coding.hpp>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
std::size_t count_leading_ones(uint8_t v)
|
||||
{
|
||||
std::size_t count = 0;
|
||||
while ((v & 0x80) != 0) {
|
||||
v <<= 1;
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::size_t length_coding_size(std::uintmax_t length) {
|
||||
std::size_t size = 1;
|
||||
while ((length & ~0x7f) != 0) {
|
||||
// prefix enlongates by one additional leading "1" per shift
|
||||
length >>= 7; // shift by 7
|
||||
++size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
ByteBuffer encode_length(std::uintmax_t length)
|
||||
{
|
||||
static_assert(sizeof(std::uintmax_t) <= 8, "size of length type exceeds implementation capabilities");
|
||||
std::list<uint8_t> length_info;
|
||||
|
||||
while (length != 0) {
|
||||
length_info.push_front(static_cast<uint8_t>(length));
|
||||
length >>= 8;
|
||||
}
|
||||
|
||||
unsigned prefix_length = length_info.size();
|
||||
if (prefix_length == 0) {
|
||||
// Zero-size encoding
|
||||
length_info.push_back(0x00);
|
||||
}
|
||||
else {
|
||||
assert(prefix_length <= 8);
|
||||
uint8_t prefix_mask = ~((1 << (8 - prefix_length)) - 1);
|
||||
if ((length_info.front() & ~prefix_mask) != length_info.front()) {
|
||||
// additional byte needed for prefix
|
||||
length_info.push_front(prefix_mask);
|
||||
}
|
||||
else {
|
||||
// enough free bits available for prefix
|
||||
length_info.front() |= (prefix_mask << 1);
|
||||
}
|
||||
// Huge lengths have all bits set in leading prefix bytes
|
||||
length_info.insert(length_info.begin(), prefix_length / 8, 0xff);
|
||||
}
|
||||
|
||||
return ByteBuffer(length_info.begin(), length_info.end());
|
||||
}
|
||||
|
||||
std::tuple<ByteBuffer::const_iterator, std::uintmax_t> decode_length(const ByteBuffer& buffer)
|
||||
{
|
||||
if (!buffer.empty()) {
|
||||
std::size_t additional_prefix = count_leading_ones(buffer.front());
|
||||
|
||||
if (additional_prefix >= sizeof(std::uintmax_t)) {
|
||||
// encoded length is wider than uintmax_t, we cannot represent this number
|
||||
return std::make_tuple(buffer.begin(), 0);
|
||||
} else if (buffer.size() > additional_prefix) {
|
||||
uint8_t prefix_mask = (1 << (8 - additional_prefix)) - 1;
|
||||
std::uintmax_t length = buffer.front() & prefix_mask;
|
||||
for (std::size_t i = 1; i <= additional_prefix; ++i) {
|
||||
length <<= 8;
|
||||
length |= buffer[i];
|
||||
}
|
||||
|
||||
auto start = buffer.begin();
|
||||
std::advance(start, additional_prefix + 1);
|
||||
return std::make_tuple(start, length);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple(buffer.end(), 0);
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanextza
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef LENGTH_CODING_HPP_UQ1OIDUN
|
||||
#define LENGTH_CODING_HPP_UQ1OIDUN
|
||||
|
||||
#include <vanetza/common/byte_buffer.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/**
|
||||
* Calculate length coding for variable length fields
|
||||
* \param length Data field length in bytes, e.g. size of a buffer
|
||||
* \return byte buffer containing encoded length, prepend to data
|
||||
*/
|
||||
ByteBuffer encode_length(std::uintmax_t length);
|
||||
|
||||
/**
|
||||
* Extract length information
|
||||
* \param buffer Buffer with input data, shall start with first byte of encoded length
|
||||
* \return iterator pointing at start of payload buffer (begin indicates error) and its length
|
||||
*/
|
||||
std::tuple<ByteBuffer::const_iterator, std::uintmax_t> decode_length(const ByteBuffer& buffer);
|
||||
|
||||
/**
|
||||
* Count number of leading one bits
|
||||
* \param a byte
|
||||
* \return number of leadings ones in given byte
|
||||
*/
|
||||
std::size_t count_leading_ones(std::uint8_t);
|
||||
|
||||
/**
|
||||
* Determines the number of bytes, needed to store a given size
|
||||
* \param size
|
||||
* \return number of bytes needed to store length
|
||||
*/
|
||||
std::size_t length_coding_size(std::uintmax_t);
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* LENGTH_CODING_HPP_UQ1OIDUN */
|
||||
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
//#include <vanetza/security/v2/basic_elements.hpp>
|
||||
//#include <vanetza/security/v2/ecc_point.hpp>
|
||||
#include <vanetza/security/v2/naive_certificate_provider.hpp>
|
||||
//#include <vanetza/security/v2/payload.hpp>
|
||||
//#include <vanetza/security/v2/secured_message.hpp>
|
||||
//#include <vanetza/security/v2/signature.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
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()
|
||||
{
|
||||
// renew certificate if necessary
|
||||
for (auto& validity_restriction : m_own_certificate.validity_restriction) {
|
||||
auto start_and_end = boost::get<StartAndEndValidity>(&validity_restriction);
|
||||
auto renewal_deadline = convert_time32(m_runtime.now() + std::chrono::hours(1));
|
||||
if (start_and_end && start_and_end->end_validity < renewal_deadline) {
|
||||
m_own_certificate = generate_authorization_ticket();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return m_own_certificate;
|
||||
}
|
||||
|
||||
std::list<Certificate> NaiveCertificateProvider::own_chain()
|
||||
{
|
||||
static const std::list<Certificate> chain = { aa_certificate() };
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
const ecdsa256::PrivateKey& NaiveCertificateProvider::own_private_key()
|
||||
{
|
||||
return m_own_key_pair.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;
|
||||
|
||||
// section 6.1 in TS 103 097 v1.2.1
|
||||
certificate.signer_info = calculate_hash(aa_certificate());
|
||||
|
||||
// section 6.3 in TS 103 097 v1.2.1
|
||||
certificate.subject_info.subject_type = SubjectType::Authorization_Ticket;
|
||||
// section 7.4.2 in TS 103 097 v1.2.1, subject_name implicit empty
|
||||
|
||||
// set assurance level
|
||||
certificate.subject_attributes.push_back(SubjectAssurance(0x00));
|
||||
|
||||
certificate.add_permission(aid::CA, ByteBuffer({ 1, 0, 0 }));
|
||||
certificate.add_permission(aid::DEN, ByteBuffer({ 1, 0xff, 0xff, 0xff}));
|
||||
certificate.add_permission(aid::GN_MGMT, ByteBuffer({})); // required for beacons
|
||||
certificate.add_permission(aid::IPV6_ROUTING, ByteBuffer({})); // required for routing tests
|
||||
|
||||
// section 7.4.1 in TS 103 097 v1.2.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());
|
||||
EccPoint ecc_point = coordinates;
|
||||
ecdsa_nistp256_with_sha256 ecdsa;
|
||||
ecdsa.public_key = ecc_point;
|
||||
VerificationKey verification_key;
|
||||
verification_key.key = ecdsa;
|
||||
certificate.subject_attributes.push_back(verification_key);
|
||||
|
||||
// section 6.7 in TS 103 097 v1.2.1
|
||||
// set validity restriction
|
||||
StartAndEndValidity start_and_end;
|
||||
start_and_end.start_validity = convert_time32(m_runtime.now() - std::chrono::hours(1));
|
||||
start_and_end.end_validity = convert_time32(m_runtime.now() + std::chrono::hours(23));
|
||||
certificate.validity_restriction.push_back(start_and_end);
|
||||
|
||||
sign_authorization_ticket(certificate);
|
||||
|
||||
return certificate;
|
||||
}
|
||||
|
||||
void NaiveCertificateProvider::sign_authorization_ticket(Certificate& certificate)
|
||||
{
|
||||
sort(certificate);
|
||||
|
||||
ByteBuffer data_buffer = convert_for_signing(certificate);
|
||||
certificate.signature = m_crypto_backend->sign_data(aa_key_pair().private_key, data_buffer);
|
||||
}
|
||||
|
||||
Certificate NaiveCertificateProvider::generate_aa_certificate(const std::string& subject_name)
|
||||
{
|
||||
// create certificate
|
||||
Certificate certificate;
|
||||
|
||||
// section 6.1 in TS 103 097 v1.2.1
|
||||
certificate.signer_info = calculate_hash(root_certificate());
|
||||
|
||||
// section 6.3 in TS 103 097 v1.2.1
|
||||
certificate.subject_info.subject_type = SubjectType::Authorization_Authority;
|
||||
|
||||
// section 7.4.2 in TS 103 097 v1.2.1
|
||||
std::vector<unsigned char> subject(subject_name.begin(), subject_name.end());
|
||||
certificate.subject_info.subject_name = subject;
|
||||
|
||||
// section 6.6 in TS 103 097 v1.2.1 - levels currently undefined
|
||||
certificate.subject_attributes.push_back(SubjectAssurance(0x00));
|
||||
|
||||
certificate.add_permission(aid::CA);
|
||||
certificate.add_permission(aid::DEN);
|
||||
certificate.add_permission(aid::GN_MGMT); // required for beacons
|
||||
certificate.add_permission(aid::IPV6_ROUTING); // required for routing tests
|
||||
|
||||
// section 7.4.1 in TS 103 097 v1.2.1
|
||||
// set subject attributes
|
||||
// set the verification_key
|
||||
Uncompressed coordinates;
|
||||
coordinates.x.assign(aa_key_pair().public_key.x.begin(), aa_key_pair().public_key.x.end());
|
||||
coordinates.y.assign(aa_key_pair().public_key.y.begin(), aa_key_pair().public_key.y.end());
|
||||
EccPoint ecc_point = coordinates;
|
||||
ecdsa_nistp256_with_sha256 ecdsa;
|
||||
ecdsa.public_key = ecc_point;
|
||||
VerificationKey verification_key;
|
||||
verification_key.key = ecdsa;
|
||||
certificate.subject_attributes.push_back(verification_key);
|
||||
|
||||
// section 6.7 in TS 103 097 v1.2.1
|
||||
// set validity restriction
|
||||
StartAndEndValidity start_and_end;
|
||||
start_and_end.start_validity = convert_time32(m_runtime.now() - std::chrono::hours(1));
|
||||
start_and_end.end_validity = convert_time32(m_runtime.now() + std::chrono::hours(23));
|
||||
certificate.validity_restriction.push_back(start_and_end);
|
||||
|
||||
sort(certificate);
|
||||
|
||||
// set signature
|
||||
ByteBuffer data_buffer = convert_for_signing(certificate);
|
||||
certificate.signature = m_crypto_backend->sign_data(root_key_pair().private_key, data_buffer);
|
||||
|
||||
return certificate;
|
||||
}
|
||||
|
||||
Certificate NaiveCertificateProvider::generate_root_certificate(const std::string& subject_name)
|
||||
{
|
||||
// create certificate
|
||||
Certificate certificate;
|
||||
|
||||
// section 6.1 in TS 103 097 v1.2.1
|
||||
certificate.signer_info = nullptr; /* self */
|
||||
|
||||
// section 6.3 in TS 103 097 v1.2.1
|
||||
certificate.subject_info.subject_type = SubjectType::Root_CA;
|
||||
|
||||
// section 7.4.2 in TS 103 097 v1.2.1
|
||||
std::vector<unsigned char> subject(subject_name.begin(), subject_name.end());
|
||||
certificate.subject_info.subject_name = subject;
|
||||
|
||||
// section 6.6 in TS 103 097 v1.2.1 - levels currently undefined
|
||||
certificate.subject_attributes.push_back(SubjectAssurance(0x00));
|
||||
|
||||
certificate.add_permission(aid::CA);
|
||||
certificate.add_permission(aid::DEN);
|
||||
certificate.add_permission(aid::GN_MGMT); // required for beacons
|
||||
certificate.add_permission(aid::IPV6_ROUTING); // required for routing tests
|
||||
|
||||
// section 7.4.1 in TS 103 097 v1.2.1
|
||||
// set subject attributes
|
||||
// set the verification_key
|
||||
Uncompressed coordinates;
|
||||
coordinates.x.assign(root_key_pair().public_key.x.begin(), root_key_pair().public_key.x.end());
|
||||
coordinates.y.assign(root_key_pair().public_key.y.begin(), root_key_pair().public_key.y.end());
|
||||
EccPoint ecc_point = coordinates;
|
||||
ecdsa_nistp256_with_sha256 ecdsa;
|
||||
ecdsa.public_key = ecc_point;
|
||||
VerificationKey verification_key;
|
||||
verification_key.key = ecdsa;
|
||||
certificate.subject_attributes.push_back(verification_key);
|
||||
|
||||
// section 6.7 in TS 103 097 v1.2.1
|
||||
// set validity restriction
|
||||
StartAndEndValidity start_and_end;
|
||||
start_and_end.start_validity = convert_time32(m_runtime.now() - std::chrono::hours(1));
|
||||
start_and_end.end_validity = convert_time32(m_runtime.now() + std::chrono::hours(365 * 24));
|
||||
certificate.validity_restriction.push_back(start_and_end);
|
||||
|
||||
sort(certificate);
|
||||
|
||||
// set signature
|
||||
ByteBuffer data_buffer = convert_for_signing(certificate);
|
||||
certificate.signature = m_crypto_backend->sign_data(root_key_pair().private_key, data_buffer);
|
||||
|
||||
return certificate;
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
#ifndef NAIVE_CERTIFICATE_PROVIDER_HPP_MTULFLKX
|
||||
#define NAIVE_CERTIFICATE_PROVIDER_HPP_MTULFLKX
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vanetza/common/runtime.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/v2/certificate_provider.hpp>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace security
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
|
||||
/**
|
||||
* \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 CertificateProvider
|
||||
{
|
||||
public:
|
||||
NaiveCertificateProvider(const Runtime&);
|
||||
|
||||
/**
|
||||
* \brief get own certificate for signing
|
||||
* \return own certificate
|
||||
*/
|
||||
const Certificate& own_certificate() override;
|
||||
|
||||
/**
|
||||
* Get own certificate chain, excluding the leaf certificate and root CA
|
||||
* \return own certificate chain
|
||||
*/
|
||||
std::list<Certificate> own_chain() override;
|
||||
|
||||
/**
|
||||
* \brief get own private key
|
||||
* \return private key
|
||||
*/
|
||||
const ecdsa256::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 v2
|
||||
} // namespace security
|
||||
} // namespace vanetza
|
||||
|
||||
#endif /* NAIVE_CERTIFICATE_PROVIDER_HPP_MTULFLKX */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user