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