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