2 * Copyright (C) 2010 Martin Willi
3 * Copyright (C) 2010 revosec AG
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>.
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
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>
28 * Entry for a revoked certificate
37 * Add a revocation to the list
39 static void add_revoked(linked_list_t
*list
,
40 chunk_t serial
, crl_reason_t reason
, time_t date
)
45 .serial
= chunk_clone(serial
),
49 list
->insert_last(list
, revoked
);
53 * Destroy a reason entry
55 static void revoked_destroy(revoked_t
*revoked
)
57 free(revoked
->serial
.ptr
);
62 * Filter for revoked enumerator
64 static bool filter(void *data
, revoked_t
**revoked
, chunk_t
*serial
, void *p2
,
65 time_t *date
, void *p3
, crl_reason_t
*reason
)
67 *serial
= (*revoked
)->serial
;
68 *date
= (*revoked
)->date
;
69 *reason
= (*revoked
)->reason
;
74 * Extract the serial of a certificate, write it into buf
76 static int read_serial(char *file
, char *buf
, int buflen
)
82 x509
= lib
->creds
->create(lib
->creds
, CRED_CERTIFICATE
, CERT_X509
,
83 BUILD_FROM_FILE
, file
, BUILD_END
);
84 cert
= &x509
->interface
;
89 serial
= x509
->get_serial(x509
);
90 if (serial
.len
== 0 || serial
.len
> buflen
)
95 memcpy(buf
, serial
.ptr
, serial
.len
);
103 static void cdp_destroy(x509_cdp_t
*this)
112 static int sign_crl()
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
;
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
;
125 crl_reason_t reason
= CRL_REASON_UNSPECIFIED
;
126 time_t thisUpdate
, nextUpdate
, date
= time(NULL
);
127 time_t lifetime
= 15 * 24 * 60 * 60;
128 char *datetu
= NULL
, *datenu
= NULL
, *dateform
= NULL
;
129 linked_list_t
*list
, *cdps
;
130 enumerator_t
*enumerator
, *lastenum
= NULL
;
132 chunk_t crl_serial
= chunk_empty
, baseCrlNumber
= chunk_empty
;
133 chunk_t encoding
= chunk_empty
;
135 list
= linked_list_create();
136 cdps
= linked_list_create();
140 switch (command_getopt(&arg
))
145 if (!enum_from_name(hash_algorithm_short_names
, arg
, &digest
))
147 error
= "invalid --digest type";
164 lifetime
= atoi(arg
) * 24 * 60 * 60;
167 error
= "invalid --lifetime value";
181 serial_len
= read_serial(arg
, serial
, sizeof(serial
));
184 snprintf(serial
, sizeof(serial
),
185 "parsing certificate '%s' failed", arg
);
189 add_revoked(list
, chunk_create(serial
, serial_len
), reason
, date
);
192 reason
= CRL_REASON_UNSPECIFIED
;
199 hex_len
= strlen(arg
);
200 if ((hex_len
/ 2) + (hex_len
% 2) > sizeof(serial
))
202 error
= "invalid serial";
205 chunk
= chunk_from_hex(chunk_create(arg
, hex_len
), serial
);
206 serial_len
= chunk
.len
;
207 add_revoked(list
, chunk_create(serial
, serial_len
), reason
, date
);
210 reason
= CRL_REASON_UNSPECIFIED
;
220 cdps
->insert_last(cdps
, cdp
);
223 if (streq(arg
, "key-compromise"))
225 reason
= CRL_REASON_KEY_COMPROMISE
;
227 else if (streq(arg
, "ca-compromise"))
229 reason
= CRL_REASON_CA_COMPROMISE
;
231 else if (streq(arg
, "affiliation-changed"))
233 reason
= CRL_REASON_AFFILIATION_CHANGED
;
235 else if (streq(arg
, "superseded"))
237 reason
= CRL_REASON_SUPERSEDED
;
239 else if (streq(arg
, "cessation-of-operation"))
241 reason
= CRL_REASON_CESSATION_OF_OPERATON
;
243 else if (streq(arg
, "certificate-hold"))
245 reason
= CRL_REASON_CERTIFICATE_HOLD
;
249 error
= "invalid revocation reason";
257 error
= "invalid date";
262 if (!get_form(arg
, &form
, CRED_CERTIFICATE
))
264 error
= "invalid output format";
271 error
= "invalid --signcrl option";
279 error
= "--cacert is required";
282 if (!cakey
&& !keyid
)
284 error
= "--cakey or --keyid is required";
287 if (!calculate_lifetime(dateform
, datetu
, datenu
, lifetime
,
288 &thisUpdate
, &nextUpdate
))
290 error
= "invalid --this/next-update datetime";
294 ca
= lib
->creds
->create(lib
->creds
, CRED_CERTIFICATE
, CERT_X509
,
295 BUILD_FROM_FILE
, cacert
, BUILD_END
);
298 error
= "parsing CA certificate failed";
302 if (!(x509
->get_flags(x509
) & (X509_CA
| X509_CRL_SIGN
)))
304 error
= "CA certificate misses CA basicConstraint / CRLSign keyUsage";
307 public = ca
->get_public_key(ca
);
310 error
= "extracting CA certificate public key failed";
315 private = lib
->creds
->create(lib
->creds
, CRED_PRIVATE_KEY
,
316 public->get_type(public),
317 BUILD_FROM_FILE
, cakey
, BUILD_END
);
323 chunk
= chunk_from_hex(chunk_create(keyid
, strlen(keyid
)), NULL
);
324 private = lib
->creds
->create(lib
->creds
, CRED_PRIVATE_KEY
, KEY_ANY
,
325 BUILD_PKCS11_KEYID
, chunk
, BUILD_END
);
330 error
= "loading CA private key failed";
333 if (!private->belongs_to(private, public))
335 error
= "CA private key does not match CA certificate";
341 lastcrl
= lib
->creds
->create(lib
->creds
, CRED_CERTIFICATE
, CERT_X509_CRL
,
342 BUILD_FROM_FILE
, basecrl
, BUILD_END
);
345 error
= "loading base CRL failed";
348 baseCrlNumber
= chunk_clone(lastcrl
->get_serial(lastcrl
));
349 crl_serial
= baseCrlNumber
;
350 DESTROY_IF((certificate_t
*)lastcrl
);
356 lastcrl
= lib
->creds
->create(lib
->creds
, CRED_CERTIFICATE
, CERT_X509_CRL
,
357 BUILD_FROM_FILE
, lastupdate
, BUILD_END
);
360 error
= "loading lastUpdate CRL failed";
363 crl_serial
= lastcrl
->get_serial(lastcrl
);
364 lastenum
= lastcrl
->create_enumerator(lastcrl
);
368 crl_serial
= chunk_from_chars(0x00);
369 lastenum
= enumerator_create_empty();
372 /* remove superfluous leading zeros */
373 while (crl_serial
.len
> 1 && crl_serial
.ptr
[0] == 0x00 &&
374 (crl_serial
.ptr
[1] & 0x80) == 0x00)
376 crl_serial
= chunk_skip_zero(crl_serial
);
378 crl_serial
= chunk_clone(crl_serial
);
380 /* increment the serial number by one */
381 chunk_increment(crl_serial
);
383 enumerator
= enumerator_create_filter(list
->create_enumerator(list
),
384 (void*)filter
, NULL
, NULL
);
385 crl
= lib
->creds
->create(lib
->creds
, CRED_CERTIFICATE
, CERT_X509_CRL
,
386 BUILD_SIGNING_KEY
, private, BUILD_SIGNING_CERT
, ca
,
387 BUILD_SERIAL
, crl_serial
,
388 BUILD_NOT_BEFORE_TIME
, thisUpdate
, BUILD_NOT_AFTER_TIME
, nextUpdate
,
389 BUILD_REVOKED_ENUMERATOR
, enumerator
,
390 BUILD_REVOKED_ENUMERATOR
, lastenum
, BUILD_DIGEST_ALG
, digest
,
391 BUILD_CRL_DISTRIBUTION_POINTS
, cdps
, BUILD_BASE_CRL
, baseCrlNumber
,
393 enumerator
->destroy(enumerator
);
394 lastenum
->destroy(lastenum
);
395 DESTROY_IF((certificate_t
*)lastcrl
);
396 free(crl_serial
.ptr
);
400 error
= "generating CRL failed";
403 if (!crl
->get_encoding(crl
, form
, &encoding
))
405 error
= "encoding CRL failed";
408 set_file_mode(stdout
, form
);
409 if (fwrite(encoding
.ptr
, encoding
.len
, 1, stdout
) != 1)
411 error
= "writing CRL failed";
421 free(baseCrlNumber
.ptr
);
422 list
->destroy_function(list
, (void*)revoked_destroy
);
423 cdps
->destroy_function(cdps
, (void*)cdp_destroy
);
426 fprintf(stderr
, "%s\n", error
);
432 list
->destroy_function(list
, (void*)revoked_destroy
);
433 cdps
->destroy_function(cdps
, (void*)cdp_destroy
);
434 return command_usage(error
);
438 * Register the command.
440 static void __attribute__ ((constructor
))reg()
442 command_register((command_t
) {
443 sign_crl
, 'c', "signcrl",
444 "issue a CRL using a CA certificate and key",
445 {"--cacert file --cakey file|--cakeyid hex [--lifetime days]",
446 " [--lastcrl crl] [--basecrl crl] [--crluri uri]+",
447 " [[--reason key-compromise|ca-compromise|affiliation-changed|",
448 " superseded|cessation-of-operation|certificate-hold]",
449 " [--date timestamp] --cert file|--serial hex]*",
450 " [--digest md5|sha1|sha224|sha256|sha384|sha512]",
451 " [--outform der|pem]"},
453 {"help", 'h', 0, "show usage information"},
454 {"cacert", 'c', 1, "CA certificate file"},
455 {"cakey", 'k', 1, "CA private key file"},
456 {"cakeyid", 'x', 1, "keyid on smartcard of CA private key"},
457 {"lifetime", 'l', 1, "days the CRL gets a nextUpdate, default: 15"},
458 {"this-update", 'F', 1, "date/time the validity of the CRL starts"},
459 {"next-update", 'T', 1, "date/time the validity of the CRL ends"},
460 {"dateform", 'D', 1, "strptime(3) input format, default: %d.%m.%y %T"},
461 {"lastcrl", 'a', 1, "CRL of lastUpdate to copy revocations from"},
462 {"basecrl", 'b', 1, "base CRL to create a delta CRL for"},
463 {"crluri", 'u', 1, "freshest delta CRL URI to include"},
464 {"cert", 'z', 1, "certificate file to revoke"},
465 {"serial", 's', 1, "hex encoded certificate serial number to revoke"},
466 {"reason", 'r', 1, "reason for certificate revocation"},
467 {"date", 'd', 1, "revocation date as unix timestamp, default: now"},
468 {"digest", 'g', 1, "digest for signature creation, default: sha1"},
469 {"outform", 'f', 1, "encoding of generated crl, default: der"},