Skip to content

Commit

Permalink
Support hashing directories
Browse files Browse the repository at this point in the history
Signed-off-by: Ivan Rogach <[email protected]>
  • Loading branch information
jannejy committed Nov 28, 2024
1 parent 9836ac4 commit 865c0db
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 7 deletions.
310 changes: 310 additions & 0 deletions 3rd_party/cert_rehash/c_rehash.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
/* c_rehash.c - Create hash symlinks for certificates
* C implementation based on the original Perl and shell versions
*
* Copyright (c) 2013-2014 Timo Teräs <[email protected]>
* All rights reserved.
*
* This software is licensed under the MIT License.
* Full license available at: http://opensource.org/licenses/MIT
*/

#include <dirent.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>

#include <everest/logging.hpp>

#define MAX_COLLISIONS 256
#define countof(x) (sizeof(x) / sizeof(x[0]))

namespace ocpp {

struct entry_info {
struct entry_info* next;
char* filename;
unsigned short old_id;
unsigned char need_symlink;
unsigned char digest[EVP_MAX_MD_SIZE];
};

struct bucket_info {
struct bucket_info* next;
struct entry_info *first_entry, *last_entry;
unsigned int hash;
unsigned short type;
unsigned short num_needed;
};

enum Type {
TYPE_CERT = 0,
TYPE_CRL
};

static const char* symlink_extensions[] = {"", "r"};
static const char* file_extensions[] = {"pem", "crt", "cer", "crl"};

static int evpmdsize;
static const EVP_MD* evpmd;

static struct bucket_info* hash_table[257];

static void bit_set(unsigned char* set, unsigned bit) {
set[bit / 8] |= 1 << (bit % 8);
}

static int bit_isset(unsigned char* set, unsigned bit) {
return set[bit / 8] & (1 << (bit % 8));
}

static void add_entry(int type, unsigned int hash, const char* filename, const unsigned char* digest, int need_symlink,
unsigned short old_id) {
struct bucket_info* bi;
struct entry_info *ei, *found = NULL;
unsigned int ndx = (type + hash) % countof(hash_table);

for (bi = hash_table[ndx]; bi; bi = bi->next)
if (bi->type == type && bi->hash == hash)
break;
if (!bi) {
bi = (bucket_info*)(calloc(1, sizeof(*bi)));
if (!bi)
return;
bi->next = hash_table[ndx];
bi->type = type;
bi->hash = hash;
hash_table[ndx] = bi;
}

for (ei = bi->first_entry; ei; ei = ei->next) {
if (digest && memcmp(digest, ei->digest, evpmdsize) == 0) {
EVLOG_warning << "Skipping duplicate certificate in file " << std::string(filename);
return;
}
if (!strcmp(filename, ei->filename)) {
found = ei;
if (!digest)
break;
}
}
ei = found;
if (!ei) {
if (bi->num_needed >= MAX_COLLISIONS)
return;
ei = (entry_info*)(calloc(1, sizeof(*ei)));
if (!ei)
return;

ei->old_id = ~0;
ei->filename = strdup(filename);
if (bi->last_entry)
bi->last_entry->next = ei;
if (!bi->first_entry)
bi->first_entry = ei;
bi->last_entry = ei;
}

if (old_id < ei->old_id)
ei->old_id = old_id;
if (need_symlink && !ei->need_symlink) {
ei->need_symlink = 1;
bi->num_needed++;
memcpy(ei->digest, digest, evpmdsize);
}
}

static int handle_symlink(const char* filename, const char* fullpath) {
static char xdigit[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11,
12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15};
char linktarget[NAME_MAX], *endptr;
unsigned int hash = 0;
unsigned char ch;
int i, type, id;
ssize_t n;

for (i = 0; i < 8; i++) {
ch = filename[i] - '0';
if (ch >= countof(xdigit) || xdigit[ch] < 0)
return -1;
hash <<= 4;
hash += xdigit[ch];
}
if (filename[i++] != '.')
return -1;
for (type = countof(symlink_extensions) - 1; type > 0; type--)
if (strcasecmp(symlink_extensions[type], &filename[i]) == 0)
break;
i += strlen(symlink_extensions[type]);

id = strtoul(&filename[i], &endptr, 10);
if (*endptr != 0)
return -1;

n = readlink(fullpath, linktarget, sizeof(linktarget));
if (n >= sizeof(linktarget) || n < 0)
return -1;
linktarget[n] = 0;

EVLOG_debug << "Found existing symlink " << std::string(filename) << " for " << hash << " (" << type
<< "), certname " << std::string(linktarget, strlen(linktarget));
add_entry(type, hash, linktarget, NULL, 0, id);
return 0;
}

static int handle_certificate(const char* filename, const char* fullpath) {
STACK_OF(X509_INFO) * inf;
X509_INFO* x;
BIO* b;
const char* ext;
unsigned char digest[EVP_MAX_MD_SIZE];
X509_NAME* name = NULL;
int i, type, ret = -1;

ext = strrchr(filename, '.');
if (ext == NULL)
return 0;
for (i = 0; i < countof(file_extensions); i++) {
if (strcasecmp(file_extensions[i], ext + 1) == 0)
break;
}
if (i >= countof(file_extensions))
return -1;

b = BIO_new_file(fullpath, "r");
if (!b)
return -1;
inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL);
BIO_free(b);
if (!inf)
return -1;

if (sk_X509_INFO_num(inf) == 1) {
x = sk_X509_INFO_value(inf, 0);
if (x->x509) {
type = TYPE_CERT;
name = X509_get_subject_name(x->x509);
X509_digest(x->x509, evpmd, digest, NULL);
} else if (x->crl) {
type = TYPE_CRL;
name = X509_CRL_get_issuer(x->crl);
X509_CRL_digest(x->crl, evpmd, digest, NULL);
}
if (name)
add_entry(type, X509_NAME_hash(name), filename, digest, 1, ~0);
} else {
EVLOG_warning << std::string(filename) << " does not contain exactly one certificate or CRL: skipping";
}

sk_X509_INFO_pop_free(inf, X509_INFO_free);

return ret;
}

