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 <timo.teras@iki.fi>
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
0 commit comments