Skip to content

Commit

Permalink
Move hashing directories to evse-security
Browse files Browse the repository at this point in the history
Signed-off-by: Ivan Rogach <[email protected]>
  • Loading branch information
jannejy committed Nov 25, 2024
1 parent ac71139 commit e69a3c3
Show file tree
Hide file tree
Showing 4 changed files with 324 additions and 4 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 evse_security {

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)));

Check notice on line 76 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L76

C-style pointer casting
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)));

Check notice on line 100 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L100

C-style pointer casting
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);

Check failure on line 118 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L118

The `memcpy` family of functions require the developer to validate that the destination buffer is the same size or larger than the source buffer.
}
}

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;

Check notice on line 128 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L128

The scope of the variable 'ch' can be reduced.
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]);

Check notice on line 144 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L144

Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126).

Check failure on line 144 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L144

The `strlen` family of functions does not handle strings that are not null terminated.

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

n = readlink(fullpath, linktarget, sizeof(linktarget));

Check warning on line 150 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L150

Usage of the `readlink` function call hints at a potential Time Of Check Time Of Use (TOCTOU) vulnerability.
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));

Check notice on line 156 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L156

Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126).
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;

Check notice on line 163 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L163

The scope of the variable 'x' can be reduced.
BIO* b;
const char* ext;
unsigned char digest[EVP_MAX_MD_SIZE];

Check notice on line 166 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L166

The scope of the variable 'digest' can be reduced.
X509_NAME* name = NULL;
int i, type, ret = -1;

Check notice on line 168 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L168

The scope of the variable 'type' can be reduced.

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) {

Check warning on line 224 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L224

Usage of the `access` function call hints at a potential Time Of Check Time Of Use (TOCTOU) vulnerability.
EVLOG_error << "Access denied '" << std::string(dirname) << "'";
return -1;
}

buflen = strlen(dirname);

Check notice on line 229 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L229

Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126).

Check failure on line 229 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L229

The `strlen` family of functions does not handle strings that are not null terminated.
pathsep = (buflen && dirname[buflen - 1] == '/') ? "" : "/";
buflen += NAME_MAX + 2;
buf = (char*)(malloc(buflen));

Check notice on line 232 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L232

C-style pointer casting
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);

Check warning on line 263 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L263

When handling sensitive information in a buffer, it's important to ensure that the data is securely erased before the buffer is deleted or reused.
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)) << ":";

Check failure on line 271 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L271

The `strlen` family of functions does not handle strings that are not null terminated.

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)) << " -> "

Check notice on line 276 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L276

Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126).

Check failure on line 276 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L276

The `strlen` family of functions does not handle strings that are not null terminated.
<< std::string(buf, strlen(buf));

Check notice on line 277 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L277

Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126).

Check failure on line 277 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L277

The `strlen` family of functions does not handle strings that are not null terminated.
} 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));

Check notice on line 286 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L286

Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126).

Check failure on line 286 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L286

The `strlen` family of functions does not handle strings that are not null terminated.
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));

Check notice on line 293 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L293

Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126).

Check failure on line 293 in 3rd_party/cert_rehash/c_rehash.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

3rd_party/cert_rehash/c_rehash.hpp#L293

The `strlen` family of functions does not handle strings that are not null terminated.
unlink(buf);
}
free(ei->filename);
free(ei);
}
free(bi);
}
hash_table[i] = NULL;
}

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

} // namespace evse_security
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ if (EVSE_SECURITY_INSTALL)
PATTERN "detail" EXCLUDE
)

install(
DIRECTORY 3rd_party/
TYPE INCLUDE
)

evc_setup_package(
NAME everest-evse_security
NAMESPACE everest
Expand Down
1 change: 1 addition & 0 deletions lib/evse_security/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ endif()
target_include_directories(evse_security
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/3rd_party>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)

Expand Down
12 changes: 8 additions & 4 deletions lib/evse_security/evse_security.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <set>
#include <stdio.h>

#include <cert_rehash/c_rehash.hpp>

#include <evse_security/certificate/x509_bundle.hpp>
#include <evse_security/certificate/x509_hierarchy.hpp>
#include <evse_security/certificate/x509_wrapper.hpp>
Expand Down Expand Up @@ -1563,12 +1565,14 @@ std::string EvseSecurity::get_verify_location(CaCertificateType certificate_type
// multiple entries (should be 3) as per the specification
X509CertificateBundle verify_location(this->ca_bundle_path_map.at(certificate_type), EncodingFormat::PEM);

const auto location_path = verify_location.get_path();

EVLOG_info << "Requesting certificate location: ["
<< conversions::ca_certificate_type_to_string(certificate_type)
<< "] location:" << verify_location.get_path();
<< conversions::ca_certificate_type_to_string(certificate_type) << "] location:" << location_path;

if (!verify_location.empty()) {
return verify_location.get_path();
if (!verify_location.empty() &&
(!verify_location.is_using_directory() || hash_dir(location_path.c_str()) == 0)) {
return location_path;
}

} catch (const CertificateLoadException& e) {
Expand Down

0 comments on commit e69a3c3

Please sign in to comment.