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