]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 2009 Martin Willi | |
3 | * Copyright (C) 2015-2019 Andreas Steffen | |
4 | * | |
5 | * Copyright (C) secunet Security Networks AG | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License as published by the | |
9 | * Free Software Foundation; either version 2 of the License, or (at your | |
10 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
15 | * for more details. | |
16 | */ | |
17 | ||
18 | #include <time.h> | |
19 | #include <errno.h> | |
20 | ||
21 | #include "pki.h" | |
22 | ||
23 | #include <collections/linked_list.h> | |
24 | #include <credentials/certificates/certificate.h> | |
25 | #include <credentials/certificates/x509.h> | |
26 | #include <selectors/traffic_selector.h> | |
27 | #include <asn1/asn1.h> | |
28 | ||
29 | /** | |
30 | * Free cert policy with OID | |
31 | */ | |
32 | static void destroy_cert_policy(x509_cert_policy_t *policy) | |
33 | { | |
34 | free(policy->oid.ptr); | |
35 | free(policy); | |
36 | } | |
37 | ||
38 | /** | |
39 | * Free policy mapping | |
40 | */ | |
41 | static void destroy_policy_mapping(x509_policy_mapping_t *mapping) | |
42 | { | |
43 | free(mapping->issuer.ptr); | |
44 | free(mapping->subject.ptr); | |
45 | free(mapping); | |
46 | } | |
47 | ||
48 | /** | |
49 | * Create a self signed certificate. | |
50 | */ | |
51 | static int self() | |
52 | { | |
53 | cred_encoding_type_t form = CERT_ASN1_DER; | |
54 | key_type_t type = KEY_ANY; | |
55 | hash_algorithm_t digest = HASH_UNKNOWN; | |
56 | signature_params_t *scheme = NULL; | |
57 | certificate_t *cert = NULL; | |
58 | private_key_t *private = NULL; | |
59 | public_key_t *public = NULL; | |
60 | char *file = NULL, *dn = NULL, *hex = NULL, *error = NULL, *keyid = NULL; | |
61 | identification_t *id = NULL; | |
62 | linked_list_t *san, *ocsp, *permitted, *excluded, *policies, *mappings; | |
63 | linked_list_t *addrblocks; | |
64 | int pathlen = X509_NO_CONSTRAINT, inhibit_any = X509_NO_CONSTRAINT; | |
65 | int inhibit_mapping = X509_NO_CONSTRAINT; | |
66 | int require_explicit = X509_NO_CONSTRAINT; | |
67 | chunk_t serial = chunk_empty; | |
68 | chunk_t encoding = chunk_empty; | |
69 | chunk_t critical_extension_oid = chunk_empty; | |
70 | time_t not_before, not_after, lifetime = 1095 * 24 * 60 * 60; | |
71 | char *datenb = NULL, *datena = NULL, *dateform = NULL; | |
72 | x509_flag_t flags = 0; | |
73 | x509_cert_policy_t *policy = NULL; | |
74 | traffic_selector_t *ts; | |
75 | char *arg; | |
76 | bool pss = lib->settings->get_bool(lib->settings, "%s.rsa_pss", FALSE, | |
77 | lib->ns); | |
78 | ||
79 | san = linked_list_create(); | |
80 | ocsp = linked_list_create(); | |
81 | permitted = linked_list_create(); | |
82 | excluded = linked_list_create(); | |
83 | policies = linked_list_create(); | |
84 | mappings = linked_list_create(); | |
85 | addrblocks = linked_list_create(); | |
86 | ||
87 | while (TRUE) | |
88 | { | |
89 | switch (command_getopt(&arg)) | |
90 | { | |
91 | case 'h': | |
92 | goto usage; | |
93 | case 't': | |
94 | if (streq(arg, "rsa")) | |
95 | { | |
96 | type = KEY_RSA; | |
97 | } | |
98 | else if (streq(arg, "ecdsa")) | |
99 | { | |
100 | type = KEY_ECDSA; | |
101 | } | |
102 | else if (streq(arg, "ed25519")) | |
103 | { | |
104 | type = KEY_ED25519; | |
105 | } | |
106 | else if (streq(arg, "ed448")) | |
107 | { | |
108 | type = KEY_ED448; | |
109 | } | |
110 | else if (streq(arg, "priv")) | |
111 | { | |
112 | type = KEY_ANY; | |
113 | } | |
114 | else | |
115 | { | |
116 | error = "invalid input type"; | |
117 | goto usage; | |
118 | } | |
119 | continue; | |
120 | case 'g': | |
121 | if (!enum_from_name(hash_algorithm_short_names, arg, &digest)) | |
122 | { | |
123 | error = "invalid --digest type"; | |
124 | goto usage; | |
125 | } | |
126 | continue; | |
127 | case 'R': | |
128 | if (!parse_rsa_padding(arg, &pss)) | |
129 | { | |
130 | error = "invalid RSA padding"; | |
131 | goto usage; | |
132 | } | |
133 | continue; | |
134 | case 'i': | |
135 | file = arg; | |
136 | continue; | |
137 | case 'x': | |
138 | keyid = arg; | |
139 | continue; | |
140 | case 'd': | |
141 | dn = arg; | |
142 | continue; | |
143 | case 'a': | |
144 | san->insert_last(san, identification_create_from_string(arg)); | |
145 | continue; | |
146 | case 'l': | |
147 | lifetime = atoi(arg) * 24 * 60 * 60; | |
148 | if (!lifetime) | |
149 | { | |
150 | error = "invalid --lifetime value"; | |
151 | goto usage; | |
152 | } | |
153 | continue; | |
154 | case 'D': | |
155 | dateform = arg; | |
156 | continue; | |
157 | case 'F': | |
158 | datenb = arg; | |
159 | continue; | |
160 | case 'T': | |
161 | datena = arg; | |
162 | continue; | |
163 | case 's': | |
164 | hex = arg; | |
165 | continue; | |
166 | case 'b': | |
167 | flags |= X509_CA; | |
168 | continue; | |
169 | case 'p': | |
170 | pathlen = atoi(arg); | |
171 | continue; | |
172 | case 'B': | |
173 | ts = parse_ts(arg); | |
174 | if (!ts) | |
175 | { | |
176 | error = "invalid addressBlock"; | |
177 | goto usage; | |
178 | } | |
179 | addrblocks->insert_last(addrblocks, ts); | |
180 | continue; | |
181 | case 'n': | |
182 | permitted->insert_last(permitted, | |
183 | identification_create_from_string(arg)); | |
184 | continue; | |
185 | case 'N': | |
186 | excluded->insert_last(excluded, | |
187 | identification_create_from_string(arg)); | |
188 | continue; | |
189 | case 'P': | |
190 | { | |
191 | chunk_t oid; | |
192 | ||
193 | oid = asn1_oid_from_string(arg); | |
194 | if (!oid.len) | |
195 | { | |
196 | error = "--cert-policy OID invalid"; | |
197 | goto usage; | |
198 | } | |
199 | INIT(policy, | |
200 | .oid = oid, | |
201 | ); | |
202 | policies->insert_last(policies, policy); | |
203 | continue; | |
204 | } | |
205 | case 'C': | |
206 | if (!policy) | |
207 | { | |
208 | error = "--cps-uri must follow a --cert-policy"; | |
209 | goto usage; | |
210 | } | |
211 | policy->cps_uri = arg; | |
212 | continue; | |
213 | case 'U': | |
214 | if (!policy) | |
215 | { | |
216 | error = "--user-notice must follow a --cert-policy"; | |
217 | goto usage; | |
218 | } | |
219 | policy->unotice_text = arg; | |
220 | continue; | |
221 | case 'M': | |
222 | { | |
223 | char *pos = strchr(arg, ':'); | |
224 | x509_policy_mapping_t *mapping; | |
225 | chunk_t subject_oid, issuer_oid; | |
226 | ||
227 | if (pos) | |
228 | { | |
229 | *pos++ = '\0'; | |
230 | issuer_oid = asn1_oid_from_string(arg); | |
231 | subject_oid = asn1_oid_from_string(pos); | |
232 | } | |
233 | if (!pos || !issuer_oid.len || !subject_oid.len) | |
234 | { | |
235 | error = "--policy-map OIDs invalid"; | |
236 | goto usage; | |
237 | } | |
238 | INIT(mapping, | |
239 | .issuer = issuer_oid, | |
240 | .subject = subject_oid, | |
241 | ); | |
242 | mappings->insert_last(mappings, mapping); | |
243 | continue; | |
244 | } | |
245 | case 'E': | |
246 | require_explicit = atoi(arg); | |
247 | continue; | |
248 | case 'H': | |
249 | inhibit_mapping = atoi(arg); | |
250 | continue; | |
251 | case 'A': | |
252 | inhibit_any = atoi(arg); | |
253 | continue; | |
254 | case 'e': | |
255 | if (streq(arg, "serverAuth")) | |
256 | { | |
257 | flags |= X509_SERVER_AUTH; | |
258 | } | |
259 | else if (streq(arg, "clientAuth")) | |
260 | { | |
261 | flags |= X509_CLIENT_AUTH; | |
262 | } | |
263 | else if (streq(arg, "ikeIntermediate")) | |
264 | { | |
265 | flags |= X509_IKE_INTERMEDIATE; | |
266 | } | |
267 | else if (streq(arg, "crlSign")) | |
268 | { | |
269 | flags |= X509_CRL_SIGN; | |
270 | } | |
271 | else if (streq(arg, "ocspSigning")) | |
272 | { | |
273 | flags |= X509_OCSP_SIGNER; | |
274 | } | |
275 | else if (streq(arg, "msSmartcardLogon")) | |
276 | { | |
277 | flags |= X509_MS_SMARTCARD_LOGON; | |
278 | } | |
279 | continue; | |
280 | case 'f': | |
281 | if (!get_form(arg, &form, CRED_CERTIFICATE)) | |
282 | { | |
283 | error = "invalid output format"; | |
284 | goto usage; | |
285 | } | |
286 | continue; | |
287 | case 'o': | |
288 | ocsp->insert_last(ocsp, arg); | |
289 | continue; | |
290 | case 'X': | |
291 | chunk_free(&critical_extension_oid); | |
292 | critical_extension_oid = asn1_oid_from_string(arg); | |
293 | continue; | |
294 | case EOF: | |
295 | break; | |
296 | default: | |
297 | error = "invalid --self option"; | |
298 | goto usage; | |
299 | } | |
300 | break; | |
301 | } | |
302 | ||
303 | if (!dn) | |
304 | { | |
305 | error = "--dn is required"; | |
306 | goto usage; | |
307 | } | |
308 | if (!calculate_lifetime(dateform, datenb, datena, lifetime, | |
309 | ¬_before, ¬_after)) | |
310 | { | |
311 | error = "invalid --not-before/after datetime"; | |
312 | goto usage; | |
313 | } | |
314 | id = identification_create_from_string(dn); | |
315 | if (id->get_type(id) != ID_DER_ASN1_DN) | |
316 | { | |
317 | error = "supplied --dn is not a distinguished name"; | |
318 | goto end; | |
319 | } | |
320 | if (file) | |
321 | { | |
322 | private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, type, | |
323 | BUILD_FROM_FILE, file, BUILD_END); | |
324 | } | |
325 | else if (keyid) | |
326 | { | |
327 | chunk_t chunk; | |
328 | ||
329 | chunk = chunk_from_hex(chunk_create(keyid, strlen(keyid)), NULL); | |
330 | private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY, | |
331 | BUILD_PKCS11_KEYID, chunk, BUILD_END); | |
332 | free(chunk.ptr); | |
333 | } | |
334 | else | |
335 | { | |
336 | chunk_t chunk; | |
337 | ||
338 | set_file_mode(stdin, CERT_ASN1_DER); | |
339 | if (!chunk_from_fd(0, &chunk)) | |
340 | { | |
341 | fprintf(stderr, "%s: ", strerror(errno)); | |
342 | error = "reading private key failed"; | |
343 | goto end; | |
344 | } | |
345 | private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, type, | |
346 | BUILD_BLOB, chunk, BUILD_END); | |
347 | free(chunk.ptr); | |
348 | } | |
349 | if (!private) | |
350 | { | |
351 | error = "loading private key failed"; | |
352 | goto end; | |
353 | } | |
354 | public = private->get_public_key(private); | |
355 | if (!public) | |
356 | { | |
357 | error = "extracting public key failed"; | |
358 | goto end; | |
359 | } | |
360 | if (hex) | |
361 | { | |
362 | serial = chunk_from_hex(chunk_create(hex, strlen(hex)), NULL); | |
363 | } | |
364 | else if (!allocate_serial(8, &serial)) | |
365 | { | |
366 | error = "failed to generate serial number"; | |
367 | goto end; | |
368 | } | |
369 | scheme = get_signature_scheme(private, digest, pss); | |
370 | if (!scheme) | |
371 | { | |
372 | error = "no signature scheme found"; | |
373 | goto end; | |
374 | } | |
375 | ||
376 | cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, | |
377 | BUILD_SIGNING_KEY, private, BUILD_PUBLIC_KEY, public, | |
378 | BUILD_SUBJECT, id, BUILD_NOT_BEFORE_TIME, not_before, | |
379 | BUILD_NOT_AFTER_TIME, not_after, BUILD_SERIAL, serial, | |
380 | BUILD_SIGNATURE_SCHEME, scheme, BUILD_X509_FLAG, flags, | |
381 | BUILD_PATHLEN, pathlen, BUILD_SUBJECT_ALTNAMES, san, | |
382 | BUILD_ADDRBLOCKS, addrblocks, | |
383 | BUILD_OCSP_ACCESS_LOCATIONS, ocsp, | |
384 | BUILD_PERMITTED_NAME_CONSTRAINTS, permitted, | |
385 | BUILD_EXCLUDED_NAME_CONSTRAINTS, excluded, | |
386 | BUILD_CERTIFICATE_POLICIES, policies, | |
387 | BUILD_POLICY_MAPPINGS, mappings, | |
388 | BUILD_POLICY_REQUIRE_EXPLICIT, require_explicit, | |
389 | BUILD_POLICY_INHIBIT_MAPPING, inhibit_mapping, | |
390 | BUILD_POLICY_INHIBIT_ANY, inhibit_any, | |
391 | BUILD_CRITICAL_EXTENSION, critical_extension_oid, | |
392 | BUILD_END); | |
393 | if (!cert) | |
394 | { | |
395 | error = "generating certificate failed"; | |
396 | goto end; | |
397 | } | |
398 | if (!cert->get_encoding(cert, form, &encoding)) | |
399 | { | |
400 | error = "encoding certificate failed"; | |
401 | goto end; | |
402 | } | |
403 | set_file_mode(stdout, form); | |
404 | if (fwrite(encoding.ptr, encoding.len, 1, stdout) != 1) | |
405 | { | |
406 | error = "writing certificate key failed"; | |
407 | goto end; | |
408 | } | |
409 | ||
410 | end: | |
411 | DESTROY_IF(id); | |
412 | DESTROY_IF(cert); | |
413 | DESTROY_IF(public); | |
414 | DESTROY_IF(private); | |
415 | san->destroy_offset(san, offsetof(identification_t, destroy)); | |
416 | permitted->destroy_offset(permitted, offsetof(identification_t, destroy)); | |
417 | excluded->destroy_offset(excluded, offsetof(identification_t, destroy)); | |
418 | addrblocks->destroy_offset(addrblocks, offsetof(traffic_selector_t, destroy)); | |
419 | policies->destroy_function(policies, (void*)destroy_cert_policy); | |
420 | mappings->destroy_function(mappings, (void*)destroy_policy_mapping); | |
421 | ocsp->destroy(ocsp); | |
422 | signature_params_destroy(scheme); | |
423 | free(critical_extension_oid.ptr); | |
424 | free(encoding.ptr); | |
425 | free(serial.ptr); | |
426 | ||
427 | if (error) | |
428 | { | |
429 | fprintf(stderr, "%s\n", error); | |
430 | return 1; | |
431 | } | |
432 | return 0; | |
433 | ||
434 | usage: | |
435 | san->destroy_offset(san, offsetof(identification_t, destroy)); | |
436 | permitted->destroy_offset(permitted, offsetof(identification_t, destroy)); | |
437 | excluded->destroy_offset(excluded, offsetof(identification_t, destroy)); | |
438 | addrblocks->destroy_offset(addrblocks, offsetof(traffic_selector_t, destroy)); | |
439 | policies->destroy_function(policies, (void*)destroy_cert_policy); | |
440 | mappings->destroy_function(mappings, (void*)destroy_policy_mapping); | |
441 | ocsp->destroy(ocsp); | |
442 | free(critical_extension_oid.ptr); | |
443 | return command_usage(error); | |
444 | } | |
445 | ||
446 | /** | |
447 | * Register the command. | |
448 | */ | |
449 | static void __attribute__ ((constructor))reg() | |
450 | { | |
451 | command_register((command_t) { | |
452 | self, 's', "self", | |
453 | "create a self signed certificate", | |
454 | {"[--in file|--keyid hex] [--type rsa|ecdsa|ed25519|ed448|priv]", | |
455 | "--dn distinguished-name [--san subjectAltName]+", | |
456 | "[--lifetime days] [--serial hex] [--ca] [--ocsp uri]+", | |
457 | "[--flag serverAuth|clientAuth|crlSign|ocspSigning|msSmartcardLogon]+", | |
458 | "[--nc-permitted name] [--nc-excluded name]", | |
459 | "[--policy-map issuer-oid:subject-oid]", | |
460 | "[--policy-explicit len] [--policy-inhibit len] [--policy-any len]", | |
461 | "[--cert-policy oid [--cps-uri uri] [--user-notice text]]+", | |
462 | "[--digest md5|sha1|sha224|sha256|sha384|sha512|sha3_224|sha3_256|sha3_384|sha3_512]", | |
463 | "[--rsa-padding pkcs1|pss] [--critical oid]", | |
464 | "[--outform der|pem]"}, | |
465 | { | |
466 | {"help", 'h', 0, "show usage information"}, | |
467 | {"in", 'i', 1, "private key input file, default: stdin"}, | |
468 | {"keyid", 'x', 1, "smartcard or TPM private key object handle"}, | |
469 | {"type", 't', 1, "type of input key, default: priv"}, | |
470 | {"dn", 'd', 1, "subject and issuer distinguished name"}, | |
471 | {"san", 'a', 1, "subjectAltName to include in certificate"}, | |
472 | {"lifetime", 'l', 1, "days the certificate is valid, default: 1095"}, | |
473 | {"not-before", 'F', 1, "date/time the validity of the cert starts"}, | |
474 | {"not-after", 'T', 1, "date/time the validity of the cert ends"}, | |
475 | {"dateform", 'D', 1, "strptime(3) input format, default: %d.%m.%y %T"}, | |
476 | {"serial", 's', 1, "serial number in hex, default: random"}, | |
477 | {"ca", 'b', 0, "include CA basicConstraint, default: no"}, | |
478 | {"pathlen", 'p', 1, "set path length constraint"}, | |
479 | {"addrblock", 'B', 1, "RFC 3779 addrBlock to include"}, | |
480 | {"nc-permitted", 'n', 1, "add permitted NameConstraint"}, | |
481 | {"nc-excluded", 'N', 1, "add excluded NameConstraint"}, | |
482 | {"cert-policy", 'P', 1, "certificatePolicy OID to include"}, | |
483 | {"cps-uri", 'C', 1, "Certification Practice statement URI for certificatePolicy"}, | |
484 | {"user-notice", 'U', 1, "user notice for certificatePolicy"}, | |
485 | {"policy-mapping", 'M', 1, "policyMapping from issuer to subject OID"}, | |
486 | {"policy-explicit", 'E', 1, "requireExplicitPolicy constraint"}, | |
487 | {"policy-inhibit", 'H', 1, "inhibitPolicyMapping constraint"}, | |
488 | {"policy-any", 'A', 1, "inhibitAnyPolicy constraint"}, | |
489 | {"flag", 'e', 1, "include extendedKeyUsage flag"}, | |
490 | {"ocsp", 'o', 1, "OCSP AuthorityInfoAccess URI to include"}, | |
491 | {"digest", 'g', 1, "digest for signature creation, default: key-specific"}, | |
492 | {"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"}, | |
493 | {"critical", 'X', 1, "critical extension OID to include for test purposes"}, | |
494 | {"outform", 'f', 1, "encoding of generated cert, default: der"}, | |
495 | } | |
496 | }); | |
497 | } |