pki: Add pki --signcrl man page
[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;
128 linked_list_t *list, *cdps;
129 enumerator_t *enumerator, *lastenum = NULL;
130 x509_cdp_t *cdp;
131 chunk_t crl_serial = chunk_empty, baseCrlNumber = chunk_empty;
132 chunk_t encoding = chunk_empty;
133
134 list = linked_list_create();
135 cdps = linked_list_create();
136
137 while (TRUE)
138 {
139 switch (command_getopt(&arg))
140 {
141 case 'h':
142 goto usage;
143 case 'g':
144 digest = enum_from_name(hash_algorithm_short_names, arg);
145 if (digest == -1)
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);
165 if (!lifetime)
166 {
167 error = "invalid lifetime";
168 goto usage;
169 }
170 continue;
171 case 'z':
172 serial_len = read_serial(arg, serial, sizeof(serial));
173 if (serial_len < 0)
174 {
175 snprintf(serial, sizeof(serial),
176 "parsing certificate '%s' failed", arg);
177 error = serial;
178 goto error;
179 }
180 add_revoked(list, chunk_create(serial, serial_len), reason, date);
181 date = time(NULL);
182 serial_len = 0;
183 reason = CRL_REASON_UNSPECIFIED;
184 continue;
185 case 's':
186 {
187 chunk_t chunk;
188 int hex_len;
189
190 hex_len = strlen(arg);
191 if ((hex_len / 2) + (hex_len % 2) > sizeof(serial))
192 {
193 error = "invalid serial";
194 goto usage;
195 }
196 chunk = chunk_from_hex(chunk_create(arg, hex_len), serial);
197 serial_len = chunk.len;
198 add_revoked(list, chunk_create(serial, serial_len), reason, date);
199 date = time(NULL);
200 serial_len = 0;
201 reason = CRL_REASON_UNSPECIFIED;
202 continue;
203 }
204 case 'b':
205 basecrl = arg;
206 continue;
207 case 'u':
208 INIT(cdp,
209 .uri = strdup(arg),
210 );
211 cdps->insert_last(cdps, cdp);
212 continue;
213 case 'r':
214 if (streq(arg, "key-compromise"))
215 {
216 reason = CRL_REASON_KEY_COMPROMISE;
217 }
218 else if (streq(arg, "ca-compromise"))
219 {
220 reason = CRL_REASON_CA_COMPROMISE;
221 }
222 else if (streq(arg, "affiliation-changed"))
223 {
224 reason = CRL_REASON_AFFILIATION_CHANGED;
225 }
226 else if (streq(arg, "superseded"))
227 {
228 reason = CRL_REASON_SUPERSEDED;
229 }
230 else if (streq(arg, "cessation-of-operation"))
231 {
232 reason = CRL_REASON_CESSATION_OF_OPERATON;
233 }
234 else if (streq(arg, "certificate-hold"))
235 {
236 reason = CRL_REASON_CERTIFICATE_HOLD;
237 }
238 else
239 {
240 error = "invalid revocation reason";
241 goto usage;
242 }
243 continue;
244 case 'd':
245 date = atol(arg);
246 if (!date)
247 {
248 error = "invalid date";
249 goto usage;
250 }
251 continue;
252 case 'f':
253 if (!get_form(arg, &form, CRED_CERTIFICATE))
254 {
255 error = "invalid output format";
256 goto usage;
257 }
258 continue;
259 case EOF:
260 break;
261 default:
262 error = "invalid --signcrl option";
263 goto usage;
264 }
265 break;
266 }
267
268 if (!cacert)
269 {
270 error = "--cacert is required";
271 goto usage;
272 }
273 if (!cakey && !keyid)
274 {
275 error = "--cakey or --keyid is required";
276 goto usage;
277 }
278
279 ca = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
280 BUILD_FROM_FILE, cacert, BUILD_END);
281 if (!ca)
282 {
283 error = "parsing CA certificate failed";
284 goto error;
285 }
286 x509 = (x509_t*)ca;
287 if (!(x509->get_flags(x509) & (X509_CA | X509_CRL_SIGN)))
288 {
289 error = "CA certificate misses CA basicConstraint / CRLSign keyUsage";
290 goto error;
291 }
292 public = ca->get_public_key(ca);
293 if (!public)
294 {
295 error = "extracting CA certificate public key failed";
296 goto error;
297 }
298 if (cakey)
299 {
300 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
301 public->get_type(public),
302 BUILD_FROM_FILE, cakey, BUILD_END);
303 }
304 else
305 {
306 chunk_t chunk;
307
308 chunk = chunk_from_hex(chunk_create(keyid, strlen(keyid)), NULL);
309 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY,
310 BUILD_PKCS11_KEYID, chunk, BUILD_END);
311 free(chunk.ptr);
312 }
313 if (!private)
314 {
315 error = "loading CA private key failed";
316 goto error;
317 }
318 if (!private->belongs_to(private, public))
319 {
320 error = "CA private key does not match CA certificate";
321 goto error;
322 }
323
324 thisUpdate = time(NULL);
325 nextUpdate = thisUpdate + lifetime * 24 * 60 * 60;
326
327 if (basecrl)
328 {
329 lastcrl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
330 BUILD_FROM_FILE, basecrl, BUILD_END);
331 if (!lastcrl)
332 {
333 error = "loading base CRL failed";
334 goto error;
335 }
336 baseCrlNumber = chunk_clone(lastcrl->get_serial(lastcrl));
337 crl_serial = baseCrlNumber;
338 DESTROY_IF((certificate_t*)lastcrl);
339 lastcrl = NULL;
340 }
341
342 if (lastupdate)
343 {
344 lastcrl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
345 BUILD_FROM_FILE, lastupdate, BUILD_END);
346 if (!lastcrl)
347 {
348 error = "loading lastUpdate CRL failed";
349 goto error;
350 }
351 crl_serial = lastcrl->get_serial(lastcrl);
352 lastenum = lastcrl->create_enumerator(lastcrl);
353 }
354 else
355 {
356 crl_serial = chunk_from_chars(0x00);
357 lastenum = enumerator_create_empty();
358 }
359
360 /* remove superfluous leading zeros */
361 while (crl_serial.len > 1 && crl_serial.ptr[0] == 0x00 &&
362 (crl_serial.ptr[1] & 0x80) == 0x00)
363 {
364 crl_serial = chunk_skip_zero(crl_serial);
365 }
366 crl_serial = chunk_clone(crl_serial);
367
368 /* increment the serial number by one */
369 chunk_increment(crl_serial);
370
371 enumerator = enumerator_create_filter(list->create_enumerator(list),
372 (void*)filter, NULL, NULL);
373 crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
374 BUILD_SIGNING_KEY, private, BUILD_SIGNING_CERT, ca,
375 BUILD_SERIAL, crl_serial,
376 BUILD_NOT_BEFORE_TIME, thisUpdate, BUILD_NOT_AFTER_TIME, nextUpdate,
377 BUILD_REVOKED_ENUMERATOR, enumerator,
378 BUILD_REVOKED_ENUMERATOR, lastenum, BUILD_DIGEST_ALG, digest,
379 BUILD_CRL_DISTRIBUTION_POINTS, cdps, BUILD_BASE_CRL, baseCrlNumber,
380 BUILD_END);
381 enumerator->destroy(enumerator);
382 lastenum->destroy(lastenum);
383 DESTROY_IF((certificate_t*)lastcrl);
384 free(crl_serial.ptr);
385
386 if (!crl)
387 {
388 error = "generating CRL failed";
389 goto error;
390 }
391 if (!crl->get_encoding(crl, form, &encoding))
392 {
393 error = "encoding CRL failed";
394 goto error;
395 }
396 if (fwrite(encoding.ptr, encoding.len, 1, stdout) != 1)
397 {
398 error = "writing CRL failed";
399 goto error;
400 }
401
402 error:
403 DESTROY_IF(public);
404 DESTROY_IF(private);
405 DESTROY_IF(ca);
406 DESTROY_IF(crl);
407 free(encoding.ptr);
408 free(baseCrlNumber.ptr);
409 list->destroy_function(list, (void*)revoked_destroy);
410 cdps->destroy_function(cdps, (void*)cdp_destroy);
411 if (error)
412 {
413 fprintf(stderr, "%s\n", error);
414 return 1;
415 }
416 return 0;
417
418 usage:
419 list->destroy_function(list, (void*)revoked_destroy);
420 cdps->destroy_function(cdps, (void*)cdp_destroy);
421 return command_usage(error);
422 }
423
424 /**
425 * Register the command.
426 */
427 static void __attribute__ ((constructor))reg()
428 {
429 command_register((command_t) {
430 sign_crl, 'c', "signcrl",
431 "issue a CRL using a CA certificate and key",
432 {"--cacert file --cakey file|--cakeyid hex [--lifetime days]",
433 " [--lastcrl crl] [--basecrl crl] [--crluri uri]+",
434 " [[--reason key-compromise|ca-compromise|affiliation-changed|",
435 " superseded|cessation-of-operation|certificate-hold]",
436 " [--date timestamp] --cert file|--serial hex]*",
437 " [--digest md5|sha1|sha224|sha256|sha384|sha512]",
438 " [--outform der|pem]"},
439 {
440 {"help", 'h', 0, "show usage information"},
441 {"cacert", 'c', 1, "CA certificate file"},
442 {"cakey", 'k', 1, "CA private key file"},
443 {"cakeyid", 'x', 1, "keyid on smartcard of CA private key"},
444 {"lifetime", 'l', 1, "days the CRL gets a nextUpdate, default: 15"},
445 {"lastcrl", 'a', 1, "CRL of lastUpdate to copy revocations from"},
446 {"basecrl", 'b', 1, "base CRL to create a delta CRL for"},
447 {"crluri", 'u', 1, "freshest delta CRL URI to include"},
448 {"cert", 'z', 1, "certificate file to revoke"},
449 {"serial", 's', 1, "hex encoded certificate serial number to revoke"},
450 {"reason", 'r', 1, "reason for certificate revocation"},
451 {"date", 'd', 1, "revocation date as unix timestamp, default: now"},
452 {"digest", 'g', 1, "digest for signature creation, default: sha1"},
453 {"outform", 'f', 1, "encoding of generated crl, default: der"},
454 }
455 });
456 }