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
0 commit comments