Skip to content

Commit

Permalink
Added same verification in libwebsocket implementation
Browse files Browse the repository at this point in the history
Note: a workaround is used since libwebsocket overwrites the param usage when checking the server certificate

Signed-off-by: AssemblyJohn <[email protected]>
  • Loading branch information
AssemblyJohn committed Mar 8, 2024
1 parent ba68d6d commit 39c1688
Showing 1 changed file with 63 additions and 68 deletions.
131 changes: 63 additions & 68 deletions lib/ocpp/common/websocket/websocket_libwebsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,38 +151,8 @@ struct WebsocketMessage {
std::atomic_bool message_sent;
};

static std::vector<std::string> get_subject_alt_names(const X509* x509) {
std::vector<std::string> list;
GENERAL_NAMES* subject_alt_names =
static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL));
if (subject_alt_names == nullptr) {
return list;
}
for (int i = 0; i < sk_GENERAL_NAME_num(subject_alt_names); i++) {
GENERAL_NAME* gen = sk_GENERAL_NAME_value(subject_alt_names, i);
if (gen == nullptr) {
continue;
}
if (gen->type == GEN_URI || gen->type == GEN_DNS || gen->type == GEN_EMAIL) {
ASN1_IA5STRING* asn1_str = gen->d.uniformResourceIdentifier;
std::string san = std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(asn1_str)),
ASN1_STRING_length(asn1_str));
list.push_back(san);
} else if (gen->type == GEN_IPADD) {
unsigned char* ip = gen->d.ip->data;
if (gen->d.ip->length == 4) { // only support IPv4 for now
std::stringstream ip_stream;
ip_stream << static_cast<int>(ip[0]) << '.' << static_cast<int>(ip[1]) << '.' << static_cast<int>(ip[2])
<< '.' << static_cast<int>(ip[3]);
list.push_back(ip_stream.str());
}
}
}
GENERAL_NAMES_free(subject_alt_names);
return list;
}

static bool verify_csms_cn(const std::string& hostname, bool preverified, const X509_STORE_CTX* ctx) {
static bool verify_csms_cn(const std::string& hostname, bool preverified, const X509_STORE_CTX* ctx,
bool allow_wildcards) {

// Error depth gives the depth in the chain (with 0 = leaf certificate) where
// a potential (!) error occurred; error here means current error code and can also be "OK".
Expand All @@ -202,37 +172,31 @@ static bool verify_csms_cn(const std::string& hostname, bool preverified, const
// Get server certificate
X509* server_cert = X509_STORE_CTX_get_current_cert(ctx);

// Extract CN from csms server's certificate
X509_NAME* subject_name = X509_get_subject_name(server_cert);
char common_name[256];
if (X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, sizeof(common_name)) <= 0) {
EVLOG_error << "Could not extract CN from CSMS server certificate";
return false;
}
// TODO (ioan): remove this function after the libwebsocket parameter issue is solved

auto alt_names = get_subject_alt_names(server_cert);
// Verify host-name manually
int result;

// Compare the extracted CN with the expected FQDN
if (hostname == common_name) {
EVLOG_debug << "FQDN matches CN of server certificate: " << hostname;
return true;
if (allow_wildcards) {
result = X509_check_host(server_cert, hostname.c_str(), hostname.length(),
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS, nullptr);
} else {
result = X509_check_host(server_cert, hostname.c_str(), hostname.length(), X509_CHECK_FLAG_NO_WILDCARDS,
nullptr);
}

// If the CN does not match, go through all alternative names
for (auto name : alt_names) {
if (hostname == name) {
EVLOG_debug << "FQDN matches alternative name of server certificate: " << hostname;
return true;
if (result != 1) {
X509_NAME* subject_name = X509_get_subject_name(server_cert);
char common_name[256];

if (X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, sizeof(common_name)) <= 0) {
EVLOG_error << "Failed to verify server certificate cn with hostname: " << hostname
<< " and with server certificate cs: " << common_name
<< " with wildcards: " << allow_wildcards;
}
}

std::stringstream s;
s << "FQDN '" << hostname << "' does not match CN '" << common_name << "' or any alternative names";
for (auto alt_name : alt_names) {
s << " '" << alt_name << "'";
return false;
}
EVLOG_warning << s.str();
return false;
}

return preverified;
Expand Down Expand Up @@ -359,9 +323,38 @@ void WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, cons
}
}

// TODO (ioan): libwebsockets seems not to take this parameters into account
// and this code should be re-introduced after the issue is solved
/*
if (this->connection_options.verify_csms_common_name) {
// Verify hostname
X509_VERIFY_PARAM* param = X509_VERIFY_PARAM_new();
if (this->connection_options.verify_csms_allow_wildcards) {
X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
} else {
X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_WILDCARDS);
}
// Set the host and parameter check
EVLOG_error << "Set wrong host param!";
if(1 != X509_VERIFY_PARAM_set1_host(param, this->connection_options.csms_uri.get_hostname().c_str(),
this->connection_options.csms_uri.get_hostname().length())) {
EVLOG_error << "Could not set host name: " << this->connection_options.csms_uri.get_hostname();
EVLOG_AND_THROW(std::runtime_error("Could not set verification hostname!"));
}
SSL_CTX_set1_param(ctx, param);
X509_VERIFY_PARAM_free(param);
} else {
EVLOG_warning << "Not verifying the CSMS certificates commonName with the Fully Qualified Domain Name "
"(FQDN) of the server because it has been explicitly turned off via the configuration!";
}
*/

// Extra info
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); // to verify server certificate
// SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
}

void WebsocketTlsTPM::recv_loop() {
Expand Down Expand Up @@ -415,8 +408,8 @@ void WebsocketTlsTPM::client_loop() {
// Bind thread for checks
local_data->bind_thread(std::this_thread::get_id());

// lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG | LLL_PARSER | LLL_HEADER
// | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_THREAD | LLL_USER, nullptr);
// lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG | LLL_PARSER | LLL_HEADER | LLL_EXT |
// LLL_CLIENT | LLL_LATENCY | LLL_THREAD | LLL_USER, nullptr);
lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, nullptr);

lws_context_creation_info info;
Expand Down Expand Up @@ -528,9 +521,6 @@ void WebsocketTlsTPM::client_loop() {
i.pwsi = &local_data->wsi;
i.userdata = local_data.get(); // See lws_context 'user'

// TODO (ioan): See if we need retry policy since we handle this manually
// i.retry_and_idle_policy = &retry;

// Print data for debug
EVLOG_info << "LWS connect with info "
<< "port: [" << i.port << "] address: [" << i.address << "] path: [" << i.path << "] protocol: ["
Expand Down Expand Up @@ -1014,12 +1004,17 @@ int WebsocketTlsTPM::process_callback(void* wsi_ptr, int callback_reason, void*

switch (reason) {
case LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION:
// user is X509_STORE and and len is preverify_ok
if (false == verify_csms_cn(this->connection_options.csms_uri.get_hostname(), (len == 1),
reinterpret_cast<X509_STORE_CTX*>(user))) {
EVLOG_error << "Failed to verify server certificate cn!";
// Return 1 to fail the cert
return 1;

if (this->connection_options.verify_csms_common_name) {
// user is X509_STORE and and len is preverify_ok
EVLOG_info << "Verifying server certs!";

if (false == verify_csms_cn(this->connection_options.csms_uri.get_hostname(), (len == 1),
reinterpret_cast<X509_STORE_CTX*>(user),
this->connection_options.verify_csms_allow_wildcards)) {
// Return 1 to fail the cert
return 1;
}
}

break;
Expand Down

0 comments on commit 39c1688

Please sign in to comment.