Skip to content

Commit e69a3c3

Browse files
committed
Move hashing directories to evse-security
Signed-off-by: Ivan Rogach <[email protected]>
1 parent ac71139 commit e69a3c3

File tree

4 files changed

+324
-4
lines changed

4 files changed

+324
-4
lines changed

3rd_party/cert_rehash/c_rehash.hpp

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
/* c_rehash.c - Create hash symlinks for certificates
2+
* C implementation based on the original Perl and shell versions
3+
*
4+
* Copyright (c) 2013-2014 Timo Teräs <[email protected]>
5+
* All rights reserved.
6+
*
7+
* This software is licensed under the MIT License.
8+
* Full license available at: http://opensource.org/licenses/MIT
9+
*/
10+
11+
#include <dirent.h>
12+
#include <limits.h>
13+
#include <stdio.h>
14+
#include <string.h>
15+
#include <sys/stat.h>
16+
#include <unistd.h>
17+
18+
#include <openssl/evp.h>
19+
#include <openssl/pem.h>
20+
#include <openssl/x509.h>
21+
22+
#include <everest/logging.hpp>
23+
24+
#define MAX_COLLISIONS 256
25+
#define countof(x) (sizeof(x) / sizeof(x[0]))
26+
27+
namespace evse_security {
28+
29+
struct entry_info {
30+
struct entry_info* next;
31+
char* filename;
32+
unsigned short old_id;
33+
unsigned char need_symlink;
34+
unsigned char digest[EVP_MAX_MD_SIZE];
35+
};
36+
37+
struct bucket_info {
38+
struct bucket_info* next;
39+
struct entry_info *first_entry, *last_entry;
40+
unsigned int hash;
41+
unsigned short type;
42+
unsigned short num_needed;
43+
};
44+
45+
enum Type {
46+
TYPE_CERT = 0,
47+
TYPE_CRL
48+
};
49+
50+
static const char* symlink_extensions[] = {"", "r"};
51+
static const char* file_extensions[] = {"pem", "crt", "cer", "crl"};
52+
53+
static int evpmdsize;
54+
static const EVP_MD* evpmd;
55+
56+
static struct bucket_info* hash_table[257];
57+
58+
static void bit_set(unsigned char* set, unsigned bit) {
59+
set[bit / 8] |= 1 << (bit % 8);
60+
}
61+
62+
static int bit_isset(unsigned char* set, unsigned bit) {
63+
return set[bit / 8] & (1 << (bit % 8));
64+
}
65+
66+
static void add_entry(int type, unsigned int hash, const char* filename, const unsigned char* digest, int need_symlink,
67+
unsigned short old_id) {
68+
struct bucket_info* bi;
69+
struct entry_info *ei, *found = NULL;
70+
unsigned int ndx = (type + hash) % countof(hash_table);
71+
72+
for (bi = hash_table[ndx]; bi; bi = bi->next)
73+
if (bi->type == type && bi->hash == hash)
74+
break;
75+
if (!bi) {
76+
bi = (bucket_info*)(calloc(1, sizeof(*bi)));
77+
if (!bi)
78+
return;
79+
bi->next = hash_table[ndx];
80+
bi->type = type;
81+
bi->hash = hash;
82+
hash_table[ndx] = bi;
83+
}
84+
85+
for (ei = bi->first_entry; ei; ei = ei->next) {
86+
if (digest && memcmp(digest, ei->digest, evpmdsize) == 0) {
87+
EVLOG_warning << "Skipping duplicate certificate in file " << std::string(filename);
88+
return;
89+
}
90+
if (!strcmp(filename, ei->filename)) {
91+
found = ei;
92+
if (!digest)
93+
break;
94+
}
95+
}
96+
ei = found;
97+
if (!ei) {
98+
if (bi->num_needed >= MAX_COLLISIONS)
99+
return;
100+
ei = (entry_info*)(calloc(1, sizeof(*ei)));
101+
if (!ei)
102+
return;
103+
104+
ei->old_id = ~0;
105+
ei->filename = strdup(filename);
106+
if (bi->last_entry)
107+
bi->last_entry->next = ei;
108+
if (!bi->first_entry)
109+
bi->first_entry = ei;
110+
bi->last_entry = ei;
111+
}
112+
113+
if (old_id < ei->old_id)
114+
ei->old_id = old_id;
115+
if (need_symlink && !ei->need_symlink) {
116+
ei->need_symlink = 1;
117+
bi->num_needed++;
118+
memcpy(ei->digest, digest, evpmdsize);
119+
}
120+
}
121+
122+
static int handle_symlink(const char* filename, const char* fullpath) {
123+
static char xdigit[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11,
124+
12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
125+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15};
126+
char linktarget[NAME_MAX], *endptr;
127+
unsigned int hash = 0;
128+
unsigned char ch;
129+
int i, type, id;
130+
ssize_t n;
131+
132+
for (i = 0; i < 8; i++) {
133+
ch = filename[i] - '0';
134+
if (ch >= countof(xdigit) || xdigit[ch] < 0)
135+
return -1;
136+
hash <<= 4;
137+
hash += xdigit[ch];
138+
}
139+
if (filename[i++] != '.')
140+
return -1;
141+
for (type = countof(symlink_extensions) - 1; type > 0; type--)
142+
if (strcasecmp(symlink_extensions[type], &filename[i]) == 0)
143+
break;
144+
i += strlen(symlink_extensions[type]);
145+
146+
id = strtoul(&filename[i], &endptr, 10);
147+
if (*endptr != 0)
148+
return -1;
149+
150+
n = readlink(fullpath, linktarget, sizeof(linktarget));
151+
if (n >= sizeof(linktarget) || n < 0)
152+
return -1;
153+
linktarget[n] = 0;
154+
155+
EVLOG_debug << "Found existing symlink " << std::string(filename) << " for " << hash << " (" << type
156+
<< "), certname " << std::string(linktarget, strlen(linktarget));
157+
add_entry(type, hash, linktarget, NULL, 0, id);
158+
return 0;
159+
}
160+
161+
static int handle_certificate(const char* filename, const char* fullpath) {
162+
STACK_OF(X509_INFO) * inf;
163+
X509_INFO* x;
164+
BIO* b;
165+
const char* ext;
166+
unsigned char digest[EVP_MAX_MD_SIZE];
167+
X509_NAME* name = NULL;
168+
int i, type, ret = -1;
169+
170+
ext = strrchr(filename, '.');
171+
if (ext == NULL)
172+
return 0;
173+
for (i = 0; i < countof(file_extensions); i++) {
174+
if (strcasecmp(file_extensions[i], ext + 1) == 0)
175+
break;
176+
}
177+
if (i >= countof(file_extensions))
178+
return -1;
179+
180+
b = BIO_new_file(fullpath, "r");
181+
if (!b)
182+
return -1;
183+
inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL);
184+
BIO_free(b);
185+
if (!inf)
186+
return -1;
187+
188+
if (sk_X509_INFO_num(inf) == 1) {
189+
x = sk_X509_INFO_value(inf, 0);
190+
if (x->x509) {
191+
type = TYPE_CERT;
192+
name = X509_get_subject_name(x->x509);
193+
X509_digest(x->x509, evpmd, digest, NULL);
194+
} else if (x->crl) {
195+
type = TYPE_CRL;
196+
name = X509_CRL_get_issuer(x->crl);
197+
X509_CRL_digest(x->crl, evpmd, digest, NULL);
198+
}
199+
if (name)
200+
add_entry(type, X509_NAME_hash(name), filename, digest, 1, ~0);
201+
} else {
202+
EVLOG_warning << std::string(filename) << " does not contain exactly one certificate or CRL: skipping";
203+
}
204+
205+
sk_X509_INFO_pop_free(inf, X509_INFO_free);
206+
207+
return ret;
208+
}
209+
210+
static int hash_dir(const char* dirname) {
211+
struct bucket_info *bi, *nextbi;
212+
struct entry_info *ei, *nextei;
213+
struct dirent* de;
214+
struct stat st;
215+
unsigned char idmask[MAX_COLLISIONS / 8];
216+
int i, n, nextid, buflen, ret = -1;
217+
const char* pathsep;
218+
char* buf;
219+
DIR* d;
220+
221+
evpmd = EVP_sha1();
222+
evpmdsize = EVP_MD_size(evpmd);
223+
224+
if (access(dirname, R_OK | W_OK | X_OK) != 0) {
225+
EVLOG_error << "Access denied '" << std::string(dirname) << "'";
226+
return -1;
227+
}
228+
229+
buflen = strlen(dirname);
230+
pathsep = (buflen && dirname[buflen - 1] == '/') ? "" : "/";
231+
buflen += NAME_MAX + 2;
232+
buf = (char*)(malloc(buflen));
233+
if (buf == NULL)
234+
goto err;
235+
236+
EVLOG_debug << "Doing " << std::string(dirname);
237+
d = opendir(dirname);
238+
if (!d)
239+
goto err;
240+
241+
while ((de = readdir(d)) != NULL) {
242+
if (snprintf(buf, buflen, "%s%s%s", dirname, pathsep, de->d_name) >= buflen)
243+
continue;
244+
if (lstat(buf, &st) < 0)
245+
continue;
246+
if (S_ISLNK(st.st_mode) && handle_symlink(de->d_name, buf) == 0)
247+
continue;
248+
if (strcmp(buf, "/etc/ssl/certs/ca-certificates.crt") == 0) {
249+
/* Ignore the /etc/ssl/certs/ca-certificates.crt file */
250+
EVLOG_debug << "Skipping /etc/ssl/certs/ca-certificates.crt file";
251+
continue;
252+
}
253+
handle_certificate(de->d_name, buf);
254+
}
255+
closedir(d);
256+
257+
for (i = 0; i < countof(hash_table); i++) {
258+
for (bi = hash_table[i]; bi; bi = nextbi) {
259+
nextbi = bi->next;
260+
EVLOG_debug << "Type " << bi->type << " hash " << bi->hash << " num entries " << bi->num_needed << ":";
261+
262+
nextid = 0;
263+
memset(idmask, 0, (bi->num_needed + 7) / 8);
264+
for (ei = bi->first_entry; ei; ei = ei->next)
265+
if (ei->old_id < bi->num_needed)
266+
bit_set(idmask, ei->old_id);
267+
268+
for (ei = bi->first_entry; ei; ei = nextei) {
269+
nextei = ei->next;
270+
EVLOG_debug << "\t(old_id " << ei->old_id << ", need_symlink " << ei->need_symlink << ") Cert "
271+
<< std::string(ei->filename, strlen(ei->filename)) << ":";
272+
273+
if (ei->old_id < bi->num_needed) {
274+
/* Link exists, and is used as-is */
275+
snprintf(buf, buflen, "%08x.%s%d", bi->hash, symlink_extensions[bi->type], ei->old_id);
276+
EVLOG_debug << "link " << std::string(ei->filename, strlen(ei->filename)) << " -> "
277+
<< std::string(buf, strlen(buf));
278+
} else if (ei->need_symlink) {
279+
/* New link needed (it may replace something) */
280+
while (bit_isset(idmask, nextid))
281+
nextid++;
282+
283+
snprintf(buf, buflen, "%s%s%n%08x.%s%d", dirname, pathsep, &n, bi->hash,
284+
symlink_extensions[bi->type], nextid);
285+
EVLOG_debug << "link " << std::string(ei->filename, strlen(ei->filename)) << " -> "
286+
<< std::string(buf + n, strlen(buf + n));
287+
unlink(buf);
288+
symlink(ei->filename, buf);
289+
} else {
290+
/* Link to be deleted */
291+
snprintf(buf, buflen, "%s%s%n%08x.%s%d", dirname, pathsep, &n, bi->hash,
292+
symlink_extensions[bi->type], ei->old_id);
293+
EVLOG_debug << "unlink " << std::string(buf + n, strlen(buf + n));
294+
unlink(buf);
295+
}
296+
free(ei->filename);
297+
free(ei);
298+
}
299+
free(bi);
300+
}
301+
hash_table[i] = NULL;
302+
}
303+
304+
ret = 0;
305+
err:
306+
free(buf);
307+
return ret;
308+
}
309+
310+
} // namespace evse_security

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ if (EVSE_SECURITY_INSTALL)
7575
PATTERN "detail" EXCLUDE
7676
)
7777

