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