obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but that tree was gitignored, so a clone of this repository could not build the firmware it ships. It is now committed here as ordinary files in its own folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP bridge's signature verification (--trust) used on the bench. Nothing is fetched from or pushed to the colleague's repository; this repository and its remotes carry everything. The folder's own .gitignore keeps build output, downloaded components and private key material out, as it did there; the committed file set is identical to that repository's tracked files. The ESP32-C5 is still flashed from obu-firmware/, which only takes vanetza-idf from microbu-esp32c5/, so the two stay separate folders. FLASHING.md says how to take a newer version of the colleague's tree (copy it over the folder, rebuild, test, commit).
228 lines
6.3 KiB
C++
228 lines
6.3 KiB
C++
#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
|