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