]>
Commit | Line | Data |
---|---|---|
997358a6 | 1 | /* |
a2ddcc36 | 2 | * Copyright (C) 2012 Tobias Brunner |
997358a6 MW |
3 | * Copyright (C) 2005 Jan Hutter, Martin Willi |
4 | * Hochschule fuer Technik Rapperswil | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
14 | * for more details. | |
15 | */ | |
16 | ||
997358a6 MW |
17 | #include <stdarg.h> |
18 | #include <stdio.h> | |
19 | #include <stdlib.h> | |
20 | #include <string.h> | |
21 | #include <getopt.h> | |
22 | #include <ctype.h> | |
23 | #include <unistd.h> | |
24 | #include <time.h> | |
dd93aefc | 25 | #include <limits.h> |
a2ddcc36 | 26 | #include <syslog.h> |
b9ee059c | 27 | #include <errno.h> |
997358a6 | 28 | |
a6e3ec13 | 29 | #include <library.h> |
f05b4272 | 30 | #include <utils/debug.h> |
08b2d288 | 31 | #include <asn1/asn1.h> |
96cc677c | 32 | #include <asn1/oid.h> |
54c4de63 | 33 | #include <utils/optionsfrom.h> |
12642a68 TB |
34 | #include <collections/enumerator.h> |
35 | #include <collections/linked_list.h> | |
0eff9f65 | 36 | #include <crypto/hashers/hasher.h> |
71e725d3 AS |
37 | #include <crypto/crypters/crypter.h> |
38 | #include <crypto/proposal/proposal_keywords.h> | |
8b799d55 AS |
39 | #include <credentials/keys/private_key.h> |
40 | #include <credentials/keys/public_key.h> | |
0eff9f65 AS |
41 | #include <credentials/certificates/certificate.h> |
42 | #include <credentials/certificates/x509.h> | |
43 | #include <credentials/certificates/pkcs10.h> | |
74cc41c7 | 44 | #include <credentials/sets/mem_cred.h> |
9cd7f384 | 45 | #include <plugins/plugin.h> |
997358a6 | 46 | |
997358a6 MW |
47 | #include "scep.h" |
48 | ||
49 | /* | |
50 | * definition of some defaults | |
51 | */ | |
52 | ||
dd93aefc TB |
53 | /* some paths */ |
54 | #define REQ_PATH IPSEC_CONFDIR "/ipsec.d/reqs" | |
55 | #define HOST_CERT_PATH IPSEC_CONFDIR "/ipsec.d/certs" | |
56 | #define CA_CERT_PATH IPSEC_CONFDIR "/ipsec.d/cacerts" | |
57 | #define PRIVATE_KEY_PATH IPSEC_CONFDIR "/ipsec.d/private" | |
58 | ||
997358a6 | 59 | /* default name of DER-encoded PKCS#1 private key file */ |
3d7a244b | 60 | #define DEFAULT_FILENAME_PKCS1 "myKey.der" |
997358a6 MW |
61 | |
62 | /* default name of DER-encoded PKCS#10 certificate request file */ | |
3d7a244b | 63 | #define DEFAULT_FILENAME_PKCS10 "myReq.der" |
997358a6 MW |
64 | |
65 | /* default name of DER-encoded PKCS#7 file */ | |
3d7a244b | 66 | #define DEFAULT_FILENAME_PKCS7 "pkcs7.der" |
997358a6 MW |
67 | |
68 | /* default name of DER-encoded self-signed X.509 certificate file */ | |
3d7a244b | 69 | #define DEFAULT_FILENAME_CERT_SELF "selfCert.der" |
997358a6 MW |
70 | |
71 | /* default name of DER-encoded X.509 certificate file */ | |
3d7a244b | 72 | #define DEFAULT_FILENAME_CERT "myCert.der" |
997358a6 MW |
73 | |
74 | /* default name of DER-encoded CA cert file used for key encipherment */ | |
3d7a244b | 75 | #define DEFAULT_FILENAME_CACERT_ENC "caCert.der" |
997358a6 MW |
76 | |
77 | /* default name of the der encoded CA cert file used for signature verification */ | |
3d7a244b | 78 | #define DEFAULT_FILENAME_CACERT_SIG "caCert.der" |
997358a6 MW |
79 | |
80 | /* default prefix of the der encoded CA certificates received from the SCEP server */ | |
3d7a244b | 81 | #define DEFAULT_FILENAME_PREFIX_CACERT "caCert.der" |
997358a6 MW |
82 | |
83 | /* default certificate validity */ | |
3d7a244b | 84 | #define DEFAULT_CERT_VALIDITY 5 * 3600 * 24 * 365 /* seconds */ |
997358a6 MW |
85 | |
86 | /* default polling time interval in SCEP manual mode */ | |
3d7a244b | 87 | #define DEFAULT_POLL_INTERVAL 20 /* seconds */ |
997358a6 MW |
88 | |
89 | /* default key length for self-generated RSA keys */ | |
3d7a244b | 90 | #define DEFAULT_RSA_KEY_LENGTH 2048 /* bits */ |
997358a6 MW |
91 | |
92 | /* default distinguished name */ | |
93 | #define DEFAULT_DN "C=CH, O=Linux strongSwan, CN=" | |
94 | ||
c6a2aa49 TB |
95 | /* minimum RSA key size */ |
96 | #define RSA_MIN_OCTETS (512 / BITS_PER_BYTE) | |
97 | ||
997358a6 MW |
98 | /* challenge password buffer size */ |
99 | #define MAX_PASSWORD_LENGTH 256 | |
100 | ||
101 | /* Max length of filename for tempfile */ | |
102 | #define MAX_TEMP_FILENAME_LENGTH 256 | |
103 | ||
104 | ||
105 | /* current scepclient version */ | |
106 | static const char *scepclient_version = "1.0"; | |
107 | ||
108 | /* by default the CRL policy is lenient */ | |
109 | bool strict_crl_policy = FALSE; | |
110 | ||
111 | /* by default pluto does not check crls dynamically */ | |
112 | long crl_check_interval = 0; | |
113 | ||
114 | /* by default pluto logs out after every smartcard use */ | |
115 | bool pkcs11_keep_state = FALSE; | |
116 | ||
f476ff84 MW |
117 | /* by default HTTP fetch timeout is 30s */ |
118 | static u_int http_timeout = 30; | |
119 | ||
78af570f MW |
120 | /* address to bind for HTTP fetches */ |
121 | static char* http_bind = NULL; | |
122 | ||
54c4de63 AS |
123 | /* options read by optionsfrom */ |
124 | options_t *options; | |
997358a6 MW |
125 | |
126 | /* | |
127 | * Global variables | |
128 | */ | |
997358a6 MW |
129 | chunk_t pkcs1; |
130 | chunk_t pkcs7; | |
997358a6 MW |
131 | chunk_t challengePassword; |
132 | chunk_t serialNumber; | |
133 | chunk_t transID; | |
134 | chunk_t fingerprint; | |
0eff9f65 AS |
135 | chunk_t encoding; |
136 | chunk_t pkcs10_encoding; | |
997358a6 MW |
137 | chunk_t issuerAndSubject; |
138 | chunk_t getCertInitial; | |
139 | chunk_t scep_response; | |
997358a6 | 140 | |
0eff9f65 AS |
141 | linked_list_t *subjectAltNames; |
142 | ||
143 | identification_t *subject = NULL; | |
144 | private_key_t *private_key = NULL; | |
145 | public_key_t *public_key = NULL; | |
146 | certificate_t *x509_signer = NULL; | |
147 | certificate_t *x509_ca_enc = NULL; | |
148 | certificate_t *x509_ca_sig = NULL; | |
149 | certificate_t *pkcs10_req = NULL; | |
997358a6 | 150 | |
74cc41c7 MW |
151 | mem_cred_t *creds = NULL; |
152 | ||
a2ddcc36 TB |
153 | /* logging */ |
154 | static bool log_to_stderr = TRUE; | |
155 | static bool log_to_syslog = TRUE; | |
156 | static level_t default_loglevel = 1; | |
157 | ||
158 | /** | |
159 | * logging function for scepclient | |
160 | */ | |
161 | static void scepclient_dbg(debug_t group, level_t level, char *fmt, ...) | |
162 | { | |
163 | char buffer[8192]; | |
164 | char *current = buffer, *next; | |
165 | va_list args; | |
166 | ||
167 | if (level <= default_loglevel) | |
168 | { | |
169 | if (log_to_stderr) | |
170 | { | |
171 | va_start(args, fmt); | |
172 | vfprintf(stderr, fmt, args); | |
173 | va_end(args); | |
174 | fprintf(stderr, "\n"); | |
175 | } | |
176 | if (log_to_syslog) | |
177 | { | |
178 | /* write in memory buffer first */ | |
179 | va_start(args, fmt); | |
180 | vsnprintf(buffer, sizeof(buffer), fmt, args); | |
181 | va_end(args); | |
182 | ||
183 | /* do a syslog with every line */ | |
184 | while (current) | |
185 | { | |
186 | next = strchr(current, '\n'); | |
187 | if (next) | |
188 | { | |
189 | *(next++) = '\0'; | |
190 | } | |
191 | syslog(LOG_INFO, "%s\n", current); | |
192 | current = next; | |
193 | } | |
194 | } | |
195 | } | |
196 | } | |
197 | ||
198 | /** | |
199 | * Initialize logging to stderr/syslog | |
200 | */ | |
201 | static void init_log(const char *program) | |
202 | { | |
203 | dbg = scepclient_dbg; | |
204 | ||
205 | if (log_to_stderr) | |
206 | { | |
207 | setbuf(stderr, NULL); | |
208 | } | |
209 | if (log_to_syslog) | |
210 | { | |
211 | openlog(program, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_AUTHPRIV); | |
212 | } | |
213 | } | |
214 | ||
dd93aefc TB |
215 | /** |
216 | * join two paths if filename is not absolute | |
217 | */ | |
218 | static void join_paths(char *target, size_t target_size, char *parent, | |
219 | char *filename) | |
220 | { | |
221 | if (*filename == '/' || *filename == '.') | |
222 | { | |
223 | snprintf(target, target_size, "%s", filename); | |
224 | } | |
225 | else | |
226 | { | |
227 | snprintf(target, target_size, "%s/%s", parent, filename); | |
228 | } | |
229 | } | |
230 | ||
04ff78aa TB |
231 | /** |
232 | * add a suffix to a given filename, properly handling extensions like '.der' | |
233 | */ | |
234 | static void add_path_suffix(char *target, size_t target_size, char *filename, | |
f912fedc | 235 | char *suffix_fmt, ...) |
04ff78aa | 236 | { |
f912fedc TB |
237 | char suffix[PATH_MAX], *start, *dot; |
238 | va_list args; | |
239 | ||
240 | va_start(args, suffix_fmt); | |
241 | vsnprintf(suffix, sizeof(suffix), suffix_fmt, args); | |
242 | va_end(args); | |
04ff78aa TB |
243 | |
244 | start = strrchr(filename, '/'); | |
245 | start = start ?: filename; | |
246 | dot = strrchr(start, '.'); | |
247 | ||
248 | if (!dot || dot == start || dot[1] == '\0') | |
249 | { /* no extension add suffix at the end */ | |
250 | snprintf(target, target_size, "%s%s", filename, suffix); | |
251 | } | |
252 | else | |
253 | { /* add the suffix between the filename and the extension */ | |
254 | snprintf(target, target_size, "%.*s%s%s", (int)(dot - filename), | |
255 | filename, suffix, dot); | |
256 | } | |
257 | } | |
258 | ||
997358a6 MW |
259 | /** |
260 | * @brief exit scepclient | |
261 | * | |
997358a6 MW |
262 | * @param status 0 = OK, 1 = general discomfort |
263 | */ | |
25924d3e | 264 | static void exit_scepclient(err_t message, ...) |
997358a6 | 265 | { |
3d7a244b AS |
266 | int status = 0; |
267 | ||
74cc41c7 MW |
268 | if (creds) |
269 | { | |
270 | lib->credmgr->remove_set(lib->credmgr, &creds->set); | |
271 | creds->destroy(creds); | |
272 | } | |
273 | ||
0eff9f65 | 274 | DESTROY_IF(subject); |
8b799d55 AS |
275 | DESTROY_IF(private_key); |
276 | DESTROY_IF(public_key); | |
0eff9f65 AS |
277 | DESTROY_IF(x509_signer); |
278 | DESTROY_IF(x509_ca_enc); | |
279 | DESTROY_IF(x509_ca_sig); | |
280 | DESTROY_IF(pkcs10_req); | |
281 | subjectAltNames->destroy_offset(subjectAltNames, | |
282 | offsetof(identification_t, destroy)); | |
3d7a244b AS |
283 | free(pkcs1.ptr); |
284 | free(pkcs7.ptr); | |
3d7a244b AS |
285 | free(serialNumber.ptr); |
286 | free(transID.ptr); | |
287 | free(fingerprint.ptr); | |
0eff9f65 AS |
288 | free(encoding.ptr); |
289 | free(pkcs10_encoding.ptr); | |
3d7a244b AS |
290 | free(issuerAndSubject.ptr); |
291 | free(getCertInitial.ptr); | |
292 | free(scep_response.ptr); | |
3d7a244b AS |
293 | options->destroy(options); |
294 | ||
295 | /* print any error message to stderr */ | |
296 | if (message != NULL && *message != '\0') | |
297 | { | |
298 | va_list args; | |
a2ddcc36 | 299 | char m[8192]; |
3d7a244b AS |
300 | |
301 | va_start(args, message); | |
302 | vsnprintf(m, sizeof(m), message, args); | |
303 | va_end(args); | |
304 | ||
305 | fprintf(stderr, "error: %s\n", m); | |
306 | status = -1; | |
307 | } | |
308 | library_deinit(); | |
3d7a244b | 309 | exit(status); |
997358a6 MW |
310 | } |
311 | ||
312 | /** | |
313 | * @brief prints the program version and exits | |
314 | * | |
315 | */ | |
25924d3e | 316 | static void version(void) |
997358a6 | 317 | { |
3d7a244b AS |
318 | printf("scepclient %s\n", scepclient_version); |
319 | exit_scepclient(NULL); | |
997358a6 MW |
320 | } |
321 | ||
322 | /** | |
323 | * @brief prints the usage of the program to the stderr output | |
324 | * | |
325 | * If message is set, program is exitet with 1 (error) | |
326 | * @param message message in case of an error | |
327 | */ | |
25924d3e | 328 | static void usage(const char *message) |
997358a6 | 329 | { |
3d7a244b AS |
330 | fprintf(stderr, |
331 | "Usage: scepclient\n" | |
332 | " --help (-h) show usage and exit\n" | |
333 | " --version (-v) show version and exit\n" | |
334 | " --quiet (-q) do not write log output to stderr\n" | |
d7c3fd54 TB |
335 | " --in (-i) <type>[=<filename>] use <filename> of <type> for input\n" |
336 | " <type> = pkcs1 | pkcs10 | cert-self\n" | |
337 | " cacert-enc | cacert-sig\n" | |
cea9bf56 TB |
338 | " - if no pkcs1 input is defined, an RSA\n" |
339 | " key will be generated\n" | |
d7c3fd54 TB |
340 | " - if no pkcs10 input is defined, a\n" |
341 | " PKCS#10 request will be generated\n" | |
cea9bf56 TB |
342 | " - if no cert-self input is defined, a\n" |
343 | " self-signed certificate will be generated\n" | |
3d7a244b AS |
344 | " - if no filename is given, default is used\n" |
345 | " --out (-o) <type>[=<filename>] write output of <type> to <filename>\n" | |
346 | " multiple outputs are allowed\n" | |
1d81b1ab TB |
347 | " <type> = pkcs1 | pkcs10 | pkcs7 | cert-self |\n" |
348 | " cert | cacert\n" | |
3d7a244b AS |
349 | " - type cacert defines filename prefix of\n" |
350 | " received CA certificate(s)\n" | |
351 | " - if no filename is given, default is used\n" | |
352 | " --optionsfrom (-+) <filename> reads additional options from given file\n" | |
353 | " --force (-f) force existing file(s)\n" | |
f476ff84 | 354 | " --httptimeout (-T) timeout for HTTP operations (default: 30s)\n" |
78af570f | 355 | " --bind (-b) source address to bind for HTTP operations\n" |
3d7a244b AS |
356 | "\n" |
357 | "Options for key generation (pkcs1):\n" | |
358 | " --keylength (-k) <bits> key length for RSA key generation\n" | |
a2ddcc36 | 359 | " (default: 2048 bits)\n" |
3d7a244b AS |
360 | "\n" |
361 | "Options for validity:\n" | |
362 | " --days (-D) <days> validity in days\n" | |
363 | " --startdate (-S) <YYMMDDHHMMSS>Z not valid before date\n" | |
364 | " --enddate (-E) <YYMMDDHHMMSS>Z not valid after date\n" | |
365 | "\n" | |
366 | "Options for request generation (pkcs10):\n" | |
367 | " --dn (-d) <dn> comma separated list of distinguished names\n" | |
368 | " --subjectAltName (-s) <t>=<v> include subjectAltName in certificate request\n" | |
369 | " <t> = email | dns | ip \n" | |
370 | " --password (-p) <pw> challenge password\n" | |
1d81b1ab TB |
371 | " - use '%%prompt' as pw for a password prompt\n" |
372 | " --algorithm (-a) [<type>=]<algo> algorithm to be used for PKCS#7 encryption,\n" | |
373 | " PKCS#7 digest or PKCS#10 signature\n" | |
374 | " <type> = enc | dgst | sig\n" | |
375 | " - if no type is given enc is assumed\n" | |
376 | " <algo> = des (default) | 3des | aes128 |\n" | |
377 | " aes192 | aes256 | camellia128 |\n" | |
378 | " camellia192 | camellia256\n" | |
379 | " <algo> = md5 (default) | sha1 | sha256 |\n" | |
380 | " sha384 | sha512\n" | |
3d7a244b | 381 | "\n" |
2fc0232a MW |
382 | "Options for CA certificate acquisition:\n" |
383 | " --caname (-c) <name> name of CA to fetch CA certificate(s)\n" | |
384 | " (default: CAIdentifier)\n" | |
3d7a244b AS |
385 | "Options for enrollment (cert):\n" |
386 | " --url (-u) <url> url of the SCEP server\n" | |
387 | " --method (-m) post | get http request type\n" | |
1d81b1ab | 388 | " --interval (-t) <seconds> poll interval in seconds (default 20s)\n" |
3d7a244b AS |
389 | " --maxpolltime (-x) <seconds> max poll time in seconds when in manual mode\n" |
390 | " (default: unlimited)\n" | |
3d7a244b AS |
391 | "\n" |
392 | "Debugging output:\n" | |
a2ddcc36 | 393 | " --debug (-l) <level> changes the log level (-1..4, default: 1)\n" |
3d7a244b AS |
394 | ); |
395 | exit_scepclient(message); | |
997358a6 MW |
396 | } |
397 | ||
398 | /** | |
399 | * @brief main of scepclient | |
400 | * | |
401 | * @param argc number of arguments | |
402 | * @param argv pointer to the argument values | |
403 | */ | |
404 | int main(int argc, char **argv) | |
405 | { | |
3d7a244b AS |
406 | /* external values */ |
407 | extern char * optarg; | |
408 | extern int optind; | |
409 | ||
410 | /* type of input and output files */ | |
411 | typedef enum { | |
412 | PKCS1 = 0x01, | |
413 | PKCS10 = 0x02, | |
414 | PKCS7 = 0x04, | |
415 | CERT_SELF = 0x08, | |
416 | CERT = 0x10, | |
417 | CACERT_ENC = 0x20, | |
cea9bf56 | 418 | CACERT_SIG = 0x40, |
3d7a244b AS |
419 | } scep_filetype_t; |
420 | ||
421 | /* filetype to read from, defaults to "generate a key" */ | |
422 | scep_filetype_t filetype_in = 0; | |
423 | ||
424 | /* filetype to write to, no default here */ | |
425 | scep_filetype_t filetype_out = 0; | |
426 | ||
427 | /* input files */ | |
428 | char *file_in_pkcs1 = DEFAULT_FILENAME_PKCS1; | |
d7c3fd54 | 429 | char *file_in_pkcs10 = DEFAULT_FILENAME_PKCS10; |
cea9bf56 | 430 | char *file_in_cert_self = DEFAULT_FILENAME_CERT_SELF; |
3d7a244b AS |
431 | char *file_in_cacert_enc = DEFAULT_FILENAME_CACERT_ENC; |
432 | char *file_in_cacert_sig = DEFAULT_FILENAME_CACERT_SIG; | |
433 | ||
434 | /* output files */ | |
435 | char *file_out_pkcs1 = DEFAULT_FILENAME_PKCS1; | |
436 | char *file_out_pkcs10 = DEFAULT_FILENAME_PKCS10; | |
437 | char *file_out_pkcs7 = DEFAULT_FILENAME_PKCS7; | |
438 | char *file_out_cert_self = DEFAULT_FILENAME_CERT_SELF; | |
439 | char *file_out_cert = DEFAULT_FILENAME_CERT; | |
72a1b2e3 | 440 | char *file_out_ca_cert = DEFAULT_FILENAME_CACERT_ENC; |
3d7a244b AS |
441 | |
442 | /* by default user certificate is requested */ | |
443 | bool request_ca_certificate = FALSE; | |
444 | ||
445 | /* by default existing files are not overwritten */ | |
446 | bool force = FALSE; | |
447 | ||
448 | /* length of RSA key in bits */ | |
449 | u_int rsa_keylength = DEFAULT_RSA_KEY_LENGTH; | |
450 | ||
451 | /* validity of self-signed certificate */ | |
452 | time_t validity = DEFAULT_CERT_VALIDITY; | |
453 | time_t notBefore = 0; | |
454 | time_t notAfter = 0; | |
455 | ||
456 | /* distinguished name for requested certificate, ASCII format */ | |
457 | char *distinguishedName = NULL; | |
458 | ||
459 | /* challenge password */ | |
460 | char challenge_password_buffer[MAX_PASSWORD_LENGTH]; | |
461 | ||
1d81b1ab TB |
462 | /* symmetric encryption algorithm used by pkcs7, default is DES */ |
463 | encryption_algorithm_t pkcs7_symmetric_cipher = ENCR_DES; | |
c6a2aa49 | 464 | size_t pkcs7_key_size = 0; |
3d7a244b | 465 | |
1d81b1ab TB |
466 | /* digest algorithm used by pkcs7, default is MD5 */ |
467 | hash_algorithm_t pkcs7_digest_alg = HASH_MD5; | |
3d7a244b | 468 | |
1d81b1ab TB |
469 | /* signature algorithm used by pkcs10, default is MD5 */ |
470 | hash_algorithm_t pkcs10_signature_alg = HASH_MD5; | |
3d7a244b AS |
471 | |
472 | /* URL of the SCEP-Server */ | |
473 | char *scep_url = NULL; | |
474 | ||
2fc0232a MW |
475 | /* Name of CA to fetch CA certs for */ |
476 | char *ca_name = "CAIdentifier"; | |
477 | ||
3d7a244b | 478 | /* http request method, default is GET */ |
e67197a7 | 479 | bool http_get_request = TRUE; |
3d7a244b AS |
480 | |
481 | /* poll interval time in manual mode in seconds */ | |
482 | u_int poll_interval = DEFAULT_POLL_INTERVAL; | |
483 | ||
484 | /* maximum poll time */ | |
485 | u_int max_poll_time = 0; | |
486 | ||
487 | err_t ugh = NULL; | |
488 | ||
2f5b1e0e | 489 | /* initialize library */ |
34d3bfcf | 490 | if (!library_init(NULL, "scepclient")) |
2f5b1e0e AS |
491 | { |
492 | library_deinit(); | |
493 | exit(SS_RC_LIBSTRONGSWAN_INTEGRITY); | |
494 | } | |
bde541ac AS |
495 | if (lib->integrity && |
496 | !lib->integrity->check_file(lib->integrity, "scepclient", argv[0])) | |
497 | { | |
498 | fprintf(stderr, "integrity check of scepclient failed\n"); | |
499 | library_deinit(); | |
500 | exit(SS_RC_DAEMON_INTEGRITY); | |
501 | } | |
2f5b1e0e | 502 | |
0eff9f65 AS |
503 | /* initialize global variables */ |
504 | pkcs1 = chunk_empty; | |
505 | pkcs7 = chunk_empty; | |
506 | serialNumber = chunk_empty; | |
507 | transID = chunk_empty; | |
508 | fingerprint = chunk_empty; | |
509 | encoding = chunk_empty; | |
da9724e6 | 510 | pkcs10_encoding = chunk_empty; |
0eff9f65 AS |
511 | issuerAndSubject = chunk_empty; |
512 | challengePassword = chunk_empty; | |
513 | getCertInitial = chunk_empty; | |
514 | scep_response = chunk_empty; | |
515 | subjectAltNames = linked_list_create(); | |
516 | options = options_create(); | |
3d7a244b AS |
517 | |
518 | for (;;) | |
519 | { | |
520 | static const struct option long_opts[] = { | |
521 | /* name, has_arg, flag, val */ | |
522 | { "help", no_argument, NULL, 'h' }, | |
523 | { "version", no_argument, NULL, 'v' }, | |
524 | { "optionsfrom", required_argument, NULL, '+' }, | |
525 | { "quiet", no_argument, NULL, 'q' }, | |
a2ddcc36 | 526 | { "debug", required_argument, NULL, 'l' }, |
3d7a244b AS |
527 | { "in", required_argument, NULL, 'i' }, |
528 | { "out", required_argument, NULL, 'o' }, | |
529 | { "force", no_argument, NULL, 'f' }, | |
f476ff84 | 530 | { "httptimeout", required_argument, NULL, 'T' }, |
78af570f | 531 | { "bind", required_argument, NULL, 'b' }, |
3d7a244b AS |
532 | { "keylength", required_argument, NULL, 'k' }, |
533 | { "dn", required_argument, NULL, 'd' }, | |
534 | { "days", required_argument, NULL, 'D' }, | |
535 | { "startdate", required_argument, NULL, 'S' }, | |
536 | { "enddate", required_argument, NULL, 'E' }, | |
537 | { "subjectAltName", required_argument, NULL, 's' }, | |
538 | { "password", required_argument, NULL, 'p' }, | |
539 | { "algorithm", required_argument, NULL, 'a' }, | |
540 | { "url", required_argument, NULL, 'u' }, | |
2fc0232a | 541 | { "caname", required_argument, NULL, 'c'}, |
3d7a244b AS |
542 | { "method", required_argument, NULL, 'm' }, |
543 | { "interval", required_argument, NULL, 't' }, | |
544 | { "maxpolltime", required_argument, NULL, 'x' }, | |
3d7a244b AS |
545 | { 0,0,0,0 } |
546 | }; | |
547 | ||
548 | /* parse next option */ | |
2fc0232a | 549 | int c = getopt_long(argc, argv, "hv+:qi:o:fk:d:s:p:a:u:c:m:t:x:APRCMS", long_opts, NULL); |
3d7a244b AS |
550 | |
551 | switch (c) | |
552 | { | |
25924d3e TB |
553 | case EOF: /* end of flags */ |
554 | break; | |
3d7a244b | 555 | |
25924d3e TB |
556 | case 'h': /* --help */ |
557 | usage(NULL); | |
3d7a244b | 558 | |
25924d3e TB |
559 | case 'v': /* --version */ |
560 | version(); | |
3d7a244b | 561 | |
25924d3e TB |
562 | case 'q': /* --quiet */ |
563 | log_to_stderr = FALSE; | |
564 | continue; | |
3d7a244b | 565 | |
a2ddcc36 TB |
566 | case 'l': /* --debug <level> */ |
567 | default_loglevel = atoi(optarg); | |
568 | continue; | |
569 | ||
25924d3e | 570 | case 'i': /* --in <type> [= <filename>] */ |
3d7a244b AS |
571 | { |
572 | char *filename = strstr(optarg, "="); | |
573 | ||
574 | if (filename) | |
575 | { | |
576 | /* replace '=' by '\0' */ | |
577 | *filename = '\0'; | |
578 | /* set pointer to start of filename */ | |
579 | filename++; | |
580 | } | |
581 | if (strcaseeq("pkcs1", optarg)) | |
582 | { | |
583 | filetype_in |= PKCS1; | |
584 | if (filename) | |
585 | file_in_pkcs1 = filename; | |
586 | } | |
d7c3fd54 TB |
587 | else if (strcaseeq("pkcs10", optarg)) |
588 | { | |
589 | filetype_in |= PKCS10; | |
590 | if (filename) | |
591 | file_in_pkcs10 = filename; | |
592 | } | |
3d7a244b AS |
593 | else if (strcaseeq("cacert-enc", optarg)) |
594 | { | |
595 | filetype_in |= CACERT_ENC; | |
596 | if (filename) | |
597 | file_in_cacert_enc = filename; | |
598 | } | |
599 | else if (strcaseeq("cacert-sig", optarg)) | |
600 | { | |
601 | filetype_in |= CACERT_SIG; | |
602 | if (filename) | |
cea9bf56 TB |
603 | file_in_cacert_sig = filename; |
604 | } | |
605 | else if (strcaseeq("cert-self", optarg)) | |
606 | { | |
607 | filetype_in |= CERT_SELF; | |
608 | if (filename) | |
609 | file_in_cert_self = filename; | |
3d7a244b AS |
610 | } |
611 | else | |
612 | { | |
613 | usage("invalid --in file type"); | |
614 | } | |
615 | continue; | |
616 | } | |
617 | ||
25924d3e | 618 | case 'o': /* --out <type> [= <filename>] */ |
3d7a244b AS |
619 | { |
620 | char *filename = strstr(optarg, "="); | |
621 | ||
622 | if (filename) | |
623 | { | |
624 | /* replace '=' by '\0' */ | |
625 | *filename = '\0'; | |
626 | /* set pointer to start of filename */ | |
627 | filename++; | |
628 | } | |
629 | if (strcaseeq("pkcs1", optarg)) | |
630 | { | |
631 | filetype_out |= PKCS1; | |
632 | if (filename) | |
633 | file_out_pkcs1 = filename; | |
634 | } | |
635 | else if (strcaseeq("pkcs10", optarg)) | |
636 | { | |
637 | filetype_out |= PKCS10; | |
638 | if (filename) | |
639 | file_out_pkcs10 = filename; | |
640 | } | |
641 | else if (strcaseeq("pkcs7", optarg)) | |
642 | { | |
643 | filetype_out |= PKCS7; | |
644 | if (filename) | |
645 | file_out_pkcs7 = filename; | |
646 | } | |
647 | else if (strcaseeq("cert-self", optarg)) | |
648 | { | |
649 | filetype_out |= CERT_SELF; | |
650 | if (filename) | |
651 | file_out_cert_self = filename; | |
652 | } | |
653 | else if (strcaseeq("cert", optarg)) | |
654 | { | |
655 | filetype_out |= CERT; | |
656 | if (filename) | |
657 | file_out_cert = filename; | |
658 | } | |
659 | else if (strcaseeq("cacert", optarg)) | |
660 | { | |
661 | request_ca_certificate = TRUE; | |
72a1b2e3 TE |
662 | if (filename) |
663 | file_out_ca_cert = filename; | |
3d7a244b AS |
664 | } |
665 | else | |
666 | { | |
667 | usage("invalid --out file type"); | |
668 | } | |
669 | continue; | |
670 | } | |
7daf5226 | 671 | |
25924d3e TB |
672 | case 'f': /* --force */ |
673 | force = TRUE; | |
674 | continue; | |
3d7a244b | 675 | |
f476ff84 MW |
676 | case 'T': /* --httptimeout */ |
677 | http_timeout = atoi(optarg); | |
678 | if (http_timeout <= 0) | |
679 | { | |
680 | usage("invalid httptimeout specified"); | |
681 | } | |
682 | continue; | |
683 | ||
78af570f MW |
684 | case 'b': /* --bind */ |
685 | http_bind = optarg; | |
686 | continue; | |
687 | ||
25924d3e TB |
688 | case '+': /* --optionsfrom <filename> */ |
689 | if (!options->from(options, optarg, &argc, &argv, optind)) | |
690 | { | |
691 | exit_scepclient("optionsfrom failed"); | |
692 | } | |
693 | continue; | |
3d7a244b | 694 | |
25924d3e | 695 | case 'k': /* --keylength <length> */ |
3d7a244b AS |
696 | { |
697 | div_t q; | |
698 | ||
699 | rsa_keylength = atoi(optarg); | |
700 | if (rsa_keylength == 0) | |
701 | usage("invalid keylength"); | |
702 | ||
703 | /* check if key length is a multiple of 8 bits */ | |
704 | q = div(rsa_keylength, 2*BITS_PER_BYTE); | |
705 | if (q.rem != 0) | |
706 | { | |
707 | exit_scepclient("keylength is not a multiple of %d bits!" | |
708 | , 2*BITS_PER_BYTE); | |
709 | } | |
710 | continue; | |
711 | } | |
712 | ||
25924d3e TB |
713 | case 'D': /* --days */ |
714 | if (optarg == NULL || !isdigit(optarg[0])) | |
715 | { | |
716 | usage("missing number of days"); | |
717 | } | |
718 | else | |
719 | { | |
720 | char *endptr; | |
721 | long days = strtol(optarg, &endptr, 0); | |
3d7a244b | 722 | |
25924d3e TB |
723 | if (*endptr != '\0' || endptr == optarg |
724 | || days <= 0) | |
725 | usage("<days> must be a positive number"); | |
726 | validity = 24*3600*days; | |
727 | } | |
728 | continue; | |
3d7a244b | 729 | |
25924d3e TB |
730 | case 'S': /* --startdate */ |
731 | if (optarg == NULL || strlen(optarg) != 13 || optarg[12] != 'Z') | |
732 | { | |
733 | usage("date format must be YYMMDDHHMMSSZ"); | |
734 | } | |
735 | else | |
736 | { | |
737 | chunk_t date = { optarg, 13 }; | |
738 | notBefore = asn1_to_time(&date, ASN1_UTCTIME); | |
739 | } | |
740 | continue; | |
3d7a244b | 741 | |
25924d3e TB |
742 | case 'E': /* --enddate */ |
743 | if (optarg == NULL || strlen(optarg) != 13 || optarg[12] != 'Z') | |
744 | { | |
745 | usage("date format must be YYMMDDHHMMSSZ"); | |
746 | } | |
747 | else | |
748 | { | |
749 | chunk_t date = { optarg, 13 }; | |
750 | notAfter = asn1_to_time(&date, ASN1_UTCTIME); | |
751 | } | |
752 | continue; | |
3d7a244b | 753 | |
25924d3e TB |
754 | case 'd': /* --dn */ |
755 | if (distinguishedName) | |
756 | { | |
757 | usage("only one distinguished name allowed"); | |
758 | } | |
759 | distinguishedName = optarg; | |
760 | continue; | |
3d7a244b | 761 | |
25924d3e | 762 | case 's': /* --subjectAltName */ |
3d7a244b | 763 | { |
3d7a244b AS |
764 | char *value = strstr(optarg, "="); |
765 | ||
766 | if (value) | |
767 | { | |
768 | /* replace '=' by '\0' */ | |
769 | *value = '\0'; | |
770 | /* set pointer to start of value */ | |
771 | value++; | |
772 | } | |
773 | ||
0eff9f65 | 774 | if (strcaseeq("email", optarg) || |
25924d3e | 775 | strcaseeq("dns", optarg) || |
0eff9f65 | 776 | strcaseeq("ip", optarg)) |
3d7a244b | 777 | { |
0eff9f65 AS |
778 | subjectAltNames->insert_last(subjectAltNames, |
779 | identification_create_from_string(value)); | |
780 | continue; | |
3d7a244b AS |
781 | } |
782 | else | |
783 | { | |
784 | usage("invalid --subjectAltName type"); | |
785 | continue; | |
786 | } | |
3d7a244b AS |
787 | } |
788 | ||
25924d3e TB |
789 | case 'p': /* --password */ |
790 | if (challengePassword.len > 0) | |
791 | { | |
792 | usage("only one challenge password allowed"); | |
793 | } | |
794 | if (strcaseeq("%prompt", optarg)) | |
3d7a244b | 795 | { |
25924d3e TB |
796 | printf("Challenge password: "); |
797 | if (fgets(challenge_password_buffer, | |
798 | sizeof(challenge_password_buffer) - 1, stdin)) | |
799 | { | |
800 | challengePassword.ptr = challenge_password_buffer; | |
801 | /* discard the terminating '\n' from the input */ | |
802 | challengePassword.len = strlen(challenge_password_buffer) - 1; | |
803 | } | |
804 | else | |
805 | { | |
806 | usage("challenge password could not be read"); | |
807 | } | |
3d7a244b AS |
808 | } |
809 | else | |
810 | { | |
25924d3e TB |
811 | challengePassword.ptr = optarg; |
812 | challengePassword.len = strlen(optarg); | |
3d7a244b | 813 | } |
25924d3e | 814 | continue; |
3d7a244b | 815 | |
25924d3e TB |
816 | case 'u': /* -- url */ |
817 | if (scep_url) | |
818 | { | |
819 | usage("only one URL argument allowed"); | |
820 | } | |
821 | scep_url = optarg; | |
822 | continue; | |
3d7a244b | 823 | |
2fc0232a MW |
824 | case 'c': /* -- caname */ |
825 | ca_name = optarg; | |
826 | continue; | |
827 | ||
25924d3e TB |
828 | case 'm': /* --method */ |
829 | if (strcaseeq("get", optarg)) | |
830 | { | |
831 | http_get_request = TRUE; | |
832 | } | |
833 | else if (strcaseeq("post", optarg)) | |
834 | { | |
835 | http_get_request = FALSE; | |
836 | } | |
837 | else | |
838 | { | |
839 | usage("invalid http request method specified"); | |
840 | } | |
841 | continue; | |
3d7a244b | 842 | |
25924d3e TB |
843 | case 't': /* --interval */ |
844 | poll_interval = atoi(optarg); | |
845 | if (poll_interval <= 0) | |
846 | { | |
847 | usage("invalid interval specified"); | |
848 | } | |
849 | continue; | |
3d7a244b | 850 | |
25924d3e TB |
851 | case 'x': /* --maxpolltime */ |
852 | max_poll_time = atoi(optarg); | |
853 | continue; | |
71e725d3 | 854 | |
1d81b1ab | 855 | case 'a': /*--algorithm [<type>=]algo */ |
3d7a244b | 856 | { |
25924d3e | 857 | const proposal_token_t *token; |
1d81b1ab TB |
858 | char *type = optarg; |
859 | char *algo = strstr(optarg, "="); | |
25924d3e | 860 | |
1d81b1ab | 861 | if (algo) |
25924d3e | 862 | { |
1d81b1ab TB |
863 | *algo = '\0'; |
864 | algo++; | |
25924d3e | 865 | } |
1d81b1ab TB |
866 | else |
867 | { | |
868 | type = "enc"; | |
869 | algo = optarg; | |
870 | } | |
871 | ||
872 | if (strcaseeq("enc", type)) | |
873 | { | |
4c57c630 | 874 | token = lib->proposal->get_token(lib->proposal, algo); |
1d81b1ab TB |
875 | if (token == NULL || token->type != ENCRYPTION_ALGORITHM) |
876 | { | |
877 | usage("invalid algorithm specified"); | |
878 | } | |
879 | pkcs7_symmetric_cipher = token->algorithm; | |
880 | pkcs7_key_size = token->keysize; | |
881 | if (encryption_algorithm_to_oid(token->algorithm, | |
882 | token->keysize) == OID_UNKNOWN) | |
883 | { | |
884 | usage("unsupported encryption algorithm specified"); | |
885 | } | |
886 | } | |
887 | else if (strcaseeq("dgst", type) || | |
888 | strcaseeq("sig", type)) | |
889 | { | |
890 | hash_algorithm_t hash; | |
891 | ||
4c57c630 | 892 | token = lib->proposal->get_token(lib->proposal, algo); |
1d81b1ab TB |
893 | if (token == NULL || token->type != INTEGRITY_ALGORITHM) |
894 | { | |
895 | usage("invalid algorithm specified"); | |
896 | } | |
903093d4 TB |
897 | hash = hasher_algorithm_from_integrity(token->algorithm, |
898 | NULL); | |
1d81b1ab TB |
899 | if (hash == OID_UNKNOWN) |
900 | { | |
901 | usage("invalid algorithm specified"); | |
902 | } | |
903 | if (strcaseeq("dgst", type)) | |
904 | { | |
905 | pkcs7_digest_alg = hash; | |
906 | } | |
907 | else | |
908 | { | |
909 | pkcs10_signature_alg = hash; | |
910 | } | |
911 | } | |
912 | else | |
25924d3e | 913 | { |
1d81b1ab | 914 | usage("invalid --algorithm type"); |
25924d3e TB |
915 | } |
916 | continue; | |
3d7a244b | 917 | } |
25924d3e TB |
918 | default: |
919 | usage("unknown option"); | |
3d7a244b AS |
920 | } |
921 | /* break from loop */ | |
922 | break; | |
923 | } | |
997358a6 | 924 | |
3d7a244b | 925 | init_log("scepclient"); |
6e9fe153 AS |
926 | |
927 | /* load plugins, further infrastructure may need it */ | |
b18a5317 | 928 | if (!lib->plugins->load(lib->plugins, |
8fb4edc4 MW |
929 | lib->settings->get_str(lib->settings, "scepclient.load", PLUGINS))) |
930 | { | |
931 | exit_scepclient("plugin loading failed"); | |
932 | } | |
607f8e99 | 933 | lib->plugins->status(lib->plugins, LEVEL_DIAG); |
6e9fe153 | 934 | |
3d7a244b | 935 | if ((filetype_out == 0) && (!request_ca_certificate)) |
997358a6 | 936 | { |
a2ddcc36 | 937 | usage("--out filetype required"); |
3d7a244b AS |
938 | } |
939 | if (request_ca_certificate && (filetype_out > 0 || filetype_in > 0)) | |
940 | { | |
941 | usage("in CA certificate request, no other --in or --out option allowed"); | |
942 | } | |
997358a6 | 943 | |
3d7a244b AS |
944 | /* check if url is given, if cert output defined */ |
945 | if (((filetype_out & CERT) || request_ca_certificate) && !scep_url) | |
946 | { | |
947 | usage("URL of SCEP server required"); | |
948 | } | |
997358a6 | 949 | |
3d7a244b AS |
950 | /* check for sanity of --in/--out */ |
951 | if (!filetype_in && (filetype_in > filetype_out)) | |
952 | { | |
953 | usage("cannot generate --out of given --in!"); | |
954 | } | |
997358a6 | 955 | |
72a1b2e3 TE |
956 | /* get CA cert */ |
957 | if (request_ca_certificate) | |
958 | { | |
04ff78aa | 959 | char ca_path[PATH_MAX]; |
74cc41c7 | 960 | container_t *container; |
04ff78aa | 961 | pkcs7_t *pkcs7; |
72a1b2e3 | 962 | |
2fc0232a | 963 | if (!scep_http_request(scep_url, chunk_create(ca_name, strlen(ca_name)), |
f476ff84 | 964 | SCEP_GET_CA_CERT, http_get_request, |
78af570f | 965 | http_timeout, http_bind, &scep_response)) |
72a1b2e3 TE |
966 | { |
967 | exit_scepclient("did not receive a valid scep response"); | |
968 | } | |
969 | ||
04ff78aa TB |
970 | join_paths(ca_path, sizeof(ca_path), CA_CERT_PATH, file_out_ca_cert); |
971 | ||
74cc41c7 MW |
972 | pkcs7 = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7, |
973 | BUILD_BLOB_ASN1_DER, scep_response, BUILD_END); | |
974 | ||
975 | if (!pkcs7) | |
04ff78aa | 976 | { /* no PKCS#7 encoded CA+RA certificates, assume simple CA cert */ |
74cc41c7 MW |
977 | |
978 | DBG1(DBG_APP, "unable to parse PKCS#7, assuming plain CA cert"); | |
b9ee059c | 979 | if (!chunk_write(scep_response, ca_path, 0022, force)) |
04ff78aa | 980 | { |
b9ee059c MW |
981 | exit_scepclient("could not write ca cert file '%s': %s", |
982 | ca_path, strerror(errno)); | |
04ff78aa TB |
983 | } |
984 | } | |
985 | else | |
72a1b2e3 | 986 | { |
04ff78aa TB |
987 | enumerator_t *enumerator; |
988 | certificate_t *cert; | |
f912fedc TB |
989 | int ra_certs = 0, ca_certs = 0; |
990 | int ra_index = 1, ca_index = 1; | |
991 | ||
74cc41c7 | 992 | enumerator = pkcs7->create_cert_enumerator(pkcs7); |
f912fedc TB |
993 | while (enumerator->enumerate(enumerator, &cert)) |
994 | { | |
995 | x509_t *x509 = (x509_t*)cert; | |
996 | if (x509->get_flags(x509) & X509_CA) | |
997 | { | |
998 | ca_certs++; | |
999 | } | |
1000 | else | |
1001 | { | |
1002 | ra_certs++; | |
1003 | } | |
1004 | } | |
1005 | enumerator->destroy(enumerator); | |
04ff78aa | 1006 | |
74cc41c7 | 1007 | enumerator = pkcs7->create_cert_enumerator(pkcs7); |
04ff78aa TB |
1008 | while (enumerator->enumerate(enumerator, &cert)) |
1009 | { | |
1010 | x509_t *x509 = (x509_t*)cert; | |
1011 | bool ca_cert = x509->get_flags(x509) & X509_CA; | |
f912fedc | 1012 | char cert_path[PATH_MAX], *path = ca_path; |
04ff78aa | 1013 | |
f912fedc TB |
1014 | if (ca_cert && ca_certs > 1) |
1015 | { | |
1016 | add_path_suffix(cert_path, sizeof(cert_path), ca_path, | |
1017 | "-%.1d", ca_index++); | |
1018 | path = cert_path; | |
1019 | } | |
1020 | else if (!ca_cert) | |
04ff78aa | 1021 | { /* use CA name as base for RA certs */ |
f912fedc TB |
1022 | if (ra_certs > 1) |
1023 | { | |
1024 | add_path_suffix(cert_path, sizeof(cert_path), ca_path, | |
1025 | "-ra-%.1d", ra_index++); | |
1026 | } | |
1027 | else | |
1028 | { | |
1029 | add_path_suffix(cert_path, sizeof(cert_path), ca_path, | |
1030 | "-ra"); | |
1031 | } | |
1032 | path = cert_path; | |
04ff78aa TB |
1033 | } |
1034 | ||
1035 | if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoding) || | |
b9ee059c | 1036 | !chunk_write(encoding, path, 0022, force)) |
04ff78aa | 1037 | { |
b9ee059c MW |
1038 | exit_scepclient("could not write cert file '%s': %s", |
1039 | path, strerror(errno)); | |
04ff78aa TB |
1040 | } |
1041 | chunk_free(&encoding); | |
1042 | } | |
1043 | enumerator->destroy(enumerator); | |
74cc41c7 MW |
1044 | container = &pkcs7->container; |
1045 | container->destroy(container); | |
72a1b2e3 TE |
1046 | } |
1047 | exit_scepclient(NULL); /* no further output required */ | |
1048 | } | |
1049 | ||
74cc41c7 MW |
1050 | creds = mem_cred_create(); |
1051 | lib->credmgr->add_set(lib->credmgr, &creds->set); | |
1052 | ||
3d7a244b AS |
1053 | /* |
1054 | * input of PKCS#1 file | |
1055 | */ | |
7daf5226 | 1056 | if (filetype_in & PKCS1) /* load an RSA key pair from file */ |
3d7a244b | 1057 | { |
dd93aefc TB |
1058 | char path[PATH_MAX]; |
1059 | ||
1060 | join_paths(path, sizeof(path), PRIVATE_KEY_PATH, file_in_pkcs1); | |
997358a6 | 1061 | |
17722d44 AS |
1062 | private_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, |
1063 | BUILD_FROM_FILE, path, BUILD_END); | |
3d7a244b AS |
1064 | } |
1065 | else /* generate an RSA key pair */ | |
1066 | { | |
8b799d55 AS |
1067 | private_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, |
1068 | BUILD_KEY_SIZE, rsa_keylength, | |
7b3814f7 | 1069 | BUILD_END); |
3d7a244b | 1070 | } |
8b799d55 AS |
1071 | if (private_key == NULL) |
1072 | { | |
1073 | exit_scepclient("no RSA private key available"); | |
1074 | } | |
74cc41c7 | 1075 | creds->add_key(creds, private_key->get_ref(private_key)); |
8b799d55 | 1076 | public_key = private_key->get_public_key(private_key); |
997358a6 | 1077 | |
3d7a244b | 1078 | /* check for minimum key length */ |
a944d209 | 1079 | if (private_key->get_keysize(private_key) < RSA_MIN_OCTETS / BITS_PER_BYTE) |
3d7a244b | 1080 | { |
25924d3e TB |
1081 | exit_scepclient("length of RSA key has to be at least %d bits", |
1082 | RSA_MIN_OCTETS * BITS_PER_BYTE); | |
3d7a244b | 1083 | } |
997358a6 | 1084 | |
3d7a244b AS |
1085 | /* |
1086 | * input of PKCS#10 file | |
1087 | */ | |
1088 | if (filetype_in & PKCS10) | |
1089 | { | |
d7c3fd54 | 1090 | char path[PATH_MAX]; |
997358a6 | 1091 | |
d7c3fd54 | 1092 | join_paths(path, sizeof(path), REQ_PATH, file_in_pkcs10); |
3d7a244b | 1093 | |
d7c3fd54 TB |
1094 | pkcs10_req = lib->creds->create(lib->creds, CRED_CERTIFICATE, |
1095 | CERT_PKCS10_REQUEST, BUILD_FROM_FILE, | |
1096 | path, BUILD_END); | |
1097 | if (!pkcs10_req) | |
1098 | { | |
1099 | exit_scepclient("could not read certificate request '%s'", path); | |
1100 | } | |
1101 | subject = pkcs10_req->get_subject(pkcs10_req); | |
1102 | subject = subject->clone(subject); | |
3d7a244b AS |
1103 | } |
1104 | else | |
1105 | { | |
3d7a244b | 1106 | if (distinguishedName == NULL) |
997358a6 | 1107 | { |
3d7a244b AS |
1108 | char buf[BUF_LEN]; |
1109 | int n = sprintf(buf, DEFAULT_DN); | |
1110 | ||
1111 | /* set the common name to the hostname */ | |
1112 | if (gethostname(buf + n, BUF_LEN - n) || strlen(buf) == n) | |
1113 | { | |
1114 | exit_scepclient("no hostname defined, use " | |
1115 | "--dn <distinguished name> option"); | |
1116 | } | |
1117 | distinguishedName = buf; | |
997358a6 | 1118 | } |
3d7a244b | 1119 | |
a2ddcc36 | 1120 | DBG2(DBG_APP, "dn: '%s'", distinguishedName); |
0eff9f65 AS |
1121 | subject = identification_create_from_string(distinguishedName); |
1122 | if (subject->get_type(subject) != ID_DER_ASN1_DN) | |
997358a6 | 1123 | { |
0eff9f65 | 1124 | exit_scepclient("parsing of distinguished name failed"); |
997358a6 | 1125 | } |
3d7a244b | 1126 | |
a2ddcc36 | 1127 | DBG2(DBG_APP, "building pkcs10 object:"); |
0eff9f65 | 1128 | pkcs10_req = lib->creds->create(lib->creds, CRED_CERTIFICATE, |
25924d3e TB |
1129 | CERT_PKCS10_REQUEST, |
1130 | BUILD_SIGNING_KEY, private_key, | |
1131 | BUILD_SUBJECT, subject, | |
1132 | BUILD_SUBJECT_ALTNAMES, subjectAltNames, | |
1133 | BUILD_CHALLENGE_PWD, challengePassword, | |
1134 | BUILD_DIGEST_ALG, pkcs10_signature_alg, | |
1135 | BUILD_END); | |
0eff9f65 AS |
1136 | if (!pkcs10_req) |
1137 | { | |
1138 | exit_scepclient("generating pkcs10 request failed"); | |
1139 | } | |
997358a6 | 1140 | } |
d7c3fd54 TB |
1141 | pkcs10_req->get_encoding(pkcs10_req, CERT_ASN1_DER, &pkcs10_encoding); |
1142 | fingerprint = scep_generate_pkcs10_fingerprint(pkcs10_encoding); | |
1143 | DBG1(DBG_APP, " fingerprint: %s", fingerprint.ptr); | |
3d7a244b | 1144 | |
7daf5226 | 1145 | /* |
3d7a244b AS |
1146 | * output of PKCS#10 file |
1147 | */ | |
1148 | if (filetype_out & PKCS10) | |
997358a6 | 1149 | { |
dd93aefc TB |
1150 | char path[PATH_MAX]; |
1151 | ||
1152 | join_paths(path, sizeof(path), REQ_PATH, file_out_pkcs10); | |
3d7a244b | 1153 | |
b9ee059c | 1154 | if (!chunk_write(pkcs10_encoding, path, 0022, force)) |
0eff9f65 | 1155 | { |
b9ee059c MW |
1156 | exit_scepclient("could not write pkcs10 file '%s': %s", |
1157 | path, strerror(errno)); | |
0eff9f65 | 1158 | } |
3d7a244b | 1159 | filetype_out &= ~PKCS10; /* delete PKCS10 flag */ |
997358a6 MW |
1160 | } |
1161 | ||
3d7a244b | 1162 | if (!filetype_out) |
63176bbc | 1163 | { |
3d7a244b | 1164 | exit_scepclient(NULL); /* no further output required */ |
63176bbc | 1165 | } |
997358a6 | 1166 | |
3d7a244b AS |
1167 | /* |
1168 | * output of PKCS#1 file | |
1169 | */ | |
1170 | if (filetype_out & PKCS1) | |
63176bbc | 1171 | { |
dd93aefc TB |
1172 | char path[PATH_MAX]; |
1173 | ||
1174 | join_paths(path, sizeof(path), PRIVATE_KEY_PATH, file_out_pkcs1); | |
997358a6 | 1175 | |
a2ddcc36 | 1176 | DBG2(DBG_APP, "building pkcs1 object:"); |
da9724e6 | 1177 | if (!private_key->get_encoding(private_key, PRIVKEY_ASN1_DER, &pkcs1) || |
b9ee059c | 1178 | !chunk_write(pkcs1, path, 0066, force)) |
0eff9f65 | 1179 | { |
b9ee059c MW |
1180 | exit_scepclient("could not write pkcs1 file '%s': %s", |
1181 | path, strerror(errno)); | |
0eff9f65 | 1182 | } |
3d7a244b | 1183 | filetype_out &= ~PKCS1; /* delete PKCS1 flag */ |
997358a6 | 1184 | } |
3d7a244b AS |
1185 | |
1186 | if (!filetype_out) | |
63176bbc | 1187 | { |
3d7a244b | 1188 | exit_scepclient(NULL); /* no further output required */ |
63176bbc | 1189 | } |
997358a6 | 1190 | |
8b799d55 | 1191 | scep_generate_transaction_id(public_key, &transID, &serialNumber); |
a2ddcc36 | 1192 | DBG1(DBG_APP, " transaction ID: %.*s", (int)transID.len, transID.ptr); |
3d7a244b | 1193 | |
cea9bf56 TB |
1194 | /* |
1195 | * read or generate self-signed X.509 certificate | |
1196 | */ | |
1197 | if (filetype_in & CERT_SELF) | |
1198 | { | |
1199 | char path[PATH_MAX]; | |
1200 | ||
1201 | join_paths(path, sizeof(path), HOST_CERT_PATH, file_in_cert_self); | |
1202 | ||
1203 | x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, | |
1204 | BUILD_FROM_FILE, path, BUILD_END); | |
1205 | if (!x509_signer) | |
1206 | { | |
1207 | exit_scepclient("could not read certificate file '%s'", path); | |
1208 | } | |
1209 | } | |
1210 | else | |
0eff9f65 | 1211 | { |
cea9bf56 TB |
1212 | notBefore = notBefore ? notBefore : time(NULL); |
1213 | notAfter = notAfter ? notAfter : (notBefore + validity); | |
1214 | x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, | |
1215 | BUILD_SIGNING_KEY, private_key, | |
1216 | BUILD_PUBLIC_KEY, public_key, | |
1217 | BUILD_SUBJECT, subject, | |
1218 | BUILD_NOT_BEFORE_TIME, notBefore, | |
1219 | BUILD_NOT_AFTER_TIME, notAfter, | |
1220 | BUILD_SERIAL, serialNumber, | |
1221 | BUILD_SUBJECT_ALTNAMES, subjectAltNames, | |
1222 | BUILD_END); | |
1223 | if (!x509_signer) | |
1224 | { | |
1225 | exit_scepclient("generating certificate failed"); | |
1226 | } | |
0eff9f65 | 1227 | } |
74cc41c7 | 1228 | creds->add_cert(creds, TRUE, x509_signer->get_ref(x509_signer)); |
3d7a244b AS |
1229 | |
1230 | /* | |
1231 | * output of self-signed X.509 certificate file | |
1232 | */ | |
1233 | if (filetype_out & CERT_SELF) | |
997358a6 | 1234 | { |
dd93aefc TB |
1235 | char path[PATH_MAX]; |
1236 | ||
1237 | join_paths(path, sizeof(path), HOST_CERT_PATH, file_out_cert_self); | |
3d7a244b | 1238 | |
0406eeaa | 1239 | if (!x509_signer->get_encoding(x509_signer, CERT_ASN1_DER, &encoding)) |
0eff9f65 AS |
1240 | { |
1241 | exit_scepclient("encoding certificate failed"); | |
1242 | } | |
b9ee059c | 1243 | if (!chunk_write(encoding, path, 0022, force)) |
0eff9f65 | 1244 | { |
b9ee059c MW |
1245 | exit_scepclient("could not write self-signed cert file '%s': %s", |
1246 | path, strerror(errno)); | |
0eff9f65 AS |
1247 | } |
1248 | chunk_free(&encoding); | |
3d7a244b | 1249 | filetype_out &= ~CERT_SELF; /* delete CERT_SELF flag */ |
997358a6 | 1250 | } |
3d7a244b AS |
1251 | |
1252 | if (!filetype_out) | |
997358a6 | 1253 | { |
3d7a244b | 1254 | exit_scepclient(NULL); /* no further output required */ |
997358a6 MW |
1255 | } |
1256 | ||
3d7a244b AS |
1257 | /* |
1258 | * load ca encryption certificate | |
1259 | */ | |
997358a6 | 1260 | { |
dd93aefc TB |
1261 | char path[PATH_MAX]; |
1262 | ||
1263 | join_paths(path, sizeof(path), CA_CERT_PATH, file_in_cacert_enc); | |
da9724e6 | 1264 | |
0eff9f65 AS |
1265 | x509_ca_enc = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, |
1266 | BUILD_FROM_FILE, path, BUILD_END); | |
1267 | if (!x509_ca_enc) | |
3d7a244b AS |
1268 | { |
1269 | exit_scepclient("could not load encryption cacert file '%s'", path); | |
1270 | } | |
997358a6 MW |
1271 | } |
1272 | ||
7daf5226 | 1273 | /* |
3d7a244b AS |
1274 | * input of PKCS#7 file |
1275 | */ | |
1276 | if (filetype_in & PKCS7) | |
1277 | { | |
1278 | /* user wants to load a pkcs7 encrypted request | |
1279 | * operation is not yet supported! | |
1280 | * would require additional parsing of transaction-id | |
997358a6 | 1281 | |
3d7a244b AS |
1282 | pkcs7 = pkcs7_read_from_file(file_in_pkcs7); |
1283 | ||
1284 | */ | |
1285 | } | |
1286 | else | |
997358a6 | 1287 | { |
a2ddcc36 | 1288 | DBG2(DBG_APP, "building pkcs7 request"); |
0eff9f65 | 1289 | pkcs7 = scep_build_request(pkcs10_encoding, |
c6a2aa49 TB |
1290 | transID, SCEP_PKCSReq_MSG, x509_ca_enc, |
1291 | pkcs7_symmetric_cipher, pkcs7_key_size, | |
25924d3e | 1292 | x509_signer, pkcs7_digest_alg, private_key); |
c6a2aa49 TB |
1293 | if (!pkcs7.ptr) |
1294 | { | |
1295 | exit_scepclient("failed to build pkcs7 request"); | |
1296 | } | |
997358a6 | 1297 | } |
3d7a244b AS |
1298 | |
1299 | /* | |
1300 | * output pkcs7 encrypted and signed certificate request | |
1301 | */ | |
1302 | if (filetype_out & PKCS7) | |
997358a6 | 1303 | { |
dd93aefc TB |
1304 | char path[PATH_MAX]; |
1305 | ||
1306 | join_paths(path, sizeof(path), REQ_PATH, file_out_pkcs7); | |
3d7a244b | 1307 | |
b9ee059c | 1308 | if (!chunk_write(pkcs7, path, 0022, force)) |
25924d3e | 1309 | { |
b9ee059c MW |
1310 | exit_scepclient("could not write pkcs7 file '%s': %s", |
1311 | path, strerror(errno)); | |
25924d3e | 1312 | } |
3d7a244b | 1313 | filetype_out &= ~PKCS7; /* delete PKCS7 flag */ |
997358a6 | 1314 | } |
3d7a244b AS |
1315 | |
1316 | if (!filetype_out) | |
1317 | { | |
1318 | exit_scepclient(NULL); /* no further output required */ | |
997358a6 | 1319 | } |
3d7a244b AS |
1320 | |
1321 | /* | |
1322 | * output certificate fetch from SCEP server | |
1323 | */ | |
1324 | if (filetype_out & CERT) | |
1325 | { | |
e78c9152 | 1326 | bool stored = FALSE; |
17722d44 AS |
1327 | certificate_t *cert; |
1328 | enumerator_t *enumerator; | |
dd93aefc | 1329 | char path[PATH_MAX]; |
6180a558 | 1330 | time_t poll_start = 0; |
74cc41c7 MW |
1331 | pkcs7_t *p7; |
1332 | container_t *container = NULL; | |
1333 | chunk_t chunk; | |
c6a2aa49 | 1334 | scep_attributes_t attrs = empty_scep_attributes; |
3d7a244b | 1335 | |
dd93aefc TB |
1336 | join_paths(path, sizeof(path), CA_CERT_PATH, file_in_cacert_sig); |
1337 | ||
0eff9f65 AS |
1338 | x509_ca_sig = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, |
1339 | BUILD_FROM_FILE, path, BUILD_END); | |
1340 | if (!x509_ca_sig) | |
1341 | { | |
3d7a244b | 1342 | exit_scepclient("could not load signature cacert file '%s'", path); |
0eff9f65 | 1343 | } |
3d7a244b | 1344 | |
74cc41c7 MW |
1345 | creds->add_cert(creds, TRUE, x509_ca_sig->get_ref(x509_ca_sig)); |
1346 | ||
e67197a7 | 1347 | if (!scep_http_request(scep_url, pkcs7, SCEP_PKI_OPERATION, |
78af570f | 1348 | http_get_request, http_timeout, http_bind, &scep_response)) |
3d7a244b AS |
1349 | { |
1350 | exit_scepclient("did not receive a valid scep response"); | |
1351 | } | |
74cc41c7 | 1352 | ugh = scep_parse_response(scep_response, transID, &container, &attrs); |
3d7a244b AS |
1353 | if (ugh != NULL) |
1354 | { | |
1355 | exit_scepclient(ugh); | |
1356 | } | |
1357 | ||
1358 | /* in case of manual mode, we are going into a polling loop */ | |
1359 | if (attrs.pkiStatus == SCEP_PENDING) | |
1360 | { | |
0eff9f65 AS |
1361 | identification_t *issuer = x509_ca_sig->get_subject(x509_ca_sig); |
1362 | ||
a2ddcc36 | 1363 | DBG1(DBG_APP, " scep request pending, polling every %d seconds", |
25924d3e | 1364 | poll_interval); |
6180a558 | 1365 | poll_start = time_monotonic(NULL); |
0eff9f65 AS |
1366 | issuerAndSubject = asn1_wrap(ASN1_SEQUENCE, "cc", |
1367 | issuer->get_encoding(issuer), | |
69faf635 | 1368 | subject->get_encoding(subject)); |
3d7a244b AS |
1369 | } |
1370 | while (attrs.pkiStatus == SCEP_PENDING) | |
1371 | { | |
25924d3e TB |
1372 | if (max_poll_time > 0 && |
1373 | (time_monotonic(NULL) - poll_start >= max_poll_time)) | |
3d7a244b AS |
1374 | { |
1375 | exit_scepclient("maximum poll time reached: %d seconds" | |
1376 | , max_poll_time); | |
1377 | } | |
a2ddcc36 | 1378 | DBG2(DBG_APP, "going to sleep for %d seconds", poll_interval); |
3d7a244b AS |
1379 | sleep(poll_interval); |
1380 | free(scep_response.ptr); | |
74cc41c7 | 1381 | container->destroy(container); |
3d7a244b | 1382 | |
a2ddcc36 TB |
1383 | DBG2(DBG_APP, "fingerprint: %.*s", |
1384 | (int)fingerprint.len, fingerprint.ptr); | |
1385 | DBG2(DBG_APP, "transaction ID: %.*s", | |
1386 | (int)transID.len, transID.ptr); | |
3d7a244b AS |
1387 | |
1388 | chunk_free(&getCertInitial); | |
25924d3e | 1389 | getCertInitial = scep_build_request(issuerAndSubject, |
c6a2aa49 TB |
1390 | transID, SCEP_GetCertInitial_MSG, x509_ca_enc, |
1391 | pkcs7_symmetric_cipher, pkcs7_key_size, | |
1392 | x509_signer, pkcs7_digest_alg, private_key); | |
1393 | if (!getCertInitial.ptr) | |
1394 | { | |
1395 | exit_scepclient("failed to build scep request"); | |
1396 | } | |
e67197a7 | 1397 | if (!scep_http_request(scep_url, getCertInitial, SCEP_PKI_OPERATION, |
78af570f | 1398 | http_get_request, http_timeout, http_bind, &scep_response)) |
3d7a244b AS |
1399 | { |
1400 | exit_scepclient("did not receive a valid scep response"); | |
1401 | } | |
74cc41c7 | 1402 | ugh = scep_parse_response(scep_response, transID, &container, &attrs); |
3d7a244b AS |
1403 | if (ugh != NULL) |
1404 | { | |
1405 | exit_scepclient(ugh); | |
1406 | } | |
1407 | } | |
1408 | ||
1409 | if (attrs.pkiStatus != SCEP_SUCCESS) | |
1410 | { | |
74cc41c7 | 1411 | container->destroy(container); |
3d7a244b AS |
1412 | exit_scepclient("reply status is not 'SUCCESS'"); |
1413 | } | |
1414 | ||
74cc41c7 MW |
1415 | if (!container->get_data(container, &chunk)) |
1416 | { | |
1417 | container->destroy(container); | |
1418 | exit_scepclient("extracting signed-data failed"); | |
1419 | } | |
1420 | container->destroy(container); | |
1421 | ||
1422 | /* decrypt enveloped-data container */ | |
1423 | container = lib->creds->create(lib->creds, | |
1424 | CRED_CONTAINER, CONTAINER_PKCS7, | |
1425 | BUILD_BLOB_ASN1_DER, chunk, | |
1426 | BUILD_END); | |
1427 | free(chunk.ptr); | |
1428 | if (!container) | |
3d7a244b AS |
1429 | { |
1430 | exit_scepclient("could not decrypt envelopedData"); | |
1431 | } | |
74cc41c7 MW |
1432 | |
1433 | if (!container->get_data(container, &chunk)) | |
1434 | { | |
1435 | container->destroy(container); | |
429e6d5e | 1436 | exit_scepclient("extracting encrypted-data failed"); |
74cc41c7 MW |
1437 | } |
1438 | container->destroy(container); | |
1439 | ||
1440 | /* parse signed-data container */ | |
1441 | container = lib->creds->create(lib->creds, | |
1442 | CRED_CONTAINER, CONTAINER_PKCS7, | |
1443 | BUILD_BLOB_ASN1_DER, chunk, | |
1444 | BUILD_END); | |
1445 | free(chunk.ptr); | |
1446 | if (!container) | |
3d7a244b | 1447 | { |
74cc41c7 | 1448 | exit_scepclient("could not parse singed-data"); |
3d7a244b | 1449 | } |
74cc41c7 MW |
1450 | /* no need to verify the signed-data container, the signature does NOT |
1451 | * cover the contained certificates */ | |
3d7a244b AS |
1452 | |
1453 | /* store the end entity certificate */ | |
dd93aefc | 1454 | join_paths(path, sizeof(path), HOST_CERT_PATH, file_out_cert); |
17722d44 | 1455 | |
74cc41c7 MW |
1456 | p7 = (pkcs7_t*)container; |
1457 | enumerator = p7->create_cert_enumerator(p7); | |
17722d44 | 1458 | while (enumerator->enumerate(enumerator, &cert)) |
3d7a244b | 1459 | { |
17722d44 | 1460 | x509_t *x509 = (x509_t*)cert; |
3d7a244b | 1461 | |
0eff9f65 | 1462 | if (!(x509->get_flags(x509) & X509_CA)) |
3d7a244b AS |
1463 | { |
1464 | if (stored) | |
0eff9f65 | 1465 | { |
3d7a244b | 1466 | exit_scepclient("multiple certs received, only first stored"); |
0eff9f65 | 1467 | } |
0406eeaa | 1468 | if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoding) || |
b9ee059c | 1469 | !chunk_write(encoding, path, 0022, force)) |
0eff9f65 | 1470 | { |
b9ee059c MW |
1471 | exit_scepclient("could not write cert file '%s': %s", |
1472 | path, strerror(errno)); | |
0eff9f65 AS |
1473 | } |
1474 | chunk_free(&encoding); | |
3d7a244b AS |
1475 | stored = TRUE; |
1476 | } | |
3d7a244b | 1477 | } |
c6a2aa49 | 1478 | enumerator->destroy(enumerator); |
74cc41c7 | 1479 | container->destroy(container); |
644be5d5 MW |
1480 | chunk_free(&attrs.transID); |
1481 | chunk_free(&attrs.senderNonce); | |
1482 | chunk_free(&attrs.recipientNonce); | |
1483 | ||
3d7a244b | 1484 | filetype_out &= ~CERT; /* delete CERT flag */ |
997358a6 | 1485 | } |
997358a6 | 1486 | |
3d7a244b AS |
1487 | exit_scepclient(NULL); |
1488 | return -1; /* should never be reached */ | |
997358a6 | 1489 | } |