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