Do not query for CKA_ALWAYS_AUTHENTICATE if PKCS#11 Cryptoki version < 2.20
[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 <debug.h>
21 #include <utils/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 * Sign a CRL
102 */
103 static int sign_crl()
104 {
105 cred_encoding_type_t form = CERT_ASN1_DER;
106 private_key_t *private = NULL;
107 public_key_t *public = NULL;
108 certificate_t *ca = NULL, *crl = NULL;
109 crl_t *lastcrl = NULL;
110 x509_t *x509;
111 hash_algorithm_t digest = HASH_SHA1;
112 char *arg, *cacert = NULL, *cakey = NULL, *lastupdate = NULL, *error = NULL;
113 char serial[512], crl_serial[8], *keyid = NULL;
114 int serial_len = 0;
115 crl_reason_t reason = CRL_REASON_UNSPECIFIED;
116 time_t thisUpdate, nextUpdate, date = time(NULL);
117 int lifetime = 15;
118 linked_list_t *list;
119 enumerator_t *enumerator, *lastenum = NULL;
120 chunk_t encoding = chunk_empty;
121
122 list = linked_list_create();
123
124 memset(crl_serial, 0, sizeof(crl_serial));
125
126 while (TRUE)
127 {
128 switch (command_getopt(&arg))
129 {
130 case 'h':
131 goto usage;
132 case 'g':
133 digest = get_digest(arg);
134 if (digest == HASH_UNKNOWN)
135 {
136 error = "invalid --digest type";
137 goto usage;
138 }
139 continue;
140 case 'c':
141 cacert = arg;
142 continue;
143 case 'k':
144 cakey = arg;
145 continue;
146 case 'x':
147 keyid = arg;
148 continue;
149 case 'a':
150 lastupdate = arg;
151 continue;
152 case 'l':
153 lifetime = atoi(arg);
154 if (!lifetime)
155 {
156 error = "invalid lifetime";
157 goto usage;
158 }
159 continue;
160 case 'z':
161 serial_len = read_serial(arg, serial, sizeof(serial));
162 if (serial_len < 0)
163 {
164 snprintf(serial, sizeof(serial),
165 "parsing certificate '%s' failed", arg);
166 error = serial;
167 goto error;
168 }
169 add_revoked(list, chunk_create(serial, serial_len), reason, date);
170 date = time(NULL);
171 serial_len = 0;
172 reason = CRL_REASON_UNSPECIFIED;
173 continue;
174 case 's':
175 {
176 chunk_t chunk;
177 int hex_len;
178
179 hex_len = strlen(arg);
180 if ((hex_len / 2) + (hex_len % 2) > sizeof(serial))
181 {
182 error = "invalid serial";
183 goto usage;
184 }
185 chunk = chunk_from_hex(chunk_create(arg, hex_len), serial);
186 serial_len = chunk.len;
187 add_revoked(list, chunk_create(serial, serial_len), reason, date);
188 date = time(NULL);
189 serial_len = 0;
190 reason = CRL_REASON_UNSPECIFIED;
191 continue;
192 }
193 case 'r':
194 if (streq(arg, "key-compromise"))
195 {
196 reason = CRL_REASON_KEY_COMPROMISE;
197 }
198 else if (streq(arg, "ca-compromise"))
199 {
200 reason = CRL_REASON_CA_COMPROMISE;
201 }
202 else if (streq(arg, "affiliation-changed"))
203 {
204 reason = CRL_REASON_AFFILIATION_CHANGED;
205 }
206 else if (streq(arg, "superseded"))
207 {
208 reason = CRL_REASON_SUPERSEDED;
209 }
210 else if (streq(arg, "cessation-of-operation"))
211 {
212 reason = CRL_REASON_CESSATION_OF_OPERATON;
213 }
214 else if (streq(arg, "certificate-hold"))
215 {
216 reason = CRL_REASON_CERTIFICATE_HOLD;
217 }
218 else
219 {
220 return command_usage( "invalid revocation reason");
221 }
222 continue;
223 case 'd':
224 date = atol(arg);
225 if (!date)
226 {
227 error = "invalid date";
228 goto usage;
229 }
230 continue;
231 case 'f':
232 if (!get_form(arg, &form, CRED_CERTIFICATE))
233 {
234 return command_usage("invalid output format");
235 }
236 continue;
237 case EOF:
238 break;
239 default:
240 error = "invalid --signcrl option";
241 goto usage;
242 }
243 break;
244 }
245
246 if (!cacert)
247 {
248 error = "--cacert is required";
249 goto usage;
250 }
251 if (!cakey && !keyid)
252 {
253 error = "--cakey or --keyid is required";
254 goto usage;
255 }
256
257 ca = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
258 BUILD_FROM_FILE, cacert, BUILD_END);
259 if (!ca)
260 {
261 error = "parsing CA certificate failed";
262 goto error;
263 }
264 x509 = (x509_t*)ca;
265 if (!(x509->get_flags(x509) & X509_CA))
266 {
267 error = "CA certificate misses CA basicConstraint";
268 goto error;
269 }
270 public = ca->get_public_key(ca);
271 if (!public)
272 {
273 error = "extracting CA certificate public key failed";
274 goto error;
275 }
276 if (cakey)
277 {
278 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
279 public->get_type(public),
280 BUILD_FROM_FILE, cakey, BUILD_END);
281 }
282 else
283 {
284 chunk_t chunk;
285
286 chunk = chunk_from_hex(chunk_create(keyid, strlen(keyid)), NULL);
287 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY,
288 BUILD_PKCS11_KEYID, chunk, BUILD_END);
289 free(chunk.ptr);
290 }
291 if (!private)
292 {
293 error = "loading CA private key failed";
294 goto error;
295 }
296 if (!private->belongs_to(private, public))
297 {
298 error = "CA private key does not match CA certificate";
299 goto error;
300 }
301
302 thisUpdate = time(NULL);
303 nextUpdate = thisUpdate + lifetime * 24 * 60 * 60;
304
305 if (lastupdate)
306 {
307 lastcrl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
308 BUILD_FROM_FILE, lastupdate, BUILD_END);
309 if (!lastcrl)
310 {
311 error = "loading lastUpdate CRL failed";
312 goto error;
313 }
314 memcpy(crl_serial, lastcrl->get_serial(lastcrl).ptr,
315 min(lastcrl->get_serial(lastcrl).len, sizeof(crl_serial)));
316 lastenum = lastcrl->create_enumerator(lastcrl);
317 }
318
319 chunk_increment(chunk_create(crl_serial, sizeof(crl_serial)));
320
321 enumerator = enumerator_create_filter(list->create_enumerator(list),
322 (void*)filter, NULL, NULL);
323 crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
324 BUILD_SIGNING_KEY, private, BUILD_SIGNING_CERT, ca,
325 BUILD_SERIAL, chunk_create(crl_serial, sizeof(crl_serial)),
326 BUILD_NOT_BEFORE_TIME, thisUpdate, BUILD_NOT_AFTER_TIME, nextUpdate,
327 BUILD_REVOKED_ENUMERATOR, enumerator, BUILD_DIGEST_ALG, digest,
328 lastenum ? BUILD_REVOKED_ENUMERATOR : BUILD_END, lastenum,
329 BUILD_END);
330 enumerator->destroy(enumerator);
331 DESTROY_IF(lastenum);
332 DESTROY_IF((certificate_t*)lastcrl);
333
334 if (!crl)
335 {
336 error = "generating CRL failed";
337 goto error;
338 }
339 if (!crl->get_encoding(crl, form, &encoding))
340 {
341 error = "encoding CRL failed";
342 goto error;
343 }
344 if (fwrite(encoding.ptr, encoding.len, 1, stdout) != 1)
345 {
346 error = "writing CRL failed";
347 goto error;
348 }
349
350 error:
351 DESTROY_IF(public);
352 DESTROY_IF(private);
353 DESTROY_IF(ca);
354 DESTROY_IF(crl);
355 free(encoding.ptr);
356 list->destroy_function(list, (void*)revoked_destroy);
357 if (error)
358 {
359 fprintf(stderr, "%s\n", error);
360 return 1;
361 }
362 return 0;
363
364 usage:
365 list->destroy_function(list, (void*)revoked_destroy);
366 return command_usage(error);
367 }
368
369 /**
370 * Register the command.
371 */
372 static void __attribute__ ((constructor))reg()
373 {
374 command_register((command_t) {
375 sign_crl, 'c', "signcrl",
376 "issue a CRL using a CA certificate and key",
377 {"--cacert file --cakey file | --cakeyid hex --lifetime days",
378 "[ [--reason key-compromise|ca-compromise|affiliation-changed|",
379 " superseded|cessation-of-operation|certificate-hold]",
380 " [--date timestamp]",
381 " --cert file | --serial hex ]*",
382 "[--digest md5|sha1|sha224|sha256|sha384|sha512] [--outform der|pem]"},
383 {
384 {"help", 'h', 0, "show usage information"},
385 {"cacert", 'c', 1, "CA certificate file"},
386 {"cakey", 'k', 1, "CA private key file"},
387 {"cakeyid", 'x', 1, "keyid on smartcard of CA private key"},
388 {"lifetime",'l', 1, "days the CRL gets a nextUpdate, default: 15"},
389 {"lastcrl", 'a', 1, "CRL of lastUpdate to copy revocations from"},
390 {"cert", 'z', 1, "certificate file to revoke"},
391 {"serial", 's', 1, "hex encoded certificate serial number to revoke"},
392 {"reason", 'r', 1, "reason for certificate revocation"},
393 {"date", 'd', 1, "revocation date as unix timestamp, default: now"},
394 {"digest", 'g', 1, "digest for signature creation, default: sha1"},
395 {"outform", 'f', 1, "encoding of generated crl, default: der"},
396 }
397 });
398 }