78+
install(
79+
DIRECTORY 3rd_party/
80+
TYPE INCLUDE
81+
)
82+
7883
evc_setup_package(
7984
NAME everest-evse_security
8085
NAMESPACE everest

lib/evse_security/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ endif()
2828
target_include_directories(evse_security
2929
PUBLIC
3030
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
31+
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/3rd_party>
3132
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
3233
)
3334

lib/evse_security/evse_security.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include <set>
1313
#include <stdio.h>
1414

15+
#include <cert_rehash/c_rehash.hpp>
16+
1517
#include <evse_security/certificate/x509_bundle.hpp>
1618
#include <evse_security/certificate/x509_hierarchy.hpp>
1719
#include <evse_security/certificate/x509_wrapper.hpp>
@@ -1563,12 +1565,14 @@ std::string EvseSecurity::get_verify_location(CaCertificateType certificate_type
15631565
// multiple entries (should be 3) as per the specification
15641566
X509CertificateBundle verify_location(this->ca_bundle_path_map.at(certificate_type), EncodingFormat::PEM);
15651567

1568+
const auto location_path = verify_location.get_path();
1569+
15661570
EVLOG_info << "Requesting certificate location: ["
1567-
<< conversions::ca_certificate_type_to_string(certificate_type)
1568-
<< "] location:" << verify_location.get_path();
1571+
<< conversions::ca_certificate_type_to_string(certificate_type) << "] location:" << location_path;
15691572

1570-
if (!verify_location.empty()) {
1571-
return verify_location.get_path();
1573+
if (!verify_location.empty() &&
1574+
(!verify_location.is_using_directory() || hash_dir(location_path.c_str()) == 0)) {
1575+
return location_path;
15721576
}
15731577

15741578
} catch (const CertificateLoadException& e) {

0 commit comments

Comments
 (0)