Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef CRTYPTO_COMMON_HPP_
|
||||
#define CRTYPTO_COMMON_HPP_
|
||||
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace crypto {
|
||||
|
||||
using verify_result_t = openssl::verify_result_t;
|
||||
|
||||
constexpr std::size_t MAX_EXI_SIZE = 8192;
|
||||
|
||||
} // namespace crypto
|
||||
|
||||
#endif // CRTYPTO_COMMON_HPP_
|
||||
@@ -0,0 +1,209 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
|
||||
#include "crypto_openssl.hpp"
|
||||
#include "iso_server.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include <cbv2g/common/exi_bitstream.h>
|
||||
#include <cbv2g/exi_v2gtp.h> //for V2GTP_HEADER_LENGTHs
|
||||
#include <cbv2g/iso_2/iso2_msgDefDatatypes.h>
|
||||
#include <cbv2g/iso_2/iso2_msgDefDecoder.h>
|
||||
#include <cbv2g/iso_2/iso2_msgDefEncoder.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/store.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
namespace crypto ::openssl {
|
||||
using ::openssl::bn_const_t;
|
||||
using ::openssl::bn_t;
|
||||
using ::openssl::log_error;
|
||||
using ::openssl::sha_256;
|
||||
using ::openssl::sha_256_digest_t;
|
||||
using ::openssl::verify;
|
||||
|
||||
bool check_iso2_signature(const struct iso2_SignatureType* iso2_signature, EVP_PKEY* pkey,
|
||||
struct iso2_exiFragment* iso2_exi_fragment) {
|
||||
assert(pkey != nullptr);
|
||||
assert(iso2_signature != nullptr);
|
||||
assert(iso2_exi_fragment != nullptr);
|
||||
|
||||
bool bRes{true};
|
||||
|
||||
// signature information
|
||||
const struct iso2_ReferenceType* req_ref = &iso2_signature->SignedInfo.Reference.array[0];
|
||||
const auto signature_len = iso2_signature->SignatureValue.CONTENT.bytesLen;
|
||||
const auto* signature = &iso2_signature->SignatureValue.CONTENT.bytes[0];
|
||||
|
||||
// build data to check signature against
|
||||
std::array<std::uint8_t, MAX_EXI_SIZE> exi_buffer{};
|
||||
exi_bitstream_t stream;
|
||||
exi_bitstream_init(&stream, exi_buffer.data(), MAX_EXI_SIZE, 0, NULL);
|
||||
|
||||
auto err = encode_iso2_exiFragment(&stream, iso2_exi_fragment);
|
||||
if (err != 0) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Unable to encode fragment, error code = %d", err);
|
||||
bRes = false;
|
||||
}
|
||||
|
||||
sha_256_digest_t digest;
|
||||
|
||||
// calculate hash of data
|
||||
if (bRes) {
|
||||
const auto frag_data_len = exi_bitstream_get_length(&stream);
|
||||
bRes = sha_256(exi_buffer.data(), frag_data_len, digest);
|
||||
}
|
||||
|
||||
// check hash matches the value in the message
|
||||
if (bRes) {
|
||||
if (req_ref->DigestValue.bytesLen != digest.size()) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Invalid digest length %u in signature", req_ref->DigestValue.bytesLen);
|
||||
bRes = false;
|
||||
}
|
||||
}
|
||||
if (bRes) {
|
||||
if (std::memcmp(req_ref->DigestValue.bytes, digest.data(), digest.size()) != 0) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Invalid digest in signature");
|
||||
bRes = false;
|
||||
}
|
||||
}
|
||||
|
||||
// verify the signature
|
||||
if (bRes) {
|
||||
struct iso2_xmldsigFragment sig_fragment {};
|
||||
init_iso2_xmldsigFragment(&sig_fragment);
|
||||
sig_fragment.SignedInfo_isUsed = 1;
|
||||
sig_fragment.SignedInfo = iso2_signature->SignedInfo;
|
||||
|
||||
/** \req [V2G2-771] Don't use following fields */
|
||||
sig_fragment.SignedInfo.Id_isUsed = 0;
|
||||
sig_fragment.SignedInfo.CanonicalizationMethod.ANY_isUsed = 0;
|
||||
sig_fragment.SignedInfo.SignatureMethod.HMACOutputLength_isUsed = 0;
|
||||
sig_fragment.SignedInfo.SignatureMethod.ANY_isUsed = 0;
|
||||
for (auto* ref = sig_fragment.SignedInfo.Reference.array;
|
||||
ref != (sig_fragment.SignedInfo.Reference.array + sig_fragment.SignedInfo.Reference.arrayLen); ++ref) {
|
||||
ref->Type_isUsed = 0;
|
||||
ref->Transforms.Transform.ANY_isUsed = 0;
|
||||
ref->Transforms.Transform.XPath_isUsed = 0;
|
||||
ref->DigestMethod.ANY_isUsed = 0;
|
||||
}
|
||||
|
||||
stream.byte_pos = 0;
|
||||
stream.bit_count = 0;
|
||||
err = encode_iso2_xmldsigFragment(&stream, &sig_fragment);
|
||||
|
||||
if (err != 0) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Unable to encode XML signature fragment, error code = %d", err);
|
||||
bRes = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bRes) {
|
||||
// hash again (different data) buffer_pos has been updated ...
|
||||
const auto frag_data_len = exi_bitstream_get_length(&stream);
|
||||
bRes = sha_256(exi_buffer.data(), frag_data_len, digest);
|
||||
}
|
||||
|
||||
if (bRes) {
|
||||
/* Validate the ecdsa signature using the public key */
|
||||
if (signature_len != ::openssl::signature_size) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Signature len is invalid (%i)", signature_len);
|
||||
bRes = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bRes) {
|
||||
const std::uint8_t* r = &signature[0];
|
||||
const std::uint8_t* s = &signature[32];
|
||||
bRes = verify(pkey, r, s, digest);
|
||||
}
|
||||
|
||||
return bRes;
|
||||
}
|
||||
|
||||
bool load_contract_root_cert(::openssl::certificate_list& trust_anchors, const char* V2G_file_path,
|
||||
const char* MO_file_path) {
|
||||
trust_anchors.clear();
|
||||
|
||||
auto mo_certs = ::openssl::load_certificates(MO_file_path);
|
||||
trust_anchors = std::move(mo_certs);
|
||||
|
||||
auto v2g_certs = ::openssl::load_certificates(V2G_file_path);
|
||||
trust_anchors.insert(trust_anchors.end(), std::make_move_iterator(v2g_certs.begin()),
|
||||
std::make_move_iterator(v2g_certs.end()));
|
||||
|
||||
if (trust_anchors.empty()) {
|
||||
log_error("Unable to load any MO or V2G root(s)");
|
||||
}
|
||||
|
||||
return !trust_anchors.empty();
|
||||
}
|
||||
|
||||
int load_certificate(::openssl::certificate_list* chain, const std::uint8_t* bytes, std::uint16_t bytesLen) {
|
||||
assert(chain != nullptr);
|
||||
int result{-1};
|
||||
|
||||
auto tmp_cert = ::openssl::der_to_certificate(bytes, bytesLen);
|
||||
if (tmp_cert != nullptr) {
|
||||
chain->push_back(std::move(tmp_cert));
|
||||
result = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int parse_contract_certificate(::openssl::certificate_ptr& crt, const std::uint8_t* buf, std::size_t buflen) {
|
||||
crt = ::openssl::der_to_certificate(buf, buflen);
|
||||
return (crt == nullptr) ? -1 : 0;
|
||||
}
|
||||
|
||||
std::string getEmaidFromContractCert(const ::openssl::certificate_ptr& crt) {
|
||||
std::string cert_emaid;
|
||||
const auto subject = ::openssl::certificate_subject(crt.get());
|
||||
if (auto itt = subject.find("CN"); itt != subject.end()) {
|
||||
cert_emaid = itt->second;
|
||||
}
|
||||
|
||||
return cert_emaid;
|
||||
}
|
||||
|
||||
std::string chain_to_pem(const ::openssl::certificate_ptr& cert, const ::openssl::certificate_list* chain) {
|
||||
assert(chain != nullptr);
|
||||
|
||||
std::string contract_cert_chain_pem(::openssl::certificate_to_pem(cert.get()));
|
||||
for (const auto& crt : *chain) {
|
||||
const auto pem = ::openssl::certificate_to_pem(crt.get());
|
||||
if (pem.empty()) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Unable to encode certificate chain");
|
||||
break;
|
||||
}
|
||||
contract_cert_chain_pem.append(pem);
|
||||
}
|
||||
|
||||
return contract_cert_chain_pem;
|
||||
}
|
||||
|
||||
verify_result_t verify_certificate(const ::openssl::certificate_ptr& cert, const ::openssl::certificate_list* chain,
|
||||
const char* v2g_root_cert_path, const char* mo_root_cert_path,
|
||||
bool /* debugMode */) {
|
||||
assert(chain != nullptr);
|
||||
|
||||
verify_result_t result{verify_result_t::Verified};
|
||||
::openssl::certificate_list trust_anchors;
|
||||
|
||||
if (!load_contract_root_cert(trust_anchors, v2g_root_cert_path, mo_root_cert_path)) {
|
||||
result = verify_result_t::NoCertificateAvailable;
|
||||
} else {
|
||||
result = ::openssl::verify_certificate(cert.get(), trust_anchors, *chain);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace crypto::openssl
|
||||
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef CRYPTO_OPENSSL_HPP_
|
||||
#define CRYPTO_OPENSSL_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include "crypto_common.hpp"
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
|
||||
/**
|
||||
* \file OpenSSL implementation
|
||||
*/
|
||||
|
||||
struct evp_pkey_st;
|
||||
struct iso2_SignatureType;
|
||||
struct iso2_exiFragment;
|
||||
struct x509_st;
|
||||
struct v2g_connection;
|
||||
|
||||
namespace crypto::openssl {
|
||||
|
||||
/**
|
||||
* \brief check the signature of a signed 15118 message
|
||||
* \param iso2_signature the signature to check
|
||||
* \param public_key the public key from the contract certificate
|
||||
* \param iso2_exi_fragment the signed data
|
||||
* \return true when the signature is valid
|
||||
*/
|
||||
bool check_iso2_signature(const struct iso2_SignatureType* iso2_signature, evp_pkey_st* pkey,
|
||||
struct iso2_exiFragment* iso2_exi_fragment);
|
||||
|
||||
/**
|
||||
* \brief load the trust anchor for the contract certificate.
|
||||
* Use the mobility operator certificate if it exists otherwise
|
||||
* the V2G certificate
|
||||
* \param contract_root_crt the retrieved trust anchor
|
||||
* \param V2G_file_path the file containing the V2G trust anchor (PEM format)
|
||||
* \param MO_file_path the file containing the mobility operator trust anchor (PEM format)
|
||||
* \return true when a certificate was found
|
||||
*/
|
||||
bool load_contract_root_cert(::openssl::certificate_list& trust_anchors, const char* V2G_file_path,
|
||||
const char* MO_file_path);
|
||||
|
||||
/**
|
||||
* \brief clear certificate and public key from previous connection
|
||||
* \param conn the V2G connection data
|
||||
* \note not needed for the OpenSSL implementation
|
||||
*/
|
||||
constexpr void free_connection_crypto_data(v2g_connection* conn) {
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief load a contract certificate's certification path certificate from the V2G message as DER bytes
|
||||
* \param chain the certificate path certificates (this certificate is added to the list)
|
||||
* \param bytes the DER (ASN.1) X509v3 certificate in the V2G message
|
||||
* \param bytesLen the length of the DER encoded certificate
|
||||
* \return 0 when certificate successfully loaded
|
||||
*/
|
||||
int load_certificate(::openssl::certificate_list* chain, const std::uint8_t* bytes, std::uint16_t bytesLen);
|
||||
|
||||
/**
|
||||
* \brief load the contract certificate from the V2G message as DER bytes
|
||||
* \param crt the certificate
|
||||
* \param bytes the DER (ASN.1) X509v3 certificate in the V2G message
|
||||
* \param bytesLen the length of the DER encoded certificate
|
||||
* \return 0 when certificate successfully loaded
|
||||
*/
|
||||
int parse_contract_certificate(::openssl::certificate_ptr& crt, const std::uint8_t* buf, std::size_t buflen);
|
||||
|
||||
/**
|
||||
* \brief get the EMAID from the certificate (CommonName from the SubjectName)
|
||||
* \param crt the certificate
|
||||
* \return the EMAD or empty on error
|
||||
*/
|
||||
std::string getEmaidFromContractCert(const ::openssl::certificate_ptr& crt);
|
||||
|
||||
/**
|
||||
* \brief convert a list of certificates into a PEM string starting with the contract certificate
|
||||
* \param contract_crt the contract certificate (when not the first certificate in the chain)
|
||||
* \param chain the certification path chain (might include the contract certificate as the first item)
|
||||
* \return PEM string or empty on error
|
||||
*/
|
||||
std::string chain_to_pem(const ::openssl::certificate_ptr& cert, const ::openssl::certificate_list* chain);
|
||||
|
||||
/**
|
||||
* \brief verify certification path of the contract certificate through to a trust anchor
|
||||
* \param contract_crt (single certificate or chain with the contract certificate as the first item)
|
||||
* \param chain intermediate certificates (may be nullptr)
|
||||
* \param v2g_root_cert_path V2G trust anchor file name
|
||||
* \param mo_root_cert_path mobility operator trust anchor file name
|
||||
* \param debugMode additional information on verification failures
|
||||
* \result a subset of possible verification failures where known or 'verified' on success
|
||||
*/
|
||||
verify_result_t verify_certificate(const ::openssl::certificate_ptr& cert, const ::openssl::certificate_list* chain,
|
||||
const char* v2g_root_cert_path, const char* mo_root_cert_path, bool debugMode);
|
||||
|
||||
} // namespace crypto::openssl
|
||||
|
||||
#endif // CRYPTO_OPENSSL_HPP_
|
||||
Reference in New Issue
Block a user