Keep the colleague's microbu-esp32c5 tree in this repository
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).
This commit is contained in:
+85
@@ -0,0 +1,85 @@
|
||||
#include "extract-public-key.hpp"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/v2/basic_elements.hpp>
|
||||
#include <vanetza/security/v2/persistence.hpp>
|
||||
|
||||
namespace po = boost::program_options;
|
||||
using namespace vanetza::security;
|
||||
|
||||
bool ExtractPublicKeyCommand::parse(const std::vector<std::string>& opts)
|
||||
{
|
||||
po::options_description desc("Available options");
|
||||
desc.add_options()
|
||||
("help", "Print out available options.")
|
||||
("output", po::value<std::string>(&output)->required(), "Output file.")
|
||||
("certificate", po::value<std::string>(&certificate_path), "Certificate file to extract public key from.")
|
||||
("private-key", po::value<std::string>(&private_key_path), "Private key file to extract public key from.")
|
||||
;
|
||||
|
||||
po::positional_options_description pos;
|
||||
pos.add("output", 1);
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(opts).options(desc).positional(pos).run(), vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cerr << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
po::notify(vm);
|
||||
|
||||
if (!(vm.count("certificate") ^ vm.count("private-key"))) {
|
||||
std::cerr << "Error: One of certificate / private-key parameters must be present." << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl << std::endl << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ExtractPublicKeyCommand::execute()
|
||||
{
|
||||
std::cout << "Loading key... ";
|
||||
|
||||
std::unique_ptr<Backend> backend = create_backend("default");
|
||||
ecdsa256::PublicKey public_key;
|
||||
if (certificate_path.length() > 0) {
|
||||
auto certificate = v2::load_certificate_from_file(certificate_path);
|
||||
auto certificate_key = get_public_key(certificate, *backend);
|
||||
|
||||
if (!certificate_key) {
|
||||
std::cerr << "Reading public key from certificate failed." << std::endl;
|
||||
}
|
||||
|
||||
public_key = *certificate_key;
|
||||
} else {
|
||||
auto private_key = v2::load_private_key_from_file(private_key_path);
|
||||
public_key = private_key.public_key;
|
||||
}
|
||||
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
Uncompressed coordinates;
|
||||
coordinates.x.assign(public_key.x.begin(), public_key.x.end());
|
||||
coordinates.y.assign(public_key.y.begin(), public_key.y.end());
|
||||
|
||||
v2::ecdsa_nistp256_with_sha256 public_key_etsi;
|
||||
public_key_etsi.public_key = coordinates;
|
||||
|
||||
std::cout << "Writing public key to '" << output << "'... ";
|
||||
v2::save_public_key_to_file(output, public_key_etsi);
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#ifndef CERTIFY_COMMANDS_EXTRACT_PUBLIC_KEY_HPP
|
||||
#define CERTIFY_COMMANDS_EXTRACT_PUBLIC_KEY_HPP
|
||||
|
||||
#include "command.hpp"
|
||||
|
||||
class ExtractPublicKeyCommand : public Command
|
||||
{
|
||||
public:
|
||||
bool parse(const std::vector<std::string>&) override;
|
||||
int execute() override;
|
||||
|
||||
private:
|
||||
std::string output;
|
||||
std::string certificate_path;
|
||||
std::string private_key_path;
|
||||
};
|
||||
|
||||
#endif /* CERTIFY_COMMANDS_EXTRACT_PUBLIC_KEY_HPP */
|
||||
@@ -0,0 +1,138 @@
|
||||
#include "generate-aa.hpp"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <boost/variant/get.hpp>
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/v2/basic_elements.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/v2/persistence.hpp>
|
||||
#include <vanetza/security/v2/subject_attribute.hpp>
|
||||
#include <vanetza/security/v2/subject_info.hpp>
|
||||
|
||||
namespace aid = vanetza::aid;
|
||||
namespace po = boost::program_options;
|
||||
using namespace vanetza::security;
|
||||
using namespace vanetza::security::v2;
|
||||
|
||||
bool GenerateAaCommand::parse(const std::vector<std::string>& opts)
|
||||
{
|
||||
po::options_description desc("Available options");
|
||||
desc.add_options()
|
||||
("help", "Print out available options.")
|
||||
("output", po::value<std::string>(&output)->required(), "Output file.")
|
||||
("sign-key", po::value<std::string>(&sign_key_path)->required(), "Private key file of the signer.")
|
||||
("sign-cert", po::value<std::string>(&sign_cert_path)->required(), "Private certificate file of the signer.")
|
||||
("subject-key", po::value<std::string>(&subject_key_path)->required(), "Private key file to issue the certificate for.")
|
||||
("subject-name", po::value<std::string>(&subject_name)->default_value("Hello World Auth-CA"), "Subject name.")
|
||||
("days", po::value<int>(&validity_days)->default_value(180), "Validity in days.")
|
||||
("aid", po::value<std::vector<unsigned> >(&aids)->multitoken(), "Allowed ITS-AIDs to restrict permissions, defaults to 36 (CA) and 37 (DEN) if empty.")
|
||||
;
|
||||
|
||||
po::positional_options_description pos;
|
||||
pos.add("output", 1);
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(opts).options(desc).positional(pos).run(), vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cerr << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
po::notify(vm);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl << std::endl << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GenerateAaCommand::execute()
|
||||
{
|
||||
auto backend = create_backend_or_throw("default");
|
||||
|
||||
std::cout << "Loading keys... ";
|
||||
auto sign_key = v2::load_private_key_from_file(sign_key_path);
|
||||
ecdsa256::PublicKey subject_key;
|
||||
try {
|
||||
auto subject_private_key = v2::load_private_key_from_file(subject_key_path);
|
||||
subject_key = subject_private_key.public_key;
|
||||
} catch (std::exception& e) {
|
||||
auto subject_key_etsi = v2::load_public_key_from_file(subject_key_path);
|
||||
if (get_type(subject_key_etsi) != PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256) {
|
||||
std::cerr << "Wrong public key algorithm." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto subject_key_etsi_ecdsa = boost::get<ecdsa_nistp256_with_sha256>(subject_key_etsi);
|
||||
auto uncompressed_subject_ecc_point = backend->decompress_point(subject_key_etsi_ecdsa.public_key);
|
||||
if (!uncompressed_subject_ecc_point) {
|
||||
std::cerr << "Cannot get uncompressed ECC point from public key.";
|
||||
return 1;
|
||||
} else {
|
||||
subject_key = ecdsa256::create_public_key(*uncompressed_subject_ecc_point);
|
||||
}
|
||||
}
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
Certificate sign_cert = v2::load_certificate_from_file(sign_cert_path);
|
||||
|
||||
auto time_now = vanetza::Clock::at(boost::posix_time::microsec_clock::universal_time());
|
||||
|
||||
Certificate certificate;
|
||||
std::list<v2::IntX> certificate_aids;
|
||||
|
||||
if (aids.size()) {
|
||||
for (unsigned aid : aids) {
|
||||
certificate_aids.push_back(v2::IntX(aid));
|
||||
}
|
||||
} else {
|
||||
certificate_aids.push_back(v2::IntX(aid::CA));
|
||||
certificate_aids.push_back(v2::IntX(aid::DEN));
|
||||
}
|
||||
certificate.subject_attributes.push_back(certificate_aids);
|
||||
|
||||
certificate.signer_info = calculate_hash(sign_cert);
|
||||
|
||||
std::vector<unsigned char> subject(subject_name.begin(), subject_name.end());
|
||||
certificate.subject_info.subject_name = subject;
|
||||
certificate.subject_info.subject_type = SubjectType::Authorization_Authority;
|
||||
certificate.subject_attributes.push_back(SubjectAssurance(0x00));
|
||||
|
||||
Uncompressed coordinates;
|
||||
coordinates.x.assign(subject_key.x.begin(), subject_key.x.end());
|
||||
coordinates.y.assign(subject_key.y.begin(), subject_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);
|
||||
|
||||
StartAndEndValidity start_and_end;
|
||||
start_and_end.start_validity = convert_time32(time_now - std::chrono::hours(1));
|
||||
start_and_end.end_validity = convert_time32(time_now + std::chrono::hours(24 * validity_days));
|
||||
certificate.validity_restriction.push_back(start_and_end);
|
||||
|
||||
std::cout << "Signing certificate... ";
|
||||
|
||||
sort(certificate);
|
||||
auto data_buffer = convert_for_signing(certificate);
|
||||
certificate.signature = backend->sign_data(sign_key.private_key, data_buffer);
|
||||
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
std::cout << "Writing certificate to '" << output << "'... ";
|
||||
save_certificate_to_file(output, certificate);
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CERTIFY_COMMANDS_GENERATE_AA_HPP
|
||||
#define CERTIFY_COMMANDS_GENERATE_AA_HPP
|
||||
|
||||
#include "command.hpp"
|
||||
|
||||
class GenerateAaCommand : public Command
|
||||
{
|
||||
public:
|
||||
bool parse(const std::vector<std::string>&) override;
|
||||
int execute() override;
|
||||
|
||||
private:
|
||||
std::string output;
|
||||
std::string sign_key_path;
|
||||
std::string sign_cert_path;
|
||||
std::string subject_key_path;
|
||||
std::string subject_name;
|
||||
int validity_days;
|
||||
std::vector<unsigned> aids;
|
||||
};
|
||||
|
||||
#endif /* CERTIFY_COMMANDS_GENERATE_AA_HPP */
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "generate-key.hpp"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vanetza/security/backend.hpp>
|
||||
#include <vanetza/security/v2/persistence.hpp>
|
||||
|
||||
namespace po = boost::program_options;
|
||||
using namespace vanetza::security;
|
||||
|
||||
bool GenerateKeyCommand::parse(const std::vector<std::string>& opts)
|
||||
{
|
||||
po::options_description desc("Available options");
|
||||
desc.add_options()
|
||||
("help", "Print out available options.")
|
||||
("output", po::value<std::string>(&output)->required(), "Output file.")
|
||||
;
|
||||
|
||||
po::positional_options_description pos;
|
||||
pos.add("output", 1);
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(opts).options(desc).positional(pos).run(), vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cerr << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
po::notify(vm);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl << std::endl << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GenerateKeyCommand::execute()
|
||||
{
|
||||
auto backend = create_backend_or_throw("default");
|
||||
|
||||
std::cout << "Generating key... ";
|
||||
auto key_pair = backend->generate_key_pair();
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
std::cout << "Writing key to '" << output << "'... ";
|
||||
std::ofstream ofs(output, std::ios::binary);
|
||||
v2::save_private_key_pkcs8_der(ofs, key_pair);
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef CERTIFY_COMMANDS_GENERATE_KEY_HPP
|
||||
#define CERTIFY_COMMANDS_GENERATE_KEY_HPP
|
||||
|
||||
#include "command.hpp"
|
||||
|
||||
class GenerateKeyCommand : public Command
|
||||
{
|
||||
public:
|
||||
bool parse(const std::vector<std::string>&) override;
|
||||
int execute() override;
|
||||
|
||||
private:
|
||||
std::string output;
|
||||
};
|
||||
|
||||
#endif /* CERTIFY_COMMANDS_GENERATE_KEY_HPP */
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
#include "generate-root.hpp"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/backend_cryptopp.hpp>
|
||||
#include <vanetza/security/v2/basic_elements.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/v2/persistence.hpp>
|
||||
#include <vanetza/security/v2/subject_attribute.hpp>
|
||||
#include <vanetza/security/v2/subject_info.hpp>
|
||||
|
||||
namespace aid = vanetza::aid;
|
||||
namespace po = boost::program_options;
|
||||
using namespace vanetza::security;
|
||||
|
||||
bool GenerateRootCommand::parse(const std::vector<std::string>& opts)
|
||||
{
|
||||
po::options_description desc("Available options");
|
||||
desc.add_options()
|
||||
("help", "Print out available options.")
|
||||
("output", po::value<std::string>(&output)->required(), "Output file.")
|
||||
("subject-key", po::value<std::string>(&subject_key_path)->required(), "Private key file.")
|
||||
("subject-name", po::value<std::string>(&subject_name)->default_value("Hello World Root-CA"), "Subject name.")
|
||||
("days", po::value<int>(&validity_days)->default_value(365), "Validity in days.")
|
||||
("aid", po::value<std::vector<unsigned> >(&aids)->multitoken(), "Allowed ITS-AIDs to restrict permissions, defaults to 36 (CA) and 37 (DEN) if empty.")
|
||||
;
|
||||
|
||||
po::positional_options_description pos;
|
||||
pos.add("output", 1);
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(opts).options(desc).positional(pos).run(), vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cerr << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
po::notify(vm);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl << std::endl << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GenerateRootCommand::execute()
|
||||
{
|
||||
auto backend = create_backend_or_throw("default");
|
||||
|
||||
std::cout << "Loading key... ";
|
||||
auto subject_key = v2::load_private_key_from_file(subject_key_path);
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
auto time_now = vanetza::Clock::at(boost::posix_time::microsec_clock::universal_time());
|
||||
|
||||
// create certificate
|
||||
v2::Certificate certificate;
|
||||
std::list<v2::IntX> certificate_aids;
|
||||
|
||||
if (aids.size()) {
|
||||
for (unsigned aid : aids) {
|
||||
certificate_aids.push_back(v2::IntX(aid));
|
||||
}
|
||||
} else {
|
||||
certificate_aids.push_back(v2::IntX(aid::CA));
|
||||
certificate_aids.push_back(v2::IntX(aid::DEN));
|
||||
}
|
||||
certificate.subject_attributes.push_back(certificate_aids);
|
||||
|
||||
// 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 = v2::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(v2::SubjectAssurance(0x00));
|
||||
|
||||
// section 7.4.1 in TS 103 097 v1.2.1
|
||||
// set subject attributes
|
||||
// set the verification_key
|
||||
Uncompressed coordinates;
|
||||
coordinates.x.assign(subject_key.public_key.x.begin(), subject_key.public_key.x.end());
|
||||
coordinates.y.assign(subject_key.public_key.y.begin(), subject_key.public_key.y.end());
|
||||
EccPoint ecc_point = coordinates;
|
||||
v2::ecdsa_nistp256_with_sha256 ecdsa;
|
||||
ecdsa.public_key = ecc_point;
|
||||
v2::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
|
||||
v2::StartAndEndValidity start_and_end;
|
||||
start_and_end.start_validity = v2::convert_time32(time_now - std::chrono::hours(1));
|
||||
start_and_end.end_validity = v2::convert_time32(time_now + std::chrono::hours(24 * validity_days));
|
||||
certificate.validity_restriction.push_back(start_and_end);
|
||||
|
||||
std::cout << "Signing certificate... ";
|
||||
|
||||
sort(certificate);
|
||||
vanetza::ByteBuffer data_buffer = convert_for_signing(certificate);
|
||||
certificate.signature = backend->sign_data(subject_key.private_key, data_buffer);
|
||||
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
std::cout << "Writing certificate to '" << output << "'... ";
|
||||
save_certificate_to_file(output, certificate);
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#ifndef CERTIFY_COMMANDS_GENERATE_ROOT_HPP
|
||||
#define CERTIFY_COMMANDS_GENERATE_ROOT_HPP
|
||||
|
||||
#include "command.hpp"
|
||||
|
||||
class GenerateRootCommand : public Command
|
||||
{
|
||||
public:
|
||||
bool parse(const std::vector<std::string>&) override;
|
||||
int execute() override;
|
||||
|
||||
private:
|
||||
std::string subject_key_path;
|
||||
std::string output;
|
||||
std::string subject_name;
|
||||
int validity_days;
|
||||
std::vector<unsigned> aids;
|
||||
};
|
||||
|
||||
#endif /* CERTIFY_COMMANDS_GENERATE_ROOT_HPP */
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#include "generate-ticket.hpp"
|
||||
#include "utils.hpp"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <boost/variant/get.hpp>
|
||||
#include <cryptopp/cryptlib.h>
|
||||
#include <vanetza/common/clock.hpp>
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/backend_cryptopp.hpp>
|
||||
#include <vanetza/security/v2/basic_elements.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/v2/persistence.hpp>
|
||||
#include <vanetza/security/v2/subject_attribute.hpp>
|
||||
#include <vanetza/security/v2/subject_info.hpp>
|
||||
|
||||
namespace aid = vanetza::aid;
|
||||
namespace po = boost::program_options;
|
||||
using namespace vanetza::security;
|
||||
|
||||
bool GenerateTicketCommand::parse(const std::vector<std::string>& opts)
|
||||
{
|
||||
po::options_description desc("Available options");
|
||||
desc.add_options()
|
||||
("help", "Print out available options.")
|
||||
("output", po::value<std::string>(&output)->required(), "Output file.")
|
||||
("sign-key", po::value<std::string>(&sign_key_path)->required(), "Private key file of the signer.")
|
||||
("sign-cert", po::value<std::string>(&sign_cert_path)->required(), "Private certificate file of the signer.")
|
||||
("subject-key", po::value<std::string>(&subject_key_path)->required(), "Private key file to issue the certificate for.")
|
||||
("days", po::value<int>(&validity_days)->default_value(7), "Validity in days.")
|
||||
("cam-permissions", po::value<std::string>(&cam_permissions), "CAM permissions as binary string (e.g. '1111111111111100' to grant all SSPs)")
|
||||
("denm-permissions", po::value<std::string>(&denm_permissions), "DENM permissions as binary string (e.g. '000000000000000000000000' to grant no SSPs)")
|
||||
("permit-gn-mgmt", po::bool_switch(&permit_gn_mgmt), "Generated ticket can be used to sign GN-MGMT messages (e.g. beacons).")
|
||||
;
|
||||
|
||||
po::positional_options_description pos;
|
||||
pos.add("output", 1);
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(opts).options(desc).positional(pos).run(), vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cerr << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
po::notify(vm);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl << std::endl << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GenerateTicketCommand::execute()
|
||||
{
|
||||
auto backend = create_backend_or_throw("default");
|
||||
|
||||
std::cout << "Loading keys... ";
|
||||
auto sign_key = v2::load_private_key_from_file(sign_key_path);
|
||||
ecdsa256::PublicKey subject_key;
|
||||
try {
|
||||
auto subject_private_key = v2::load_private_key_from_file(subject_key_path);
|
||||
subject_key = subject_private_key.public_key;
|
||||
} catch (CryptoPP::BERDecodeErr& e) {
|
||||
auto subject_key_etsi = v2::load_public_key_from_file(subject_key_path);
|
||||
if (v2::get_type(subject_key_etsi) != v2::PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256) {
|
||||
std::cerr << "Wrong public key algorithm." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto subject_key_etsi_ecdsa = boost::get<v2::ecdsa_nistp256_with_sha256>(subject_key_etsi);
|
||||
if (v2::get_type(subject_key_etsi_ecdsa.public_key) != v2::EccPointType::Uncompressed) {
|
||||
std::cerr << "Unsupported ECC point type, must be uncompressed.";
|
||||
return 1;
|
||||
}
|
||||
|
||||
subject_key = ecdsa256::create_public_key(boost::get<Uncompressed>(subject_key_etsi_ecdsa.public_key));
|
||||
}
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
auto sign_cert = v2::load_certificate_from_file(sign_cert_path);
|
||||
auto time_now = vanetza::Clock::at(boost::posix_time::microsec_clock::universal_time());
|
||||
|
||||
auto cam_ssps = vanetza::ByteBuffer({ 1, 0, 0 }); // no special permissions
|
||||
auto denm_ssps = vanetza::ByteBuffer({ 1, 0, 0, 0 }); // no special permissions
|
||||
|
||||
if (cam_permissions.size()) {
|
||||
permission_string_to_buffer(cam_permissions, cam_ssps);
|
||||
}
|
||||
|
||||
if (denm_permissions.size()) {
|
||||
permission_string_to_buffer(denm_permissions, denm_ssps);
|
||||
}
|
||||
|
||||
v2::Certificate certificate;
|
||||
std::list<v2::ItsAidSsp> certificate_ssp;
|
||||
|
||||
// see ETSI EN 302 637-2 V1.3.1 (2014-09)
|
||||
v2::ItsAidSsp certificate_ssp_ca;
|
||||
certificate_ssp_ca.its_aid = v2::IntX(aid::CA);
|
||||
certificate_ssp_ca.service_specific_permissions = cam_ssps;
|
||||
certificate_ssp.push_back(certificate_ssp_ca);
|
||||
|
||||
// see ETSI EN 302 637-3 V1.2.2 (2014-11)
|
||||
v2::ItsAidSsp certificate_ssp_den;
|
||||
certificate_ssp_den.its_aid = v2::IntX(aid::DEN);
|
||||
certificate_ssp_den.service_specific_permissions = denm_ssps;
|
||||
certificate_ssp.push_back(certificate_ssp_den);
|
||||
|
||||
if (permit_gn_mgmt) {
|
||||
certificate_ssp.push_back({v2::IntX(aid::GN_MGMT), vanetza::ByteBuffer{}});
|
||||
}
|
||||
|
||||
certificate.signer_info = calculate_hash(sign_cert);
|
||||
certificate.subject_info.subject_type = v2::SubjectType::Authorization_Ticket;
|
||||
certificate.subject_attributes.push_back(v2::SubjectAssurance(0x00));
|
||||
certificate.subject_attributes.push_back(certificate_ssp);
|
||||
|
||||
Uncompressed coordinates;
|
||||
coordinates.x.assign(subject_key.x.begin(), subject_key.x.end());
|
||||
coordinates.y.assign(subject_key.y.begin(), subject_key.y.end());
|
||||
EccPoint ecc_point = coordinates;
|
||||
v2::ecdsa_nistp256_with_sha256 ecdsa;
|
||||
ecdsa.public_key = ecc_point;
|
||||
v2::VerificationKey verification_key;
|
||||
verification_key.key = ecdsa;
|
||||
certificate.subject_attributes.push_back(verification_key);
|
||||
|
||||
v2::StartAndEndValidity start_and_end;
|
||||
start_and_end.start_validity = v2::convert_time32(time_now - std::chrono::hours(1));
|
||||
start_and_end.end_validity = v2::convert_time32(time_now + std::chrono::hours(24 * validity_days));
|
||||
certificate.validity_restriction.push_back(start_and_end);
|
||||
|
||||
std::cout << "Signing certificate... ";
|
||||
|
||||
sort(certificate);
|
||||
auto data_buffer = convert_for_signing(certificate);
|
||||
certificate.signature = backend->sign_data(sign_key.private_key, data_buffer);
|
||||
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
std::cout << "Writing certificate to '" << output << "'... ";
|
||||
save_certificate_to_file(output, certificate);
|
||||
std::cout << "OK" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#ifndef CERTIFY_COMMANDS_GENERATE_TICKET_HPP
|
||||
#define CERTIFY_COMMANDS_GENERATE_TICKET_HPP
|
||||
|
||||
#include "command.hpp"
|
||||
|
||||
class GenerateTicketCommand : public Command
|
||||
{
|
||||
public:
|
||||
bool parse(const std::vector<std::string>&) override;
|
||||
int execute() override;
|
||||
|
||||
private:
|
||||
std::string output;
|
||||
std::string sign_key_path;
|
||||
std::string sign_cert_path;
|
||||
std::string subject_key_path;
|
||||
int validity_days;
|
||||
std::string cam_permissions;
|
||||
std::string denm_permissions;
|
||||
bool permit_gn_mgmt = false;
|
||||
};
|
||||
|
||||
#endif /* CERTIFY_COMMANDS_GENERATE_TICKET_HPP */
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
#include "show-certificate.hpp"
|
||||
#include <boost/algorithm/hex.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vanetza/common/its_aid.hpp>
|
||||
#include <vanetza/security/cam_ssp.hpp>
|
||||
#include <vanetza/security/v2/certificate.hpp>
|
||||
#include <vanetza/security/v2/ecc_point.hpp>
|
||||
#include <vanetza/security/v2/persistence.hpp>
|
||||
#include <vanetza/security/v2/public_key.hpp>
|
||||
|
||||
namespace po = boost::program_options;
|
||||
using namespace vanetza;
|
||||
using namespace vanetza::security;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string hex_buffer(const ByteBuffer& buffer)
|
||||
{
|
||||
std::string bytes(buffer.begin(), buffer.end());
|
||||
return boost::algorithm::hex(bytes);
|
||||
}
|
||||
|
||||
void print_ecc_point(const EccPoint& point, const std::string& indent)
|
||||
{
|
||||
switch (v2::get_type(point)) {
|
||||
case v2::EccPointType::X_Coordinate_Only:
|
||||
std::cout << indent << "X: " << hex_buffer(boost::get<X_Coordinate_Only>(point).x)
|
||||
<< " (x-coordinate only)" << std::endl;
|
||||
break;
|
||||
case v2::EccPointType::Compressed_Lsb_Y_0:
|
||||
std::cout << indent << "X: " << hex_buffer(boost::get<Compressed_Lsb_Y_0>(point).x)
|
||||
<< " (compressed, y LSB 0)" << std::endl;
|
||||
break;
|
||||
case v2::EccPointType::Compressed_Lsb_Y_1:
|
||||
std::cout << indent << "X: " << hex_buffer(boost::get<Compressed_Lsb_Y_1>(point).x)
|
||||
<< " (compressed, y LSB 1)" << std::endl;
|
||||
break;
|
||||
case v2::EccPointType::Uncompressed: {
|
||||
const Uncompressed& uncompressed = boost::get<Uncompressed>(point);
|
||||
std::cout << indent << "X: " << hex_buffer(uncompressed.x) << std::endl;
|
||||
std::cout << indent << "Y: " << hex_buffer(uncompressed.y) << " (uncompressed)" << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print_public_key(const v2::PublicKey& key, const std::string& indent)
|
||||
{
|
||||
switch (v2::get_type(key)) {
|
||||
case v2::PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256:
|
||||
std::cout << indent << "Algorithm: ECDSA NISTP256 with SHA-256" << std::endl;
|
||||
print_ecc_point(boost::get<v2::ecdsa_nistp256_with_sha256>(key).public_key, indent);
|
||||
break;
|
||||
case v2::PublicKeyAlgorithm::ECIES_NISTP256:
|
||||
std::cout << indent << "Algorithm: ECIES NISTP256" << std::endl;
|
||||
print_ecc_point(boost::get<v2::ecies_nistp256>(key).public_key, indent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowCertificateCommand::parse(const std::vector<std::string>& opts)
|
||||
{
|
||||
po::options_description desc("Available options");
|
||||
desc.add_options()
|
||||
("help", "Print out available options.")
|
||||
("certificate", po::value<std::string>(&certificate_path)->required(), "Certificate to show.")
|
||||
;
|
||||
|
||||
po::positional_options_description pos;
|
||||
pos.add("certificate", 1);
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(opts).options(desc).positional(pos).run(), vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cerr << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
po::notify(vm);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl << std::endl << desc << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ShowCertificateCommand::execute()
|
||||
{
|
||||
v2::Certificate cert = v2::load_certificate_from_file(certificate_path);
|
||||
|
||||
// subject info
|
||||
|
||||
std::cout << "Subject: ";
|
||||
|
||||
if (cert.subject_info.subject_type == v2::SubjectType::Enrollment_Credential) {
|
||||
std::cout << "Enrollment Credential";
|
||||
} else if (cert.subject_info.subject_type == v2::SubjectType::Authorization_Ticket) {
|
||||
std::cout << "Authorization Ticket";
|
||||
} else if (cert.subject_info.subject_type == v2::SubjectType::Authorization_Authority) {
|
||||
std::cout << "Authorization Authority";
|
||||
} else if (cert.subject_info.subject_type == v2::SubjectType::Enrollment_Authority) {
|
||||
std::cout << "Enrollment Authority";
|
||||
} else if (cert.subject_info.subject_type == v2::SubjectType::Root_CA) {
|
||||
std::cout << "Root Authority";
|
||||
} else if (cert.subject_info.subject_type == v2::SubjectType::CRL_Signer) {
|
||||
std::cout << "CRL Signer";
|
||||
}
|
||||
|
||||
if (cert.subject_info.subject_name.size() > 0) {
|
||||
std::string subject_name(reinterpret_cast<const char*>(&cert.subject_info.subject_name[0]), cert.subject_info.subject_name.size());
|
||||
std::cout << " (" << subject_name << ")";
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
{
|
||||
HashedId8 cert_id = calculate_hash(cert);
|
||||
std::string cert_id_string(reinterpret_cast<const char*>(&cert_id[0]), cert_id.size());
|
||||
std::cout << "Digest: " << boost::algorithm::hex(cert_id_string) << " (SHA-256)" << std::endl;
|
||||
}
|
||||
|
||||
// signer info
|
||||
|
||||
std::cout << "Signer: ";
|
||||
|
||||
v2::SignerInfoType signer_type = get_type(cert.signer_info);
|
||||
|
||||
if (signer_type == v2::SignerInfoType::Self) {
|
||||
std::cout << "Self-Signed";
|
||||
} else if (signer_type == v2::SignerInfoType::Certificate_Digest_With_SHA256) {
|
||||
HashedId8 signer = boost::get<HashedId8>(cert.signer_info);
|
||||
std::string signer_id(reinterpret_cast<const char*>(&signer[0]), signer.size());
|
||||
std::cout << boost::algorithm::hex(signer_id) << " (SHA-256)";
|
||||
} else {
|
||||
std::cout << "Unknown (" << static_cast<int>(signer_type) << ")";
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
// subject attributes
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
unsigned certificate_application_ids = 0;
|
||||
|
||||
for (auto& subject_attr : cert.subject_attributes) {
|
||||
v2::SubjectAttributeType attr_type = get_type(subject_attr);
|
||||
if (attr_type == v2::SubjectAttributeType::Verification_Key) {
|
||||
std::cout << "Verification Key:" << std::endl;
|
||||
print_public_key(boost::get<v2::VerificationKey>(subject_attr).key, " - ");
|
||||
std::cout << std::endl;
|
||||
} else if (attr_type == v2::SubjectAttributeType::Encryption_Key) {
|
||||
std::cout << "Encryption Key:" << std::endl;
|
||||
print_public_key(boost::get<v2::EncryptionKey>(subject_attr).key, " - ");
|
||||
std::cout << std::endl;
|
||||
} else if (attr_type == v2::SubjectAttributeType::Reconstruction_Value) {
|
||||
std::cout << "Reconstruction Value:" << std::endl;
|
||||
print_ecc_point(boost::get<EccPoint>(subject_attr), " - ");
|
||||
std::cout << std::endl;
|
||||
} else if (attr_type == v2::SubjectAttributeType::Assurance_Level) {
|
||||
v2::SubjectAssurance assurance = boost::get<v2::SubjectAssurance>(subject_attr);
|
||||
std::cout << "Assurance: " << (assurance.raw & assurance.assurance_mask);
|
||||
std::cout << " with a confidence of " << (assurance.raw & assurance.confidence_mask);
|
||||
std::cout << std::endl << std::endl;
|
||||
} else if (attr_type == v2::SubjectAttributeType::ITS_AID_List) {
|
||||
std::list<v2::IntX> its_application_ids = boost::get<std::list<v2::IntX>>(subject_attr);
|
||||
|
||||
std::cout << "ITS Application IDs:" << std::endl;
|
||||
if (its_application_ids.size() == 0) {
|
||||
std::cout << "None";
|
||||
} else {
|
||||
for (auto& its_application_id : its_application_ids) {
|
||||
certificate_application_ids++;
|
||||
|
||||
std::cout << " - ";
|
||||
if (its_application_id == aid::CA) {
|
||||
std::cout << "36 (CA-Basic service)";
|
||||
} else if (its_application_id == aid::DEN) {
|
||||
std::cout << "37 (DEN-Basic service)";
|
||||
} else {
|
||||
std::cout << its_application_id.get();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
} else if (attr_type == v2::SubjectAttributeType::ITS_AID_SSP_List) {
|
||||
std::list<v2::ItsAidSsp> its_service_specific_permissions = boost::get<std::list<v2::ItsAidSsp>>(subject_attr);
|
||||
for (auto& its_ssp : its_service_specific_permissions) {
|
||||
if (its_ssp.its_aid == aid::CA) {
|
||||
std::cout << "CA - ITS Service Specific Permissions:" << std::endl;
|
||||
ByteBuffer& ssp = its_ssp.service_specific_permissions;
|
||||
|
||||
if (ssp.size() == 0) {
|
||||
std::cerr << "Invalid service specific permissions for CA" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
// See final draft ETSI EN 302 637-2 V1.3.1 (2014-09)
|
||||
if (ssp[0] == 0) {
|
||||
if (ssp.size() != 1) {
|
||||
std::cout << " - Warning: Length of SSP is expected to be 1, but was " << ssp.size() << std::endl;
|
||||
} else {
|
||||
std::cout << " - No version, shall be used only for testing." << std::endl;
|
||||
}
|
||||
} else if (ssp[0] == 1) {
|
||||
if (ssp.size() != 3) {
|
||||
std::cout << " - Warning: Length of SSP is expected to be 3, but was " << ssp.size() << std::endl;
|
||||
} else {
|
||||
CamPermissions ssp_decoded = CamPermissions::decode(ssp);
|
||||
for (auto permission : ssp_decoded.permissions()) {
|
||||
std::cout << " - " << stringify(permission) << "\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << " - Reserved for future usage and not implemented." << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (certificate_application_ids == 0) {
|
||||
std::cout << "Warning: Certificate doesn't contain any application IDs." << std::endl << std::endl;
|
||||
}
|
||||
|
||||
// validity restrictions
|
||||
|
||||
const boost::posix_time::ptime epoch {
|
||||
boost::gregorian::date(2004, 1, 1),
|
||||
boost::posix_time::milliseconds(0)
|
||||
};
|
||||
|
||||
unsigned certificate_time_constraints = 0;
|
||||
|
||||
for (auto& validity_restriction : cert.validity_restriction) {
|
||||
v2::ValidityRestrictionType restriction_type = get_type(validity_restriction);
|
||||
if (restriction_type == v2::ValidityRestrictionType::Time_End) {
|
||||
certificate_time_constraints++;
|
||||
|
||||
boost::posix_time::ptime time_end = epoch + boost::posix_time::seconds(boost::get<v2::EndValidity>(validity_restriction));
|
||||
std::cout << "Validity ends " << time_end << std::endl;
|
||||
} else if (restriction_type == v2::ValidityRestrictionType::Time_Start_And_End) {
|
||||
certificate_time_constraints++;
|
||||
|
||||
v2::StartAndEndValidity start_and_end = boost::get<v2::StartAndEndValidity>(validity_restriction);
|
||||
boost::posix_time::ptime time_start = epoch + boost::posix_time::seconds(start_and_end.start_validity);
|
||||
boost::posix_time::ptime time_end = epoch + boost::posix_time::seconds(start_and_end.end_validity);
|
||||
std::cout << "Validity starts " << time_start << " and ends " << time_end << std::endl;
|
||||
} else if (restriction_type == v2::ValidityRestrictionType::Time_Start_And_Duration) {
|
||||
certificate_time_constraints++;
|
||||
|
||||
v2::StartAndDurationValidity start_and_duration = boost::get<v2::StartAndDurationValidity>(validity_restriction);
|
||||
boost::posix_time::ptime time_start = epoch + boost::posix_time::seconds(start_and_duration.start_validity);
|
||||
boost::posix_time::ptime time_end = epoch + boost::posix_time::seconds(start_and_duration.duration.to_seconds().count());
|
||||
std::cout << "Validity starts " << time_start << " and ends " << time_end << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (certificate_time_constraints == 0) {
|
||||
std::cout << "Warning: Certificate doesn't have any time based validity restriction." << std::endl;
|
||||
} else if (certificate_time_constraints > 1) {
|
||||
std::cout << "Warning: Certificate has multiple time based validity restrictions." << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
bool certificate_region_constraints = false;
|
||||
|
||||
for (auto& validity_restriction : cert.validity_restriction) {
|
||||
v2::ValidityRestrictionType restriction_type = get_type(validity_restriction);
|
||||
if (restriction_type == v2::ValidityRestrictionType::Region) {
|
||||
certificate_region_constraints = true;
|
||||
|
||||
v2::GeographicRegion region = boost::get<v2::GeographicRegion>(validity_restriction);
|
||||
|
||||
std::cout << "This certificate is regionally restricted by ";
|
||||
|
||||
v2::RegionType region_type = get_type(region);
|
||||
if (region_type == v2::RegionType::None) {
|
||||
std::cout << "nothing";
|
||||
} else if (region_type == v2::RegionType::Circle) {
|
||||
std::cout << "a circle";
|
||||
} else if (region_type == v2::RegionType::Rectangle) {
|
||||
std::cout << "a set of rectangles";
|
||||
} else if (region_type == v2::RegionType::Polygon) {
|
||||
std::cout << "a polygon";
|
||||
} else if (region_type == v2::RegionType::ID) {
|
||||
std::cout << "an identified region";
|
||||
}
|
||||
|
||||
std::cout << "." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!certificate_region_constraints) {
|
||||
std::cout << "This certificate doesn't have any regional restriction." << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#ifndef CERTIFY_COMMANDS_SHOW_CERTIFICATE_HPP
|
||||
#define CERTIFY_COMMANDS_SHOW_CERTIFICATE_HPP
|
||||
|
||||
#include "command.hpp"
|
||||
|
||||
class ShowCertificateCommand : public Command
|
||||
{
|
||||
public:
|
||||
bool parse(const std::vector<std::string>&) override;
|
||||
int execute() override;
|
||||
|
||||
private:
|
||||
std::string certificate_path;
|
||||
};
|
||||
|
||||
#endif /* CERTIFY_COMMANDS_SHOW_CERTIFICATE_HPP */
|
||||
Reference in New Issue
Block a user