89c9cc7080e87792407be631edbda391ec0c625e
[strongswan.git] / src / pki / commands / issue.c
1 /*
2 * Copyright (C) 2009 Martin Willi
3 * Hochschule fuer Technik Rapperswil
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/pkcs10.h>
25
26 /**
27 * Issue a certificate using a CA certificate and key
28 */
29 static int issue()
30 {
31 hash_algorithm_t digest = HASH_SHA1;
32 certificate_t *cert_req = NULL, *cert = NULL, *ca =NULL;
33 private_key_t *private = NULL;
34 public_key_t *public = NULL;
35 bool pkcs10 = FALSE;
36 char *file = NULL, *dn = NULL, *hex = NULL, *cacert = NULL, *cakey = NULL;
37 char *error = NULL;
38 identification_t *id = NULL;
39 linked_list_t *san, *cdps, *ocsp;
40 int lifetime = 1080;
41 int pathlen = X509_NO_PATH_LEN_CONSTRAINT;
42 chunk_t serial = chunk_empty;
43 chunk_t encoding = chunk_empty;
44 time_t not_before, not_after;
45 x509_flag_t flags = 0;
46 x509_t *x509;
47 char *arg;
48
49 san = linked_list_create();
50 cdps = linked_list_create();
51 ocsp = linked_list_create();
52
53 while (TRUE)
54 {
55 switch (command_getopt(&arg))
56 {
57 case 'h':
58 goto usage;
59 case 't':
60 if (streq(arg, "pkcs10"))
61 {
62 pkcs10 = TRUE;
63 }
64 else if (!streq(arg, "pub"))
65 {
66 error = "invalid input type";
67 goto usage;
68 }
69 continue;
70 case 'g':
71 digest = get_digest(arg);
72 if (digest == HASH_UNKNOWN)
73 {
74 error = "invalid --digest type";
75 goto usage;
76 }
77 continue;
78 case 'i':
79 file = arg;
80 continue;
81 case 'c':
82 cacert = arg;
83 continue;
84 case 'k':
85 cakey = arg;
86 continue;
87 case 'd':
88 dn = arg;
89 continue;
90 case 'a':
91 san->insert_last(san, identification_create_from_string(arg));
92 continue;
93 case 'l':
94 lifetime = atoi(arg);
95 if (!lifetime)
96 {
97 error = "invalid --lifetime value";
98 goto usage;
99 }
100 continue;
101 case 's':
102 hex = arg;
103 continue;
104 case 'b':
105 flags |= X509_CA;
106 continue;
107 case 'p':
108 pathlen = atoi(arg);
109 continue;
110 case 'f':
111 if (streq(arg, "serverAuth"))
112 {
113 flags |= X509_SERVER_AUTH;
114 }
115 else if (streq(arg, "ocspSigning"))
116 {
117 flags |= X509_OCSP_SIGNER;
118 }
119 continue;
120 case 'u':
121 cdps->insert_last(cdps, arg);
122 continue;
123 case 'o':
124 ocsp->insert_last(ocsp, arg);
125 continue;
126 case EOF:
127 break;
128 default:
129 error = "invalid --issue option";
130 goto usage;
131 }
132 break;
133 }
134
135 if (!pkcs10 && !dn)
136 {
137 error = "--dn is required";
138 goto usage;
139 }
140 if (!cacert)
141 {
142 error = "--cacert is required";
143 goto usage;
144 }
145 if (!cakey)
146 {
147 error = "--cakey is required";
148 goto usage;
149 }
150 if (dn)
151 {
152 id = identification_create_from_string(dn);
153 if (id->get_type(id) != ID_DER_ASN1_DN)
154 {
155 error = "supplied --dn is not a distinguished name";
156 goto end;
157 }
158 }
159
160 DBG2("Reading ca certificate:");
161 ca = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
162 BUILD_FROM_FILE, cacert, BUILD_END);
163 if (!ca)
164 {
165 error = "parsing CA certificate failed";
166 goto end;
167 }
168 x509 = (x509_t*)ca;
169 if (!(x509->get_flags(x509) & X509_CA))
170 {
171 error = "CA certificate misses CA basicConstraint";
172 goto end;
173 }
174 public = ca->get_public_key(ca);
175 if (!public)
176 {
177 error = "extracting CA certificate public key failed";
178 goto end;
179 }
180
181 DBG2("Reading ca private key:");
182 private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY,
183 public->get_type(public),
184 BUILD_FROM_FILE, cakey, BUILD_END);
185 if (!private)
186 {
187 error = "parsing CA private key failed";
188 goto end;
189 }
190 if (!private->belongs_to(private, public))
191 {
192 error = "CA private key does not match CA certificate";
193 goto end;
194 }
195 public->destroy(public);
196
197 if (hex)
198 {
199 serial = chunk_from_hex(chunk_create(hex, strlen(hex)), NULL);
200 }
201 else
202 {
203 rng_t *rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
204
205 if (!rng)
206 {
207 error = "no random number generator found";
208 goto end;
209 }
210 rng->allocate_bytes(rng, 8, &serial);
211 rng->destroy(rng);
212 }
213
214 if (pkcs10)
215 {
216 enumerator_t *enumerator;
217 identification_t *subjectAltName;
218 pkcs10_t *req;
219
220 DBG2("Reading certificate request");
221 if (file)
222 {
223 cert_req = lib->creds->create(lib->creds, CRED_CERTIFICATE,
224 CERT_PKCS10_REQUEST,
225 BUILD_FROM_FILE, file, BUILD_END);
226 }
227 else
228 {
229 cert_req = lib->creds->create(lib->creds, CRED_CERTIFICATE,
230 CERT_PKCS10_REQUEST,
231 BUILD_FROM_FD, 0, BUILD_END);
232 }
233 if (!cert_req)
234 {
235 error = "parsing certificate request failed";
236 goto end;
237 }
238
239 /* If not set yet use subject from PKCS#10 certificate request as DN */
240 if (!id)
241 {
242 id = cert_req->get_subject(cert_req);
243 id = id->clone(id);
244 }
245
246 /* Add subjectAltNames from PKCS#10 certificate request */
247 req = (pkcs10_t*)cert_req;
248 enumerator = req->create_subjectAltName_enumerator(req);
249 while (enumerator->enumerate(enumerator, &subjectAltName))
250 {
251 san->insert_last(san, subjectAltName->clone(subjectAltName));
252 }
253 enumerator->destroy(enumerator);
254
255 /* Use public key from PKCS#10 certificate request */
256 public = cert_req->get_public_key(cert_req);
257 }
258 else
259 {
260 DBG2("Reading public key:");
261 if (file)
262 {
263 public = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_ANY,
264 BUILD_FROM_FILE, file, BUILD_END);
265 }
266 else
267 {
268 public = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_ANY,
269 BUILD_FROM_FD, 0, BUILD_END);
270 }
271 }
272 if (!public)
273 {
274 error = "parsing public key failed";
275 goto end;
276 }
277
278 not_before = time(NULL);
279 not_after = not_before + lifetime * 24 * 60 * 60;
280
281 cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
282 BUILD_SIGNING_KEY, private, BUILD_SIGNING_CERT, ca,
283 BUILD_PUBLIC_KEY, public, BUILD_SUBJECT, id,
284 BUILD_NOT_BEFORE_TIME, not_before, BUILD_DIGEST_ALG, digest,
285 BUILD_NOT_AFTER_TIME, not_after, BUILD_SERIAL, serial,
286 BUILD_SUBJECT_ALTNAMES, san, BUILD_X509_FLAG, flags,
287 BUILD_PATHLEN, pathlen,
288 BUILD_CRL_DISTRIBUTION_POINTS, cdps,
289 BUILD_OCSP_ACCESS_LOCATIONS, ocsp, BUILD_END);
290 if (!cert)
291 {
292 error = "generating certificate failed";
293 goto end;
294 }
295 encoding = cert->get_encoding(cert);
296 if (!encoding.ptr)
297 {
298 error = "encoding certificate failed";
299 goto end;
300 }
301 if (fwrite(encoding.ptr, encoding.len, 1, stdout) != 1)
302 {
303 error = "writing certificate key failed";
304 goto end;
305 }
306
307 end:
308 DESTROY_IF(id);
309 DESTROY_IF(cert_req);
310 DESTROY_IF(cert);
311 DESTROY_IF(ca);
312 DESTROY_IF(public);
313 DESTROY_IF(private);
314 san->destroy_offset(san, offsetof(identification_t, destroy));
315 cdps->destroy(cdps);
316 ocsp->destroy(ocsp);
317 free(encoding.ptr);
318 free(serial.ptr);
319
320 if (error)
321 {
322 fprintf(stderr, "%s\n", error);
323 return 1;
324 }
325 return 0;
326
327 usage:
328 san->destroy_offset(san, offsetof(identification_t, destroy));
329 cdps->destroy(cdps);
330 ocsp->destroy(ocsp);
331 return command_usage(error);
332 }
333
334 /**
335 * Register the command.
336 */
337 static void __attribute__ ((constructor))reg()
338 {
339 command_register((command_t) {
340 issue, 'i', "issue",
341 "issue a certificate using a CA certificate and key",
342 {"[--in file] [--type pub|pkcs10]",
343 " --cacert file --cakey file --dn subject-dn [--san subjectAltName]+",
344 "[--lifetime days] [--serial hex] [--crl uri]+ [--ocsp uri]+",
345 "[--ca] [--pathlen len] [--flag serverAuth|ocspSigning]+",
346 "[--digest md5|sha1|sha224|sha256|sha384|sha512]"},
347 {
348 {"help", 'h', 0, "show usage information"},
349 {"in", 'i', 1, "public key/request file to issue, default: stdin"},
350 {"type", 't', 1, "type of input, default: pub"},
351 {"cacert", 'c', 1, "CA certificate file"},
352 {"cakey", 'k', 1, "CA private key file"},
353 {"dn", 'd', 1, "distinguished name to include as subject"},
354 {"san", 'a', 1, "subjectAltName to include in certificate"},
355 {"lifetime",'l', 1, "days the certificate is valid, default: 1080"},
356 {"serial", 's', 1, "serial number in hex, default: random"},
357 {"ca", 'b', 0, "include CA basicConstraint, default: no"},
358 {"pathlen", 'p', 1, "set path length constraint"},
359 {"flag", 'f', 1, "include extendedKeyUsage flag"},
360 {"crl", 'u', 1, "CRL distribution point URI to include"},
361 {"ocsp", 'o', 1, "OCSP AuthorityInfoAccess URI to include"},
362 {"digest", 'g', 1, "digest for signature creation, default: sha1"},
363 }
364 });
365 }
366