]> git.ipfire.org Git - people/ms/strongswan.git/blob - src/pki/commands/signcrl.c
pki: Switch to binary mode on Windows when reading/writing DER to FDs
[people/ms/strongswan.git] / src / pki / commands / signcrl.c
1 /*
2 * Copyright (C) 2010 Martin Willi
3 * Copyright (C) 2010 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 <time.h>
17
18 #include "pki.h"
19
20 #include <utils/debug.h>
21 #include <collections/linked_list.h>
22 #include <credentials/certificates/certificate.h>
23 #include <credentials/certificates/x509.h>
24 #include <credentials/certificates/crl.h>
25
26
27 /**
28 * Entry for a revoked certificate
29 */
30 typedef struct {
31 chunk_t serial;
32 crl_reason_t reason;
33 time_t date;
34 } revoked_t;
35
36 /**
37 * Add a revocation to the list
38 */
39 static void add_revoked(linked_list_t *list,
40 chunk_t serial, crl_reason_t reason, time_t date)
41 {
42 revoked_t *revoked;
43
44 INIT(revoked,
45 .serial = chunk_clone(serial),
46 .reason = reason,
47 .date = date,
48 );
49 list->insert_last(list, revoked);
50 }
51
52 /**
53 * Destroy a reason entry
54 */
55 static void revoked_destroy(revoked_t *revoked)
56 {
57 free(revoked->serial.ptr);
58 free(revoked);
59 }
60
61 /**
62 * Filter for revoked enumerator
63 */
64 static bool filter(void *data, revoked_t **revoked, chunk_t *serial, void *p2,
65 time_t *date, void *p3, crl_reason_t *reason)
66 {
67 *serial = (*revoked)->serial;
68 *date = (*revoked)->date;
69 *reason = (*revoked)->reason;
70 return TRUE;
71 }
72
73 /**
74 * Extract the serial of a certificate, write it into buf
75 */
76 static int read_serial(char *file, char *buf, int buflen)
77 {
78 certificate_t *cert;
79 x509_t *x509;
80 chunk_t serial;
81
82 x509 = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
83 BUILD_FROM_FILE, file, BUILD_END);
84 cert = &x509->interface;
85 if (!cert)
86 {
87 return -1;
88 }
89 serial = x509->get_serial(x509);
90 if (serial.len == 0 || serial.len > buflen)
91 {
92 cert->destroy(cert);
93 return -2;
94 }
95 memcpy(buf, serial.ptr, serial.len);
96 cert->destroy(cert);
97 return serial.len;
98 }
99
100 /**
101 * Destroy a CDP
102 */
103 static void cdp_destroy(x509_cdp_t *this)
104 {
105 free(this->uri);
106 free(this);
107 }
108
109 /**
110 * Sign a CRL
111 */
112 static int sign_crl()
113 {
114 cred_encoding_type_t form = CERT_ASN1_DER;
115 private_key_t *private = NULL;
116 public_key_t *public = NULL;
117 certificate_t *ca = NULL, *crl = NULL;
118 crl_t *lastcrl = NULL;
119 x509_t *x509;
120 hash_algorithm_t digest = HASH_SHA1;
121 char *arg, *cacert = NULL, *cakey = NULL, *lastupdate = NULL, *error = NULL;
122 char *basecrl = NULL;
123 char serial[512], *keyid = NULL;
124 int serial_len = 0;
125 crl_reason_t reason = CRL_REASON_UNSPECIFIED;
126 time_t thisUpdate, nextUpdate, date = time(NULL);
127 time_t lifetime = 15 * 24 * 60 * 60;
128 char *datetu = NULL, *datenu = NULL, *dateform = NULL;
129 linked_list_t *list, *cdps;
130 enumerator_t *enumerator, *lastenum = NULL;
131 x509_cdp_t *cdp;
132 chunk_t crl_serial = chunk_empty, baseCrlNumber = chunk_empty;
133 chunk_t encoding = chunk_empty;
134
135 list = linked_list_create();
136 cdps = linked_list_create();
137
138 while (TRUE)
139 {
140 switch (command_getopt(&arg))
141 {
142 case 'h':
143 goto usage;
144 case 'g':
145 if (!enum_from_name(hash_algorithm_short_names, arg, &digest))
146 {
147 error = "invalid --digest type";
148 goto usage;
149 }
150 continue;
151 case 'c':
152 cacert = arg;
153 continue;
154 case 'k':
155 cakey = arg;
156 continue;
157 case 'x':
158 keyid = arg;
159 continue;
160 case 'a':
161 lastupdate = arg;
162 continue;
163 case 'l':
164 lifetime = atoi(arg) * 24 * 60 * 60;
165 if (!lifetime)
166 {
167 error = "invalid --lifetime value";
168 goto usage;
169 }
170 continue;
171 case 'D':
172 dateform = arg;
173 continue;
174 case 'F':
175 datetu = arg;
176 continue;
177 case 'T':
178 datenu = arg;
179 continue;
180 case 'z':
181 serial_len = read_serial(arg, serial, sizeof(serial));
182 if (serial_len < 0)
183 {
184 snprintf(serial, sizeof(serial),
185 "parsing certificate '%s' failed", arg);
186 error = serial;
187 goto error;
188 }
189 add_revoked(list, chunk_create(serial, serial_len), reason, date);
190 date = time(NULL);
191 serial_len = 0;
192 reason = CRL_REASON_UNSPECIFIED;
193 continue;
194 case 's':
195 {
196 chunk_t chunk;
197 int hex_len;
198
199 hex_len = strlen(arg);
200 if ((hex_len / 2) + (hex_len % 2) > sizeof(serial))
201 {
202 error = "invalid serial";
203 goto usage;
204 }
205 chunk = chunk_from_hex(chunk_create(arg, hex_len), serial);
206 serial_len = chunk.len;
207 add_revoked(list, chunk_create(serial, serial_len), reason, date);
208 date = time(NULL);
209 serial_len = 0;
210 reason = CRL_REASON_UNSPECIFIED;
211 continue;
212 }
213 case 'b':
214 basecrl = arg;
215 continue;
216 case 'u':
217 INIT(cdp,
218 .uri = strdup(arg),
219 );
220 cdps->insert_last(cdps, cdp);
221 continue;
222 case 'r':
223 if (streq(arg, "key-compromise"))
224 {
225 reason = CRL_REASON_KEY_COMPROMISE;
226 }
227 else if (streq(arg, "ca-compromise"))
228 {
229 reason = CRL_REASON_CA_COMPROMISE;
230 }
231 else if (streq(arg, "affiliation-changed"))
232 {
233 reason = CRL_REASON_AFFILIATION_CHANGED;
234 }
235 else if (streq(arg, "superseded"))
236 {
237 reason = CRL_REASON_SUPERSEDED;
238 }
239 else if (streq(arg, "cessation-of-operation"))
240 {
241 reason = CRL_REASON_CESSATION_OF_OPERATON;
242 }
243 else if (streq(arg, "certificate-hold"))
244 {
245 reason = CRL_REASON_CERTIFICATE_HOLD;
246 }
247 else
248 {
249 error = "invalid revocation reason";
250 goto usage;
251 }
252 continue;
253 case 'd':
254 date = atol(arg);
255 if (!date)
256 {
257 error = "invalid date";
258 goto usage;
259 }
260 continue;
261 case 'f':
262 if (!get_form(arg, &form, CRED_CERTIFICATE))
263 {
264 error = "invalid output format";
265 goto usage;
266 }
267 continue;
268 case EOF:
269 break;
270 default:
271 error = "invalid --signcrl option";
272 goto usage;
273 }
274 break;
275 }
276
277 if (!cacert)
278 {
279 error = "--cacert is required";
280 goto usage;
281 }
282 if (!cakey && !keyid)
283 {
284 error = "--cakey or --keyid is required";
285 goto usage;
286 }
287 if (!calculate_lifetime(dateform, datetu, datenu, lifetime,
288 &thisUpdate, &nextUpdate))
289 {
290 error = "invalid --this/next-update datetime";
291 goto usage;
292 }
293
294 ca = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
295 BUILD_FROM_FILE, cacert, BUILD_END);
296 if (!ca)
297 {
298 error = "parsing CA certificate failed";
299 goto error;
300 }
301 x509 = (x509_t*)ca;
302 if (!(x509->get_flags(x509) & (X509_CA | X509_CRL_SIGN)))
303 {
304 error = "CA certificate misses CA basicConstraint / CRLSign keyUsage";
305 goto error;
306 }
307 public = ca->get_public_key(ca);
308 if (!public)
309 {
310 error = "extracting CA certificate public key failed";
311 goto error;
312 }
313 if (cakey)
314 {
315 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
316 public->get_type(public),
317 BUILD_FROM_FILE, cakey, BUILD_END);
318 }
319 else
320 {
321 chunk_t chunk;
322
323 chunk = chunk_from_hex(chunk_create(keyid, strlen(keyid)), NULL);
324 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY,
325 BUILD_PKCS11_KEYID, chunk, BUILD_END);
326 free(chunk.ptr);
327 }
328 if (!private)
329 {
330 error = "loading CA private key failed";
331 goto error;
332 }
333 if (!private->belongs_to(private, public))
334 {
335 error = "CA private key does not match CA certificate";
336 goto error;
337 }
338
339 if (basecrl)
340 {
341 lastcrl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
342 BUILD_FROM_FILE, basecrl, BUILD_END);
343 if (!lastcrl)
344 {
345 error = "loading base CRL failed";
346 goto error;
347 }
348 baseCrlNumber = chunk_clone(lastcrl->get_serial(lastcrl));
349 crl_serial = baseCrlNumber;
350 DESTROY_IF((certificate_t*)lastcrl);
351 lastcrl = NULL;
352 }
353
354 if (lastupdate)
355 {
356 lastcrl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
357 BUILD_FROM_FILE, lastupdate, BUILD_END);
358 if (!lastcrl)
359 {
360 error = "loading lastUpdate CRL failed";
361 goto error;
362 }
363 crl_serial = lastcrl->get_serial(lastcrl);
364 lastenum = lastcrl->create_enumerator(lastcrl);
365 }
366 else
367 {
368 crl_serial = chunk_from_chars(0x00);
369 lastenum = enumerator_create_empty();
370 }
371
372 /* remove superfluous leading zeros */
373 while (crl_serial.len > 1 && crl_serial.ptr[0] == 0x00 &&
374 (crl_serial.ptr[1] & 0x80) == 0x00)
375 {
376 crl_serial = chunk_skip_zero(crl_serial);
377 }
378 crl_serial = chunk_clone(crl_serial);
379
380 /* increment the serial number by one */
381 chunk_increment(crl_serial);
382
383 enumerator = enumerator_create_filter(list->create_enumerator(list),
384 (void*)filter, NULL, NULL);
385 crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
386 BUILD_SIGNING_KEY, private, BUILD_SIGNING_CERT, ca,
387 BUILD_SERIAL, crl_serial,
388 BUILD_NOT_BEFORE_TIME, thisUpdate, BUILD_NOT_AFTER_TIME, nextUpdate,
389 BUILD_REVOKED_ENUMERATOR, enumerator,
390 BUILD_REVOKED_ENUMERATOR, lastenum, BUILD_DIGEST_ALG, digest,
391 BUILD_CRL_DISTRIBUTION_POINTS, cdps, BUILD_BASE_CRL, baseCrlNumber,
392 BUILD_END);
393 enumerator->destroy(enumerator);
394 lastenum->destroy(lastenum);
395 DESTROY_IF((certificate_t*)lastcrl);
396 free(crl_serial.ptr);
397
398 if (!crl)
399 {
400 error = "generating CRL failed";
401 goto error;
402 }
403 if (!crl->get_encoding(crl, form, &encoding))
404 {
405 error = "encoding CRL failed";
406 goto error;
407 }
408 set_file_mode(stdout, form);
409 if (fwrite(encoding.ptr, encoding.len, 1, stdout) != 1)
410 {
411 error = "writing CRL failed";
412 goto error;
413 }
414
415 error:
416 DESTROY_IF(public);
417 DESTROY_IF(private);
418 DESTROY_IF(ca);
419 DESTROY_IF(crl);
420 free(encoding.ptr);
421 free(baseCrlNumber.ptr);
422 list->destroy_function(list, (void*)revoked_destroy);
423 cdps->destroy_function(cdps, (void*)cdp_destroy);
424 if (error)
425 {
426 fprintf(stderr, "%s\n", error);
427 return 1;
428 }
429 return 0;
430
431 usage:
432 list->destroy_function(list, (void*)revoked_destroy);
433 cdps->destroy_function(cdps, (void*)cdp_destroy);
434 return command_usage(error);
435 }
436
437 /**
438 * Register the command.
439 */
440 static void __attribute__ ((constructor))reg()
441 {
442 command_register((command_t) {
443 sign_crl, 'c', "signcrl",
444 "issue a CRL using a CA certificate and key",
445 {"--cacert file --cakey file|--cakeyid hex [--lifetime days]",
446 " [--lastcrl crl] [--basecrl crl] [--crluri uri]+",
447 " [[--reason key-compromise|ca-compromise|affiliation-changed|",
448 " superseded|cessation-of-operation|certificate-hold]",
449 " [--date timestamp] --cert file|--serial hex]*",
450 " [--digest md5|sha1|sha224|sha256|sha384|sha512]",
451 " [--outform der|pem]"},
452 {
453 {"help", 'h', 0, "show usage information"},
454 {"cacert", 'c', 1, "CA certificate file"},
455 {"cakey", 'k', 1, "CA private key file"},
456 {"cakeyid", 'x', 1, "keyid on smartcard of CA private key"},
457 {"lifetime", 'l', 1, "days the CRL gets a nextUpdate, default: 15"},
458 {"this-update", 'F', 1, "date/time the validity of the CRL starts"},
459 {"next-update", 'T', 1, "date/time the validity of the CRL ends"},
460 {"dateform", 'D', 1, "strptime(3) input format, default: %d.%m.%y %T"},
461 {"lastcrl", 'a', 1, "CRL of lastUpdate to copy revocations from"},
462 {"basecrl", 'b', 1, "base CRL to create a delta CRL for"},
463 {"crluri", 'u', 1, "freshest delta CRL URI to include"},
464 {"cert", 'z', 1, "certificate file to revoke"},
465 {"serial", 's', 1, "hex encoded certificate serial number to revoke"},
466 {"reason", 'r', 1, "reason for certificate revocation"},
467 {"date", 'd', 1, "revocation date as unix timestamp, default: now"},
468 {"digest", 'g', 1, "digest for signature creation, default: sha1"},
469 {"outform", 'f', 1, "encoding of generated crl, default: der"},
470 }
471 });
472 }