1 /* ASN.1 data display code, copyright Peter Gutmann
2 <pgut001@cs.auckland.ac.nz>, based on ASN.1 dump program by David Kemp,
3 with contributions from various people including Matthew Hamrick, Bruno
4 Couillard, Hallvard Furuseth, Geoff Thorpe, David Boyce, John Hughes,
5 'Life is hard, and then you die', Hans-Olof Hermansson, Tor Rustad,
6 Kjetil Barvik, James Sweeny, Chris Ridd, David Lemley, John Tobey, James
7 Manger and several other people whose names I've misplaced.
8
9 Available from http://www.cs.auckland.ac.nz/~pgut001/dumpasn1.c. Last
10 updated 8 August 2015 (version 20150808, if you prefer it that way).
11 To build under Windows, use 'cl /MD dumpasn1.c'. To build on OS390 or
12 z/OS, use '/bin/c89 -D OS390 -o dumpasn1 dumpasn1.c'.
13
14 This code grew slowly over time without much design or planning, and with
15 extra features being tacked on as required. It's not representative of my
16 normal coding style, and should only be used as a debugging/diagnostic
17 tool and not in a production environment (I'm not sure how you'd use
18 it in production anyway, but felt I should point that out). cryptlib,
19 http://www.cs.auckland.ac.nz/~pgut001/cryptlib/, does a much better job of
20 checking ASN.1 than this does, since dumpasn1 is a display program written
21 to accept the widest possible range of input and not a compliance checker.
22 In other words it will bend over backwards to accept even invalid data,
23 since a common use for it is to try and locate encoding problems that lead
24 to invalid encoded data. While it will warn about some types of common
25 errors, the fact that dumpasn1 will display an ASN.1 data item doesn't mean
26 that the item is valid.
27
28 dumpasn1 requires a config file dumpasn1.cfg to be present in the same
29 location as the program itself or in a standard directory where binaries
30 live (it will run without it but will display a warning message, you can
31 configure the path either by hardcoding it in or using an environment
32 variable as explained further down). The config file is available from
33 http://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg.
34
35 This code assumes that the input data is binary, having come from a MIME-
36 aware mailer or been piped through a decoding utility if the original
37 format used base64 encoding. If you need to decode it, it's recommended
38 that you use a utility like uudeview, which will strip virtually any kind
39 of encoding (MIME, PEM, PGP, whatever) to recover the binary original.
40
41 You can use this code in whatever way you want, as long as you don't try
42 to claim you wrote it.
43
44 (Someone asked for clarification on what this means, treat it as a very
45 mild form of the BSD license in which you're not required to include LONG
46 LEGAL DISCLAIMERS IN ALL CAPS but just a small note in a corner somewhere
47 (e.g. the back of a manual) that you're using the dumpasn1 code. And if
48 you do use it, please make sure you're using a recent version, I
49 occasionally see screen shots from incredibly ancient versions that are
50 nowhere near as good as what current versions produce).
51
52 Editing notes: Tabs to 4, phasers to malky (and in case anyone wants to
53 complain about that, see "Program Indentation and Comprehensiblity",
54 Richard Miara, Joyce Musselman, Juan Navarro, and Ben Shneiderman,
55 Communications of the ACM, Vol.26, No.11 (November 1983), p.861) */
56
57 #include <ctype.h>
58 #include <limits.h>
59 #include <stdarg.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #ifdef OS390
64 #include <unistd.h>
65 #endif /* OS390 */
66
67 /* The update string, printed as part of the help screen */
68
69 #define UPDATE_STRING "8 August 2015"
70
71 /* Useful defines */
72
73 #ifndef TRUE
74 #define FALSE 0
75 #define TRUE ( !FALSE )
76 #endif /* TRUE */
77 #ifndef BYTE
78 typedef unsigned char BYTE;
79 #endif /* BYTE */
80
81 /* Tandem Guardian NonStop Kernel options */
82
83 #ifdef __TANDEM
84 #pragma nolist /* Spare us the source listing, no GUI... */
85 #pragma nowarn (1506) /* Implicit type conversion: int to char etc */
86 #endif /* __TANDEM */
87
88 /* SunOS 4.x doesn't define seek codes or exit codes or FILENAME_MAX (it does
89 define _POSIX_MAX_PATH, but in funny locations and to different values
90 depending on which include file you use). Strictly speaking this code
91 isn't right since we need to use PATH_MAX, however not all systems define
92 this, some use _POSIX_PATH_MAX, and then there are all sorts of variations
93 and other defines that you have to check, which require about a page of
94 code to cover each OS, so we just use max( FILENAME_MAX, 512 ) which
95 should work for everything */
96
97 #ifndef SEEK_SET
98 #define SEEK_SET 0
99 #define SEEK_CUR 2
100 #endif /* No fseek() codes defined */
101 #ifndef EXIT_FAILURE
102 #define EXIT_FAILURE 1
103 #define EXIT_SUCCESS ( !EXIT_FAILURE )
104 #endif /* No exit() codes defined */
105 #ifndef FILENAME_MAX
106 #define FILENAME_MAX 512
107 #else
108 #if FILENAME_MAX < 128
109 #undef FILENAME_MAX
110 #define FILENAME_MAX 512
111 #endif /* FILENAME_MAX < 128 */
112 #endif /* FILENAME_MAX */
113
114 /* Under Windows we can do special-case handling for paths and Unicode
115 strings (although in practice it can't really handle much except
116 latin-1) */
117
118 #if ( defined( _WINDOWS ) || defined( WIN32 ) || defined( _WIN32 ) || \
119 defined( __WIN32__ ) )
120 #include <windows.h>
121 #include <io.h> /* For _setmode() */
122 #include <fcntl.h> /* For _setmode() codes */
123 #ifndef _O_U16TEXT
124 #define _O_U16TEXT 0x20000 /* _setmode() code */
125 #endif /* !_O_U16TEXT */
126 #define __WIN32__
127 #endif /* Win32 */
128
129 /* Under Unix we can do special-case handling for paths and Unicode strings.
130 Detecting Unix systems is a bit tricky but the following should find most
131 versions. This define implicitly assumes that the system has wchar_t
132 support, but this is almost always the case except for very old systems,
133 so it's best to default to allow-all rather than deny-all */
134
135 #if defined( linux ) || defined( __linux__ ) || defined( sun ) || \
136 defined( __bsdi__ ) || (defined( __FreeBSD__ )||defined(__DragonFly__)) || defined( __NetBSD__ ) || \
137 defined( __OpenBSD__ ) || defined( __hpux ) || defined( _M_XENIX ) || \
138 defined( __osf__ ) || defined( _AIX ) || defined( __MACH__ )
139 #define __UNIX__
140 #endif /* Every commonly-used Unix */
141 #if defined( linux ) || defined( __linux__ )
142 #ifndef __USE_ISOC99
143 #define __USE_ISOC99
144 #endif /* __USE_ISOC99 */
145 #include <wchar.h>
146 #endif /* Linux */
147
148 /* For IBM mainframe OSes we use the Posix environment, so it looks like
149 Unix */
150
151 #ifdef OS390
152 #define __OS390__
153 #define __UNIX__
154 #endif /* OS390 / z/OS */
155
156 /* Tandem NSK: Don't tangle with Tandem OSS, which is almost UNIX */
157
158 #ifdef __TANDEM
159 #ifdef _GUARDIAN_TARGET
160 #define __TANDEM_NSK__
161 #else
162 #define __UNIX__
163 #endif /* _GUARDIAN_TARGET */
164 #endif /* __TANDEM */
165
166 /* Some OS's don't define the min() macro */
167
168 #ifndef min
169 #define min(a,b) ( ( a ) < ( b ) ? ( a ) : ( b ) )
170 #endif /* !min */
171
172 /* Macros to avoid problems with sign extension */
173
174 #define byteToInt( x ) ( ( BYTE ) ( x ) )
175
176 /* The level of recursion can get scary for deeply-nested structures so we
177 use a larger-than-normal stack under DOS */
178
179 #ifdef __TURBOC__
180 extern unsigned _stklen = 16384;
181 #endif /* __TURBOC__ */
182
183 /* Turn off pointless VC++ warnings */
184
185 #ifdef _MSC_VER
186 #pragma warning( disable: 4018 )
187 #endif /* VC++ */
188
189 /* When we dump a nested data object encapsulated within a larger object, the
190 length is initially set to a magic value which is adjusted to the actual
191 length once we start parsing the object */
192
193 #define LENGTH_MAGIC 177545L
194
195 /* Tag classes */
196
197 #define CLASS_MASK 0xC0 /* Bits 8 and 7 */
198 #define UNIVERSAL 0x00 /* 0 = Universal (defined by ITU X.680) */
199 #define APPLICATION 0x40 /* 1 = Application */
200 #define CONTEXT 0x80 /* 2 = Context-specific */
201 #define PRIVATE 0xC0 /* 3 = Private */
202
203 /* Encoding type */
204
205 #define FORM_MASK 0x20 /* Bit 6 */
206 #define PRIMITIVE 0x00 /* 0 = primitive */
207 #define CONSTRUCTED 0x20 /* 1 = constructed */
208
209 /* Universal tags */
210
211 #define TAG_MASK 0x1F /* Bits 5 - 1 */
212 #define EOC 0x00 /* 0: End-of-contents octets */
213 #define BOOLEAN 0x01 /* 1: Boolean */
214 #define INTEGER 0x02 /* 2: Integer */
215 #define BITSTRING 0x03 /* 2: Bit string */
216 #define OCTETSTRING 0x04 /* 4: Byte string */
217 #define NULLTAG 0x05 /* 5: NULL */
218 #define OID 0x06 /* 6: Object Identifier */
219 #define OBJDESCRIPTOR 0x07 /* 7: Object Descriptor */
220 #define EXTERNAL 0x08 /* 8: External */
221 #define REAL 0x09 /* 9: Real */
222 #define ENUMERATED 0x0A /* 10: Enumerated */
223 #define EMBEDDED_PDV 0x0B /* 11: Embedded Presentation Data Value */
224 #define UTF8STRING 0x0C /* 12: UTF8 string */
225 #define SEQUENCE 0x10 /* 16: Sequence/sequence of */
226 #define SET 0x11 /* 17: Set/set of */
227 #define NUMERICSTRING 0x12 /* 18: Numeric string */
228 #define PRINTABLESTRING 0x13 /* 19: Printable string (ASCII subset) */
229 #define T61STRING 0x14 /* 20: T61/Teletex string */
230 #define VIDEOTEXSTRING 0x15 /* 21: Videotex string */
231 #define IA5STRING 0x16 /* 22: IA5/ASCII string */
232 #define UTCTIME 0x17 /* 23: UTC time */
233 #define GENERALIZEDTIME 0x18 /* 24: Generalized time */
234 #define GRAPHICSTRING 0x19 /* 25: Graphic string */
235 #define VISIBLESTRING 0x1A /* 26: Visible string (ASCII subset) */
236 #define GENERALSTRING 0x1B /* 27: General string */
237 #define UNIVERSALSTRING 0x1C /* 28: Universal string */
238 #define BMPSTRING 0x1E /* 30: Basic Multilingual Plane/Unicode string */
239
240 /* Length encoding */
241
242 #define LEN_XTND 0x80 /* Indefinite or long form */
243 #define LEN_MASK 0x7F /* Bits 7 - 1 */
244
245 /* Various special-case operations to perform on strings */
246
247 typedef enum {
248 STR_NONE, /* No special handling */
249 STR_UTCTIME, /* Check it's UTCTime */
250 STR_GENERALIZED, /* Check it's GeneralizedTime */
251 STR_PRINTABLE, /* Check it's a PrintableString */
252 STR_IA5, /* Check it's an IA5String */
253 STR_LATIN1, /* Read and display string as latin-1 */
254 STR_UTF8, /* Read and display string as UTF8 */
255 STR_BMP, /* Read and display string as Unicode */
256 STR_BMP_REVERSED /* STR_BMP with incorrect endianness */
257 } STR_OPTION;
258
259 /* Structure to hold info on an ASN.1 item */
260
261 typedef struct {
262 int id; /* Tag class + primitive/constructed */
263 int tag; /* Tag */
264 long length; /* Data length */
265 int indefinite; /* Item has indefinite length */
266 int nonCanonical; /* Non-canonical length encoding used */
267 BYTE header[ 16 ]; /* Tag+length data */
268 int headerSize; /* Size of tag+length */
269 } ASN1_ITEM;
270
271 /* Configuration options */
272
273 static int printDots = FALSE; /* Whether to print dots to align columns */
274 static int doPure = FALSE; /* Print data without LHS info column */
275 static int doDumpHeader = FALSE; /* Dump tag+len in hex (level = 0, 1, 2) */
276 static int extraOIDinfo = FALSE; /* Print extra information about OIDs */
277 static int doHexValues = FALSE; /* Display size, offset in hex not dec.*/
278 static int useStdin = FALSE; /* Take input from stdin */
279 static int zeroLengthAllowed = FALSE;/* Zero-length items allowed */
280 static int dumpText = FALSE; /* Dump text alongside hex data */
281 static int printAllData = FALSE; /* Whether to print all data in long blocks */
282 static int checkEncaps = TRUE; /* Print encaps.data in BIT/OCTET STRINGs */
283 static int checkCharset = TRUE; /* Check val.of char strs.hidden in OCTET STRs */
284 #ifndef __OS390__
285 static int reverseBitString = TRUE; /* Print BIT STRINGs in natural order */
286 #else
287 static int reverseBitString = FALSE;/* Natural order on OS390 is the same as ASN.1 */
288 #endif /* __OS390__ */
289 static int rawTimeString = FALSE; /* Print raw time strings */
290 static int shallowIndent = FALSE; /* Perform shallow indenting */
291 static int outputWidth = 80; /* 80-column display */
292 static int maxNestLevel = 100; /* Maximum nesting level for which to display output */
293 static int doOutlineOnly = FALSE; /* Only display constructed-object outline */
294
295 /* Formatting information used for the fixed informational column to the
296 left of the displayed data */
297
298 static int infoWidth = 4;
299 static const char *indentStringTbl[] = {
300 NULL, NULL, NULL,
301 " : ", /* "xxx xxx: " (3) */
302 " : ", /* "xxxx xxxx: " (4) */
303 " : ", /* "xxxxx xxxxx: " (5) */
304 " : ", /* "xxxxxx xxxxxx: " (6) */
305 " : ", /* "xxxxxxx xxxxxxx: " (7) */
306 " : ", /* "xxxxxxxx xxxxxxxx: " (8) */
307 "", "", "", ""
308 };
309 static const char *lenTbl[] = {
310 NULL, NULL, NULL,
311 "%3ld %3ld: ", "%4ld %4ld: ", "%5ld %5ld: ",
312 "%6ld %6ld: ", "%7ld %7ld: ", "%8ld %8ld: ",
313 "", "", "", ""
314 };
315 static const char *lenIndefTbl[] = {
316 NULL, NULL, NULL,
317 "%3ld NDF: ", "%4ld NDEF: ", "%5ld INDEF: ",
318 "%6ld INDEF : ", "%7ld INDEF : ", "%8ld INDEF : ",
319 "", "", "", ""
320 };
321 static const char *lenHexTbl[] = {
322 NULL, NULL, NULL,
323 "%03lX %3lX: ", "%04lX %4lX: ", "%05lX %5lX: ",
324 "%06lX %6lX: ", "%07lX %7lX: ", "%08lX %8lX: ",
325 "", "", "", ""
326 };
327 static const char *lenHexIndefTbl[] = {
328 NULL, NULL, NULL,
329 "%03lX NDF: ", "%04lX NDEF: ", "%05lX INDEF: ",
330 "%06lX INDEF : ", "%07lX INDEF : ", "%08lX INDEF : ",
331 "", "", "", ""
332 };
333
334 #define INDENT_SIZE ( infoWidth + 1 + infoWidth + 1 + 1 )
335 #define INDENT_STRING indentStringTbl[ infoWidth ]
336 #define LEN lenTbl[ infoWidth ]
337 #define LEN_INDEF lenIndefTbl[ infoWidth ]
338 #define LEN_HEX lenHexTbl[ infoWidth ]
339 #define LEN_HEX_INDEF lenHexIndefTbl[ infoWidth ]
340
341 /* Error and warning information */
342
343 static int noErrors = 0; /* Number of errors found */
344 static int noWarnings = 0; /* Number of warnings */
345
346 /* Position in the input stream */
347
348 static int fPos = 0; /* Absolute position in data */
349
350 /* The output stream */
351
352 static FILE *output; /* Output stream */
353
354 /* OID data sizes. Because of Microsoft's "encode random noise and call it
355 an OID" approach, we maintain two size limits, a sane one and one capable
356 of holding the random-noise OID data, which we warn about */
357
358 #define MAX_OID_SIZE 40
359 #define MAX_SANE_OID_SIZE 32
360
361 /* Information on an ASN.1 Object Identifier */
362
363 typedef struct tagOIDINFO {
364 struct tagOIDINFO *next; /* Next item in list */
365 BYTE oid[ MAX_OID_SIZE ];
366 int oidLength;
367 char *comment, *description; /* Name, rank, serial number */
368 int warn; /* Whether to warn if OID encountered */
369 } OIDINFO;
370
371 static OIDINFO *oidList = NULL;
372
373 /* If the config file isn't present in the current directory, we search the
374 following paths (this is needed for Unix with dumpasn1 somewhere in the
375 path, since this doesn't set up argv[0] to the full path). Anything
376 beginning with a '$' uses the appropriate environment variable. In
377 addition under Unix we also walk down $PATH looking for it */
378
379 #ifdef __TANDEM_NSK__
380 #define CONFIG_NAME "asn1cfg"
381 #else
382 #define CONFIG_NAME "dumpasn1.cfg"
383 #endif /* __TANDEM_NSK__ */
384
385 #if defined( __TANDEM_NSK__ )
386
387 static const char *configPaths[] = {
388 "$system.security", "$system.system",
389
390 NULL
391 };
392
393 #elif defined( __WIN32__ )
394
395 static const char *configPaths[] = {
396 /* Windoze absolute paths (yeah, this code has been around for awhile,
397 why do you ask?) */
398 "c:\\windows\\", "c:\\winnt\\",
399
400 /* It's my program, I'm allowed to hardcode in strange paths that no-one
401 else uses */
402 "c:\\program files\\bin\\",
403 "c:\\program files (x86)\\bin\\",
404
405 /* This one seems to be popular as well */
406 "c:\\program files\\utilities\\",
407 "c:\\program files (x86)\\utilities\\",
408
409 /* General environment-based paths */
410 "$DUMPASN1_PATH/",
411
412 NULL
413 };
414
415 #elif defined( __OS390__ )
416
417 static const char *configPaths[] = {
418 /* General environment-based paths */
419 "$DUMPASN1_PATH/",
420
421 NULL
422 };
423
424 #else
425
426 static const char *configPaths[] = {
427 #ifndef DEBIAN
428 /* Unix absolute paths */
429 "/usr/bin/", "/usr/local/bin/", "/etc/dumpasn1/",
430
431 /* Unix environment-based paths */
432 "$HOME/", "$HOME/bin/",
433
434 /* It's my program, I'm allowed to hardcode in strange paths that no-one
435 else uses */
436 "$HOME/BIN/",
437 #else
438 /* Debian has specific places where you're supposed to dump things. Note
439 the dot after $HOME, since config files are supposed to start with a
440 dot for Debian */
441 "$HOME/.", "/etc/dumpasn1/",
442 #endif /* DEBIAN-specific paths */
443
444 /* General environment-based paths */
445 "$DUMPASN1_PATH/",
446
447 NULL
448 };
449 #endif /* OS-specific search paths */
450
451 #define isEnvTerminator( c ) \
452 ( ( ( c ) == '/' ) || ( ( c ) == '.' ) || ( ( c ) == '$' ) || \
453 ( ( c ) == '\0' ) || ( ( c ) == '~' ) )
454
455 /****************************************************************************
456 * *
457 * Object Identification/Description Routines *
458 * *
459 ****************************************************************************/
460
461 /* Return descriptive strings for universal tags */
462
idstr(const int tagID)463 static char *idstr( const int tagID )
464 {
465 switch( tagID )
466 {
467 case EOC:
468 return( "End-of-contents octets" );
469 case BOOLEAN:
470 return( "BOOLEAN" );
471 case INTEGER:
472 return( "INTEGER" );
473 case BITSTRING:
474 return( "BIT STRING" );
475 case OCTETSTRING:
476 return( "OCTET STRING" );
477 case NULLTAG:
478 return( "NULL" );
479 case OID:
480 return( "OBJECT IDENTIFIER" );
481 case OBJDESCRIPTOR:
482 return( "ObjectDescriptor" );
483 case EXTERNAL:
484 return( "EXTERNAL" );
485 case REAL:
486 return( "REAL" );
487 case ENUMERATED:
488 return( "ENUMERATED" );
489 case EMBEDDED_PDV:
490 return( "EMBEDDED PDV" );
491 case UTF8STRING:
492 return( "UTF8String" );
493 case SEQUENCE:
494 return( "SEQUENCE" );
495 case SET:
496 return( "SET" );
497 case NUMERICSTRING:
498 return( "NumericString" );
499 case PRINTABLESTRING:
500 return( "PrintableString" );
501 case T61STRING:
502 return( "TeletexString" );
503 case VIDEOTEXSTRING:
504 return( "VideotexString" );
505 case IA5STRING:
506 return( "IA5String" );
507 case UTCTIME:
508 return( "UTCTime" );
509 case GENERALIZEDTIME:
510 return( "GeneralizedTime" );
511 case GRAPHICSTRING:
512 return( "GraphicString" );
513 case VISIBLESTRING:
514 return( "VisibleString" );
515 case GENERALSTRING:
516 return( "GeneralString" );
517 case UNIVERSALSTRING:
518 return( "UniversalString" );
519 case BMPSTRING:
520 return( "BMPString" );
521 default:
522 return( "Unknown (Reserved)" );
523 }
524 }
525
526 /* Return information on an object identifier */
527
getOIDinfo(const BYTE * oid,const int oidLength)528 static OIDINFO *getOIDinfo( const BYTE *oid, const int oidLength )
529 {
530 const BYTE oidByte = oid[ 1 ];
531 OIDINFO *oidPtr;
532
533 for( oidPtr = oidList; oidPtr != NULL; oidPtr = oidPtr->next )
534 {
535 if( oidLength != oidPtr->oidLength - 2 )
536 continue; /* Quick-reject check */
537 if( oidByte != oidPtr->oid[ 2 + 1 ] )
538 continue; /* Quick-reject check */
539 if( !memcmp( oidPtr->oid + 2, oid, oidLength ) )
540 return( oidPtr );
541 }
542
543 return( NULL );
544 }
545
546 /* Add an OID attribute */
547
addAttribute(char ** buffer,char * attribute)548 static int addAttribute( char **buffer, char *attribute )
549 {
550 if( ( *buffer = ( char * ) malloc( strlen( attribute ) + 1 ) ) == NULL )
551 {
552 puts( "Out of memory." );
553 return( FALSE );
554 }
555 strcpy( *buffer, attribute );
556 return( TRUE );
557 }
558
559 /* Table to identify valid string chars (taken from cryptlib). Note that
560 IA5String also allows control chars, but we warn about these since
561 finding them in a certificate is a sign that there's something
562 seriously wrong */
563
564 #define P 1 /* PrintableString */
565 #define I 2 /* IA5String */
566 #define PI 3 /* IA5String and PrintableString */
567
568 static int charFlags[] = {
569 /* 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F */
570 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
571 /* 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F */
572 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
573 /* ! " # $ % & ' ( ) * + , - . / */
574 PI, I, I, I, I, I, I, PI, PI, PI, I, PI, PI, PI, PI, PI,
575 /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
576 PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, I, I, PI, I, PI,
577 /* @ A B C D E F G H I J K L M N O */
578 I, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI,
579 /* P Q R S T U V W X Y Z [ \ ] ^ _ */
580 PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, I, I, I, I, I,
581 /* ` a b c d e f g h i j k l m n o */
582 I, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI,
583 /* p q r s t u v w x y z { | } ~ DL */
584 PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, I, I, I, I, 0
585 };
586
isPrintable(int ch)587 static int isPrintable( int ch )
588 {
589 if( ch >= 128 || !( charFlags[ ch ] & P ) )
590 return( FALSE );
591 return( TRUE );
592 }
593
isIA5(int ch)594 static int isIA5( int ch )
595 {
596 if( ch >= 128 || !( charFlags[ ch ] & I ) )
597 return( FALSE );
598 return( TRUE );
599 }
600
601 /****************************************************************************
602 * *
603 * Config File Read Routines *
604 * *
605 ****************************************************************************/
606
607 /* Files coming from DOS/Windows systems may have a ^Z (the CP/M EOF char)
608 at the end, so we need to filter this out */
609
610 #define CPM_EOF 0x1A /* ^Z = CPM EOF char */
611
612 /* The maximum input line length */
613
614 #define MAX_LINESIZE 512
615
616 /* Read a line of text from the config file */
617
618 static int lineNo;
619
readLine(FILE * file,char * buffer)620 static int readLine( FILE *file, char *buffer )
621 {
622 int bufCount = 0, ch;
623
624 /* Skip whitespace */
625 while( ( ( ch = getc( file ) ) == ' ' || ch == '\t' ) && !feof( file ) );
626
627 /* Get a line into the buffer */
628 while( ch != '\r' && ch != '\n' && ch != CPM_EOF && !feof( file ) )
629 {
630 /* Check for an illegal char in the data. Note that we don't just
631 check for chars with high bits set because these are legal in
632 non-ASCII strings */
633 if( !isprint( ch ) )
634 {
635 printf( "Bad character '%c' in config file line %d.\n",
636 ch, lineNo );
637 return( FALSE );
638 }
639
640 /* Check to see if it's a comment line */
641 if( ch == '#' && !bufCount )
642 {
643 /* Skip comment section and trailing whitespace */
644 while( ch != '\r' && ch != '\n' && ch != CPM_EOF && !feof( file ) )
645 ch = getc( file );
646 break;
647 }
648
649 /* Make sure that the line is of the correct length */
650 if( bufCount > MAX_LINESIZE )
651 {
652 printf( "Config file line %d too long.\n", lineNo );
653 return( FALSE );
654 }
655 else
656 if( ch ) /* Can happen if we read a binary file */
657 buffer[ bufCount++ ] = ch;
658
659 /* Get next character */
660 ch = getc( file );
661 }
662
663 /* If we've just passed a CR, check for a following LF */
664 if( ch == '\r' )
665 {
666 if( ( ch = getc( file ) ) != '\n' )
667 ungetc( ch, file );
668 }
669
670 /* Skip trailing whitespace and add der terminador */
671 while( bufCount > 0 &&
672 ( ( ch = buffer[ bufCount - 1 ] ) == ' ' || ch == '\t' ) )
673 bufCount--;
674 buffer[ bufCount ] = '\0';
675
676 /* Handle special-case of ^Z if file came off an MSDOS system */
677 if( ch == CPM_EOF )
678 {
679 while( !feof( file ) )
680 {
681 /* Keep going until we hit the true EOF (or some sort of error) */
682 ( void ) getc( file );
683 }
684 }
685
686 return( ferror( file ) ? FALSE : TRUE );
687 }
688
689 /* Process an OID specified as space-separated decimal or hex digits */
690
processOID(OIDINFO * oidInfo,char * string)691 static int processOID( OIDINFO *oidInfo, char *string )
692 {
693 BYTE binaryOID[ MAX_OID_SIZE ];
694 long value;
695 int firstValue = -1, valueIndex = 0, oidIndex = 3;
696
697 memset( binaryOID, 0, MAX_OID_SIZE );
698 binaryOID[ 0 ] = OID;
699 while( *string && oidIndex < MAX_OID_SIZE )
700 {
701 if( oidIndex >= MAX_OID_SIZE - 4 )
702 {
703 printf( "Excessively long OID in config file line %d.\n",
704 lineNo );
705 return( FALSE );
706 }
707 if( sscanf( string, "%8ld", &value ) != 1 || value < 0 )
708 {
709 printf( "Invalid value in config file line %d.\n", lineNo );
710 return( FALSE );
711 }
712 if( valueIndex == 0 )
713 {
714 firstValue = value;
715 valueIndex++;
716 }
717 else
718 {
719 if( valueIndex == 1 )
720 {
721 if( firstValue < 0 || firstValue > 2 || value < 0 || \
722 ( ( firstValue < 2 && value > 39 ) || \
723 ( firstValue == 2 && value > 175 ) ) )
724 {
725 printf( "Invalid value in config file line %d.\n",
726 lineNo );
727 return( FALSE );
728 }
729 binaryOID[ 2 ] = ( firstValue * 40 ) + ( int ) value;
730 valueIndex++;
731 }
732 else
733 {
734 int hasHighBits = FALSE;
735
736 if( value >= 0x200000L ) /* 2^21 */
737 {
738 binaryOID[ oidIndex++ ] = 0x80 | ( int ) ( value >> 21 );
739 value %= 0x200000L;
740 hasHighBits = TRUE;
741 }
742 if( ( value >= 0x4000 ) || hasHighBits ) /* 2^14 */
743 {
744 binaryOID[ oidIndex++ ] = 0x80 | ( int ) ( value >> 14 );
745 value %= 0x4000;
746 hasHighBits = TRUE;
747 }
748 if( ( value >= 0x80 ) || hasHighBits ) /* 2^7 */
749 {
750 binaryOID[ oidIndex++ ] = 0x80 | ( int ) ( value >> 7 );
751 value %= 128;
752 }
753 binaryOID[ oidIndex++ ] = ( int ) value;
754 }
755 }
756 while( *string && isdigit( byteToInt( *string ) ) )
757 string++;
758 if( *string && *string++ != ' ' )
759 {
760 printf( "Invalid OID string in config file line %d.\n", lineNo );
761 return( FALSE );
762 }
763 }
764 binaryOID[ 1 ] = oidIndex - 2;
765 memcpy( oidInfo->oid, binaryOID, oidIndex );
766 oidInfo->oidLength = oidIndex;
767
768 return( TRUE );
769 }
770
processHexOID(OIDINFO * oidInfo,char * string)771 static int processHexOID( OIDINFO *oidInfo, char *string )
772 {
773 int value, index = 0;
774
775 while( *string && index < MAX_OID_SIZE - 1 )
776 {
777 if( sscanf( string, "%4x", &value ) != 1 || value < 0 || value > 255 )
778 {
779 printf( "Invalid hex value in config file line %d.\n", lineNo );
780 return( FALSE );
781 }
782 oidInfo->oid[ index++ ] = value;
783 string += 2;
784 if( *string && *string++ != ' ' )
785 {
786 printf( "Invalid hex string in config file line %d.\n", lineNo );
787 return( FALSE );
788 }
789 }
790 oidInfo->oid[ index ] = 0;
791 oidInfo->oidLength = index;
792 if( index >= MAX_OID_SIZE - 1 )
793 {
794 printf( "OID value in config file line %d too long.\n", lineNo );
795 return( FALSE );
796 }
797 return( TRUE );
798 }
799
800 /* Read a config file */
801
readConfig(const char * path,const int isDefaultConfig)802 static int readConfig( const char *path, const int isDefaultConfig )
803 {
804 OIDINFO dummyOID = { NULL, "Dummy", 0, "Dummy", "Dummy", 1 }, *oidPtr;
805 FILE *file;
806 int seenHexOID = FALSE;
807 char buffer[ MAX_LINESIZE ];
808 int status;
809
810 /* Try and open the config file */
811 if( ( file = fopen( path, "rb" ) ) == NULL )
812 {
813 /* If we can't open the default config file, issue a warning but
814 continue anyway */
815 if( isDefaultConfig )
816 {
817 puts( "Cannot open config file 'dumpasn1.cfg', which should be in the same" );
818 puts( "directory as the dumpasn1 program, a standard system directory, or" );
819 puts( "in a location pointed to by the DUMPASN1_PATH environment variable." );
820 puts( "Operation will continue without the ability to display Object " );
821 puts( "Identifier information." );
822 puts( "" );
823 puts( "If the config file is located elsewhere, you can set the environment" );
824 puts( "variable DUMPASN1_PATH to the path to the file." );
825 return( TRUE );
826 }
827
828 printf( "Cannot open config file '%s'.\n", path );
829 return( FALSE );
830 }
831
832 /* Add the new config entries at the appropriate point in the OID list */
833 if( oidList == NULL )
834 oidPtr = &dummyOID;
835 else
836 for( oidPtr = oidList; oidPtr->next != NULL; oidPtr = oidPtr->next );
837
838 /* Read each line in the config file */
839 lineNo = 1;
840 while( ( status = readLine( file, buffer ) ) == TRUE && !feof( file ) )
841 {
842 /* If it's a comment line, skip it */
843 if( !*buffer )
844 {
845 lineNo++;
846 continue;
847 }
848
849 /* Check for an attribute tag */
850 if( !strncmp( buffer, "OID = ", 6 ) )
851 {
852 /* Make sure that all of the required attributes for the current
853 OID are present */
854 if( oidPtr->description == NULL )
855 {
856 printf( "OID ending on config file line %d has no "
857 "description attribute.\n", lineNo - 1 );
858 return( FALSE );
859 }
860
861 /* Allocate storage for the new OID */
862 if( ( oidPtr->next = ( OIDINFO * ) malloc( sizeof( OIDINFO ) ) ) == NULL )
863 {
864 puts( "Out of memory." );
865 return( FALSE );
866 }
867 oidPtr = oidPtr->next;
868 if( oidList == NULL )
869 oidList = oidPtr;
870 memset( oidPtr, 0, sizeof( OIDINFO ) );
871
872 /* Add the new OID */
873 if( !strncmp( buffer + 6, "06", 2 ) )
874 {
875 seenHexOID = TRUE;
876 if( !processHexOID( oidPtr, buffer + 6 ) )
877 return( FALSE );
878 }
879 else
880 {
881 if( !processOID( oidPtr, buffer + 6 ) )
882 return( FALSE );
883 }
884
885 /* Check that this OID isn't already present in the OID list.
886 This is a quick-and-dirty n^2 algorithm so it's not enabled
887 by default */
888 #if 0
889 {
890 OIDINFO *oidCursor;
891
892 for( oidCursor = oidList; oidCursor->next != NULL; oidCursor = oidCursor->next )
893 {
894 if( oidCursor->oidLength == oidPtr->oidLength && \
895 !memcmp( oidCursor->oid, oidPtr->oid, oidCursor->oidLength ) )
896 {
897 printf( "Duplicate OID '%s' at line %d.\n",
898 buffer, lineNo );
899 }
900 }
901 }
902 #endif /* 0 */
903 }
904 else if( !strncmp( buffer, "Description = ", 14 ) )
905 {
906 if( oidPtr->description != NULL )
907 {
908 printf( "Duplicate OID description in config file line %d.\n",
909 lineNo );
910 return( FALSE );
911 }
912 if( !addAttribute( &oidPtr->description, buffer + 14 ) )
913 return( FALSE );
914 }
915 else if( !strncmp( buffer, "Comment = ", 10 ) )
916 {
917 if( oidPtr->comment != NULL )
918 {
919 printf( "Duplicate OID comment in config file line %d.\n",
920 lineNo );
921 return( FALSE );
922 }
923 if( !addAttribute( &oidPtr->comment, buffer + 10 ) )
924 return( FALSE );
925 }
926 else if( !strncmp( buffer, "Warning", 7 ) )
927 {
928 if( oidPtr->warn )
929 {
930 printf( "Duplicate OID warning in config file line %d.\n",
931 lineNo );
932 return( FALSE );
933 }
934 oidPtr->warn = TRUE;
935 }
936 else
937 {
938 printf( "Unrecognised attribute '%s', line %d.\n", buffer,
939 lineNo );
940 return( FALSE );
941 }
942
943 lineNo++;
944 }
945 fclose( file );
946
947 /* If we're processing an old-style config file, tell the user to
948 upgrade */
949 if( seenHexOID )
950 {
951 puts( "\nWarning: Use of old-style hex OIDs detected in "
952 "configuration file, please\n update your dumpasn1 "
953 "configuration file.\n" );
954 }
955
956 return( status );
957 }
958
959 /* Check for the existence of a config file path (access() isn't available
960 on all systems) */
961
testConfigPath(const char * path)962 static int testConfigPath( const char *path )
963 {
964 FILE *file;
965
966 /* Try and open the config file */
967 if( ( file = fopen( path, "rb" ) ) == NULL )
968 return( FALSE );
969 fclose( file );
970
971 return( TRUE );
972 }
973
974 /* Build a config path by substituting environment strings for $NAMEs */
975
buildConfigPath(char * path,const char * pathTemplate)976 static void buildConfigPath( char *path, const char *pathTemplate )
977 {
978 char pathBuffer[ FILENAME_MAX ], newPath[ FILENAME_MAX ];
979 int pathLen, pathPos = 0, newPathPos = 0;
980
981 /* Add the config file name at the end */
982 strcpy( pathBuffer, pathTemplate );
983 strcat( pathBuffer, CONFIG_NAME );
984 pathLen = strlen( pathBuffer );
985
986 while( pathPos < pathLen )
987 {
988 char *strPtr;
989 int substringSize;
990
991 /* Find the next $ and copy the data before it to the new path */
992 if( ( strPtr = strstr( pathBuffer + pathPos, "$" ) ) != NULL )
993 substringSize = ( int ) ( ( strPtr - pathBuffer ) - pathPos );
994 else
995 substringSize = pathLen - pathPos;
996 if( substringSize > 0 )
997 {
998 memcpy( newPath + newPathPos, pathBuffer + pathPos,
999 substringSize );
1000 }
1001 newPathPos += substringSize;
1002 pathPos += substringSize;
1003
1004 /* Get the environment string for the $NAME */
1005 if( strPtr != NULL )
1006 {
1007 char envName[ MAX_LINESIZE ], *envString;
1008 int i;
1009
1010 /* Skip the '$', find the end of the $NAME, and copy the name
1011 into an internal buffer */
1012 pathPos++; /* Skip the $ */
1013 for( i = 0; !isEnvTerminator( pathBuffer[ pathPos + i ] ); i++ );
1014 memcpy( envName, pathBuffer + pathPos, i );
1015 envName[ i ] = '\0';
1016
1017 /* Get the env.string and copy it over */
1018 if( ( envString = getenv( envName ) ) != NULL )
1019 {
1020 const int envStrLen = strlen( envString );
1021
1022 if( newPathPos + envStrLen < FILENAME_MAX - 2 )
1023 {
1024 memcpy( newPath + newPathPos, envString, envStrLen );
1025 newPathPos += envStrLen;
1026 }
1027 }
1028 pathPos += i;
1029 }
1030 }
1031 newPath[ newPathPos ] = '\0'; /* Add der terminador */
1032
1033 /* Copy the new path to the output */
1034 strcpy( path, newPath );
1035 }
1036
1037 /* Read the global config file */
1038
readGlobalConfig(const char * path)1039 static int readGlobalConfig( const char *path )
1040 {
1041 char buffer[ FILENAME_MAX ];
1042 char *searchPos = ( char * ) path, *namePos, *lastPos = NULL;
1043 #ifdef __UNIX__
1044 char *envPath;
1045 #endif /* __UNIX__ */
1046 #ifdef __WIN32__
1047 char filePath[ _MAX_PATH ];
1048 DWORD count;
1049 #endif /* __WIN32__ */
1050 int i;
1051
1052 /* First, try and find the config file in the same directory as the
1053 executable by walking down the path until we find the last occurrence
1054 of the program name. This requires that argv[0] be set up properly,
1055 which isn't the case if Unix search paths are being used and is a
1056 bit hit-and-miss under Windows where the contents of argv[0] depend
1057 on how the program is being executed. To avoid this we perform some
1058 Windows-specific processing to try and find the path to the
1059 executable if we can't otherwise find it */
1060 do
1061 {
1062 namePos = lastPos;
1063 lastPos = strstr( searchPos, "dumpasn1" );
1064 if( lastPos == NULL )
1065 lastPos = strstr( searchPos, "DUMPASN1" );
1066 searchPos = lastPos + 1;
1067 }
1068 while( lastPos != NULL );
1069 #ifdef __UNIX__
1070 if( namePos == NULL && ( namePos = strrchr( path, '/' ) ) != NULL )
1071 {
1072 const int endPos = ( int ) ( namePos - path ) + 1;
1073
1074 /* If the executable isn't called dumpasn1, we won't be able to find
1075 it with the above code, fall back to looking for directory
1076 separators. This requires a system where the only separator is
1077 the directory separator (ie it doesn't work for Windows or most
1078 mainframe environments) */
1079 if( endPos < FILENAME_MAX - 13 )
1080 {
1081 memcpy( buffer, path, endPos );
1082 strcpy( buffer + endPos, CONFIG_NAME );
1083 if( testConfigPath( buffer ) )
1084 return( readConfig( buffer, TRUE ) );
1085 }
1086
1087 /* That didn't work, try the absolute locations and $PATH */
1088 namePos = NULL;
1089 }
1090 #endif /* __UNIX__ */
1091 if( strlen( path ) < FILENAME_MAX - 13 && namePos != NULL )
1092 {
1093 strcpy( buffer, path );
1094 strcpy( buffer + ( int ) ( namePos - ( char * ) path ), CONFIG_NAME );
1095 if( testConfigPath( buffer ) )
1096 return( readConfig( buffer, TRUE ) );
1097 }
1098
1099 /* Now try each of the possible absolute locations for the config file */
1100 for( i = 0; configPaths[ i ] != NULL; i++ )
1101 {
1102 buildConfigPath( buffer, configPaths[ i ] );
1103 if( testConfigPath( buffer ) )
1104 return( readConfig( buffer, TRUE ) );
1105 }
1106
1107 #ifdef __UNIX__
1108 /* On Unix systems we can also search for the config file on $PATH */
1109 if( ( envPath = getenv( "PATH" ) ) != NULL )
1110 {
1111 char *pathPtr = strtok( envPath, ":" );
1112
1113 do
1114 {
1115 sprintf( buffer, "%s/%s", pathPtr, CONFIG_NAME );
1116 if( testConfigPath( buffer ) )
1117 return( readConfig( buffer, TRUE ) );
1118 pathPtr = strtok( NULL, ":" );
1119 }
1120 while( pathPtr != NULL );
1121 }
1122 #endif /* __UNIX__ */
1123 #ifdef __WIN32__
1124 /* Under Windows we can use GetModuleFileName() to find the location of
1125 the program */
1126 count = GetModuleFileName ( NULL, filePath, _MAX_PATH );
1127 if( count > 0 )
1128 {
1129 char *progNameStart = strrchr( filePath, '\\' );
1130 if( progNameStart != NULL && \
1131 ( progNameStart - filePath ) < _MAX_PATH - 13 )
1132 {
1133 /* Replace the program name with the config file name */
1134 strcpy( progNameStart + 1, CONFIG_NAME );
1135 if( testConfigPath( filePath ) )
1136 return( readConfig( filePath, TRUE ) );
1137 }
1138 }
1139 #endif /*__WIN32__*/
1140
1141
1142 /* Default to just the config name (which should fail as it was the
1143 first entry in configPaths[]). readConfig() will display the
1144 appropriate warning */
1145 return( readConfig( CONFIG_NAME, TRUE ) );
1146 }
1147
1148 /* Free the in-memory config data */
1149
freeConfig(void)1150 static void freeConfig( void )
1151 {
1152 OIDINFO *oidPtr = oidList;
1153
1154 while( oidPtr != NULL )
1155 {
1156 OIDINFO *oidCursor = oidPtr;
1157
1158 oidPtr = oidPtr->next;
1159 if( oidCursor->comment != NULL )
1160 free( oidCursor->comment );
1161 if( oidCursor->description != NULL )
1162 free( oidCursor->description );
1163 free( oidCursor );
1164 }
1165 }
1166
1167 /****************************************************************************
1168 * *
1169 * Output/Formatting Routines *
1170 * *
1171 ****************************************************************************/
1172
1173 #ifdef __OS390__
1174
asciiToEbcdic(const int ch)1175 static int asciiToEbcdic( const int ch )
1176 {
1177 char convBuffer[ 2 ];
1178
1179 convBuffer[ 0 ] = ch;
1180 convBuffer[ 1 ] = '\0';
1181 __atoe( convBuffer ); /* Convert ASCII to EBCDIC for 390 */
1182 return( convBuffer[ 0 ] );
1183 }
1184 #endif /* __OS390__ */
1185
1186 /* Output formatted text */
1187
printString(const int level,const char * format,...)1188 static int printString( const int level, const char *format, ... )
1189 {
1190 va_list argPtr;
1191 int length;
1192
1193 if( level >= maxNestLevel )
1194 return( 0 );
1195 va_start( argPtr, format );
1196 length = vfprintf( output, format, argPtr );
1197 va_end( argPtr );
1198
1199 return( length );
1200 }
1201
1202 /* Indent a string by the appropriate amount */
1203
doIndent(const int level)1204 static void doIndent( const int level )
1205 {
1206 int i;
1207
1208 if( level >= maxNestLevel )
1209 return;
1210 for( i = 0; i < level; i++ )
1211 {
1212 fprintf( output, printDots ? ". " : \
1213 shallowIndent ? " " : " " );
1214 }
1215 }
1216
1217 /* Complain about an error in the ASN.1 object */
1218
complain(const char * message,const int messageParam,const int level)1219 static void complain( const char *message, const int messageParam,
1220 const int level )
1221 {
1222 if( level < maxNestLevel )
1223 {
1224 if( !doPure )
1225 fprintf( output, "%s", INDENT_STRING );
1226 doIndent( level + 1 );
1227 }
1228 fputs( "Error: ", output );
1229 fprintf( output, message, messageParam );
1230 fputs( ".\n", output );
1231 noErrors++;
1232 }
1233
complainLength(const ASN1_ITEM * item,const int level)1234 static void complainLength( const ASN1_ITEM *item, const int level )
1235 {
1236 #if 0
1237 /* This is a general error so we don't indent the message to the level
1238 of the item */
1239 #else
1240 if( level < maxNestLevel )
1241 {
1242 if( !doPure )
1243 fprintf( output, "%s", INDENT_STRING );
1244 doIndent( level + 1 );
1245 }
1246 #endif /* 0 */
1247 fprintf( output, "Error: %s has invalid length %ld.\n",
1248 idstr( item->tag ), item->length );
1249 noErrors++;
1250 }
1251
complainLengthCanonical(const ASN1_ITEM * item,const int level)1252 static void complainLengthCanonical( const ASN1_ITEM *item, const int level )
1253 {
1254 int i;
1255
1256 #if 0
1257 /* This is a general error so we don't indent the message to the level
1258 of the item */
1259 #else
1260 if( level < maxNestLevel )
1261 {
1262 if( !doPure )
1263 fprintf( output, "%s", INDENT_STRING );
1264 doIndent( level + 1 );
1265 }
1266 #endif /* 0 */
1267 fputs( "Error: Length '", output );
1268 for( i = item->nonCanonical; i < item->headerSize; i++ )
1269 {
1270 fprintf( output, "%02X", item->header[ i ] );
1271 if( i < item->headerSize - 1 )
1272 fputc( ' ', output );
1273 }
1274 fputs( "' has non-canonical encoding.\n", output );
1275 noErrors++;
1276 }
1277
complainInt(const BYTE * intValue,const int level)1278 static void complainInt( const BYTE *intValue, const int level )
1279 {
1280 if( level < maxNestLevel )
1281 {
1282 if( !doPure )
1283 fprintf( output, "%s", INDENT_STRING );
1284 doIndent( level + 1 );
1285 }
1286 fprintf( output, "Error: Integer '%02X %02X ...' has non-DER encoding.\n",
1287 intValue[ 0 ], intValue[ 1 ] );
1288 noErrors++;
1289 }
1290
1291 /* Adjust the nesting-level value to make sure that we don't go off the edge
1292 of the screen via doIndent() when we're displaying a text or hex dump of
1293 data */
1294
adjustLevel(const int level,const int maxLevel)1295 static int adjustLevel( const int level, const int maxLevel )
1296 {
1297 /* If we've been passed a very large pseudo-level to disable output then
1298 we don't try and override this */
1299 if( level >= 1000 )
1300 return( level );
1301
1302 /* If we've exceeded the maximum level for display, cap the value at
1303 maxLevel to make sure that we don't end up indenting output off the
1304 edge of the screen */
1305 if( level > maxLevel )
1306 return( maxLevel );
1307
1308 return( level );
1309 }
1310
1311 #if defined( __WIN32__ ) || defined( __UNIX__ ) || defined( __OS390__ )
1312
1313 /* Try and display to display a Unicode character. This is pretty hit and
1314 miss, and if it fails nothing is displayed. To try and detect this we
1315 use wcstombs() to see if anything can be displayed, if it can't we drop
1316 back to trying to display the data as non-Unicode */
1317
displayUnicode(const wchar_t wCh,const int level)1318 static int displayUnicode( const wchar_t wCh, const int level )
1319 {
1320 char outBuf[ 8 ];
1321 int outLen;
1322
1323 /* Check whether we can display this character */
1324 outLen = wcstombs( outBuf, &wCh, 1 );
1325 if( outLen < 1 )
1326 {
1327 /* Tell the caller that this can't be displayed as Unicode */
1328 return( FALSE );
1329 }
1330
1331 #if defined( __WIN32__ )
1332 if( level < maxNestLevel )
1333 {
1334 int oldmode;
1335
1336 /* To output Unicode to the Win32 console we need to switch the
1337 output stream to Unicode-16 mode, but the following may also
1338 depend on which code page is currently set for the console, which
1339 font is being used, and the phase of the moon (including the moons
1340 for Mars and Jupiter) */
1341 fflush( output );
1342 oldmode = _setmode( fileno( output ), _O_U16TEXT );
1343 fputwc( wCh, output );
1344 _setmode( fileno( output ), oldmode );
1345 }
1346 #elif defined( __UNIX__ ) && !( defined( __MACH__ ) || defined( __OpenBSD__ ) )
1347 /* Unix environments are even more broken than Win32, like Win32 the
1348 output differentiates between char and widechar output, but there's
1349 no easy way to deal with this. In theory fwide() can set it, but
1350 it's a one-way function, once we've set it a particular way we can't
1351 go back (exactly what level of braindamage it takes to have an
1352 implementation function like this is a mystery). Other sources
1353 suggest using setlocale() tricks, printf() with "%lc" or "%ls" as the
1354 format specifier, and others, but none of these seem to work properly
1355 either */
1356 if( level < maxNestLevel )
1357 {
1358 #if 0
1359 setlocale( LC_ALL, "" );
1360 fputwc( wCh, output );
1361 #elif 1
1362 /* This (and the "%ls" variant below) seem to be the least broken
1363 options */
1364 fprintf( output, "%lc", wCh );
1365 #elif 0
1366 wchar_t wChString[ 2 ];
1367
1368 wChString[ 0 ] = wCh;
1369 wChString[ 1 ] = 0;
1370 fprintf( output, "%ls", wChString );
1371 #else
1372 if( fwide( output, 1 ) > 0 )
1373 {
1374 fputwc( wCh, output );
1375 fwide( output, -1 );
1376 }
1377 else
1378 fputc( wCh, output );
1379 #endif
1380 }
1381 #else
1382 #ifdef __OS390__
1383 if( level < maxNestLevel )
1384 {
1385 char *p;
1386
1387 /* This could use some improvement */
1388 for( p = outBuf; *p != '\0'; p++ )
1389 *p = asciiToEbcdic( *p );
1390 }
1391 #endif /* IBM ASCII -> EBCDIC conversion */
1392 printString( level, "%s", outBuf );
1393 #endif /* OS-specific charset handling */
1394
1395 return( TRUE );
1396 }
1397 #endif /* __WIN32__ || __UNIX__ || __OS390__ */
1398
1399 /* Display an integer value */
1400
printValue(FILE * inFile,const int valueLength,const int level)1401 static void printValue( FILE *inFile, const int valueLength,
1402 const int level )
1403 {
1404 BYTE intBuffer[ 2 ];
1405 long value;
1406 int warnNegative = FALSE, warnNonDER = FALSE, i;
1407
1408 value = getc( inFile );
1409 if( value & 0x80 )
1410 warnNegative = TRUE;
1411 for( i = 0; i < valueLength - 1; i++ )
1412 {
1413 int ch = getc( inFile );
1414
1415 /* Check for the first 9 bits being identical */
1416 if( i == 0 )
1417 {
1418 if( ( value == 0x00 ) && ( ( ch & 0x80 ) == 0x00 ) )
1419 warnNonDER = TRUE;
1420 if( ( value == 0xFF ) && ( ( ch & 0x80 ) == 0x80 ) )
1421 warnNonDER = TRUE;
1422 if( warnNonDER )
1423 {
1424 intBuffer[ 0 ] = ( int ) value;
1425 intBuffer[ 1 ] = ch;
1426 }
1427 }
1428 value = ( value << 8 ) | ch;
1429 }
1430 fPos += valueLength;
1431
1432 /* Display the integer value and any associated warnings. Note that
1433 this will display an incorrectly-encoded integer as a negative value
1434 rather than the unsigned value that was probably intended to
1435 emphasise that it's incorrect */
1436 printString( level, " %ld\n", value );
1437 if( warnNonDER )
1438 complainInt( intBuffer, level );
1439 if( warnNegative )
1440 complain( "Integer is encoded as a negative value", 0, level );
1441 }
1442
1443 /* Dump data as a string of hex digits up to a maximum of 128 bytes */
1444
dumpHex(FILE * inFile,long length,int level,const int isInteger)1445 static void dumpHex( FILE *inFile, long length, int level,
1446 const int isInteger )
1447 {
1448 const int lineLength = ( dumpText ) ? 8 : 16;
1449 const int displayHeaderLength = ( ( doPure ) ? 0 : INDENT_SIZE ) + 2;
1450 BYTE intBuffer[ 2 ];
1451 char printable[ 9 ];
1452 long noBytes = length;
1453 int warnPadding = FALSE, warnNegative = isInteger, singleLine = FALSE;
1454 int displayLength = displayHeaderLength, prevCh = -1, i;
1455
1456 memset( printable, 0, 9 );
1457
1458 displayLength += ( length < lineLength ) ? ( length * 3 ) : \
1459 ( lineLength * 3 );
1460
1461 /* Check if the size of the displayed data (LHS status info + hex data)
1462 plus the indent-level of spaces will fit into a single line behind
1463 the initial label, e.g. "INTEGER" */
1464 if( displayHeaderLength + ( level * 2 ) + ( length * 3 ) < outputWidth )
1465 singleLine = TRUE;
1466
1467 /* By default we only output a maximum of 128 bytes to avoid dumping
1468 huge amounts of data, however if what's left is a partial lines'
1469 worth then we output that as well to avoid displaying a line of text
1470 indicating that less than a lines' worth of data remains to be
1471 displayed */
1472 if( noBytes >= 128 + lineLength && !printAllData )
1473 noBytes = 128;
1474
1475 /* Make sure that the indent level doesn't push the text off the edge of
1476 the screen */
1477 level = adjustLevel( level, ( outputWidth - displayLength ) / 2 );
1478 for( i = 0; i < noBytes; i++ )
1479 {
1480 int ch;
1481
1482 if( !( i % lineLength ) )
1483 {
1484 if( singleLine )
1485 printString( level, "%c", ' ' );
1486 else
1487 {
1488 if( dumpText )
1489 {
1490 /* If we're dumping text alongside the hex data, print
1491 the accumulated text string */
1492 printString( level, "%s", " " );
1493 printString( level, "%s", printable );
1494 }
1495 printString( level, "%c", '\n' );
1496 if( !doPure )
1497 printString( level, "%s", INDENT_STRING );
1498 doIndent( level + 1 );
1499 }
1500 }
1501 ch = getc( inFile );
1502 if( ch == EOF )
1503 {
1504 printString( level, "%c", '\n' );
1505 complain( "Unexpected EOF, %d bytes missing", length - i, level );
1506 return;
1507 }
1508 printString( level, "%s%02X", ( i % lineLength ) ? " " : "", ch );
1509 printable[ i % 8 ] = ( ch >= ' ' && ch < 127 ) ? ch : '.';
1510 fPos++;
1511
1512 /* If we need to check for negative values, check this now */
1513 if( i == 0 )
1514 {
1515 prevCh = ch;
1516 if( !( ch & 0x80 ) )
1517 warnNegative = FALSE;
1518 }
1519 if( i == 1 )
1520 {
1521 /* Check for the first 9 bits being identical */
1522 if( ( prevCh == 0x00 ) && ( ( ch & 0x80 ) == 0x00 ) )
1523 warnPadding = TRUE;
1524 if( ( prevCh == 0xFF ) && ( ( ch & 0x80 ) == 0x80 ) )
1525 warnPadding = TRUE;
1526 if( warnPadding )
1527 {
1528 intBuffer[ 0 ] = prevCh;
1529 intBuffer[ 1 ] = ch;
1530 }
1531 }
1532 }
1533 if( dumpText )
1534 {
1535 /* Print any remaining text */
1536 i %= lineLength;
1537 printable[ i ] = '\0';
1538 while( i < lineLength )
1539 {
1540 printString( level, "%s", " " );
1541 i++;
1542 }
1543 printString( level, "%s", " " );
1544 printString( level, "%s", printable );
1545 }
1546 if( length >= 128 + lineLength && !printAllData )
1547 {
1548 length -= 128;
1549 printString( level, "%c", '\n' );
1550 if( !doPure )
1551 printString( level, "%s", INDENT_STRING );
1552 doIndent( level + 5 );
1553 printString( level, "[ Another %ld bytes skipped ]", length );
1554 fPos += length;
1555 if( useStdin )
1556 {
1557 while( length-- )
1558 getc( inFile );
1559 }
1560 else
1561 fseek( inFile, length, SEEK_CUR );
1562 }
1563 printString( level, "%c", '\n' );
1564
1565 if( isInteger )
1566 {
1567 if( warnPadding )
1568 complainInt( intBuffer, level );
1569 if( warnNegative )
1570 complain( "Integer is encoded as a negative value", 0, level );
1571 }
1572 }
1573
1574 /* Convert a binary OID to its string equivalent */
1575
oidToString(char * textOID,int * textOIDlength,const BYTE * oid,const int oidLength)1576 static int oidToString( char *textOID, int *textOIDlength,
1577 const BYTE *oid, const int oidLength )
1578 {
1579 BYTE uuidBuffer[ 32 ];
1580 long value;
1581 int length = 0, uuidBufPos = -1, uuidBitCount = 5, i;
1582 int validEncoding = TRUE, isUUID = FALSE;
1583
1584 for( i = 0, value = 0; i < oidLength; i++ )
1585 {
1586 const BYTE data = oid[ i ];
1587 const long valTmp = value << 7;
1588
1589 /* Pick apart the encoding. We keep going after hitting an encoding
1590 error at the start of an arc because the overall length is
1591 bounded and we may still be able to recover something worth
1592 printing */
1593 if( value == 0 && data == 0x80 )
1594 {
1595 /* Invalid leading zero value, 0x80 & 0x7F == 0 */
1596 validEncoding = FALSE;
1597 }
1598 if( isUUID )
1599 {
1600 value = 1; /* Set up dummy value since we're bypassing normal read */
1601 if( uuidBitCount == 0 )
1602 uuidBuffer[ uuidBufPos ] = data << 1;
1603 else
1604 {
1605 if( uuidBufPos >= 0 )
1606 uuidBuffer[ uuidBufPos ] |= ( data & 0x7F ) >> ( 7 - uuidBitCount );
1607 uuidBufPos++;
1608 if( uuidBitCount < 7 )
1609 uuidBuffer[ uuidBufPos ] = data << ( uuidBitCount + 1 );
1610 }
1611 uuidBitCount++;
1612 if( uuidBitCount > 7 )
1613 uuidBitCount = 0;
1614 if( !( data & 0x80 ) )
1615 {
1616 /* The following check isn't completely accurate since we
1617 could have less than 16 bytes present if there are
1618 leading zeroes, however to handle this properly we'd
1619 have to decode the entire value as a bignum and then
1620 format it appropriately, and given the fact that the use
1621 of these things is practically nonexistent it's probably
1622 not worth the code space to deal with this */
1623 if( uuidBufPos != 16 )
1624 {
1625 validEncoding = FALSE;
1626 break;
1627 }
1628 length += sprintf( textOID + length,
1629 " { %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x }",
1630 uuidBuffer[ 0 ], uuidBuffer[ 1 ],
1631 uuidBuffer[ 2 ], uuidBuffer[ 3 ],
1632 uuidBuffer[ 4 ], uuidBuffer[ 5 ],
1633 uuidBuffer[ 6 ], uuidBuffer[ 7 ],
1634 uuidBuffer[ 8 ], uuidBuffer[ 9 ],
1635 uuidBuffer[ 10 ], uuidBuffer[ 11 ],
1636 uuidBuffer[ 12 ], uuidBuffer[ 13 ],
1637 uuidBuffer[ 14 ], uuidBuffer[ 15 ] );
1638 value = 0;
1639 }
1640 continue;
1641 }
1642 if( value >= ( LONG_MAX >> 7 ) || \
1643 valTmp >= LONG_MAX - ( data & 0x7F ) )
1644 {
1645 validEncoding = FALSE;
1646 break;
1647 }
1648 value = valTmp | ( data & 0x7F );
1649 if( value < 0 || value > LONG_MAX / 2 )
1650 {
1651 validEncoding = FALSE;
1652 break;
1653 }
1654 if( !( data & 0x80 ) )
1655 {
1656 if( length == 0 )
1657 {
1658 long x, y;
1659
1660 /* The first two levels are encoded into one byte since the
1661 root level has only 3 nodes (40*x + y), however if x =
1662 joint-iso-itu-t(2) then y may be > 39, so we have to add
1663 special-case handling for this */
1664 x = value / 40;
1665 y = value % 40;
1666 if( x > 2 )
1667 {
1668 /* Handle special case for large y if x == 2 */
1669 y += ( x - 2 ) * 40;
1670 x = 2;
1671 }
1672 if( x < 0 || x > 2 || y < 0 || \
1673 ( ( x < 2 && y > 39 ) || \
1674 ( x == 2 && ( y > 50 && y != 100 ) ) ) )
1675 {
1676 /* If x = 0 or 1 then y has to be 0...39, for x = 3
1677 it can take any value but there are no known
1678 assigned values over 50 except for one contrived
1679 example in X.690 which sets y = 100, so if we see
1680 something outside this range it's most likely an
1681 encoding error rather than some bizarre new ID
1682 that's just appeared */
1683 validEncoding = FALSE;
1684 break;
1685 }
1686 length = sprintf( textOID, "%ld %ld", x, y );
1687
1688 /* A totally stupid ITU facility lets people register UUIDs
1689 as OIDs (see http://www.itu.int/ITU-T/asn1/uuid.html), if
1690 we find one of these, which live under the arc '2 25' =
1691 0x69 we have to continue decoding the OID as a UUID
1692 instead of a standard OID */
1693 if( data == 0x69 )
1694 isUUID = TRUE;
1695 }
1696 else
1697 length += sprintf( textOID + length, " %ld", value );
1698 value = 0;
1699 }
1700 }
1701 if( value != 0 )
1702 {
1703 /* We stopped in the middle of a continued value */
1704 validEncoding = FALSE;
1705 }
1706 textOID[ length ] = '\0';
1707 *textOIDlength = length;
1708
1709 return( validEncoding );
1710 }
1711
1712 /* Dump a bitstring, reversing the bits into the standard order in the
1713 process */
1714
dumpBitString(FILE * inFile,const int length,const int unused,const int level)1715 static void dumpBitString( FILE *inFile, const int length, const int unused,
1716 const int level )
1717 {
1718 unsigned int bitString = 0, currentBitMask = 0x80, remainderMask = 0xFF;
1719 int bitFlag, value = 0, noBits, bitNo = -1, i;
1720 char *errorStr = NULL;
1721
1722 if( unused < 0 || unused > 7 )
1723 complain( "Invalid number %d of unused bits", unused, level );
1724 noBits = ( length * 8 ) - unused;
1725
1726 /* ASN.1 bitstrings start at bit 0, so we need to reverse the order of
1727 the bits if necessary */
1728 if( length > 0 )
1729 {
1730 bitString = fgetc( inFile );
1731 if( bitString == EOF )
1732 {
1733 noBits = 0;
1734 errorStr = "Truncated BIT STRING data";
1735 }
1736 fPos++;
1737 }
1738 for( i = noBits - 8; i > 0; i -= 8 )
1739 {
1740 const int ch = fgetc( inFile );
1741
1742 if( ch == EOF )
1743 {
1744 errorStr = "Truncated BIT STRING data";
1745 break;
1746 }
1747 bitString = ( bitString << 8 ) | ch;
1748 currentBitMask <<= 8;
1749 remainderMask = ( remainderMask << 8 ) | 0xFF;
1750 fPos++;
1751 }
1752 if( errorStr != NULL )
1753 {
1754 printString( level, "%c", '\n' );
1755 complain( errorStr, 0, level );
1756 return;
1757 }
1758 if( reverseBitString )
1759 {
1760 for( i = 0, bitFlag = 1; i < noBits; i++ )
1761 {
1762 if( bitString & currentBitMask )
1763 value |= bitFlag;
1764 if( !( bitString & remainderMask ) && errorStr != NULL )
1765 {
1766 /* The last valid bit should be a one bit */
1767 errorStr = "Spurious zero bits in bitstring";
1768 }
1769 bitFlag <<= 1;
1770 bitString <<= 1;
1771 }
1772 if( noBits < sizeof( int ) && \
1773 ( ( remainderMask << noBits ) & value ) && \
1774 errorStr != NULL )
1775 {
1776 /* There shouldn't be any bits set after the last valid one. We
1777 have to do the noBits check to avoid a fencepost error when
1778 there's exactly 32 bits */
1779 errorStr = "Spurious one bits in bitstring";
1780 }
1781 }
1782 else
1783 value = bitString;
1784
1785 /* Now that it's in the right order, dump it. If there's only one bit
1786 set (which is often the case for bit flags) we also print the bit
1787 number to save users having to count the zeroes to figure out which
1788 flag is set */
1789 printString( level, "%c", '\n' );
1790 if( !doPure )
1791 printString( level, "%s", INDENT_STRING );
1792 doIndent( level + 1 );
1793 printString( level, "%c", '\'' );
1794 if( reverseBitString )
1795 currentBitMask = 1 << ( noBits - 1 );
1796 for( i = 0; i < noBits; i++ )
1797 {
1798 if( value & currentBitMask )
1799 {
1800 bitNo = ( bitNo == -1 ) ? ( noBits - 1 ) - i : -2;
1801 printString( level, "%c", '1' );
1802 }
1803 else
1804 printString( level, "%c", '0' );
1805 currentBitMask >>= 1;
1806 }
1807 if( bitNo >= 0 )
1808 printString( level, "'B (bit %d)\n", bitNo );
1809 else
1810 printString( level, "%s", "'B\n" );
1811
1812 if( errorStr != NULL )
1813 complain( errorStr, 0, level );
1814 }
1815
1816 /* Display data as a text string up to a maximum of 240 characters (8 lines
1817 of 48 chars to match the hex limit of 8 lines of 16 bytes) with special
1818 treatement for control characters and other odd things that can turn up
1819 in BMPString and UniversalString types.
1820
1821 If the string is less than 40 chars in length, we try to print it on the
1822 same line as the rest of the text (even if it wraps), otherwise we break
1823 it up into 48-char chunks in a somewhat less nice text-dump format */
1824
displayString(FILE * inFile,long length,int level,const STR_OPTION strOption)1825 static void displayString( FILE *inFile, long length, int level,
1826 const STR_OPTION strOption )
1827 {
1828 char timeStr[ 64 ];
1829 long noBytes = length;
1830 int lineLength = 48, i;
1831 int firstTime = TRUE, doTimeStr = FALSE, warnIA5 = FALSE;
1832 int warnPrintable = FALSE, warnTime = FALSE, warnBMP = FALSE;
1833
1834 if( noBytes > 384 && !printAllData )
1835 noBytes = 384; /* Only output a maximum of 384 bytes */
1836 if( strOption == STR_UTCTIME || strOption == STR_GENERALIZED )
1837 {
1838 if( ( strOption == STR_UTCTIME && length != 13 ) || \
1839 ( strOption == STR_GENERALIZED && length != 15 ) )
1840 warnTime = TRUE;
1841 else
1842 doTimeStr = rawTimeString ? FALSE : TRUE;
1843 }
1844 if( !doTimeStr && length <= 40 )
1845 printString( level, "%s", " '" ); /* Print string on same line */
1846 level = adjustLevel( level, ( doPure ) ? 15 : 8 );
1847 for( i = 0; i < noBytes; i++ )
1848 {
1849 int ch;
1850
1851 /* If the string is longer than 40 chars, break it up into multiple
1852 sections */
1853 if( length > 40 && !( i % lineLength ) )
1854 {
1855 if( !firstTime )
1856 printString( level, "%c", '\'' );
1857 printString( level, "%c", '\n' );
1858 if( !doPure )
1859 printString( level, "%s", INDENT_STRING );
1860 doIndent( level + 1 );
1861 printString( level, "%c", '\'' );
1862 firstTime = FALSE;
1863 }
1864 ch = getc( inFile );
1865 #if defined( __WIN32__ ) || defined( __UNIX__ ) || defined( __OS390__ )
1866 if( strOption == STR_BMP )
1867 {
1868 if( i == noBytes - 1 && ( noBytes & 1 ) )
1869 {
1870 /* Odd-length BMP string, complain */
1871 warnBMP = TRUE;
1872 }
1873 else
1874 {
1875 const wchar_t wCh = ( ch << 8 ) | getc( inFile );
1876
1877 if( displayUnicode( wCh, level ) )
1878 {
1879 lineLength++;
1880 i++; /* We've read two characters for a wchar_t */
1881 fPos += 2;
1882 continue;
1883 }
1884
1885 /* The value can't be displayed as Unicode, fall back to
1886 displaying it as normal text */
1887 ungetc( wCh & 0xFF, inFile );
1888 }
1889 }
1890 if( strOption == STR_UTF8 && ( ch & 0x80 ) )
1891 {
1892 const int secondCh = getc( inFile );
1893 wchar_t wCh;
1894
1895 /* It's a multibyte UTF8 character, read it as a widechar */
1896 if( ( ch & 0xE0 ) == 0xC0 ) /* 111xxxxx -> 110xxxxx */
1897 {
1898 /* 2-byte character in the range 0x80...0x7FF */
1899 wCh = ( ( ch & 0x1F ) << 6 ) | ( secondCh & 0x3F );
1900 i++; /* We've read 2 characters */
1901 fPos += 2;
1902 }
1903 else
1904 {
1905 if( ( ch & 0xF0 ) == 0xE0 ) /* 1111xxxx -> 1110xxxx */
1906 {
1907 const int thirdCh = getc( inFile );
1908
1909 /* 3-byte character in the range 0x800...0xFFFF */
1910 wCh = ( ( ch & 0x1F ) << 12 ) | \
1911 ( ( secondCh & 0x3F ) << 6 ) | \
1912 ( thirdCh & 0x3F );
1913 }
1914 else
1915 wCh = '.';
1916 i += 2; /* We've read 3 characters */
1917 fPos += 3;
1918 }
1919 if( !displayUnicode( wCh, level ) )
1920 printString( level, "%c", '.' );
1921 lineLength++;
1922 continue;
1923 }
1924 #endif /* __WIN32__ || __UNIX__ || __OS390__ */
1925 switch( strOption )
1926 {
1927 case STR_PRINTABLE:
1928 case STR_IA5:
1929 case STR_LATIN1:
1930 if( strOption == STR_PRINTABLE && !isPrintable( ch ) )
1931 warnPrintable = TRUE;
1932 if( strOption == STR_IA5 && !isIA5( ch ) )
1933 warnIA5 = TRUE;
1934 if( strOption == STR_LATIN1 )
1935 {
1936 if( !isprint( ch & 0x7F ) )
1937 ch = '.'; /* Convert non-ASCII to placeholders */
1938 }
1939 else
1940 {
1941 if( !isprint( ch ) )
1942 ch = '.'; /* Convert non-ASCII to placeholders */
1943 }
1944 #ifdef __OS390__
1945 ch = asciiToEbcdic( ch );
1946 #endif /* __OS390__ */
1947 break;
1948
1949 case STR_UTCTIME:
1950 case STR_GENERALIZED:
1951 if( !isdigit( ch ) && ch != 'Z' )
1952 {
1953 warnTime = TRUE;
1954 if( !isprint( ch ) )
1955 ch = '.'; /* Convert non-ASCII to placeholders */
1956 }
1957 #ifdef __OS390__
1958 ch = asciiToEbcdic( ch );
1959 #endif /* __OS390__ */
1960 break;
1961
1962 case STR_BMP_REVERSED:
1963 if( i == noBytes - 1 && ( noBytes & 1 ) )
1964 {
1965 /* Odd-length BMP string, complain */
1966 warnBMP = TRUE;
1967 }
1968
1969 /* Wrong-endianness BMPStrings (Microsoft Unicode) can't be
1970 handled through the usual widechar-handling mechanism
1971 above since the first widechar looks like an ASCII char
1972 followed by a null terminator, so we just treat them as
1973 ASCII chars, skipping the following zero byte. This is
1974 safe since the code that detects reversed BMPStrings
1975 has already checked that every second byte is zero */
1976 getc( inFile );
1977 i++;
1978 fPos++;
1979 /* Drop through */
1980
1981 default:
1982 if( !isprint( ch ) )
1983 ch = '.'; /* Convert control chars to placeholders */
1984 #ifdef __OS390__
1985 ch = asciiToEbcdic( ch );
1986 #endif /* __OS390__ */
1987 }
1988 if( doTimeStr )
1989 timeStr[ i ] = ch;
1990 else
1991 printString( level, "%c", ch );
1992 fPos++;
1993 }
1994 if( length > 384 && !printAllData )
1995 {
1996 length -= 384;
1997 printString( level, "%s", "'\n" );
1998 if( !doPure )
1999 printString( level, "%s", INDENT_STRING );
2000 doIndent( level + 5 );
2001 printString( level, "[ Another %ld characters skipped ]", length );
2002 fPos += length;
2003 while( length-- )
2004 {
2005 int ch = getc( inFile );
2006
2007 if( strOption == STR_PRINTABLE && !isPrintable( ch ) )
2008 warnPrintable = TRUE;
2009 if( strOption == STR_IA5 && !isIA5( ch ) )
2010 warnIA5 = TRUE;
2011 }
2012 }
2013 else
2014 {
2015 if( doTimeStr )
2016 {
2017 const char *timeStrPtr = ( strOption == STR_UTCTIME ) ? \
2018 timeStr : timeStr + 2;
2019
2020 printString( level, " %c%c/%c%c/",
2021 timeStrPtr[ 4 ], timeStrPtr[ 5 ],
2022 timeStrPtr[ 2 ], timeStrPtr[ 3 ] );
2023 if( strOption == STR_UTCTIME )
2024 {
2025 printString( level, "%s",
2026 ( timeStr[ 0 ] < '5' ) ? "20" : "19" );
2027 }
2028 else
2029 {
2030 printString( level, "%c%c", timeStr[ 0 ], timeStr[ 1 ] );
2031 }
2032 printString( level, "%c%c %c%c:%c%c:%c%c GMT",
2033 timeStrPtr[ 0 ], timeStrPtr[ 1 ], timeStrPtr[ 6 ],
2034 timeStrPtr[ 7 ], timeStrPtr[ 8 ], timeStrPtr[ 9 ],
2035 timeStrPtr[ 10 ], timeStrPtr[ 11 ] );
2036 }
2037 else
2038 printString( level, "%c", '\'' );
2039 }
2040 printString( level, "%c", '\n' );
2041
2042 /* Display any problems we encountered */
2043 if( warnPrintable )
2044 complain( "PrintableString contains illegal character(s)", 0, level );
2045 if( warnIA5 )
2046 complain( "IA5String contains illegal character(s)", 0, level );
2047 if( warnTime )
2048 complain( "Time is encoded incorrectly", 0, level );
2049 if( warnBMP )
2050 complain( "BMPString has missing final byte/half character", 0, level );
2051 }
2052
2053 /****************************************************************************
2054 * *
2055 * ASN.1 Parsing Routines *
2056 * *
2057 ****************************************************************************/
2058
2059 /* Get an ASN.1 object's tag and length. Returns TRUE for an item
2060 available, FALSE for end-of-data, and a negative value for an invalid
2061 data */
2062
getItem(FILE * inFile,ASN1_ITEM * item)2063 static int getItem( FILE *inFile, ASN1_ITEM *item )
2064 {
2065 int tag, length, index = 0;
2066
2067 memset( item, 0, sizeof( ASN1_ITEM ) );
2068 item->indefinite = FALSE;
2069 tag = item->header[ index++ ] = fgetc( inFile );
2070 if( tag == EOF )
2071 return( FALSE );
2072 item->id = tag & ~TAG_MASK;
2073 tag &= TAG_MASK;
2074 if( tag == TAG_MASK )
2075 {
2076 int value;
2077
2078 /* Long tag encoded as sequence of 7-bit values. This doesn't try to
2079 handle tags > INT_MAX, it'd be pretty peculiar ASN.1 if it had to
2080 use tags this large */
2081 tag = 0;
2082 do
2083 {
2084 value = fgetc( inFile );
2085 if( value == EOF )
2086 return( FALSE );
2087 tag = ( tag << 7 ) | ( value & 0x7F );
2088 item->header[ index++ ] = value;
2089 fPos++;
2090 }
2091 while( value & LEN_XTND && index < 5 && !feof( inFile ) );
2092 if( index >= 5 )
2093 {
2094 fPos++; /* Tag */
2095 return( FALSE );
2096 }
2097 }
2098 item->tag = tag;
2099 if( feof( inFile ) )
2100 {
2101 fPos++;
2102 return( FALSE );
2103 }
2104 fPos += 2; /* Tag + length */
2105 length = item->header[ index++ ] = fgetc( inFile );
2106 if( length == EOF )
2107 return( FALSE );
2108 item->headerSize = index;
2109 if( length & LEN_XTND )
2110 {
2111 const int lengthStart = index;
2112 int i;
2113
2114 length &= LEN_MASK;
2115 if( length > 4 )
2116 {
2117 /* Impossible length value, probably because we've run into
2118 the weeds */
2119 return( -1 );
2120 }
2121 item->headerSize += length;
2122 item->length = 0;
2123 if( !length )
2124 item->indefinite = TRUE;
2125 for( i = 0; i < length; i++ )
2126 {
2127 int ch = fgetc( inFile );
2128
2129 if( ch == EOF )
2130 {
2131 fPos += length - i;
2132 return( FALSE );
2133 }
2134 item->length = ( item->length << 8 ) | ch;
2135 item->header[ i + index ] = ch;
2136 }
2137 fPos += length;
2138
2139 /* Check for the length being less then 128, which means it
2140 shouldn't be encoded as a long length */
2141 if( !item->indefinite && item->length < 128 )
2142 item->nonCanonical = lengthStart;
2143
2144 /* Check for the first 9 bits of the length being identical and
2145 if they are, remember where the encoded non-canonical length
2146 starts */
2147 if( item->headerSize - lengthStart > 1 )
2148 {
2149 if( ( item->header[ lengthStart ] == 0x00 ) && \
2150 ( ( item->header[ lengthStart + 1 ] & 0x80 ) == 0x00 ) )
2151 item->nonCanonical = lengthStart - 1;
2152 if( ( item->header[ lengthStart ] == 0xFF ) && \
2153 ( ( item->header[ lengthStart + 1 ] & 0x80 ) == 0x80 ) )
2154 item->nonCanonical = lengthStart - 1;
2155 }
2156 }
2157 else
2158 item->length = length;
2159
2160 return( TRUE );
2161 }
2162
2163 /* Check whether a BIT STRING or OCTET STRING encapsulates another object */
2164
checkEncapsulate(FILE * inFile,const int length)2165 static int checkEncapsulate( FILE *inFile, const int length )
2166 {
2167 ASN1_ITEM nestedItem;
2168 const int currentPos = fPos;
2169 int diffPos, status;
2170
2171 /* If we're not looking for encapsulated objects, return */
2172 if( !checkEncaps )
2173 return( FALSE );
2174
2175 /* Read the details of the next item in the input stream */
2176 status = getItem( inFile, &nestedItem );
2177 diffPos = fPos - currentPos;
2178 fPos = currentPos;
2179 fseek( inFile, -diffPos, SEEK_CUR );
2180 if( status <= 0 )
2181 return( FALSE );
2182
2183 /* If it's not a standard tag class, don't try and dig down into it */
2184 if( ( nestedItem.id & CLASS_MASK ) != UNIVERSAL && \
2185 ( nestedItem.id & CLASS_MASK ) != CONTEXT )
2186 return( FALSE );
2187
2188 /* There is one special-case situation that overrides the check below,
2189 which is when the nested content is indefinite-length. This is
2190 rather tricky to check for because we'd need to read some distance
2191 ahead into the stream to be able to safely decide whether we've got
2192 true nested content or a false positive, for now we require that
2193 the nested content has to be a SEQUENCE containing valid ASN.1 at
2194 the start, giving about 24 bits of checking. There's a small risk
2195 of false negatives for encapsulated primitive items, but since
2196 they're primitive it should be relatively easy to make out the
2197 contents inside the OCTET STRING */
2198 if( nestedItem.tag == SEQUENCE && nestedItem.indefinite )
2199 {
2200 /* Skip the indefinite-length SEQUENCE and make sure that it's
2201 followed by a valid item */
2202 status = getItem( inFile, &nestedItem );
2203 if( status > 0 )
2204 status = getItem( inFile, &nestedItem );
2205 diffPos = fPos - currentPos;
2206 fPos = currentPos;
2207 fseek( inFile, -diffPos, SEEK_CUR );
2208 if( status <= 0 )
2209 return( FALSE );
2210
2211 /* If the tag on the nest item looks vaguely valid, assume that we've
2212 go nested content */
2213 if( ( nestedItem.tag <= 0 || nestedItem.tag > 0x31 ) || \
2214 ( nestedItem.length >= length ) )
2215 return( FALSE );
2216 return( TRUE );
2217 }
2218
2219 /* If it doesn't fit exactly within the current item it's not an
2220 encapsulated object */
2221 if( nestedItem.length != length - diffPos )
2222 return( FALSE );
2223
2224 /* If it doesn't have a valid-looking tag, don't try and go any further */
2225 if( nestedItem.tag <= 0 || nestedItem.tag > 0x31 )
2226 return( FALSE );
2227
2228 /* Now things get a bit complicated because it's possible to get some
2229 (very rare) false positives, for example if a NUMERICSTRING of
2230 exactly the right length is nested within an OCTET STRING, since
2231 numeric values all look like constructed tags of some kind. To
2232 handle this we look for nested constructed items that should really
2233 be primitive */
2234 if( ( nestedItem.id & FORM_MASK ) == PRIMITIVE )
2235 return( TRUE );
2236
2237 /* It's constructed, make sure that it's something for which it makes
2238 sense as a constructed object. At worst this will give some false
2239 negatives for really wierd objects (nested constructed strings inside
2240 OCTET STRINGs), but these should probably never occur anyway */
2241 if( nestedItem.tag == SEQUENCE || \
2242 nestedItem.tag == SET )
2243 return( TRUE );
2244
2245 return( FALSE );
2246 }
2247
2248 /* Check whether a zero-length item is OK */
2249
zeroLengthOK(const ASN1_ITEM * item)2250 static int zeroLengthOK( const ASN1_ITEM *item )
2251 {
2252 /* An implicitly-tagged NULL can have a zero length. An occurrence of this
2253 type of item is almost always an error, however OCSP uses a weird status
2254 encoding that encodes result values in tags and then has to use a NULL
2255 value to indicate that there's nothing there except the tag that encodes
2256 the status, so we allow this as well if zero-length content is explicitly
2257 enabled */
2258 if( zeroLengthAllowed && ( item->id & CLASS_MASK ) == CONTEXT )
2259 return( TRUE );
2260
2261 /* If we can't recognise the type from the tag, reject it */
2262 if( ( item->id & CLASS_MASK ) != UNIVERSAL )
2263 return( FALSE );
2264
2265 /* The following types are zero-length by definition */
2266 if( item->tag == EOC || item->tag == NULLTAG )
2267 return( TRUE );
2268
2269 /* A real with a value of zero has zero length */
2270 if( item->tag == REAL )
2271 return( TRUE );
2272
2273 /* Everything after this point requires input from the user to say that
2274 zero-length data is OK (usually it's not, so we flag it as a
2275 problem) */
2276 if( !zeroLengthAllowed )
2277 return( FALSE );
2278
2279 /* String types can have zero length except for the Unrestricted
2280 Character String type ([UNIVERSAL 29]) which has to have at least one
2281 octet for the CH-A/CH-B index */
2282 if( item->tag == OCTETSTRING || item->tag == NUMERICSTRING || \
2283 item->tag == PRINTABLESTRING || item->tag == T61STRING || \
2284 item->tag == VIDEOTEXSTRING || item->tag == VISIBLESTRING || \
2285 item->tag == IA5STRING || item->tag == GRAPHICSTRING || \
2286 item->tag == GENERALSTRING || item->tag == UNIVERSALSTRING || \
2287 item->tag == BMPSTRING || item->tag == UTF8STRING || \
2288 item->tag == OBJDESCRIPTOR )
2289 return( TRUE );
2290
2291 /* SEQUENCE and SET can be zero if there are absent optional/default
2292 components */
2293 if( item->tag == SEQUENCE || item->tag == SET )
2294 return( TRUE );
2295
2296 return( FALSE );
2297 }
2298
2299 /* Check whether the next item looks like text */
2300
checkForText(FILE * inFile,const int length)2301 static STR_OPTION checkForText( FILE *inFile, const int length )
2302 {
2303 char buffer[ 16 ];
2304 int isBMP = FALSE, isUnicode = FALSE;
2305 int sampleLength = min( length, 16 ), i;
2306
2307 /* If the sample is very short, we're more careful about what we
2308 accept */
2309 if( sampleLength < 4 )
2310 {
2311 /* If the sample size is too small, don't try anything */
2312 if( sampleLength <= 2 )
2313 return( STR_NONE );
2314
2315 /* For samples of 3-4 characters we only allow ASCII text. These
2316 short strings are used in some places (eg PKCS #12 files) as
2317 IDs */
2318 sampleLength = fread( buffer, 1, sampleLength, inFile );
2319 if( sampleLength <= 0 )
2320 return( STR_NONE );
2321 fseek( inFile, -sampleLength, SEEK_CUR );
2322 for( i = 0; i < sampleLength; i++ )
2323 {
2324 const int ch = byteToInt( buffer[ i ] );
2325
2326 if( !( isalpha( ch ) || isdigit( ch ) || isspace( ch ) ) )
2327 return( STR_NONE );
2328 }
2329 return( STR_IA5 );
2330 }
2331
2332 /* Check for ASCII-looking text */
2333 sampleLength = fread( buffer, 1, sampleLength, inFile );
2334 if( sampleLength <= 0 )
2335 return( STR_NONE );
2336 fseek( inFile, -sampleLength, SEEK_CUR );
2337 if( isdigit( byteToInt( buffer[ 0 ] ) ) && \
2338 ( length == 13 || length == 15 ) && \
2339 buffer[ length - 1 ] == 'Z' )
2340 {
2341 /* It looks like a time string, make sure that it really is one */
2342 for( i = 0; i < length - 1; i++ )
2343 {
2344 if( !isdigit( byteToInt( buffer[ i ] ) ) )
2345 break;
2346 }
2347 if( i == length - 1 )
2348 return( ( length == 13 ) ? STR_UTCTIME : STR_GENERALIZED );
2349 }
2350 for( i = 0; i < sampleLength; i++ )
2351 {
2352 /* If even bytes are zero, it could be a BMPString. Initially
2353 we set isBMP to FALSE, if it looks like a BMPString we set it to
2354 TRUE, if we then encounter a nonzero byte it's neither an ASCII
2355 nor a BMPString */
2356 if( !( i & 1 ) )
2357 {
2358 if( !buffer[ i ] )
2359 {
2360 /* If we thought we were in a Unicode string but we've found a
2361 zero byte where it'd occur in a BMP string, it's neither a
2362 Unicode nor BMP string */
2363 if( isUnicode )
2364 return( STR_NONE );
2365
2366 /* We've collapsed the eigenstate (in an earlier incarnation
2367 isBMP could take values of -1, 0, or 1, with 0 being
2368 undecided, in which case this comment made a bit more
2369 sense) */
2370 if( i < sampleLength - 2 )
2371 {
2372 /* If the last char(s) are zero but preceding ones
2373 weren't, don't treat it as a BMP string. This can
2374 happen when storing a null-terminated string if the
2375 implementation gets the length wrong and stores the
2376 null as well */
2377 isBMP = TRUE;
2378 }
2379 continue;
2380 }
2381 else
2382 {
2383 /* If we thought we were in a BMPString but we've found a
2384 nonzero byte where there should be a zero, it's neither
2385 an ASCII nor BMP string */
2386 if( isBMP )
2387 return( STR_NONE );
2388 }
2389 }
2390 else
2391 {
2392 /* Just to make it tricky, Microsoft stuff Unicode strings into
2393 some places (to avoid having to convert them to BMPStrings,
2394 presumably) so we have to check for these as well */
2395 if( !buffer[ i ] )
2396 {
2397 if( isBMP )
2398 return( STR_NONE );
2399 isUnicode = TRUE;
2400 continue;
2401 }
2402 else
2403 {
2404 if( isUnicode )
2405 return( STR_NONE );
2406 }
2407 }
2408 if( buffer[ i ] < 0x20 || buffer[ i ] > 0x7E )
2409 return( STR_NONE );
2410 }
2411
2412 /* It looks like a text string */
2413 return( isUnicode ? STR_BMP_REVERSED : isBMP ? STR_BMP : STR_IA5 );
2414 }
2415
2416 /* Dump the header bytes for an object, useful for vgrepping the original
2417 object from a hex dump */
2418
dumpHeader(FILE * inFile,const ASN1_ITEM * item,const int level)2419 static void dumpHeader( FILE *inFile, const ASN1_ITEM *item, const int level )
2420 {
2421 int extraLen = 24 - item->headerSize, i;
2422
2423 /* Dump the tag and length bytes */
2424 if( !doPure )
2425 printString( level, "%s", " " );
2426 printString( level, "<%02X", *item->header );
2427 for( i = 1; i < item->headerSize; i++ )
2428 printString( level, " %02X", item->header[ i ] );
2429
2430 /* If we're asked for more, dump enough extra data to make up 24 bytes.
2431 This is somewhat ugly since it assumes we can seek backwards over the
2432 data, which means it won't always work on streams */
2433 if( extraLen > 0 && doDumpHeader > 1 )
2434 {
2435 /* Make sure that we don't print too much data. This doesn't work
2436 for indefinite-length data, we don't try and guess the length with
2437 this since it involves picking apart what we're printing */
2438 if( extraLen > item->length && !item->indefinite )
2439 extraLen = ( int ) item->length;
2440
2441 for( i = 0; i < extraLen; i++ )
2442 {
2443 const int ch = fgetc( inFile );
2444
2445 if( ch == EOF )
2446 {
2447 /* Exit loop and get fseek() offset correct */
2448 extraLen = i;
2449 break;
2450 }
2451 printString( level, " %02X", ch );
2452 }
2453 fseek( inFile, -extraLen, SEEK_CUR );
2454 }
2455
2456 printString( level, "%s", ">\n" );
2457 }
2458
2459 /* Print a constructed ASN.1 object */
2460
2461 static int printAsn1( FILE *inFile, const int level, long length,
2462 const int isIndefinite );
2463
printConstructed(FILE * inFile,int level,const ASN1_ITEM * item)2464 static void printConstructed( FILE *inFile, int level, const ASN1_ITEM *item )
2465 {
2466 int result;
2467
2468 /* Special case for zero-length objects */
2469 if( !item->length && !item->indefinite )
2470 {
2471 printString( level, "%s", " {}\n" );
2472 if( item->nonCanonical )
2473 complainLengthCanonical( item, level );
2474 return;
2475 }
2476
2477 printString( level, "%s", " {\n" );
2478 if( item->nonCanonical )
2479 complainLengthCanonical( item, level );
2480 result = printAsn1( inFile, level + 1, item->length, item->indefinite );
2481 if( result )
2482 {
2483 fprintf( output, "Error: Inconsistent object length, %d byte%s "
2484 "difference.\n", result, ( result > 1 ) ? "s" : "" );
2485 noErrors++;
2486 }
2487 if( !doPure )
2488 printString( level, "%s", INDENT_STRING );
2489 printString( level, "%s", ( printDots ) ? ". " : " " );
2490 doIndent( level );
2491 printString( level, "%s", "}\n" );
2492 }
2493
2494 /* Print a single ASN.1 object */
2495
printASN1object(FILE * inFile,ASN1_ITEM * item,int level)2496 static void printASN1object( FILE *inFile, ASN1_ITEM *item, int level )
2497 {
2498 OIDINFO *oidInfo;
2499 STR_OPTION stringType;
2500 BYTE buffer[ MAX_OID_SIZE ];
2501 const int nonOutlineObject = \
2502 ( doOutlineOnly && ( item->id & FORM_MASK ) != CONSTRUCTED ) ? \
2503 TRUE : FALSE;
2504
2505 if( ( item->id & CLASS_MASK ) != UNIVERSAL )
2506 {
2507 static const char *const classtext[] =
2508 { "UNIVERSAL ", "APPLICATION ", "", "PRIVATE " };
2509
2510 /* Print the object type */
2511 if( !nonOutlineObject )
2512 {
2513 printString( level, "[%s%d]",
2514 classtext[ ( item->id & CLASS_MASK ) >> 6 ], item->tag );
2515 }
2516
2517 /* Perform a sanity check */
2518 if( ( item->tag != NULLTAG ) && ( item->length < 0 ) )
2519 {
2520 int i;
2521
2522 fflush( stdout );
2523 fprintf( stderr, "\nError: Object has bad length field, tag = %02X, "
2524 "length = %lX, value =", item->tag, item->length );
2525 fprintf( stderr, "<%02X", *item->header );
2526 for( i = 1; i < item->headerSize; i++ )
2527 fprintf( stderr, " %02X", item->header[ i ] );
2528 fputs( ">.\n", stderr );
2529 exit( EXIT_FAILURE );
2530 }
2531
2532 if( !item->length && !item->indefinite && !zeroLengthOK( item ) )
2533 {
2534 printString( level, "%c", '\n' );
2535 complain( "Object has zero length", 0, level );
2536 if( item->nonCanonical )
2537 complainLengthCanonical( item, level );
2538 return;
2539 }
2540
2541 /* If it's constructed, print the various fields in it */
2542 if( ( item->id & FORM_MASK ) == CONSTRUCTED )
2543 {
2544 printConstructed( inFile, level, item );
2545 return;
2546 }
2547
2548 /* It'sprimitive, if we're only displaying the ASN.1 in outline
2549 form, supress the display by dumping it with a nesting level that
2550 ensures it won't get output (this clears the data from the input
2551 without displaying it) */
2552 if( nonOutlineObject )
2553 {
2554 dumpHex( inFile, item->length, 1000, FALSE );
2555 if( item->nonCanonical )
2556 complainLengthCanonical( item, level );
2557 printString( level, "%c", '\n' );
2558 return;
2559 }
2560
2561 /* It's primitive, if it's a seekable stream try and determine
2562 whether it's text so we can display it as such */
2563 if( !useStdin && \
2564 ( stringType = checkForText( inFile, item->length ) ) != STR_NONE )
2565 {
2566 /* It looks like a text string, dump it as text */
2567 displayString( inFile, item->length, level, stringType );
2568 if( item->nonCanonical )
2569 complainLengthCanonical( item, level );
2570 return;
2571 }
2572
2573 /* This could be anything, dump it as hex data */
2574 dumpHex( inFile, item->length, level, FALSE );
2575 if( item->nonCanonical )
2576 complainLengthCanonical( item, level );
2577
2578 return;
2579 }
2580
2581 /* Print the object type */
2582 if( !doOutlineOnly || ( item->id & FORM_MASK ) == CONSTRUCTED )
2583 printString( level, "%s", idstr( item->tag ) );
2584
2585 /* Perform a sanity check */
2586 if( ( item->tag != NULLTAG ) && ( item->length < 0 ) )
2587 {
2588 int i;
2589
2590 fflush( stdout );
2591 fprintf( stderr, "\nError: Object has bad length field, tag = %02X, "
2592 "length = %lX, value =", item->tag, item->length );
2593 fprintf( stderr, "<%02X", *item->header );
2594 for( i = 1; i < item->headerSize; i++ )
2595 fprintf( stderr, " %02X", item->header[ i ] );
2596 fputs( ">.\n", stderr );
2597 exit( EXIT_FAILURE );
2598 }
2599
2600 /* If it's constructed, print the various fields in it */
2601 if( ( item->id & FORM_MASK ) == CONSTRUCTED )
2602 {
2603 printConstructed( inFile, level, item );
2604 return;
2605 }
2606
2607 /* It's primitive */
2608 if( doOutlineOnly )
2609 {
2610 /* If we're only displaying the ASN.1 in outline form, set an
2611 artificially high nesting level that ensures it won't get output
2612 (this clears the data from the input without displaying it) */
2613 level = 1000;
2614 }
2615 if( !item->length && !zeroLengthOK( item ) )
2616 {
2617 printString( level, "%c", '\n' );
2618 complain( "Object has zero length", 0, level );
2619 if( item->nonCanonical )
2620 complainLengthCanonical( item, level );
2621 return;
2622 }
2623 switch( item->tag )
2624 {
2625 case BOOLEAN:
2626 {
2627 int ch;
2628
2629 if( item->length != 1 )
2630 complainLength( item, level );
2631 ch = getc( inFile );
2632 printString( level, " %s\n", ch ? "TRUE" : "FALSE" );
2633 if( ch != 0 && ch != 0xFF )
2634 {
2635 complain( "BOOLEAN '%02X' has non-DER encoding", ch,
2636 level );
2637 }
2638 if( item->nonCanonical )
2639 complainLengthCanonical( item, level );
2640 fPos++;
2641 break;
2642 }
2643
2644 case INTEGER:
2645 case ENUMERATED:
2646 if( item->length > 4 )
2647 {
2648 dumpHex( inFile, item->length, level, TRUE );
2649 if( item->nonCanonical )
2650 complainLengthCanonical( item, level );
2651 }
2652 else
2653 {
2654 printValue( inFile, item->length, level );
2655 if( item->nonCanonical )
2656 complainLengthCanonical( item, level );
2657 }
2658 break;
2659
2660 case BITSTRING:
2661 {
2662 int ch;
2663
2664 if( item->length < 2 )
2665 complainLength( item, level );
2666 if( ( ch = getc( inFile ) ) != 0 )
2667 {
2668 printString( level, " %d unused bit%s",
2669 ch, ( ch != 1 ) ? "s" : "" );
2670 }
2671 fPos++;
2672 if( !--item->length && !ch )
2673 {
2674 printString( level, "%c", '\n' );
2675 complain( "Object has zero length", 0, level );
2676 if( item->nonCanonical )
2677 complainLengthCanonical( item, level );
2678 return;
2679 }
2680 if( item->length <= sizeof( int ) )
2681 {
2682 /* It's short enough to be a bit flag, dump it as a sequence
2683 of bits */
2684 dumpBitString( inFile, ( int ) item->length, ch, level );
2685 if( item->nonCanonical )
2686 complainLengthCanonical( item, level );
2687 break;
2688 }
2689 /* Drop through to dump it as an octet string */
2690 }
2691
2692 case OCTETSTRING:
2693 if( checkEncapsulate( inFile, item->length ) )
2694 {
2695 /* It's something encapsulated inside the string, print it as
2696 a constructed item */
2697 printString( level, "%s", ", encapsulates" );
2698 printConstructed( inFile, level, item );
2699 break;
2700 }
2701 if( !useStdin && !dumpText && \
2702 ( stringType = checkForText( inFile, item->length ) ) != STR_NONE )
2703 {
2704 /* If we'd be doing a straight hex dump and it looks like
2705 encapsulated text, display it as such. If the user has
2706 overridden character set type checking and it's a string
2707 type for which we normally perform type checking, we reset
2708 its type to none */
2709 displayString( inFile, item->length, level, \
2710 ( !checkCharset && ( stringType == STR_IA5 || \
2711 stringType == STR_PRINTABLE ) ) ? \
2712 STR_NONE : stringType );
2713 if( item->nonCanonical )
2714 complainLengthCanonical( item, level );
2715 return;
2716 }
2717 dumpHex( inFile, item->length, level, FALSE );
2718 if( item->nonCanonical )
2719 complainLengthCanonical( item, level );
2720 break;
2721
2722 case OID:
2723 {
2724 char textOID[ 128 ];
2725 int length, isValid;
2726
2727 /* Hierarchical Object Identifier */
2728 if( item->length <= 0 || item->length >= MAX_OID_SIZE )
2729 {
2730 fflush( stdout );
2731 fprintf( stderr, "\nError: Object identifier length %ld too "
2732 "large.\n", item->length );
2733 exit( EXIT_FAILURE );
2734 }
2735 length = fread( buffer, 1, ( size_t ) item->length, inFile );
2736 fPos += item->length;
2737 if( item->length < 3 )
2738 {
2739 fputs( ".\n", output );
2740 complainLength( item, level );
2741 break;
2742 }
2743 if( length < item->length )
2744 {
2745 fputs( ".\n", output );
2746 complain( "Invalid OID data", 0, level );
2747 break;
2748 }
2749 if( ( oidInfo = getOIDinfo( buffer, ( int ) item->length ) ) != NULL )
2750 {
2751 /* Convert the binary OID to text form */
2752 isValid = oidToString( textOID, &length, buffer,
2753 ( int ) item->length );
2754
2755 /* Check if LHS status info + indent + "OID " string + oid
2756 name + "(" + oid value + ")" will wrap */
2757 if( ( ( doPure ) ? 0 : INDENT_SIZE ) + ( level * 2 ) + 18 + \
2758 strlen( oidInfo->description ) + 2 + length >= outputWidth )
2759 {
2760 printString( level, "%c", '\n' );
2761 if( !doPure )
2762 printString( level, "%s", INDENT_STRING );
2763 doIndent( level + 1 );
2764 }
2765 else
2766 printString( level, "%c", ' ' );
2767 printString( level, "%s (%s)\n", oidInfo->description, textOID );
2768
2769 /* Display extra comments about the OID if required */
2770 if( extraOIDinfo && oidInfo->comment != NULL )
2771 {
2772 if( !doPure )
2773 printString( level, "%s", INDENT_STRING );
2774 doIndent( level + 1 );
2775 printString( level, "(%s)\n", oidInfo->comment );
2776 }
2777 if( !isValid )
2778 complain( "OID has invalid encoding", 0, level );
2779 if( item->nonCanonical )
2780 complainLengthCanonical( item, level );
2781
2782 /* If there's a warning associated with this OID, remember
2783 that there was a problem */
2784 if( oidInfo->warn )
2785 noWarnings++;
2786
2787 break;
2788 }
2789
2790 /* Print the OID as a text string */
2791 isValid = oidToString( textOID, &length, buffer,
2792 ( int ) item->length );
2793 printString( level, " '%s'\n", textOID );
2794 if( item->length > MAX_SANE_OID_SIZE )
2795 {
2796 /* This only occurs with Microsoft's "encode random noise
2797 and call it an OID" values, so we warn about the fact
2798 that it's not really an OID */
2799 complain( "OID contains random garbage", 0, level );
2800 }
2801 if( !isValid )
2802 complain( "OID has invalid encoding", 0, level );
2803 if( item->nonCanonical )
2804 complainLengthCanonical( item, level );
2805 break;
2806 }
2807
2808 case EOC:
2809 case NULLTAG:
2810 printString( level, "%c", '\n' );
2811 if( item->nonCanonical )
2812 complainLengthCanonical( item, level );
2813 break;
2814
2815 case OBJDESCRIPTOR:
2816 case GRAPHICSTRING:
2817 case VISIBLESTRING:
2818 case GENERALSTRING:
2819 case UNIVERSALSTRING:
2820 case NUMERICSTRING:
2821 case VIDEOTEXSTRING:
2822 case PRINTABLESTRING:
2823 displayString( inFile, item->length, level, STR_PRINTABLE );
2824 if( item->nonCanonical )
2825 complainLengthCanonical( item, level );
2826 break;
2827 case UTF8STRING:
2828 displayString( inFile, item->length, level, STR_UTF8 );
2829 if( item->nonCanonical )
2830 complainLengthCanonical( item, level );
2831 break;
2832 case BMPSTRING:
2833 displayString( inFile, item->length, level, STR_BMP );
2834 if( item->nonCanonical )
2835 complainLengthCanonical( item, level );
2836 break;
2837 case UTCTIME:
2838 displayString( inFile, item->length, level, STR_UTCTIME );
2839 if( item->nonCanonical )
2840 complainLengthCanonical( item, level );
2841 break;
2842 case GENERALIZEDTIME:
2843 displayString( inFile, item->length, level, STR_GENERALIZED );
2844 if( item->nonCanonical )
2845 complainLengthCanonical( item, level );
2846 break;
2847 case IA5STRING:
2848 displayString( inFile, item->length, level, STR_IA5 );
2849 if( item->nonCanonical )
2850 complainLengthCanonical( item, level );
2851 break;
2852 case T61STRING:
2853 displayString( inFile, item->length, level, STR_LATIN1 );
2854 if( item->nonCanonical )
2855 complainLengthCanonical( item, level );
2856 break;
2857
2858 case SEQUENCE:
2859 printString( level, "%c", '\n' );
2860 complain( "SEQUENCE has invalid primitive encoding", 0, level );
2861 break;
2862
2863 case SET:
2864 printString( level, "%c", '\n' );
2865 complain( "SET has invalid primitive encoding", 0, level );
2866 break;
2867
2868 default:
2869 printString( level, "%c", '\n' );
2870 if( !doPure )
2871 printString( level, "%s", INDENT_STRING );
2872 doIndent( level + 1 );
2873 printString( level, "%s",
2874 "Unrecognised primitive, hex value is:");
2875 dumpHex( inFile, item->length, level, FALSE );
2876 if( item->nonCanonical )
2877 complainLengthCanonical( item, level );
2878 noErrors++; /* Treat it as an error */
2879 }
2880 }
2881
2882 /* Print a complex ASN.1 object */
2883
printAsn1(FILE * inFile,const int level,long length,const int isIndefinite)2884 static int printAsn1( FILE *inFile, const int level, long length,
2885 const int isIndefinite )
2886 {
2887 ASN1_ITEM item;
2888 long lastPos = fPos;
2889 int seenEOC = FALSE, status;
2890
2891 /* Special-case for zero-length objects */
2892 if( !length && !isIndefinite )
2893 return( 0 );
2894
2895 while( ( status = getItem( inFile, &item ) ) > 0 )
2896 {
2897 int nonOutlineObject = FALSE;
2898
2899 /* Perform various special checks the first time we're called */
2900 if( length == LENGTH_MAGIC )
2901 {
2902 /* If the length isn't known and the item has a definite length,
2903 set the length to the item's length */
2904 if( !item.indefinite )
2905 {
2906 length = item.headerSize + item.length;
2907
2908 /* We can also adjust the width of the informational data
2909 column to maximise the amount of screen real estate (for
2910 lengths less than the default of four) or get rid of
2911 oversized columns (for lengths greater than four) */
2912 if( length < 1000 )
2913 infoWidth = 3;
2914 else
2915 if( length > 9999999 )
2916 infoWidth = 8;
2917 else
2918 if( length > 999999 )
2919 infoWidth = 7;
2920 else
2921 if( length > 99999 )
2922 infoWidth = 6;
2923 else
2924 if( length > 9999 )
2925 infoWidth = 5;
2926 }
2927
2928 /* If the input isn't seekable, turn off some options that
2929 require the use of fseek(). This check isn't perfect (some
2930 streams are slightly seekable due to buffering) but it's
2931 better than nothing */
2932 if( fseek( inFile, -item.headerSize, SEEK_CUR ) )
2933 {
2934 useStdin = TRUE;
2935 checkEncaps = FALSE;
2936 puts( "Warning: Input is non-seekable, some functionality "
2937 "has been disabled." );
2938 }
2939 else
2940 fseek( inFile, item.headerSize, SEEK_CUR );
2941 }
2942
2943 /* Dump the header as hex data if requested */
2944 if( doDumpHeader )
2945 dumpHeader( inFile, &item, level );
2946
2947 /* If we're displaying the ASN.1 outline only and it's not a
2948 constructed object, don't display anything */
2949 if( doOutlineOnly && ( item.id & FORM_MASK ) != CONSTRUCTED )
2950 nonOutlineObject = TRUE;
2951
2952 /* Print the offset and length, unless we're in pure ASN.1-only
2953 output mode or we're displaying the outline only and it's not
2954 a constructed object */
2955 if( item.header[ 0 ] == EOC )
2956 {
2957 seenEOC = TRUE;
2958 if( !isIndefinite)
2959 complain( "Spurious EOC in definite-length item", 0, level );
2960 }
2961 if( !doPure && !nonOutlineObject )
2962 {
2963 if( item.indefinite )
2964 printString( level, ( doHexValues ) ? \
2965 LEN_HEX_INDEF : LEN_INDEF, lastPos );
2966 else
2967 {
2968 if( !seenEOC )
2969 printString( level, ( doHexValues ) ? \
2970 LEN_HEX : LEN, lastPos, item.length );
2971 }
2972 }
2973
2974 /* Print details on the item */
2975 if( !seenEOC )
2976 {
2977 if( !nonOutlineObject )
2978 doIndent( level );
2979 printASN1object( inFile, &item, level );
2980 }
2981
2982 /* If it was an indefinite-length object (no length was ever set) and
2983 we've come back to the top level, exit */
2984 if( length == LENGTH_MAGIC )
2985 return( 0 );
2986
2987 length -= fPos - lastPos;
2988 lastPos = fPos;
2989 if( isIndefinite )
2990 {
2991 if( seenEOC )
2992 return( 0 );
2993 }
2994 else
2995 {
2996 if( length <= 0 )
2997 {
2998 if( length < 0 )
2999 return( ( int ) -length );
3000 return( 0 );
3001 }
3002 else
3003 {
3004 if( length == 1 )
3005 {
3006 const int ch = fgetc( inFile );
3007
3008 if( ch == EOF )
3009 return( 0 );
3010
3011 /* No object can be one byte long, try and recover. This
3012 only works sometimes because it can be caused by
3013 spurious data in an OCTET STRING hole or an incorrect
3014 length encoding. The following workaround tries to
3015 recover from spurious data by skipping the byte if
3016 it's zero or a non-basic-ASN.1 tag, but keeping it if
3017 it could be valid ASN.1 */
3018 if( ch && ch <= 0x31 )
3019 ungetc( ch, inFile );
3020 else
3021 {
3022 fPos++;
3023 return( 1 );
3024 }
3025 }
3026 }
3027 }
3028 }
3029 if( status == -1 )
3030 {
3031 int i;
3032
3033 fflush( stdout );
3034 fprintf( stderr, "\nError: Invalid data encountered at position "
3035 "%d:", fPos );
3036 for( i = 0; i < item.headerSize; i++ )
3037 fprintf( stderr, " %02X", item.header[ i ] );
3038 fprintf( stderr, ".\n" );
3039 exit( EXIT_FAILURE );
3040 }
3041
3042 /* If we see an EOF and there's supposed to be more data present,
3043 complain */
3044 if( length && length != LENGTH_MAGIC )
3045 {
3046 fprintf( output, "Error: Inconsistent object length, %ld byte%s "
3047 "difference.\n", length, ( length > 1 ) ? "s" : "" );
3048 noErrors++;
3049 }
3050 return( 0 );
3051 }
3052
3053 /* Show usage and exit */
3054
usageExit(void)3055 static void usageExit( void )
3056 {
3057 puts( "DumpASN1 - ASN.1 object dump/syntax check program." );
3058 puts( "Copyright Peter Gutmann 1997 - 2012. Last updated " UPDATE_STRING "." );
3059 puts( "" );
3060
3061 puts( "Usage: dumpasn1 [-acdefghilmoprstuvwxz] <file>" );
3062 puts( " Input options:" );
3063 puts( " - = Take input from stdin (some options may not work properly)" );
3064 puts( " -<number> = Start <number> bytes into the file" );
3065 puts( " -- = End of arg list" );
3066 puts( " -c<file> = Read Object Identifier info from alternate config file" );
3067 puts( " (values will override equivalents in global config file)" );
3068 puts( "" );
3069
3070 puts( " Output options:" );
3071 puts( " -f<file> = Dump object at offset -<number> to file (allows data to be" );
3072 puts( " extracted from encapsulating objects)" );
3073 puts( " -w<number> = Set width of output, default = 80 columns" );
3074 puts( "" );
3075
3076 puts( " Display options:" );
3077 puts( " -a = Print all data in long data blocks, not just the first 128 bytes" );
3078 puts( " -d = Print dots to show column alignment" );
3079 puts( " -g = Display ASN.1 structure outline only (no primitive objects)" );
3080 puts( " -h = Hex dump object header (tag+length) before the decoded output" );
3081 puts( " -hh = Same as -h but display more of the object as hex data" );
3082 puts( " -i = Use shallow indenting, for deeply-nested objects" );
3083 puts( " -l = Long format, display extra info about Object Identifiers" );
3084 puts( " -m<number> = Maximum nesting level for which to display content" );
3085 puts( " -p = Pure ASN.1 output without encoding information" );
3086 puts( " -t = Display text values next to hex dump of data" );
3087 puts( " -v = Verbose mode, equivalent to -ahlt" );
3088 puts( "" );
3089
3090 puts( " Format options:" );
3091 puts( " -e = Don't print encapsulated data inside OCTET/BIT STRINGs" );
3092 puts( " -r = Print bits in BIT STRING as encoded in reverse order" );
3093 puts( " -u = Don't format UTCTime/GeneralizedTime string data" );
3094 puts( " -x = Display size and offset in hex not decimal" );
3095 puts( "" );
3096
3097 puts( " Checking options:" );
3098 puts( " -o = Don't check validity of character strings hidden in octet strings" );
3099 puts( " -s = Syntax check only, don't dump ASN.1 structures" );
3100 puts( " -z = Allow zero-length items" );
3101 puts( "" );
3102
3103 puts( "Warnings generated by deprecated OIDs require the use of '-l' to be displayed." );
3104 puts( "Program return code is the number of errors found or EXIT_SUCCESS." );
3105 exit( EXIT_FAILURE );
3106 }
3107
main(int argc,char * argv[])3108 int main( int argc, char *argv[] )
3109 {
3110 FILE *inFile, *outFile = NULL;
3111 #ifdef __WIN32__
3112 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
3113 #endif /* __WIN32__ */
3114 #ifdef __OS390__
3115 char pathPtr[ FILENAME_MAX ];
3116 #else
3117 char *pathPtr = argv[ 0 ];
3118 #endif /* __OS390__ */
3119 long offset = 0;
3120 int moreArgs = TRUE, doCheckOnly = FALSE;
3121
3122 #ifdef __OS390__
3123 memset( pathPtr, '\0', sizeof( pathPtr ) );
3124 getcwd( pathPtr, sizeof( pathPtr ) );
3125 strcat( pathPtr, "/" );
3126 #endif /* __OS390__ */
3127
3128 /* Skip the program name */
3129 argv++; argc--;
3130
3131 /* Display usage if no args given */
3132 if( argc < 1 )
3133 usageExit();
3134 output = stdout; /* Needs to be assigned at runtime */
3135
3136 /* Get the output width. Under Unix there's no safe way to do this, so
3137 we default to 80 columns */
3138 #ifdef __WIN32__
3139 if( GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ),
3140 &csbiInfo ) )
3141 outputWidth = csbiInfo.dwSize.X;
3142 #endif /* __WIN32__ */
3143
3144 /* Check for arguments */
3145 while( argc && *argv[ 0 ] == '-' && moreArgs )
3146 {
3147 char *argPtr = argv[ 0 ] + 1;
3148
3149 if( !*argPtr )
3150 useStdin = TRUE;
3151 while( *argPtr )
3152 {
3153 if( isdigit( byteToInt( *argPtr ) ) )
3154 {
3155 offset = atol( argPtr );
3156 break;
3157 }
3158 switch( toupper( byteToInt( *argPtr ) ) )
3159 {
3160 case '-':
3161 moreArgs = FALSE; /* GNU-style end-of-args flag */
3162 break;
3163
3164 case 'A':
3165 printAllData = TRUE;
3166 break;
3167
3168 case 'C':
3169 if( !readConfig( argPtr + 1, FALSE ) )
3170 exit( EXIT_FAILURE );
3171 while( argPtr[ 1 ] )
3172 argPtr++; /* Skip rest of arg */
3173 break;
3174
3175 case 'D':
3176 printDots = TRUE;
3177 break;
3178
3179 case 'E':
3180 checkEncaps = FALSE;
3181 break;
3182
3183 case 'F':
3184 if( ( outFile = fopen( argPtr + 1, "wb" ) ) == NULL )
3185 {
3186 perror( argPtr + 1 );
3187 exit( EXIT_FAILURE );
3188 }
3189 while( argPtr[ 1 ] )
3190 argPtr++; /* Skip rest of arg */
3191 break;
3192
3193 case 'G':
3194 doOutlineOnly = TRUE;
3195 break;
3196
3197 case 'H':
3198 doDumpHeader++;
3199 break;
3200
3201 case 'I':
3202 shallowIndent = TRUE;
3203 break;
3204
3205 case 'L':
3206 extraOIDinfo = TRUE;
3207 break;
3208
3209 case 'M':
3210 maxNestLevel = atoi( argPtr + 1 );
3211 if( maxNestLevel < 1 || maxNestLevel > 100 )
3212 {
3213 puts( "Invalid maximum nesting level." );
3214 exit( EXIT_FAILURE );
3215 }
3216 while( argPtr[ 1 ] )
3217 argPtr++; /* Skip rest of arg */
3218 break;
3219
3220 case 'O':
3221 checkCharset = TRUE;
3222 break;
3223
3224 case 'P':
3225 doPure = TRUE;
3226 break;
3227
3228 case 'R':
3229 reverseBitString = !reverseBitString;
3230 break;
3231
3232 case 'S':
3233 doCheckOnly = TRUE;
3234 #if defined( __WIN32__ )
3235 /* Under Windows we can't fclose( stdout ) because the
3236 VC++ runtime reassigns the stdout handle to the next
3237 open file (which is valid) but then scribbles stdout
3238 garbage all over it for files larger than about 16K
3239 (which isn't), so we have to make sure that the
3240 stdout handle is pointed to something somewhere */
3241 ( void ) freopen( "nul", "w", stdout );
3242 #elif defined( __UNIX__ )
3243 /* Safety feature in case any Unix libc is as broken
3244 as the Win32 version */
3245 ( void ) freopen( "/dev/null", "w", stdout );
3246 #else
3247 fclose( stdout );
3248 #endif /* OS-specific bypassing of stdout */
3249 break;
3250
3251 case 'T':
3252 dumpText = TRUE;
3253 break;
3254
3255 case 'U':
3256 rawTimeString = TRUE;
3257 break;
3258
3259 case 'V':
3260 printAllData = doDumpHeader = TRUE;
3261 extraOIDinfo = dumpText = TRUE;
3262 break;
3263
3264 case 'W':
3265 outputWidth = atoi( argPtr + 1 );
3266 if( outputWidth < 40 || outputWidth > 500 )
3267 {
3268 puts( "Invalid output width." );
3269 exit( EXIT_FAILURE );
3270 }
3271 while( argPtr[ 1 ] )
3272 argPtr++; /* Skip rest of arg */
3273 break;
3274
3275 case 'X':
3276 doHexValues = TRUE;
3277 break;
3278
3279 case 'Z':
3280 zeroLengthAllowed = TRUE;
3281 break;
3282
3283 default:
3284 printf( "Unknown argument '%c'.\n", *argPtr );
3285 return( EXIT_SUCCESS );
3286 }
3287 argPtr++;
3288 }
3289 argv++;
3290 argc--;
3291 }
3292
3293 /* We can't use options that perform an fseek() if reading from stdin */
3294 if( useStdin && ( doDumpHeader || outFile != NULL ) )
3295 {
3296 puts( "Can't use -f or -h when taking input from stdin" );
3297 exit( EXIT_FAILURE );
3298 }
3299
3300 /* Check args and read the config file. We don't bother weeding out
3301 dups during the read because (a) the linear search would make the
3302 process n^2, (b) during the dump process the search will terminate on
3303 the first match so dups aren't that serious, and (c) there should be
3304 very few dups present */
3305 if( argc != 1 && !useStdin )
3306 usageExit();
3307 if( !readGlobalConfig( pathPtr ) )
3308 exit( EXIT_FAILURE );
3309
3310 /* Dump the given file */
3311 if( useStdin )
3312 inFile = stdin;
3313 else
3314 {
3315 if( ( inFile = fopen( argv[ 0 ], "rb" ) ) == NULL )
3316 {
3317 perror( argv[ 0 ] );
3318 freeConfig();
3319 exit( EXIT_FAILURE );
3320 }
3321 }
3322 if( useStdin )
3323 {
3324 while( offset-- )
3325 getc( inFile );
3326 }
3327 else
3328 fseek( inFile, offset, SEEK_SET );
3329 if( outFile != NULL )
3330 {
3331 ASN1_ITEM item;
3332 long length;
3333 int i, status;
3334
3335 /* Make sure that there's something there, and that it has a
3336 definite length */
3337 status = getItem( inFile, &item );
3338 if( status == -1 )
3339 {
3340 puts( "Non-ASN.1 data encountered." );
3341 freeConfig();
3342 exit( EXIT_FAILURE );
3343 }
3344 if( status == 0 )
3345 {
3346 puts( "Nothing to read." );
3347 freeConfig();
3348 exit( EXIT_FAILURE );
3349 }
3350 if( item.indefinite )
3351 {
3352 puts( "Cannot process indefinite-length item." );
3353 freeConfig();
3354 exit( EXIT_FAILURE );
3355 }
3356
3357 /* Copy the item across, first the header and then the data */
3358 for( i = 0; i < item.headerSize; i++ )
3359 putc( item.header[ i ], outFile );
3360 for( length = 0; length < item.length && !feof( inFile ); length++ )
3361 putc( getc( inFile ), outFile );
3362 fclose( outFile );
3363
3364 fseek( inFile, offset, SEEK_SET );
3365 }
3366 printAsn1( inFile, 0, LENGTH_MAGIC, 0 );
3367 if( !useStdin && offset == 0 )
3368 {
3369 BYTE buffer[ 16 ];
3370 long position = ftell( inFile );
3371
3372 /* If we're dumping a standalone ASN.1 object and there's further
3373 data appended to it, warn the user of its existence. This is a
3374 bit hit-and-miss since there may or may not be additional EOCs
3375 present, dumpasn1 always stops once it knows that the data should
3376 end (without trying to read any trailing EOCs) because data from
3377 some sources has the EOCs truncated, and most apps know that they
3378 have to stop at min( data_end, EOCs ). To avoid false positives,
3379 we skip at least 4 EOCs worth of data and if there's still more
3380 present, we complain */
3381 ( void ) fread( buffer, 1, 8, inFile ); /* Skip 4 EOCs */
3382 if( !feof( inFile ) )
3383 {
3384 fprintf( output, "Warning: Further data follows ASN.1 data at "
3385 "position %ld.\n", position );
3386 noWarnings++;
3387 }
3388 }
3389 fclose( inFile );
3390 freeConfig();
3391
3392 /* Print a summary of warnings/errors if it's required or appropriate */
3393 if( !doPure )
3394 {
3395 fflush( stdout );
3396 if( !doCheckOnly )
3397 fputc( '\n', stderr );
3398 fprintf( stderr, "%d warning%s, %d error%s.\n", noWarnings,
3399 ( noWarnings != 1 ) ? "s" : "", noErrors,
3400 ( noErrors != 1 ) ? "s" : "" );
3401 }
3402
3403 return( ( noErrors ) ? noErrors : EXIT_SUCCESS );
3404 }
3405