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