static int hash_dir(const char* dirname) {
struct bucket_info *bi, *nextbi;
struct entry_info *ei, *nextei;
struct dirent* de;
struct stat st;
unsigned char idmask[MAX_COLLISIONS / 8];
int i, n, nextid, buflen, ret = -1;
const char* pathsep;
char* buf;
DIR* d;

evpmd = EVP_sha1();
evpmdsize = EVP_MD_size(evpmd);

if (access(dirname, R_OK | W_OK | X_OK) != 0) {
EVLOG_error << "Access denied '" << std::string(dirname) << "'";
return -1;
}

buflen = strlen(dirname);
pathsep = (buflen && dirname[buflen - 1] == '/') ? "" : "/";
buflen += NAME_MAX + 2;
buf = (char*)(malloc(buflen));
if (buf == NULL)
goto err;

EVLOG_debug << "Doing " << std::string(dirname);
d = opendir(dirname);
if (!d)
goto err;

while ((de = readdir(d)) != NULL) {
if (snprintf(buf, buflen, "%s%s%s", dirname, pathsep, de->d_name) >= buflen)
continue;
if (lstat(buf, &st) < 0)
continue;
if (S_ISLNK(st.st_mode) && handle_symlink(de->d_name, buf) == 0)
continue;
if (strcmp(buf, "/etc/ssl/certs/ca-certificates.crt") == 0) {
/* Ignore the /etc/ssl/certs/ca-certificates.crt file */
EVLOG_debug << "Skipping /etc/ssl/certs/ca-certificates.crt file";
continue;
}
handle_certificate(de->d_name, buf);
}
closedir(d);

for (i = 0; i < countof(hash_table); i++) {
for (bi = hash_table[i]; bi; bi = nextbi) {
nextbi = bi->next;
EVLOG_debug << "Type " << bi->type << " hash " << bi->hash << " num entries " << bi->num_needed << ":";

nextid = 0;
memset(idmask, 0, (bi->num_needed + 7) / 8);
for (ei = bi->first_entry; ei; ei = ei->next)
if (ei->old_id < bi->num_needed)
bit_set(idmask, ei->old_id);

for (ei = bi->first_entry; ei; ei = nextei) {
nextei = ei->next;
EVLOG_debug << "\t(old_id " << ei->old_id << ", need_symlink " << ei->need_symlink << ") Cert "
<< std::string(ei->filename, strlen(ei->filename)) << ":";

if (ei->old_id < bi->num_needed) {
/* Link exists, and is used as-is */
snprintf(buf, buflen, "%08x.%s%d", bi->hash, symlink_extensions[bi->type], ei->old_id);
EVLOG_debug << "link " << std::string(ei->filename, strlen(ei->filename)) << " -> "
<< std::string(buf, strlen(buf));
} else if (ei->need_symlink) {
/* New link needed (it may replace something) */
while (bit_isset(idmask, nextid))
nextid++;

snprintf(buf, buflen, "%s%s%n%08x.%s%d", dirname, pathsep, &n, bi->hash,
symlink_extensions[bi->type], nextid);
EVLOG_debug << "link " << std::string(ei->filename, strlen(ei->filename)) << " -> "
<< std::string(buf + n, strlen(buf + n));
unlink(buf);
symlink(ei->filename, buf);
} else {
/* Link to be deleted */
snprintf(buf, buflen, "%s%s%n%08x.%s%d", dirname, pathsep, &n, bi->hash,
symlink_extensions[bi->type], ei->old_id);
EVLOG_debug << "unlink " << std::string(buf + n, strlen(buf + n));
unlink(buf);
}
free(ei->filename);
free(ei);
}
free(bi);
}
hash_table[i] = NULL;
}

ret = 0;
err:
free(buf);
return ret;
}

} // namespace ocpp
2 changes: 1 addition & 1 deletion include/ocpp/common/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class EvseSecurity {
/// \brief Retrieves the PEM formatted CA bundle file for the given \p certificate_type
/// \param certificate_type
/// \return CA certificate file
virtual std::string get_verify_file(const CaCertificateType& certificate_type) = 0;
virtual std::string get_verify_location(const CaCertificateType& certificate_type) = 0;

/// \brief Gets the expiry day count for the leaf certificate of the given \p certificate_type
/// \param certificate_type
Expand Down
2 changes: 1 addition & 1 deletion include/ocpp/common/evse_security_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class EvseSecurityImpl : public EvseSecurity {
GetCertificateInfoResult get_leaf_certificate_info(const CertificateSigningUseEnum& certificate_type,
bool include_ocsp = false) override;
bool update_certificate_links(const CertificateSigningUseEnum& certificate_type) override;
std::string get_verify_file(const CaCertificateType& certificate_type) override;
std::string get_verify_location(const CaCertificateType& certificate_type) override;
int get_leaf_expiry_days_count(const CertificateSigningUseEnum& certificate_type) override;
};

Expand Down
4 changes: 2 additions & 2 deletions lib/ocpp/common/evse_security_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ bool EvseSecurityImpl::update_certificate_links(const CertificateSigningUseEnum&
return this->evse_security->update_certificate_links(conversions::from_ocpp(certificate_type));
}

std::string EvseSecurityImpl::get_verify_file(const CaCertificateType& certificate_type) {
return this->evse_security->get_verify_file(conversions::from_ocpp(certificate_type));
std::string EvseSecurityImpl::get_verify_location(const CaCertificateType& certificate_type) {
return this->evse_security->get_verify_location(conversions::from_ocpp(certificate_type));
}

int EvseSecurityImpl::get_leaf_expiry_days_count(const CertificateSigningUseEnum& certificate_type) {
Expand Down
10 changes: 8 additions & 2 deletions lib/ocpp/common/websocket/websocket_libwebsockets.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include <cert_rehash/c_rehash.hpp>
#include <evse_security/crypto/openssl/openssl_provider.hpp>
#include <ocpp/common/websocket/websocket_libwebsockets.hpp>

Expand Down Expand Up @@ -374,11 +375,16 @@ bool WebsocketLibwebsockets::tls_init(SSL_CTX* ctx, const std::string& path_chai
}

if (this->evse_security->is_ca_certificate_installed(ocpp::CaCertificateType::CSMS)) {
std::string ca_csms = this->evse_security->get_verify_file(ocpp::CaCertificateType::CSMS);
std::string ca_csms = this->evse_security->get_verify_location(ocpp::CaCertificateType::CSMS);

EVLOG_info << "Loading CA csms bundle to verify server certificate: " << ca_csms;

rc = SSL_CTX_load_verify_locations(ctx, ca_csms.c_str(), NULL);
if (std::filesystem::is_directory(ca_csms)) {
hash_dir(ca_csms.c_str());
rc = SSL_CTX_load_verify_locations(ctx, NULL, ca_csms.c_str());
} else {
rc = SSL_CTX_load_verify_locations(ctx, ca_csms.c_str(), NULL);
}

if (rc != 1) {
EVLOG_error << "Could not load CA verify locations, error: " << ERR_error_string(ERR_get_error(), NULL);
Expand Down
2 changes: 1 addition & 1 deletion tests/lib/ocpp/common/evse_security_mock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class EvseSecurityMock : public EvseSecurity {
MOCK_METHOD(GetCertificateInfoResult, get_leaf_certificate_info, (const CertificateSigningUseEnum&, bool),
(override));
MOCK_METHOD(bool, update_certificate_links, (const CertificateSigningUseEnum&), (override));
MOCK_METHOD(std::string, get_verify_file, (const CaCertificateType&), (override));
MOCK_METHOD(std::string, get_verify_location, (const CaCertificateType&), (override));
MOCK_METHOD(int, get_leaf_expiry_days_count, (const CertificateSigningUseEnum&), (override));
};

Expand Down

0 comments on commit 865c0db

Please sign in to comment.