Print this page
6418676 encrypt(1) and decrypt(1) could benefit from being 64-bit programs
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/cmd/cmd-crypto/decrypt/decrypt.c
+++ new/usr/src/cmd/cmd-crypto/decrypt/decrypt.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
↓ open down ↓ |
12 lines elided |
↑ open up ↑ |
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /* Portions Copyright 2005 Richard Lowe */
22 22 /*
23 - * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23 + * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 24 * Use is subject to license terms.
25 25 */
26 26
27 -#pragma ident "@(#)decrypt.c 1.13 07/10/04 SMI"
27 +#pragma ident "@(#)decrypt.c 1.14 08/04/30 SMI"
28 28
29 29 /*
30 30 * decrypt.c
31 31 *
32 32 * Implements encrypt(1) and decrypt(1) commands
33 33 *
34 34 * One binary performs both encrypt/decrypt operation.
35 35 *
36 - * usage:
36 + * Usage:
37 + * -a algorithm mechanism name without CKM_ prefix. Case
38 + * does not matter
39 + * -k keyfile file containing key data. If not specified user is
40 + * prompted to enter key. key length > 0 is required
41 + * -i infile input file to encrypt/decrypt. If omitted, stdin used.
42 + * -o outfile output file to encrypt/decrypt. If omitted, stdout used.
43 + * if infile & outfile are same, a temp file is used for
44 + * output and infile is replaced with this file after
45 + * operation is complete
46 + * -l Display the list of algorithms
47 + * -v Display verbose information
48 + * -T tokenspec Specify a PKCS#11 token (optionally used with -K)
49 + * -K keylabel Specify the symmetric PKCS#11 token key label
37 50 *
38 - * algorithm - mechanism name without CKM_ prefix. Case
39 - * does not matter
40 - * keyfile - file containing key data. If not specified user is
41 - * prompted to enter key. key length > 0 is required
42 - * infile - input file to encrypt/decrypt. If omitted, stdin used.
43 - * outfile - output file to encrypt/decrypt. If omitted, stdout used.
44 - * if infile & outfile are same, a temp file is used for
45 - * output and infile is replaced with this file after
46 - * operation is complete.
47 - *
48 51 * Implementation notes:
49 - * iv data - It is generated by random bytes equal to one block size.
52 + * IV data - It is generated by random bytes equal to one block size.
50 53 *
51 - * encrypted output format -
54 + * Encrypted output format -
52 55 * - Output format version number - 4 bytes in network byte order.
53 56 * - Iterations used in key gen function, 4 bytes in network byte order.
54 - * - IV ( 'ivlen' bytes)
57 + * - IV ('ivlen' bytes). Length is algorithm-dependent (see mech_aliases)
55 58 * - Salt data used in key gen (16 bytes)
56 - * - cipher text data.
57 - *
59 + * - Cipher text data (remainder of the file)
58 60 */
59 61
60 62 #include <stdio.h>
61 63 #include <stdlib.h>
62 64 #include <unistd.h>
63 65 #include <errno.h>
64 66 #include <fcntl.h>
65 67 #include <ctype.h>
66 68 #include <strings.h>
67 69 #include <libintl.h>
68 70 #include <libgen.h>
69 71 #include <locale.h>
70 72 #include <limits.h>
71 73 #include <sys/types.h>
72 74 #include <sys/stat.h>
73 75 #include <netinet/in.h>
74 76 #include <security/cryptoki.h>
75 77 #include <cryptoutil.h>
76 78 #include <kmfapi.h>
77 79
78 80 #define BUFFERSIZE (2048) /* Buffer size for reading file */
79 81 #define BLOCKSIZE (128) /* Largest guess for block size */
80 82 #define PROGRESSSIZE (BUFFERSIZE*20) /* stdin progress indicator size */
81 83
82 84 #define SUNW_ENCRYPT_FILE_VERSION 1
83 85
84 86 /*
85 87 * Exit Status codes
86 88 */
87 89 #ifndef EXIT_SUCCESS
88 90 #define EXIT_SUCCESS 0 /* No errors */
89 91 #define EXIT_FAILURE 1 /* All errors except usage */
90 92 #endif /* EXIT_SUCCESS */
91 93
92 94 #define EXIT_USAGE 2 /* usage/syntax error */
93 95
94 96 #define ENCRYPT_NAME "encrypt" /* name of encrypt command */
95 97 #define ENCRYPT_OPTIONS "a:T:K:k:i:o:lv" /* options for encrypt */
96 98 #define DECRYPT_NAME "decrypt" /* name of decrypt command */
97 99 #define DECRYPT_OPTIONS "a:T:K:k:i:o:lv" /* options for decrypt */
98 100
99 101 /*
100 102 * Structure containing info for encrypt/decrypt
101 103 * command
102 104 */
103 105 struct CommandInfo {
104 106 char *name; /* name of the command */
105 107 char *options; /* command line options */
106 108 CK_FLAGS flags;
107 109 CK_ATTRIBUTE_TYPE type; /* type of command */
108 110
109 111 /* function pointers for various operations */
110 112 CK_RV (*Init)(CK_SESSION_HANDLE, CK_MECHANISM_PTR, CK_OBJECT_HANDLE);
111 113 CK_RV (*Update)(CK_SESSION_HANDLE, CK_BYTE_PTR, CK_ULONG, CK_BYTE_PTR,
112 114 CK_ULONG_PTR);
113 115 CK_RV (*Crypt)(CK_SESSION_HANDLE, CK_BYTE_PTR, CK_ULONG, CK_BYTE_PTR,
114 116 CK_ULONG_PTR);
115 117 CK_RV (*Final)(CK_SESSION_HANDLE, CK_BYTE_PTR, CK_ULONG_PTR);
116 118 };
117 119
118 120 static struct CommandInfo encrypt_cmd = {
119 121 ENCRYPT_NAME,
120 122 ENCRYPT_OPTIONS,
121 123 CKF_ENCRYPT,
122 124 CKA_ENCRYPT,
123 125 C_EncryptInit,
124 126 C_EncryptUpdate,
125 127 C_Encrypt,
126 128 C_EncryptFinal
127 129 };
128 130
129 131 static struct CommandInfo decrypt_cmd = {
130 132 DECRYPT_NAME,
131 133 DECRYPT_OPTIONS,
132 134 CKF_DECRYPT,
133 135 CKA_DECRYPT,
134 136 C_DecryptInit,
135 137 C_DecryptUpdate,
136 138 C_Decrypt,
137 139 C_DecryptFinal
138 140 };
139 141
140 142 struct mech_alias {
141 143 CK_MECHANISM_TYPE type;
142 144 char *alias;
143 145 CK_ULONG keysize_min;
144 146 CK_ULONG keysize_max;
145 147 int keysize_unit;
146 148 int ivlen;
147 149 boolean_t available;
148 150 };
149 151
150 152 #define MECH_ALIASES_COUNT 4
151 153
152 154 static struct mech_alias mech_aliases[] = {
153 155 { CKM_AES_CBC_PAD, "aes", ULONG_MAX, 0L, 8, 16, B_FALSE },
154 156 { CKM_RC4, "arcfour", ULONG_MAX, 0L, 1, 0, B_FALSE },
155 157 { CKM_DES_CBC_PAD, "des", 8, 8, 8, 8, B_FALSE },
156 158 { CKM_DES3_CBC_PAD, "3des", 24, 24, 8, 8, B_FALSE },
157 159 };
↓ open down ↓ |
90 lines elided |
↑ open up ↑ |
158 160
159 161 static CK_BBOOL truevalue = TRUE;
160 162 static CK_BBOOL falsevalue = FALSE;
161 163
162 164 static boolean_t aflag = B_FALSE; /* -a <algorithm> flag, required */
163 165 static boolean_t kflag = B_FALSE; /* -k <keyfile> flag */
164 166 static boolean_t iflag = B_FALSE; /* -i <infile> flag, use stdin if absent */
165 167 static boolean_t oflag = B_FALSE; /* -o <outfile> flag, use stdout if absent */
166 168 static boolean_t lflag = B_FALSE; /* -l flag (list) */
167 169 static boolean_t vflag = B_FALSE; /* -v flag (verbose) */
168 -static boolean_t Tflag = B_FALSE;
169 -static boolean_t Kflag = B_FALSE;
170 +static boolean_t Tflag = B_FALSE; /* -T flag (tokenspec) */
171 +static boolean_t Kflag = B_FALSE; /* -K flag (keylabel) */
170 172
171 -static char *keyfile = NULL; /* name of keyfile */
172 -static char *inputfile = NULL; /* name of input file */
173 -static char *outputfile = NULL; /* name of output file */
174 -static char *token_label = NULL;
175 -static char *key_label = NULL;
173 +static char *keyfile = NULL; /* name of keyfile */
174 +static char *inputfile = NULL; /* name of input file */
175 +static char *outputfile = NULL; /* name of output file */
176 +static char *token_label = NULL; /* name of PKCS#11 token */
177 +static char *key_label = NULL; /* name of PKCS#11 token key label */
176 178
177 179 static int status_pos = 0; /* current position of progress bar element */
178 180
179 181 /*
180 182 * function prototypes
181 183 */
182 184 static void usage(struct CommandInfo *cmd);
183 185 static int execute_cmd(struct CommandInfo *cmd, char *algo_str);
184 186 static int crypt_multipart(struct CommandInfo *cmd, CK_SESSION_HANDLE hSession,
185 187 int infd, int outfd, off_t insize);
186 188
187 189 int
188 190 main(int argc, char **argv)
189 191 {
190 192
↓ open down ↓ |
5 lines elided |
↑ open up ↑ |
191 193 extern char *optarg;
192 194 extern int optind;
193 195 char *optstr;
194 196 char c; /* current getopts flag */
195 197 char *algo_str = NULL; /* algorithm string */
196 198 struct CommandInfo *cmd;
197 199 char *cmdname; /* name of command */
198 200 boolean_t errflag = B_FALSE;
199 201
200 202 (void) setlocale(LC_ALL, "");
201 -#if !defined(TEXT_DOMAIN) /* Should be defiend by cc -D */
203 +#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
202 204 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
203 205 #endif
204 206 (void) textdomain(TEXT_DOMAIN);
205 207
206 208 /*
207 209 * Based on command name, determine
208 210 * type of command.
209 211 */
210 212 cmdname = basename(argv[0]);
211 213
212 214 cryptodebug_init(cmdname);
213 215
214 216 if (strcmp(cmdname, encrypt_cmd.name) == 0) {
215 217 cmd = &encrypt_cmd;
216 218 } else if (strcmp(cmdname, decrypt_cmd.name) == 0) {
217 219 cmd = &decrypt_cmd;
218 220 } else {
219 221 cryptoerror(LOG_STDERR, gettext(
220 222 "command name must be either encrypt or decrypt"));
221 223 exit(EXIT_USAGE);
222 224 }
223 225
224 226 optstr = cmd->options;
225 227
226 228 /* Parse command line arguments */
227 229 while (!errflag && (c = getopt(argc, argv, optstr)) != -1) {
228 230
229 231 switch (c) {
230 232 case 'a':
231 233 aflag = B_TRUE;
232 234 algo_str = optarg;
233 235 break;
234 236 case 'k':
235 237 kflag = B_TRUE;
236 238 keyfile = optarg;
237 239 break;
238 240 case 'T':
239 241 Tflag = B_TRUE;
240 242 token_label = optarg;
241 243 break;
242 244 case 'K':
243 245 Kflag = B_TRUE;
244 246 key_label = optarg;
245 247 break;
246 248 case 'i':
247 249 iflag = B_TRUE;
248 250 inputfile = optarg;
249 251 break;
250 252 case 'o':
251 253 oflag = B_TRUE;
252 254 outputfile = optarg;
253 255 break;
254 256 case 'l':
255 257 lflag = B_TRUE;
256 258 break;
257 259 case 'v':
258 260 vflag = B_TRUE;
259 261 break;
260 262 default:
261 263 errflag = B_TRUE;
262 264 }
263 265 }
264 266
265 267 if (errflag || (!aflag && !lflag) || (lflag && argc > 2) ||
266 268 (kflag && Kflag) || (Tflag && !Kflag) ||
267 269 (optind < argc)) {
268 270 usage(cmd);
269 271 exit(EXIT_USAGE);
270 272 }
271 273
272 274 return (execute_cmd(cmd, algo_str));
273 275 }
274 276
275 277 /*
276 278 * usage message
277 279 */
278 280 static void
279 281 usage(struct CommandInfo *cmd)
280 282 {
281 283 (void) fprintf(stderr, gettext("Usage:\n"));
282 284 if (cmd->type == CKA_ENCRYPT) {
283 285 (void) fprintf(stderr, gettext(" encrypt -l\n"));
284 286 (void) fprintf(stderr, gettext(" encrypt -a <algorithm> "
285 287 "[-v] [-k <keyfile> | -K <keylabel> [-T <tokenspec>]] "
286 288 "[-i <infile>] [-o <outfile>]\n"));
287 289
288 290 } else {
289 291 (void) fprintf(stderr, gettext(" decrypt -l\n"));
290 292 (void) fprintf(stderr, gettext(" decrypt -a <algorithm> "
291 293 "[-v] [-k <keyfile> | -K <keylabel> [-T <tokenspec>]] "
292 294 "[-i <infile>] [-o <outfile>]\n"));
293 295 }
294 296 }
295 297
296 298 /*
297 299 * Print out list of algorithms in default and verbose mode
298 300 */
299 301 static void
300 302 algorithm_list()
301 303 {
302 304 int mech;
303 305
304 306 (void) printf(gettext("Algorithm Keysize: Min Max (bits)\n"
305 307 "------------------------------------------\n"));
306 308
307 309 for (mech = 0; mech < MECH_ALIASES_COUNT; mech++) {
308 310
309 311 if (mech_aliases[mech].available == B_FALSE)
310 312 continue;
311 313
312 314 (void) printf("%-15s", mech_aliases[mech].alias);
313 315
314 316 if (mech_aliases[mech].keysize_min != UINT_MAX &&
315 317 mech_aliases[mech].keysize_max != 0)
316 318 (void) printf(" %5lu %5lu\n",
317 319 (mech_aliases[mech].keysize_min *
318 320 mech_aliases[mech].keysize_unit),
319 321 (mech_aliases[mech].keysize_max *
320 322 mech_aliases[mech].keysize_unit));
321 323 else
322 324 (void) printf("\n");
323 325
324 326 }
325 327 }
326 328
327 329 /*
328 330 * This function will login into the token with the provided password and
329 331 * find the token key object with the specified keytype and keylabel.
330 332 */
331 333 static int
332 334 get_token_key(CK_SESSION_HANDLE hSession, CK_KEY_TYPE keytype,
333 335 char *keylabel, CK_BYTE *password, int password_len,
334 336 CK_OBJECT_HANDLE *keyobj)
335 337 {
336 338 CK_RV rv;
337 339 CK_ATTRIBUTE pTmpl[10];
338 340 CK_OBJECT_CLASS class = CKO_SECRET_KEY;
339 341 CK_BBOOL true = 1;
340 342 CK_BBOOL is_token = 1;
341 343 CK_ULONG key_obj_count = 1;
342 344 int i;
343 345 CK_KEY_TYPE ckKeyType = keytype;
344 346
345 347
346 348 rv = C_Login(hSession, CKU_USER, (CK_UTF8CHAR_PTR)password,
347 349 (CK_ULONG)password_len);
348 350 if (rv != CKR_OK) {
349 351 (void) fprintf(stderr, "Cannot login to the token."
350 352 " error = %s\n", pkcs11_strerror(rv));
351 353 return (-1);
352 354 }
353 355
354 356 i = 0;
355 357 pTmpl[i].type = CKA_TOKEN;
356 358 pTmpl[i].pValue = &is_token;
357 359 pTmpl[i].ulValueLen = sizeof (CK_BBOOL);
358 360 i++;
359 361
360 362 pTmpl[i].type = CKA_CLASS;
361 363 pTmpl[i].pValue = &class;
362 364 pTmpl[i].ulValueLen = sizeof (class);
363 365 i++;
364 366
365 367 pTmpl[i].type = CKA_LABEL;
366 368 pTmpl[i].pValue = keylabel;
367 369 pTmpl[i].ulValueLen = strlen(keylabel);
368 370 i++;
369 371
370 372 pTmpl[i].type = CKA_KEY_TYPE;
371 373 pTmpl[i].pValue = &ckKeyType;
372 374 pTmpl[i].ulValueLen = sizeof (ckKeyType);
373 375 i++;
374 376
375 377 pTmpl[i].type = CKA_PRIVATE;
376 378 pTmpl[i].pValue = &true;
377 379 pTmpl[i].ulValueLen = sizeof (true);
378 380 i++;
379 381
380 382 rv = C_FindObjectsInit(hSession, pTmpl, i);
381 383 if (rv != CKR_OK) {
382 384 goto out;
383 385 }
384 386
385 387 rv = C_FindObjects(hSession, keyobj, 1, &key_obj_count);
386 388
387 389 (void) C_FindObjectsFinal(hSession);
388 390
389 391 out:
390 392 if (rv != CKR_OK) {
391 393 (void) fprintf(stderr,
392 394 "Cannot retrieve key object. error = %s\n",
393 395 pkcs11_strerror(rv));
394 396 return (-1);
395 397 }
396 398
397 399 if (key_obj_count == 0) {
398 400 (void) fprintf(stderr, "Cannot find the key object.\n");
399 401 return (-1);
400 402 }
401 403
402 404 return (0);
403 405 }
404 406
405 407
406 408 /*
407 409 * Execute the command.
408 410 * cmd - command pointing to type of operation.
409 411 * algo_str - alias of the algorithm passed.
410 412 */
411 413 static int
412 414 execute_cmd(struct CommandInfo *cmd, char *algo_str)
413 415 {
414 416 CK_RV rv;
415 417 CK_ULONG slotcount;
416 418 CK_SLOT_ID slotID;
417 419 CK_SLOT_ID_PTR pSlotList = NULL;
418 420 CK_MECHANISM_TYPE mech_type = 0;
419 421 CK_MECHANISM_INFO info, kg_info;
420 422 CK_MECHANISM mech;
421 423 CK_SESSION_HANDLE hSession = CK_INVALID_HANDLE;
422 424 CK_BYTE_PTR pkeydata = NULL;
423 425 CK_BYTE salt[CK_PKCS5_PBKD2_SALT_SIZE];
424 426 CK_ULONG keysize = 0;
425 427 int i, slot, mek; /* index variables */
426 428 int status;
427 429 struct stat insbuf; /* stat buf for infile */
↓ open down ↓ |
216 lines elided |
↑ open up ↑ |
428 430 struct stat outsbuf; /* stat buf for outfile */
429 431 char tmpnam[PATH_MAX]; /* tmp file name */
430 432 CK_OBJECT_HANDLE key = (CK_OBJECT_HANDLE) 0;
431 433 int infd = 0; /* input file, stdin default */
432 434 int outfd = 1; /* output file, stdout default */
433 435 char *outfilename = NULL;
434 436 boolean_t errflag = B_TRUE;
435 437 boolean_t inoutsame = B_FALSE; /* if both input & output are same */
436 438 CK_BYTE_PTR pivbuf = NULL_PTR;
437 439 CK_ULONG ivlen = 0L;
438 - int mech_match = 0;
439 - CK_ULONG iterations = CK_PKCS5_PBKD2_ITERATIONS;
440 + int mech_match = 0;
441 + uint32_t iterations = CK_PKCS5_PBKD2_ITERATIONS;
440 442 CK_ULONG keylen;
441 - int version = SUNW_ENCRYPT_FILE_VERSION;
443 + uint32_t version = SUNW_ENCRYPT_FILE_VERSION;
442 444 CK_KEY_TYPE keytype;
443 445 KMF_RETURN kmfrv;
444 446 CK_SLOT_ID token_slot_id;
445 447
446 448 if (aflag) {
447 449 /* Determine if algorithm is valid */
448 450 for (mech_match = 0; mech_match < MECH_ALIASES_COUNT;
449 451 mech_match++) {
450 452 if (strcmp(algo_str,
451 453 mech_aliases[mech_match].alias) == 0) {
452 454 mech_type = mech_aliases[mech_match].type;
453 455 break;
454 456 }
455 457 }
456 458
457 459 if (mech_match == MECH_ALIASES_COUNT) {
458 460 cryptoerror(LOG_STDERR,
459 461 gettext("unknown algorithm -- %s"), algo_str);
460 462 return (EXIT_FAILURE);
461 463 }
462 464
463 465 /*
464 466 * Process keyfile or get the token pin if -K is specified.
465 467 *
466 468 * If a keyfile is provided, get the key data from
467 469 * the file. Otherwise, prompt for a passphrase. The
468 470 * passphrase is used as the key data.
469 471 */
470 472 if (Kflag) {
471 473 /* get the pin of the token */
472 474 if (token_label == NULL || !strlen(token_label)) {
473 475 token_label = pkcs11_default_token();
474 476 }
475 477
476 478 status = pkcs11_get_pass(token_label,
477 479 (char **)&pkeydata, (size_t *)&keysize, 0, B_FALSE);
478 480 } else if (kflag) {
479 481 /* get the key file */
480 482 status = pkcs11_read_data(keyfile, (void **)&pkeydata,
481 483 (size_t *)&keysize);
482 484 } else {
483 485 /* get the key from input */
484 486 status = pkcs11_get_pass(NULL, (char **)&pkeydata,
485 487 (size_t *)&keysize, 0, B_FALSE);
486 488 }
487 489
488 490 if (status == -1 || keysize == 0L) {
489 491 cryptoerror(LOG_STDERR,
490 492 Kflag ? gettext("invalid password.") :
491 493 gettext("invalid key."));
492 494 return (EXIT_FAILURE);
493 495 }
494 496 }
495 497
496 498 bzero(salt, sizeof (salt));
497 499 /* Initialize pkcs */
498 500 rv = C_Initialize(NULL);
499 501 if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) {
500 502 cryptoerror(LOG_STDERR, gettext("failed to initialize "
501 503 "PKCS #11 framework: %s"), pkcs11_strerror(rv));
502 504 goto cleanup;
503 505 }
504 506
505 507 /* Get slot count */
506 508 rv = C_GetSlotList(0, NULL_PTR, &slotcount);
507 509 if (rv != CKR_OK || slotcount == 0) {
508 510 cryptoerror(LOG_STDERR, gettext(
509 511 "failed to find any cryptographic provider,"
510 512 "please check with your system administrator: %s"),
511 513 pkcs11_strerror(rv));
512 514 goto cleanup;
513 515 }
514 516
515 517 /* Found at least one slot, allocate memory for slot list */
516 518 pSlotList = malloc(slotcount * sizeof (CK_SLOT_ID));
517 519 if (pSlotList == NULL_PTR) {
518 520 int err = errno;
519 521 cryptoerror(LOG_STDERR, gettext("malloc: %s"), strerror(err));
520 522 goto cleanup;
521 523 }
522 524
523 525 /* Get the list of slots */
524 526 if ((rv = C_GetSlotList(0, pSlotList, &slotcount)) != CKR_OK) {
525 527 cryptoerror(LOG_STDERR, gettext(
526 528 "failed to find any cryptographic provider,"
527 529 "please check with your system administrator: %s"),
528 530 pkcs11_strerror(rv));
529 531 goto cleanup;
530 532 }
531 533
532 534 if (lflag) {
533 535
534 536 /* Iterate through slots */
535 537 for (slot = 0; slot < slotcount; slot++) {
536 538
537 539 /* Iterate through each mechanism */
538 540 for (mek = 0; mek < MECH_ALIASES_COUNT; mek++) {
539 541 rv = C_GetMechanismInfo(pSlotList[slot],
540 542 mech_aliases[mek].type, &info);
541 543
542 544 if (rv != CKR_OK)
543 545 continue;
544 546
545 547 /*
546 548 * Set to minimum/maximum key sizes assuming
547 549 * the values available are not 0.
548 550 */
549 551 if (info.ulMinKeySize && (info.ulMinKeySize <
550 552 mech_aliases[mek].keysize_min))
551 553 mech_aliases[mek].keysize_min =
552 554 info.ulMinKeySize;
553 555
554 556 if (info.ulMaxKeySize && (info.ulMaxKeySize >
555 557 mech_aliases[mek].keysize_max))
556 558 mech_aliases[mek].keysize_max =
557 559 info.ulMaxKeySize;
558 560
559 561 mech_aliases[mek].available = B_TRUE;
560 562 }
561 563
562 564 }
563 565
564 566 algorithm_list();
565 567
566 568 errflag = B_FALSE;
567 569 goto cleanup;
568 570 }
569 571
570 572
571 573 /*
572 574 * Find a slot with matching mechanism
573 575 *
574 576 * If -K is specified, we find the slot id for the token first, then
575 577 * check if the slot supports the algorithm.
576 578 */
577 579 i = 0;
578 580 if (Kflag) {
579 581 kmfrv = kmf_pk11_token_lookup(NULL, token_label,
580 582 &token_slot_id);
581 583 if (kmfrv != KMF_OK) {
582 584 cryptoerror(LOG_STDERR,
583 585 gettext("no matching PKCS#11 token"));
584 586 errflag = B_TRUE;
585 587 goto cleanup;
586 588 }
587 589 rv = C_GetMechanismInfo(token_slot_id, mech_type, &info);
588 590 if (rv == CKR_OK && (info.flags & cmd->flags))
589 591 slotID = token_slot_id;
590 592 else
591 593 i = slotcount;
592 594 } else {
593 595 for (i = 0; i < slotcount; i++) {
594 596 slotID = pSlotList[i];
595 597 rv = C_GetMechanismInfo(slotID, mech_type, &info);
596 598 if (rv != CKR_OK) {
597 599 continue; /* to the next slot */
598 600 } else {
599 601 /*
600 602 * If the slot support the crypto, also
601 603 * make sure it supports the correct
602 604 * key generation mech if needed.
603 605 *
604 606 * We need PKCS5 when RC4 is used or
605 607 * when the key is entered on cmd line.
606 608 */
607 609 if ((info.flags & cmd->flags) &&
608 610 (mech_type == CKM_RC4) ||
609 611 (keyfile == NULL)) {
610 612 rv = C_GetMechanismInfo(slotID,
611 613 CKM_PKCS5_PBKD2, &kg_info);
612 614 if (rv == CKR_OK)
613 615 break;
614 616 } else if (info.flags & cmd->flags) {
615 617 break;
616 618 }
617 619 }
618 620 }
619 621 }
620 622
621 623 /* Show error if no matching mechanism found */
622 624 if (i == slotcount) {
623 625 cryptoerror(LOG_STDERR,
624 626 gettext("no cryptographic provider was "
625 627 "found for this algorithm -- %s"), algo_str);
626 628 goto cleanup;
627 629 }
628 630
629 631 /* Open a session */
630 632 rv = C_OpenSession(slotID, CKF_SERIAL_SESSION,
631 633 NULL_PTR, NULL, &hSession);
632 634
633 635 if (rv != CKR_OK) {
634 636 cryptoerror(LOG_STDERR,
635 637 gettext("can not open PKCS #11 session: %s"),
636 638 pkcs11_strerror(rv));
637 639 goto cleanup;
638 640 }
639 641
640 642 /*
641 643 * Generate IV data for encrypt.
642 644 */
643 645 ivlen = mech_aliases[mech_match].ivlen;
644 646 if ((pivbuf = malloc((size_t)ivlen)) == NULL) {
645 647 int err = errno;
646 648 cryptoerror(LOG_STDERR, gettext("malloc: %s"),
647 649 strerror(err));
648 650 goto cleanup;
649 651 }
650 652
651 653 if (cmd->type == CKA_ENCRYPT) {
652 654 if ((pkcs11_random_data((void *)pivbuf,
653 655 mech_aliases[mech_match].ivlen)) != 0) {
654 656 cryptoerror(LOG_STDERR, gettext(
655 657 "Unable to generate random "
656 658 "data for initialization vector."));
657 659 goto cleanup;
658 660 }
659 661 }
660 662
661 663 /*
662 664 * Create the key object
663 665 */
664 666 rv = pkcs11_mech2keytype(mech_type, &keytype);
665 667 if (rv != CKR_OK) {
666 668 cryptoerror(LOG_STDERR,
667 669 gettext("unable to find key type for algorithm."));
668 670 goto cleanup;
669 671 }
670 672
671 673 /* Open input file */
672 674 if (iflag) {
673 675 if ((infd = open(inputfile, O_RDONLY | O_NONBLOCK)) == -1) {
674 676 cryptoerror(LOG_STDERR, gettext(
675 677 "can not open input file %s"), inputfile);
676 678 goto cleanup;
677 679 }
678 680
679 681 /* Get info on input file */
680 682 if (fstat(infd, &insbuf) == -1) {
681 683 cryptoerror(LOG_STDERR, gettext(
682 684 "can not stat input file %s"), inputfile);
683 685 goto cleanup;
684 686 }
685 687 }
686 688
687 689 /*
688 690 * Prepare output file
689 691 * If the input & output file are same,
690 692 * the output is written to a temp
691 693 * file first, then renamed to the original file
692 694 * after the crypt operation
693 695 */
694 696 inoutsame = B_FALSE;
695 697 if (oflag) {
696 698 outfilename = outputfile;
697 699 if ((stat(outputfile, &outsbuf) != -1) &&
698 700 (insbuf.st_ino == outsbuf.st_ino)) {
699 701 char *dir;
700 702
701 703 /* create temp file on same dir */
702 704 dir = dirname(outputfile);
703 705 (void) snprintf(tmpnam, sizeof (tmpnam),
704 706 "%s/encrXXXXXX", dir);
705 707 outfilename = tmpnam;
706 708 if ((outfd = mkstemp(tmpnam)) == -1) {
707 709 cryptoerror(LOG_STDERR, gettext(
708 710 "cannot create temp file"));
709 711 goto cleanup;
710 712 }
711 713 inoutsame = B_TRUE;
712 714 } else {
713 715 /* Create file for output */
714 716 if ((outfd = open(outfilename,
715 717 O_CREAT|O_WRONLY|O_TRUNC, 0644)) == -1) {
716 718 cryptoerror(LOG_STDERR, gettext(
717 719 "cannot open output file %s"),
718 720 outfilename);
719 721 goto cleanup;
720 722 }
721 723 }
722 724 }
723 725
724 726 /*
725 727 * Read the version number from the head of the file
726 728 * to know how to interpret the data that follows.
727 729 */
728 730 if (cmd->type == CKA_DECRYPT) {
729 731 if (read(infd, &version, sizeof (version)) !=
730 732 sizeof (version)) {
731 733 cryptoerror(LOG_STDERR, gettext(
732 734 "failed to get format version from "
↓ open down ↓ |
281 lines elided |
↑ open up ↑ |
733 735 "input file."));
734 736 goto cleanup;
735 737 }
736 738 /* convert to host byte order */
737 739 version = ntohl(version);
738 740
739 741 switch (version) {
740 742 case 1:
741 743 /*
742 744 * Version 1 output format:
745 + * - Output format version 1 (4 bytes)
743 746 * - Iterations used in key gen function (4 bytes)
744 - * - IV ( 'ivlen' bytes)
747 + * - IV ('ivlen' bytes). The length algorithm-dependent
745 748 * - Salt data used in key gen (16 bytes)
749 + * - Cipher text data (remainder of the file)
746 750 *
747 751 * An encrypted file has IV as first block (0 or
748 752 * more bytes depending on mechanism) followed
749 753 * by cipher text. Get the IV from the encrypted
750 754 * file.
751 755 */
752 756 /*
753 757 * Read iteration count and salt data.
754 758 */
755 759 if (read(infd, &iterations,
756 760 sizeof (iterations)) != sizeof (iterations)) {
757 761 cryptoerror(LOG_STDERR, gettext(
758 762 "failed to get iterations from "
759 763 "input file."));
760 764 goto cleanup;
761 765 }
762 766 /* convert to host byte order */
763 767 iterations = ntohl(iterations);
764 768 if (ivlen > 0 &&
765 769 read(infd, pivbuf, ivlen) != ivlen) {
766 770 cryptoerror(LOG_STDERR, gettext(
767 771 "failed to get initialization "
768 772 "vector from input file."));
769 773 goto cleanup;
770 774 }
771 775 if (read(infd, salt, sizeof (salt))
772 776 != sizeof (salt)) {
773 777 cryptoerror(LOG_STDERR, gettext(
774 778 "failed to get salt data from "
775 779 "input file."));
776 780 goto cleanup;
777 781 }
778 782 break;
779 783 default:
780 784 cryptoerror(LOG_STDERR, gettext(
781 785 "Unrecognized format version read from "
782 786 "input file - expected %d, got %d."),
783 787 SUNW_ENCRYPT_FILE_VERSION, version);
784 788 goto cleanup;
785 789 break;
786 790 }
787 791 }
788 792
789 793 /*
790 794 * If Kflag is set, let's find the token key now.
791 795 *
792 796 * If Kflag is not set and if encrypting, we need some random
793 797 * salt data to create the key. If decrypting,
794 798 * the salt should come from head of the file
795 799 * to be decrypted.
796 800 */
797 801 if (Kflag) {
798 802 rv = get_token_key(hSession, keytype, key_label, pkeydata,
799 803 keysize, &key);
800 804 if (rv != CKR_OK) {
801 805 cryptoerror(LOG_STDERR, gettext(
802 806 "Can not find the token key"));
803 807 goto cleanup;
804 808 } else {
805 809 goto do_crypto;
806 810 }
807 811 } else if (cmd->type == CKA_ENCRYPT) {
808 812 rv = pkcs11_random_data((void *)salt, sizeof (salt));
809 813 if (rv != 0) {
810 814 cryptoerror(LOG_STDERR,
811 815 gettext("unable to generate random "
812 816 "data for key salt."));
813 817 goto cleanup;
814 818 }
815 819 }
816 820
817 821
818 822 /*
819 823 * If key input is read from a file, treat it as
820 824 * raw key data, unless it is to be used with RC4,
821 825 * in which case it must be used to generate a pkcs5
822 826 * key to address security concerns with RC4 keys.
823 827 */
824 828 if (kflag && keyfile != NULL && keytype != CKK_RC4) {
825 829 /* XXX : why wasn't SUNW_C_KeyToObject used here? */
826 830 CK_OBJECT_CLASS objclass = CKO_SECRET_KEY;
827 831 CK_ATTRIBUTE template[5];
828 832 int nattr = 0;
829 833
830 834 template[nattr].type = CKA_CLASS;
831 835 template[nattr].pValue = &objclass;
832 836 template[nattr].ulValueLen = sizeof (objclass);
833 837 nattr++;
834 838
835 839 template[nattr].type = CKA_KEY_TYPE;
836 840 template[nattr].pValue = &keytype;
837 841 template[nattr].ulValueLen = sizeof (keytype);
838 842 nattr++;
839 843
840 844 template[nattr].type = cmd->type;
841 845 template[nattr].pValue = &truevalue;
842 846 template[nattr].ulValueLen = sizeof (truevalue);
843 847 nattr++;
844 848
845 849 template[nattr].type = CKA_TOKEN;
846 850 template[nattr].pValue = &falsevalue;
847 851 template[nattr].ulValueLen = sizeof (falsevalue);
848 852 nattr++;
849 853
850 854 template[nattr].type = CKA_VALUE;
851 855 template[nattr].pValue = pkeydata;
852 856 template[nattr].ulValueLen = keysize;
853 857 nattr++;
854 858
855 859 rv = C_CreateObject(hSession, template, nattr, &key);
856 860 } else {
857 861 /*
858 862 * If the encryption type has a fixed key length,
859 863 * then its not necessary to set the key length
860 864 * parameter when generating the key.
861 865 */
862 866 if (keytype == CKK_DES || keytype == CKK_DES3)
863 867 keylen = 0;
864 868 else
865 869 keylen = 16;
866 870
867 871 /*
868 872 * Generate a cryptographically secure key using
869 873 * the key read from the file given (-k keyfile) or
870 874 * the passphrase entered by the user.
871 875 */
872 876 rv = pkcs11_PasswdToPBKD2Object(hSession, (char *)pkeydata,
873 877 (size_t)keysize, (void *)salt, sizeof (salt), iterations,
874 878 keytype, keylen, cmd->flags, &key);
875 879 }
876 880
877 881 if (rv != CKR_OK) {
878 882 cryptoerror(LOG_STDERR, gettext(
879 883 "failed to generate a key: %s"),
880 884 pkcs11_strerror(rv));
881 885 goto cleanup;
882 886 }
883 887
884 888
885 889 do_crypto:
886 890 /* Setup up mechanism */
887 891 mech.mechanism = mech_type;
888 892 mech.pParameter = (CK_VOID_PTR)pivbuf;
889 893 mech.ulParameterLen = ivlen;
890 894
↓ open down ↓ |
135 lines elided |
↑ open up ↑ |
891 895 if ((rv = cmd->Init(hSession, &mech, key)) != CKR_OK) {
892 896 cryptoerror(LOG_STDERR, gettext(
893 897 "failed to initialize crypto operation: %s"),
894 898 pkcs11_strerror(rv));
895 899 goto cleanup;
896 900 }
897 901
898 902 /* Write the version header encrypt command */
899 903 if (cmd->type == CKA_ENCRYPT) {
900 904 /* convert to network order for storage */
901 - int netversion = htonl(version);
902 - CK_ULONG netiter;
905 + uint32_t netversion = htonl(version);
906 + uint32_t netiter;
903 907
904 908 if (write(outfd, &netversion, sizeof (netversion))
905 909 != sizeof (netversion)) {
906 910 cryptoerror(LOG_STDERR, gettext(
907 911 "failed to write version number "
908 912 "to output file."));
909 913 goto cleanup;
910 914 }
911 915 /*
912 916 * Write the iteration and salt data, even if they
913 917 * were not used to generate a key.
914 918 */
915 919 netiter = htonl(iterations);
916 920 if (write(outfd, &netiter,
917 921 sizeof (netiter)) != sizeof (netiter)) {
918 922 cryptoerror(LOG_STDERR, gettext(
919 923 "failed to write iterations to output"));
920 924 goto cleanup;
921 925 }
922 926 if (ivlen > 0 && write(outfd, pivbuf, ivlen) != ivlen) {
923 927 cryptoerror(LOG_STDERR, gettext(
924 928 "failed to write initialization vector "
925 929 "to output"));
926 930 goto cleanup;
927 931 }
928 932 if (write(outfd, salt, sizeof (salt)) != sizeof (salt)) {
929 933 cryptoerror(LOG_STDERR, gettext(
930 934 "failed to write salt data to output"));
931 935 goto cleanup;
932 936 }
933 937 }
934 938
935 939 if (crypt_multipart(cmd, hSession, infd, outfd, insbuf.st_size) == -1) {
936 940 goto cleanup;
937 941 }
938 942
939 943 errflag = B_FALSE;
940 944
941 945 /*
942 946 * Clean up
943 947 */
944 948 cleanup:
945 949 /* Clear the key data, so others cannot snoop */
946 950 if (pkeydata != NULL) {
947 951 bzero(pkeydata, keysize);
948 952 free(pkeydata);
949 953 pkeydata = NULL;
950 954 }
951 955
952 956 /* Destroy key object */
953 957 if (Kflag != B_FALSE && key != (CK_OBJECT_HANDLE) 0) {
954 958 (void) C_DestroyObject(hSession, key);
955 959 }
956 960
957 961 /* free allocated memory */
958 962 if (pSlotList != NULL)
959 963 free(pSlotList);
960 964 if (pivbuf != NULL)
961 965 free(pivbuf);
962 966
963 967 /* close all the files */
964 968 if (iflag && (infd != -1))
965 969 (void) close(infd);
966 970 if (oflag && (outfd != -1))
967 971 (void) close(outfd);
968 972
969 973 /* rename tmp output to input file */
970 974 if (inoutsame) {
971 975 if (rename(outfilename, inputfile) == -1) {
972 976 (void) unlink(outfilename);
973 977 cryptoerror(LOG_STDERR, gettext("rename failed."));
974 978 }
975 979 }
976 980
977 981 /* If error occurred, remove the output file */
978 982 if (errflag && outfilename != NULL) {
979 983 (void) unlink(outfilename);
980 984 }
981 985
982 986 /* close pkcs11 session */
983 987 if (hSession != CK_INVALID_HANDLE)
984 988 (void) C_CloseSession(hSession);
985 989
986 990 (void) C_Finalize(NULL);
987 991
988 992 return (errflag);
989 993 }
990 994
991 995 /*
992 996 * Function for printing progress bar when the verbose flag
993 997 * is set.
994 998 *
995 999 * The vertical bar is printed at 25, 50, and 75% complete.
996 1000 *
997 1001 * The function is passed the number of positions on the screen it needs to
998 1002 * advance and loops.
999 1003 */
1000 1004
1001 1005 static void
1002 1006 print_status(int pos_to_advance)
1003 1007 {
1004 1008
1005 1009 while (pos_to_advance > 0) {
1006 1010 switch (status_pos) {
1007 1011 case 0:
1008 1012 (void) fprintf(stderr, gettext("["));
1009 1013 break;
1010 1014 case 19:
1011 1015 case 39:
1012 1016 case 59:
1013 1017 (void) fprintf(stderr, gettext("|"));
1014 1018 break;
1015 1019 default:
1016 1020 (void) fprintf(stderr, gettext("."));
1017 1021 }
1018 1022 pos_to_advance--;
1019 1023 status_pos++;
1020 1024 }
1021 1025 }
1022 1026
1023 1027 /*
1024 1028 * Encrypt/Decrypt in multi part.
1025 1029 *
1026 1030 * This function reads the input file (infd) and writes the
1027 1031 * encrypted/decrypted output to file (outfd).
1028 1032 *
1029 1033 * cmd - pointing to commandinfo
1030 1034 * hSession - pkcs session
1031 1035 * infd - input file descriptor
1032 1036 * outfd - output file descriptor
1033 1037 *
1034 1038 */
1035 1039
1036 1040 static int
1037 1041 crypt_multipart(struct CommandInfo *cmd, CK_SESSION_HANDLE hSession,
1038 1042 int infd, int outfd, off_t insize)
1039 1043 {
1040 1044 CK_RV rv;
1041 1045 CK_ULONG resultlen;
1042 1046 CK_ULONG resultbuflen;
1043 1047 CK_BYTE_PTR resultbuf;
1044 1048 CK_ULONG datalen;
1045 1049 CK_BYTE databuf[BUFFERSIZE];
1046 1050 CK_BYTE outbuf[BUFFERSIZE+BLOCKSIZE];
1047 1051 CK_ULONG status_index = 0; /* current total file size read */
1048 1052 float status_last = 0.0; /* file size of last element used */
1049 1053 float status_incr = 0.0; /* file size element increments */
1050 1054 int pos; /* # of progress bar elements to be print */
1051 1055 ssize_t nread;
1052 1056 boolean_t errflag = B_FALSE;
1053 1057
1054 1058 datalen = sizeof (databuf);
1055 1059 resultbuflen = sizeof (outbuf);
1056 1060 resultbuf = outbuf;
1057 1061
1058 1062 /* Divide into 79 increments for progress bar element spacing */
1059 1063 if (vflag && iflag)
1060 1064 status_incr = (insize / 79.0);
1061 1065
1062 1066 while ((nread = read(infd, databuf, datalen)) > 0) {
1063 1067
1064 1068 /* Start with the initial buffer */
1065 1069 resultlen = resultbuflen;
1066 1070 rv = cmd->Update(hSession, databuf, (CK_ULONG)nread,
1067 1071 resultbuf, &resultlen);
1068 1072
1069 1073 /* Need a bigger buffer? */
1070 1074 if (rv == CKR_BUFFER_TOO_SMALL) {
1071 1075
1072 1076 /* free the old buffer */
1073 1077 if (resultbuf != NULL && resultbuf != outbuf) {
1074 1078 bzero(resultbuf, resultbuflen);
1075 1079 free(resultbuf);
1076 1080 }
1077 1081
1078 1082 /* allocate a new big buffer */
1079 1083 if ((resultbuf = malloc((size_t)resultlen)) == NULL) {
1080 1084 int err = errno;
1081 1085 cryptoerror(LOG_STDERR, gettext("malloc: %s"),
1082 1086 strerror(err));
1083 1087 return (-1);
1084 1088 }
1085 1089 resultbuflen = resultlen;
1086 1090
1087 1091 /* Try again with bigger buffer */
1088 1092 rv = cmd->Update(hSession, databuf, (CK_ULONG)nread,
1089 1093 resultbuf, &resultlen);
1090 1094 }
1091 1095
1092 1096 if (rv != CKR_OK) {
1093 1097 errflag = B_TRUE;
1094 1098 cryptoerror(LOG_STDERR, gettext(
1095 1099 "crypto operation failed: %s"),
1096 1100 pkcs11_strerror(rv));
1097 1101 break;
1098 1102 }
1099 1103
1100 1104 /* write the output */
1101 1105 if (write(outfd, resultbuf, resultlen) != resultlen) {
1102 1106 cryptoerror(LOG_STDERR, gettext(
1103 1107 "failed to write result to output file."));
1104 1108 errflag = B_TRUE;
1105 1109 break;
1106 1110 }
1107 1111
1108 1112 if (vflag) {
1109 1113 status_index += resultlen;
1110 1114
1111 1115 /*
1112 1116 * If input is from stdin, do a our own progress bar
1113 1117 * by printing periods at a pre-defined increment
1114 1118 * until the file is done.
1115 1119 */
1116 1120 if (!iflag) {
1117 1121
1118 1122 /*
1119 1123 * Print at least 1 element in case the file
1120 1124 * is small, it looks better than nothing.
1121 1125 */
1122 1126 if (status_pos == 0) {
1123 1127 (void) fprintf(stderr, gettext("."));
1124 1128 status_pos = 1;
1125 1129 }
1126 1130
1127 1131 if ((status_index - status_last) >
1128 1132 (PROGRESSSIZE)) {
1129 1133 (void) fprintf(stderr, gettext("."));
1130 1134 status_last = status_index;
1131 1135 }
1132 1136 continue;
1133 1137 }
1134 1138
1135 1139 /* Calculate the number of elements need to be print */
1136 1140 if (insize <= BUFFERSIZE)
1137 1141 pos = 78;
1138 1142 else
1139 1143 pos = (int)((status_index - status_last) /
1140 1144 status_incr);
1141 1145
1142 1146 /* Add progress bar elements, if needed */
1143 1147 if (pos > 0) {
1144 1148 print_status(pos);
1145 1149 status_last += (status_incr * pos);
1146 1150 }
1147 1151 }
1148 1152 }
1149 1153
1150 1154 /* Print verbose completion */
1151 1155 if (vflag) {
1152 1156 if (iflag)
1153 1157 (void) fprintf(stderr, "]");
1154 1158
1155 1159 (void) fprintf(stderr, "\n%s\n", gettext("Done."));
1156 1160 }
1157 1161
1158 1162 /* Error in reading */
1159 1163 if (nread == -1) {
1160 1164 cryptoerror(LOG_STDERR, gettext(
1161 1165 "error reading from input file"));
1162 1166 errflag = B_TRUE;
1163 1167 }
1164 1168
1165 1169 if (!errflag) {
1166 1170
1167 1171 /* Do the final part */
1168 1172
1169 1173 rv = cmd->Final(hSession, resultbuf, &resultlen);
1170 1174
1171 1175 if (rv == CKR_OK) {
1172 1176 /* write the output */
1173 1177 if (write(outfd, resultbuf, resultlen) != resultlen) {
1174 1178 cryptoerror(LOG_STDERR, gettext(
1175 1179 "failed to write result to output file."));
1176 1180 errflag = B_TRUE;
1177 1181 }
1178 1182 } else {
1179 1183 cryptoerror(LOG_STDERR, gettext(
1180 1184 "crypto operation failed: %s"),
1181 1185 pkcs11_strerror(rv));
1182 1186 errflag = B_TRUE;
1183 1187 }
1184 1188
1185 1189 }
1186 1190
1187 1191 if (resultbuf != NULL && resultbuf != outbuf) {
1188 1192 bzero(resultbuf, resultbuflen);
1189 1193 free(resultbuf);
1190 1194 }
1191 1195
1192 1196 if (errflag) {
1193 1197 return (-1);
1194 1198 } else {
1195 1199 return (0);
1196 1200 }
1197 1201 }
↓ open down ↓ |
285 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX