]>
Commit | Line | Data |
---|---|---|
f4bdc6a3 MW |
1 | /* |
2 | * Copyright (C) 2011 Martin Willi | |
3 | * Copyright (C) 2011 revosec AG | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the | |
7 | * Free Software Foundation; either version 2 of the License, or (at your | |
8 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, but | |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
13 | * for more details. | |
14 | */ | |
15 | ||
16 | #include "certexpire_export.h" | |
17 | ||
e64fdfa1 MW |
18 | #include "certexpire_cron.h" |
19 | ||
20 | #include <time.h> | |
21 | #include <limits.h> | |
22 | #include <errno.h> | |
23 | ||
f05b4272 | 24 | #include <utils/debug.h> |
aa8898bc | 25 | #include <daemon.h> |
12642a68 | 26 | #include <collections/hashtable.h> |
f4bdc6a3 MW |
27 | #include <threading/mutex.h> |
28 | #include <credentials/certificates/x509.h> | |
29 | ||
30 | typedef struct private_certexpire_export_t private_certexpire_export_t; | |
31 | ||
32 | /** | |
33 | * Private data of an certexpire_export_t object. | |
34 | */ | |
35 | struct private_certexpire_export_t { | |
36 | ||
37 | /** | |
38 | * Public certexpire_export_t interface. | |
39 | */ | |
40 | certexpire_export_t public; | |
41 | ||
42 | /** | |
43 | * hashtable caching local trustchains, mapping entry_t => entry_t | |
44 | */ | |
45 | hashtable_t *local; | |
46 | ||
47 | /** | |
48 | * hashtable caching remote trustchains, mapping entry_t => entry_t | |
49 | */ | |
50 | hashtable_t *remote; | |
51 | ||
52 | /** | |
53 | * Mutex to lock hashtables | |
54 | */ | |
55 | mutex_t *mutex; | |
e64fdfa1 MW |
56 | |
57 | /** | |
58 | * Cronjob for export | |
59 | */ | |
60 | certexpire_cron_t *cron; | |
61 | ||
62 | /** | |
63 | * strftime() format to generate local CSV file | |
64 | */ | |
65 | char *local_path; | |
66 | ||
67 | /** | |
68 | * strftime() format to generate remote CSV file | |
69 | */ | |
70 | char *remote_path; | |
71 | ||
72 | /** | |
73 | * stftime() format of the exported expiration date | |
74 | */ | |
75 | char *format; | |
76 | ||
77 | /** | |
78 | * CSV field separator | |
79 | */ | |
80 | char *separator; | |
81 | ||
82 | /** | |
83 | * TRUE to use fixed field count, CA at end | |
84 | */ | |
85 | bool fixed_fields; | |
3f455c73 MW |
86 | |
87 | /** | |
88 | * String to use in empty fields, if using fixed_fields | |
89 | */ | |
90 | char *empty_string; | |
8fc89db7 MW |
91 | |
92 | /** | |
93 | * Force export of all trustchains we have a private key for | |
94 | */ | |
95 | bool force; | |
f4bdc6a3 MW |
96 | }; |
97 | ||
98 | /** | |
99 | * Maximum number of expiration dates we store (for subject + IM CAs + CA) | |
100 | */ | |
101 | #define MAX_TRUSTCHAIN_LENGTH 7 | |
102 | ||
103 | /** | |
104 | * Hashtable entry | |
105 | */ | |
106 | typedef struct { | |
107 | /** certificate subject as subjectAltName or CN of a DN */ | |
108 | char id[128]; | |
109 | /** list of expiration dates, 0 if no certificate */ | |
110 | time_t expire[MAX_TRUSTCHAIN_LENGTH]; | |
111 | } entry_t; | |
112 | ||
113 | /** | |
114 | * Hashtable hash function | |
115 | */ | |
116 | static u_int hash(entry_t *key) | |
117 | { | |
118 | return chunk_hash(chunk_create(key->id, strlen(key->id))); | |
119 | } | |
120 | ||
121 | /** | |
122 | * Hashtable equals function | |
123 | */ | |
124 | static bool equals(entry_t *a, entry_t *b) | |
125 | { | |
126 | return streq(a->id, b->id); | |
127 | } | |
128 | ||
e64fdfa1 MW |
129 | /** |
130 | * Export a single trustchain to a path | |
131 | */ | |
132 | static void export_csv(private_certexpire_export_t *this, char *path, | |
133 | hashtable_t *chains) | |
134 | { | |
135 | enumerator_t *enumerator; | |
136 | char buf[PATH_MAX]; | |
137 | entry_t *entry; | |
138 | FILE *file; | |
139 | struct tm tm; | |
140 | time_t t; | |
141 | int i; | |
142 | ||
143 | t = time(NULL); | |
144 | localtime_r(&t, &tm); | |
145 | ||
146 | strftime(buf, sizeof(buf), path, &tm); | |
147 | file = fopen(buf, "a"); | |
148 | if (file) | |
149 | { | |
150 | DBG1(DBG_CFG, "exporting expiration dates of %d trustchain%s to '%s'", | |
151 | chains->get_count(chains), | |
152 | chains->get_count(chains) == 1 ? "" : "s", buf); | |
153 | this->mutex->lock(this->mutex); | |
154 | enumerator = chains->create_enumerator(chains); | |
155 | while (enumerator->enumerate(enumerator, NULL, &entry)) | |
156 | { | |
157 | fprintf(file, "%s%s", entry->id, this->separator); | |
158 | for (i = 0; i < MAX_TRUSTCHAIN_LENGTH; i++) | |
159 | { | |
160 | if (entry->expire[i]) | |
161 | { | |
162 | localtime_r(&entry->expire[i], &tm); | |
163 | strftime(buf, sizeof(buf), this->format, &tm); | |
164 | fprintf(file, "%s", buf); | |
165 | } | |
166 | if (i == MAX_TRUSTCHAIN_LENGTH - 1) | |
167 | { | |
168 | fprintf(file, "\n"); | |
169 | } | |
3f455c73 | 170 | else if (entry->expire[i]) |
e64fdfa1 MW |
171 | { |
172 | fprintf(file, "%s", this->separator); | |
173 | } | |
3f455c73 MW |
174 | else if (this->fixed_fields) |
175 | { | |
176 | fprintf(file, "%s%s", this->empty_string, this->separator); | |
177 | } | |
e64fdfa1 MW |
178 | } |
179 | chains->remove_at(chains, enumerator); | |
180 | free(entry); | |
181 | } | |
182 | enumerator->destroy(enumerator); | |
183 | this->mutex->unlock(this->mutex); | |
184 | fclose(file); | |
185 | } | |
186 | else | |
187 | { | |
188 | DBG1(DBG_CFG, "opening CSV file '%s' failed: %s", buf, strerror(errno)); | |
189 | } | |
190 | } | |
191 | ||
f4bdc6a3 MW |
192 | METHOD(certexpire_export_t, add, void, |
193 | private_certexpire_export_t *this, linked_list_t *trustchain, bool local) | |
194 | { | |
195 | enumerator_t *enumerator; | |
196 | certificate_t *cert; | |
197 | int count; | |
198 | ||
e64fdfa1 MW |
199 | /* don't store expiration dates if no path configured */ |
200 | if (local) | |
201 | { | |
202 | if (!this->local_path) | |
203 | { | |
204 | return; | |
205 | } | |
206 | } | |
207 | else | |
208 | { | |
209 | if (!this->remote_path) | |
210 | { | |
211 | return; | |
212 | } | |
213 | } | |
214 | ||
f4bdc6a3 MW |
215 | count = min(trustchain->get_count(trustchain), MAX_TRUSTCHAIN_LENGTH) - 1; |
216 | ||
217 | enumerator = trustchain->create_enumerator(trustchain); | |
218 | /* get subject cert */ | |
219 | if (enumerator->enumerate(enumerator, &cert)) | |
220 | { | |
221 | identification_t *id; | |
222 | entry_t *entry; | |
223 | int i; | |
224 | ||
225 | INIT(entry); | |
226 | ||
227 | /* prefer FQDN subjectAltName... */ | |
228 | if (cert->get_type(cert) == CERT_X509) | |
229 | { | |
230 | x509_t *x509 = (x509_t*)cert; | |
231 | enumerator_t *sans; | |
232 | ||
233 | sans = x509->create_subjectAltName_enumerator(x509); | |
234 | while (sans->enumerate(sans, &id)) | |
235 | { | |
236 | if (id->get_type(id) == ID_FQDN) | |
237 | { | |
238 | snprintf(entry->id, sizeof(entry->id), "%Y", id); | |
239 | break; | |
240 | } | |
241 | } | |
242 | sans->destroy(sans); | |
243 | } | |
244 | /* fallback to CN of DN */ | |
245 | if (!entry->id[0]) | |
246 | { | |
247 | enumerator_t *parts; | |
248 | id_part_t part; | |
249 | chunk_t data; | |
250 | ||
251 | id = cert->get_subject(cert); | |
252 | parts = id->create_part_enumerator(id); | |
253 | while (parts->enumerate(parts, &part, &data)) | |
254 | { | |
255 | if (part == ID_PART_RDN_CN) | |
256 | { | |
257 | snprintf(entry->id, sizeof(entry->id), "%.*s", | |
258 | (int)data.len, data.ptr); | |
259 | break; | |
260 | } | |
261 | } | |
262 | parts->destroy(parts); | |
263 | } | |
264 | /* no usable identity? skip */ | |
265 | if (!entry->id[0]) | |
266 | { | |
267 | enumerator->destroy(enumerator); | |
268 | free(entry); | |
269 | return; | |
270 | } | |
271 | ||
272 | /* get intermediate CA expiration dates */ | |
273 | cert->get_validity(cert, NULL, NULL, &entry->expire[0]); | |
274 | for (i = 1; i < count && enumerator->enumerate(enumerator, &cert); i++) | |
275 | { | |
276 | cert->get_validity(cert, NULL, NULL, &entry->expire[i]); | |
277 | } | |
278 | /* get CA expiration date, as last array entry */ | |
279 | if (enumerator->enumerate(enumerator, &cert)) | |
280 | { | |
281 | cert->get_validity(cert, NULL, NULL, | |
282 | &entry->expire[MAX_TRUSTCHAIN_LENGTH - 1]); | |
283 | } | |
284 | this->mutex->lock(this->mutex); | |
285 | if (local) | |
286 | { | |
287 | entry = this->local->put(this->local, entry, entry); | |
288 | } | |
289 | else | |
290 | { | |
291 | entry = this->remote->put(this->remote, entry, entry); | |
292 | } | |
293 | this->mutex->unlock(this->mutex); | |
294 | if (entry) | |
295 | { | |
296 | free(entry); | |
297 | } | |
e64fdfa1 MW |
298 | if (!this->cron) |
299 | { /* export directly if no cron job defined */ | |
300 | if (local) | |
301 | { | |
302 | export_csv(this, this->local_path, this->local); | |
303 | } | |
304 | else | |
305 | { | |
306 | export_csv(this, this->remote_path, this->remote); | |
307 | } | |
308 | } | |
f4bdc6a3 MW |
309 | } |
310 | enumerator->destroy(enumerator); | |
311 | } | |
312 | ||
8fc89db7 MW |
313 | /** |
314 | * Add trustchains we have a private key for to the list | |
315 | */ | |
316 | static void add_local_certs(private_certexpire_export_t *this) | |
317 | { | |
318 | enumerator_t *enumerator; | |
319 | certificate_t *cert; | |
320 | ||
321 | enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, | |
322 | CERT_X509, KEY_ANY, NULL, FALSE); | |
323 | while (enumerator->enumerate(enumerator, &cert)) | |
324 | { | |
325 | linked_list_t *trustchain; | |
326 | private_key_t *private; | |
327 | public_key_t *public; | |
328 | identification_t *keyid; | |
329 | chunk_t chunk; | |
330 | x509_t *x509 = (x509_t*)cert; | |
331 | ||
332 | trustchain = linked_list_create(); | |
333 | ||
334 | public = cert->get_public_key(cert); | |
335 | if (public) | |
336 | { | |
337 | if (public->get_fingerprint(public, KEYID_PUBKEY_INFO_SHA1, &chunk)) | |
338 | { | |
339 | keyid = identification_create_from_encoding(ID_KEY_ID, chunk); | |
340 | private = lib->credmgr->get_private(lib->credmgr, | |
341 | public->get_type(public), keyid, NULL); | |
342 | keyid->destroy(keyid); | |
343 | if (private) | |
344 | { | |
345 | trustchain->insert_last(trustchain, cert->get_ref(cert)); | |
346 | ||
347 | while (!(x509->get_flags(x509) & X509_SELF_SIGNED)) | |
348 | { | |
349 | cert = lib->credmgr->get_cert(lib->credmgr, CERT_X509, | |
350 | KEY_ANY, cert->get_issuer(cert), FALSE); | |
351 | if (!cert) | |
352 | { | |
353 | break; | |
354 | } | |
355 | x509 = (x509_t*)cert; | |
356 | trustchain->insert_last(trustchain, cert); | |
357 | } | |
358 | private->destroy(private); | |
359 | } | |
360 | } | |
361 | public->destroy(public); | |
362 | } | |
363 | add(this, trustchain, TRUE); | |
364 | trustchain->destroy_offset(trustchain, offsetof(certificate_t, destroy)); | |
365 | } | |
366 | enumerator->destroy(enumerator); | |
367 | } | |
368 | ||
369 | /** | |
370 | * Export cached trustchain expiration dates to CSV files | |
371 | */ | |
372 | static void cron_export(private_certexpire_export_t *this) | |
373 | { | |
374 | if (this->local_path) | |
375 | { | |
376 | if (this->force) | |
377 | { | |
378 | add_local_certs(this); | |
379 | } | |
380 | export_csv(this, this->local_path, this->local); | |
381 | } | |
382 | if (this->remote_path) | |
383 | { | |
384 | export_csv(this, this->remote_path, this->remote); | |
385 | } | |
386 | } | |
387 | ||
f4bdc6a3 MW |
388 | METHOD(certexpire_export_t, destroy, void, |
389 | private_certexpire_export_t *this) | |
390 | { | |
391 | entry_t *key, *value; | |
392 | enumerator_t *enumerator; | |
393 | ||
394 | enumerator = this->local->create_enumerator(this->local); | |
395 | while (enumerator->enumerate(enumerator, &key, &value)) | |
396 | { | |
397 | free(value); | |
398 | } | |
399 | enumerator->destroy(enumerator); | |
400 | enumerator = this->remote->create_enumerator(this->remote); | |
401 | while (enumerator->enumerate(enumerator, &key, &value)) | |
402 | { | |
403 | free(value); | |
404 | } | |
405 | enumerator->destroy(enumerator); | |
406 | ||
407 | this->local->destroy(this->local); | |
408 | this->remote->destroy(this->remote); | |
e64fdfa1 | 409 | DESTROY_IF(this->cron); |
f4bdc6a3 MW |
410 | this->mutex->destroy(this->mutex); |
411 | free(this); | |
412 | } | |
413 | ||
414 | /** | |
415 | * See header | |
416 | */ | |
417 | certexpire_export_t *certexpire_export_create() | |
418 | { | |
419 | private_certexpire_export_t *this; | |
e64fdfa1 | 420 | char *cron; |
f4bdc6a3 MW |
421 | |
422 | INIT(this, | |
423 | .public = { | |
424 | .add = _add, | |
425 | .destroy = _destroy, | |
426 | }, | |
427 | .local = hashtable_create((hashtable_hash_t)hash, | |
428 | (hashtable_equals_t)equals, 4), | |
429 | .remote = hashtable_create((hashtable_hash_t)hash, | |
430 | (hashtable_equals_t)equals, 32), | |
431 | .mutex = mutex_create(MUTEX_TYPE_DEFAULT), | |
e64fdfa1 | 432 | .local_path = lib->settings->get_str(lib->settings, |
d223fe80 TB |
433 | "%s.plugins.certexpire.csv.local", |
434 | NULL, lib->ns), | |
e64fdfa1 | 435 | .remote_path = lib->settings->get_str(lib->settings, |
d223fe80 TB |
436 | "%s.plugins.certexpire.csv.remote", |
437 | NULL, lib->ns), | |
e64fdfa1 | 438 | .separator = lib->settings->get_str(lib->settings, |
d223fe80 TB |
439 | "%s.plugins.certexpire.csv.separator", |
440 | ",", lib->ns), | |
e64fdfa1 | 441 | .format = lib->settings->get_str(lib->settings, |
d223fe80 TB |
442 | "%s.plugins.certexpire.csv.format", |
443 | "%d:%m:%Y", lib->ns), | |
e64fdfa1 | 444 | .fixed_fields = lib->settings->get_bool(lib->settings, |
d223fe80 TB |
445 | "%s.plugins.certexpire.csv.fixed_fields", |
446 | TRUE, lib->ns), | |
3f455c73 | 447 | .empty_string = lib->settings->get_str(lib->settings, |
d223fe80 TB |
448 | "%s.plugins.certexpire.csv.empty_string", |
449 | "", lib->ns), | |
8fc89db7 | 450 | .force = lib->settings->get_bool(lib->settings, |
d223fe80 TB |
451 | "%s.plugins.certexpire.csv.force", |
452 | TRUE, lib->ns), | |
f4bdc6a3 MW |
453 | ); |
454 | ||
e64fdfa1 | 455 | cron = lib->settings->get_str(lib->settings, |
42500c27 | 456 | "%s.plugins.certexpire.csv.cron", |
d223fe80 | 457 | NULL, lib->ns); |
e64fdfa1 MW |
458 | if (cron) |
459 | { | |
460 | this->cron = certexpire_cron_create(cron, | |
461 | (certexpire_cron_job_t)cron_export, this); | |
462 | } | |
f4bdc6a3 MW |
463 | return &this->public; |
464 | } |