source: trunk/third/rpm/rpmio/dumpasn1.c @ 17931

Revision 17931, 53.2 KB checked in by ghudson, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r17930, which included commits to RCS files with non-trunk default branches.
Line 
1/* ASN.1 object dumping code, copyright Peter Gutmann
2   <pgut001@cs.auckland.ac.nz>, based on ASN.1 dump program by David Kemp
3   <dpkemp@missi.ncsc.mil>, with contributions from various people including
4   Matthew Hamrick <hamrick@rsa.com>, Bruno Couillard
5   <bcouillard@chrysalis-its.com>, Hallvard Furuseth
6   <h.b.furuseth@usit.uio.no>, Geoff Thorpe <geoff@raas.co.nz>, David Boyce
7   <d.boyce@isode.com>, John Hughes <john.hughes@entegrity.com>, Life is
8   hard, and then you die <ronald@trustpoint.com>, and several other people
9   whose names I've misplaced.
10
11   Available from http://www.cs.auckland.ac.nz/~pgut001/dumpasn1.c.
12   Last updated 21 November 2000.
13
14   This version of dumpasn1 requires a config file dumpasn1.cfg to be present
15   in the same location as the program itself or in a standard directory
16   where binaries live (it will run without it but will display a warning
17   message, you can configure the path either by hardcoding it in or using an
18   environment variable as explained further down).  The config file is
19   available from http://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg.
20
21   This code assumes that the input data is binary, having come from a MIME-
22   aware mailer or been piped through a decoding utility if the original
23   format used base64 encoding.  Bruno Couillard has created a modified
24   version which will read raw base64-encoded data (ie without any MIME
25   encapsulation or other headers) directly, at the expense of being somewhat
26   non-portable.  Alternatively, you can use utilities like uudeview (which
27   will strip virtually any kind of encoding, MIME, PEM, PGP, whatever) to
28   recover the binary original.
29
30   You can use this code in whatever way you want, as long as you don't try
31   to claim you wrote it.
32
33   Editing notes: Tabs to 4, phasers to stun */
34
35#include <ctype.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39
40/* Useful defines */
41
42#ifndef TRUE
43  #define FALSE 0
44  #define TRUE  ( !FALSE )
45#endif /* TRUE */
46
47/* SunOS 4.x doesn't define seek codes or exit codes or FILENAME_MAX (it does
48   define _POSIX_MAX_PATH, but in funny locations and to different values
49   depending on which include file you use).  Some OS's also define
50   FILENAME_MAX to silly values (eg 14 bytes), so we replace it with a more
51   sensible setting if necessary */
52
53#ifndef SEEK_SET
54  #define SEEK_SET      0
55  #define SEEK_CUR      2
56#endif /* No fseek() codes defined */
57#ifndef EXIT_FAILURE
58  #define EXIT_FAILURE  1
59  #define EXIT_SUCCESS  ( !EXIT_FAILURE )
60#endif /* No exit() codes defined */
61#ifndef FILENAME_MAX
62  #define FILENAME_MAX  512
63#else
64  #if FILENAME_MAX < 128
65        #undef FILENAME_MAX
66        #define FILENAME_MAX    512
67  #endif /* FILENAME_MAX < 128 */
68#endif /* FILENAME_MAX */
69
70/* Under Windows we can do special-case handling for things like BMPStrings */
71
72#if ( defined( _WINDOWS ) || defined( WIN32 ) || defined( _WIN32 ) || \
73          defined( __WIN32__ ) )
74  #define __WIN32__
75#endif /* Win32 */
76
77/* Some OS's don't define the min() macro */
78
79#ifndef min
80  #define min(a,b)              ( ( a ) < ( b ) ? ( a ) : ( b ) )
81#endif /* !min */
82
83/* The level of recursion can get scary for deeply-nested structures so we
84   use a larger-than-normal stack under DOS */
85
86#ifdef  __TURBOC__
87  extern unsigned _stklen = 16384;
88#endif /* __TURBOC__ */
89
90/* When we dump a nested data object encapsulated within a larger object, the
91   length is initially set to a magic value which is adjusted to the actual
92   length once we start parsing the object */
93
94#define LENGTH_MAGIC    177545L
95
96/* Tag classes */
97
98#define CLASS_MASK              0xC0    /* Bits 8 and 7 */
99#define UNIVERSAL               0x00    /* 0 = Universal (defined by ITU X.680) */
100#define APPLICATION             0x40    /* 1 = Application */
101#define CONTEXT                 0x80    /* 2 = Context-specific */
102#define PRIVATE                 0xC0    /* 3 = Private */
103
104/* Encoding type */
105
106#define FORM_MASK               0x20    /* Bit 6 */
107#define PRIMITIVE               0x00    /* 0 = primitive */
108#define CONSTRUCTED             0x20    /* 1 = constructed */
109
110/* Universal tags */
111
112#define TAG_MASK                0x1F    /* Bits 5 - 1 */
113#define EOC                             0x00    /*  0: End-of-contents octets */
114#define BOOLEAN                 0x01    /*  1: Boolean */
115#define INTEGER                 0x02    /*  2: Integer */
116#define BITSTRING               0x03    /*  2: Bit string */
117#define OCTETSTRING             0x04    /*  4: Byte string */
118#define NULLTAG                 0x05    /*  5: NULL */
119#define OID                             0x06    /*  6: Object Identifier */
120#define OBJDESCRIPTOR   0x07    /*  7: Object Descriptor */
121#define EXTERNAL                0x08    /*  8: External */
122#define REAL                    0x09    /*  9: Real */
123#define ENUMERATED              0x0A    /* 10: Enumerated */
124#define EMBEDDED_PDV    0x0B    /* 11: Embedded Presentation Data Value */
125#define UTF8STRING              0x0C    /* 12: UTF8 string */
126#define SEQUENCE                0x10    /* 16: Sequence/sequence of */
127#define SET                             0x11    /* 17: Set/set of */
128#define NUMERICSTRING   0x12    /* 18: Numeric string */
129#define PRINTABLESTRING 0x13    /* 19: Printable string (ASCII subset) */
130#define T61STRING               0x14    /* 20: T61/Teletex string */
131#define VIDEOTEXSTRING  0x15    /* 21: Videotex string */
132#define IA5STRING               0x16    /* 22: IA5/ASCII string */
133#define UTCTIME                 0x17    /* 23: UTC time */
134#define GENERALIZEDTIME 0x18    /* 24: Generalized time */
135#define GRAPHICSTRING   0x19    /* 25: Graphic string */
136#define VISIBLESTRING   0x1A    /* 26: Visible string (ASCII subset) */
137#define GENERALSTRING   0x1B    /* 27: General string */
138#define UNIVERSALSTRING 0x1C    /* 28: Universal string */
139#define BMPSTRING               0x1E    /* 30: Basic Multilingual Plane/Unicode string */
140
141/* Length encoding */
142
143#define LEN_XTND  0x80          /* Indefinite or long form */
144#define LEN_MASK  0x7F          /* Bits 7 - 1 */
145
146/* Various special-case operations to perform on strings */
147
148typedef enum {
149        STR_NONE,                               /* No special handling */
150        STR_UTCTIME,                    /* Check it's UTCTime */
151        STR_PRINTABLE,                  /* Check it's a PrintableString */
152        STR_IA5,                                /* Check it's an IA5String */
153        STR_BMP                                 /* Read and display string as Unicode */
154        } STR_OPTION;
155
156/* Structure to hold info on an ASN.1 item */
157
158typedef struct {
159        int id;                                         /* Identifier */
160        int tag;                                        /* Tag */
161        long length;                            /* Data length */
162        int indefinite;                         /* Item has indefinite length */
163        int headerSize;                         /* Size of tag+length */
164        unsigned char header[ 8 ];      /* Tag+length data */
165        } ASN1_ITEM;
166
167/* Config options */
168
169static int printDots = FALSE;           /* Whether to print dots to align columns */
170static int doPure = FALSE;                      /* Print data without LHS info column */
171static int doDumpHeader = FALSE;        /* Dump tag+len in hex (level = 0, 1, 2) */
172static int extraOIDinfo = FALSE;        /* Print extra information about OIDs */
173static int doHexValues = FALSE;         /* Display size, offset in hex not dec.*/
174static int useStdin = FALSE;            /* Take input from stdin */
175static int zeroLengthAllowed = FALSE;/* Zero-length items allowed */
176static int dumpText = FALSE;            /* Dump text alongside hex data */
177static int printAllData = FALSE;        /* Whether to print all data in long blocks */
178static int checkEncaps = TRUE;          /* Print encaps.data in BIT/OCTET STRINGs */
179
180/* Error and warning information */
181
182static int noErrors = 0;                        /* Number of errors found */
183static int noWarnings = 0;                      /* Number of warnings */
184
185/* Position in the input stream */
186
187static int fPos = 0;                            /* Absolute position in data */
188
189/* The output stream */
190
191static FILE *output;                            /* Output stream */
192
193/* Information on an ASN.1 Object Identifier */
194
195#define MAX_OID_SIZE    32
196
197typedef struct tagOIDINFO {
198        struct tagOIDINFO *next;                /* Next item in list */
199        char oid[ MAX_OID_SIZE ], *comment, *description;
200        int oidLength;                                  /* Name, rank, serial number */
201        int warn;                                               /* Whether to warn if OID encountered */
202        } OIDINFO;
203
204static OIDINFO *oidList = NULL;
205
206/* If the config file isn't present in the current directory, we search the
207   following paths (this is needed for Unix with dumpasn1 somewhere in the
208   path, since this doesn't set up argv[0] to the full path).  Anything
209   beginning with a '$' uses the appropriate environment variable */
210
211#define CONFIG_NAME             "dumpasn1.cfg"
212
213static const char *configPaths[] = {
214        /* Unix absolute paths */
215        "/bin/", "/usr/bin/", "/usr/local/bin/",
216
217        /* Windoze absolute paths.  Usually things are on C:, but older NT setups
218           are easier to do on D: if the initial copy is done to C: */
219        "c:\\dos\\", "d:\\dos\\", "c:\\windows\\", "d:\\windows\\",
220        "c:\\winnt\\", "d:\\winnt\\",
221
222        /* It's my program, I'm allowed to hardcode in strange paths which noone
223           else uses */
224        "$HOME/BIN/", "c:\\program files\\bin\\",
225
226        /* Unix environment-based paths */
227        "$HOME/", "$HOME/bin/",
228
229        /* General environment-based paths */
230        "$DUMPASN1_PATH/",
231
232        NULL
233        };
234
235#define isEnvTerminator( c )    \
236        ( ( ( c ) == '/' ) || ( ( c ) == '.' ) || ( ( c ) == '$' ) || \
237          ( ( c ) == '\0' ) || ( ( c ) == '~' ) )
238
239/****************************************************************************
240*                                                                                                                                                       *
241*                                       Object Identification/Description Routines                              *
242*                                                                                                                                                       *
243****************************************************************************/
244
245/* Return descriptive strings for universal tags */
246
247char *idstr( const int tagID )
248        {
249        switch( tagID )
250                {
251                case EOC:
252                        return( "End-of-contents octets" );
253                case BOOLEAN:
254                        return( "BOOLEAN" );
255                case INTEGER:
256                        return( "INTEGER" );
257                case BITSTRING:
258                        return( "BIT STRING" );
259                case OCTETSTRING:
260                        return( "OCTET STRING" );
261                case NULLTAG:
262                        return( "NULL" );
263                case OID:
264                        return( "OBJECT IDENTIFIER" );
265                case OBJDESCRIPTOR:
266                        return( "ObjectDescriptor" );
267                case EXTERNAL:
268                        return( "EXTERNAL" );
269                case REAL:
270                        return( "REAL" );
271                case ENUMERATED:
272                        return( "ENUMERATED" );
273                case EMBEDDED_PDV:
274                        return( "EMBEDDED PDV" );
275                case UTF8STRING:
276                        return( "UTF8String" );
277                case SEQUENCE:
278                        return( "SEQUENCE" );
279                case SET:
280                        return( "SET" );
281                case NUMERICSTRING:
282                        return( "NumericString" );
283                case PRINTABLESTRING:
284                        return( "PrintableString" );
285                case T61STRING:
286                        return( "TeletexString" );
287                case VIDEOTEXSTRING:
288                        return( "VideotexString" );
289                case IA5STRING:
290                        return( "IA5String" );
291                case UTCTIME:
292                        return( "UTCTime" );
293                case GENERALIZEDTIME:
294                        return( "GeneralizedTime" );
295                case GRAPHICSTRING:
296                        return( "GraphicString" );
297                case VISIBLESTRING:
298                        return( "VisibleString" );
299                case GENERALSTRING:
300                        return( "GeneralString" );
301                case UNIVERSALSTRING:
302                        return( "UniversalString" );
303                case BMPSTRING:
304                        return( "BMPString" );
305                default:
306                        return( "Unknown (Reserved)" );
307                }
308        }
309
310/* Return information on an object identifier */
311
312static OIDINFO *getOIDinfo( char *oid, const int oidLength )
313        {
314        OIDINFO *oidPtr;
315
316        memset( oid + oidLength, 0, 2 );
317        for( oidPtr = oidList; oidPtr != NULL; oidPtr = oidPtr->next )
318                if( oidLength == oidPtr->oidLength - 2 && \
319                        !memcmp( oidPtr->oid + 2, oid, oidLength ) )
320                        return( oidPtr );
321
322        return( NULL );
323        }
324
325/* Add an OID attribute */
326
327static int addAttribute( char **buffer, char *attribute )
328        {
329        if( ( *buffer = ( char * ) malloc( strlen( attribute ) + 1 ) ) == NULL )
330                {
331                puts( "Out of memory." );
332                return( FALSE );
333                }
334        strcpy( *buffer, attribute );
335        return( TRUE );
336        }
337
338/* Table to identify valid string chars (taken from cryptlib) */
339
340#define P       1                                               /* PrintableString */
341#define I       2                                               /* IA5String */
342#define PI      3                                               /* IA5String and PrintableString */
343
344static int charFlags[] = {
345        /* 00  01  02  03  04  05  06  07  08  09  0A  0B  0C  0D  0E  0F */
346                0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,
347        /* 10  11  12  13  14  15  16  17  18  19  1A  1B  1C  1D  1E  1F */
348                0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,
349        /*              !       "       #       $       %       &       '       (       )       *       +       ,       -       .       / */
350           PI,  I,      I,      I,      I,      I,      I, PI, PI, PI,  I, PI, PI, PI, PI, PI,
351        /*      0       1       2       3       4       5       6       7       8       9       :       ;       <       =       >       ? */
352           PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI,  I,      I, PI,  I, PI,
353        /*      @       A       B       C       D       E       F       G       H       I       J       K       L       M       N       O */
354                I, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI,
355        /*      P       Q       R       S       T       U       V       W       X       Y       Z       [       \       ]       ^ _ */
356           PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI,  I,      I,      I,      I,      I,
357        /*      `       a       b       c       d       e       f       g       h       i       j       k       l       m       n       o */
358                I, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI,
359        /*      p       q       r       s       t       u       v       w       x       y       z       {       |       }       ~  DL */
360           PI, PI, PI, PI, PI, PI, PI, PI, PI, PI, PI,  I,      I,      I,      I,      0
361        };
362
363static int isPrintable( int ch )
364        {
365        if( ch >= 128 || !( charFlags[ ch ] & P ) )
366                return( FALSE );
367        return( TRUE );
368        }
369
370static int isIA5( int ch )
371        {
372        if( ch >= 128 || !( charFlags[ ch ] & I ) )
373                return( FALSE );
374        return( TRUE );
375        }
376
377/****************************************************************************
378*                                                                                                                                                       *
379*                                                       Config File Read Routines                                               *
380*                                                                                                                                                       *
381****************************************************************************/
382
383/* Files coming from DOS/Windows systems may have a ^Z (the CP/M EOF char)
384   at the end, so we need to filter this out */
385
386#define CPM_EOF 0x1A            /* ^Z = CPM EOF char */
387
388/* The maximum input line length */
389
390#define MAX_LINESIZE    512
391
392/* Read a line of text from the config file */
393
394static int lineNo;
395
396static int readLine( FILE *file, char *buffer )
397        {
398        int bufCount = 0, ch;
399
400        /* Skip whitespace */
401        while( ( ( ch = getc( file ) ) == ' ' || ch == '\t' ) && !feof( file ) );
402
403        /* Get a line into the buffer */
404        while( ch != '\r' && ch != '\n' && ch != CPM_EOF && !feof( file ) )
405                {
406                /* Check for an illegal char in the data.  Note that we don't just
407                   check for chars with high bits set because these are legal in
408                   non-ASCII strings */
409                if( ( ch & 0x7F ) < ' ' )
410                        {
411                        printf( "Bad character '%c' in config file line %d.\n",
412                                        ch, lineNo );
413                        return( FALSE );
414                        }
415
416                /* Check to see if it's a comment line */
417                if( ch == '#' && !bufCount )
418                        {
419                        /* Skip comment section and trailing whitespace */
420                        while( ch != '\r' && ch != '\n' && ch != CPM_EOF && !feof( file ) )
421                                ch = getc( file );
422                        break;
423                        }
424
425                /* Make sure the line is of the correct length */
426                if( bufCount > MAX_LINESIZE )
427                        {
428                        printf( "Config file line %d too long.\n", lineNo );
429                        return( FALSE );
430                        }
431                else
432                        if( ch )        /* Can happen if we read a binary file */
433                                buffer[ bufCount++ ] = ch;
434
435                /* Get next character */
436                ch = getc( file );
437                }
438
439        /* If we've just passed a CR, check for a following LF */
440        if( ch == '\r' )
441                if( ( ch = getc( file ) ) != '\n' )
442                        ungetc( ch, file );
443
444        /* Skip trailing whitespace and add der terminador */
445        while( bufCount > 0 &&
446                   ( ( ch = buffer[ bufCount - 1 ] ) == ' ' || ch == '\t' ) )
447                bufCount--;
448        buffer[ bufCount ] = '\0';
449
450        /* Handle special-case of ^Z if file came off an MSDOS system */
451        if( ch == CPM_EOF )
452                while( !feof( file ) )
453                        /* Keep going until we hit the true EOF (or some sort of error) */
454                        ch = getc( file );
455
456        return( ferror( file ) ? FALSE : TRUE );
457        }
458
459/* Process an OID specified as space-separated hex digits */
460
461static int processHexOID( OIDINFO *oidInfo, char *string )
462        {
463        int value, index = 0;
464
465        while( *string && index < MAX_OID_SIZE - 1 )
466                {
467                if( sscanf( string, "%x", &value ) != 1 || value > 255 )
468                        {
469                        printf( "Invalid hex value in config file line %d.\n", lineNo );
470                        return( FALSE );
471                        }
472                oidInfo->oid[ index++ ] = value;
473                string += 2;
474                if( *string && *string++ != ' ' )
475                        {
476                        printf( "Invalid hex string in config file line %d.\n", lineNo );
477                        return( FALSE );
478                        }
479                }
480        oidInfo->oid[ index ] = 0;
481        oidInfo->oidLength = index;
482        if( index >= MAX_OID_SIZE - 1 )
483                {
484                printf( "OID value in config file line %d too long.\n", lineNo );
485                return( FALSE );
486                }
487        return( TRUE );
488        }
489
490/* Read a config file */
491
492static int readConfig( const char *path, const int isDefaultConfig )
493        {
494        OIDINFO dummyOID = { NULL, "Dummy", "Dummy", "Dummy", 1 }, *oidPtr;
495        FILE *file;
496        char buffer[ MAX_LINESIZE ];
497        int status;
498
499        /* Try and open the config file */
500        if( ( file = fopen( path, "rb" ) ) == NULL )
501                {
502                /* If we can't open the default config file, issue a warning but
503                   continue anyway */
504                if( isDefaultConfig )
505                        {
506                        puts( "Cannot open config file 'dumpasn1.cfg', which should be in the same" );
507                        puts( "directory as the dumpasn1 program.  Operation will continue without" );
508                        puts( "the ability to display Object Identifier information." );
509                        puts( "" );
510                        puts( "If the config file is located elsewhere, you can set the environment" );
511                        puts( "variable DUMPASN1_CFG to the path to the file." );
512                        return( TRUE );
513                        }
514
515                printf( "Cannot open config file '%s'.\n", path );
516                return( FALSE );
517                }
518
519        /* Add the new config entries at the appropriate point in the OID list */
520        if( oidList == NULL )
521                oidPtr = &dummyOID;
522        else
523                for( oidPtr = oidList; oidPtr->next != NULL; oidPtr = oidPtr->next );
524
525        /* Read each line in the config file */
526        lineNo = 1;
527        while( ( status = readLine( file, buffer ) ) == TRUE && !feof( file ) )
528                {
529                /* If it's a comment line, skip it */
530                if( !*buffer )
531                        {
532                        lineNo++;
533                        continue;
534                        }
535
536                /* Check for an attribute tag */
537                if( !strncmp( buffer, "OID = ", 6 ) )
538                        {
539                        /* Make sure all the required attributes for the current OID are
540                           present */
541                        if( oidPtr->description == NULL )
542                                {
543                                printf( "OID ending on config file line %d has no "
544                                                "description attribute.\n", lineNo - 1 );
545                                return( FALSE );
546                                }
547
548                        /* Allocate storage for the new OID */
549                        if( ( oidPtr->next = ( struct tagOIDINFO * ) \
550                                                                 malloc( sizeof( OIDINFO ) ) ) == NULL )
551                                {
552                                puts( "Out of memory." );
553                                return( FALSE );
554                                }
555                        oidPtr = oidPtr->next;
556                        if( oidList == NULL )
557                                oidList = oidPtr;
558                        memset( oidPtr, 0, sizeof( OIDINFO ) );
559
560                        /* Add the new OID */
561                        if( !processHexOID( oidPtr, buffer + 6 ) )
562                                return( FALSE );
563                        }
564                else if( !strncmp( buffer, "Description = ", 14 ) )
565                        {
566                        if( oidPtr->description != NULL )
567                                {
568                                printf( "Duplicate OID description in config file line %d.\n",
569                                                lineNo );
570                                return( FALSE );
571                                }
572                        if( !addAttribute( &oidPtr->description, buffer + 14 ) )
573                                return( FALSE );
574                        }
575                else if( !strncmp( buffer, "Comment = ", 10 ) )
576                        {
577                        if( oidPtr->comment != NULL )
578                                {
579                                printf( "Duplicate OID comment in config file line %d.\n",
580                                                lineNo );
581                                return( FALSE );
582                                }
583                        if( !addAttribute( &oidPtr->comment, buffer + 10 ) )
584                                return( FALSE );
585                        }
586                else if( !strncmp( buffer, "Warning", 7 ) )
587                        {
588                        if( oidPtr->warn )
589                                {
590                                printf( "Duplicate OID warning in config file line %d.\n",
591                                                lineNo );
592                                return( FALSE );
593                                }
594                        oidPtr->warn = TRUE;
595                        }
596                else
597                        {
598                        printf( "Unrecognised attribute '%s', line %d.\n", buffer,
599                                        lineNo );
600                        return( FALSE );
601                        }
602
603                lineNo++;
604                }
605        fclose( file );
606
607        return( status );
608        }
609
610/* Check for the existence of a config file path */
611
612static int testConfigPath( const char *path )
613        {
614        FILE *file;
615
616        /* Try and open the config file */
617        if( ( file = fopen( path, "rb" ) ) == NULL )
618                return( FALSE );
619        fclose( file );
620
621        return( TRUE );
622        }
623
624/* Build a config path by substituting environment strings for $NAMEs */
625
626static void buildConfigPath( char *path, const char *pathTemplate )
627        {
628        char pathBuffer[ FILENAME_MAX ], newPath[ FILENAME_MAX ];
629        int pathLen, pathPos = 0, newPathPos = 0;
630
631        /* Add the config file name at the end */
632        strcpy( pathBuffer, pathTemplate );
633        strcat( pathBuffer, CONFIG_NAME );
634        pathLen = strlen( pathBuffer );
635
636        while( pathPos < pathLen )
637                {
638                char *strPtr;
639                int substringSize;
640
641                /* Find the next $ and copy the data before it to the new path */
642                if( ( strPtr = strstr( pathBuffer + pathPos, "$" ) ) != NULL )
643                        substringSize = ( int ) ( ( strPtr - pathBuffer ) - pathPos );
644                else
645                        substringSize = pathLen - pathPos;
646                if( substringSize > 0 )
647                        memcpy( newPath + newPathPos, pathBuffer + pathPos,
648                                        substringSize );
649                newPathPos += substringSize;
650                pathPos += substringSize;
651
652                /* Get the environment string for the $NAME */
653                if( strPtr != NULL )
654                        {
655                        char envName[ MAX_LINESIZE ], *envString;
656                        int i;
657
658                        /* Skip the '$', find the end of the $NAME, and copy the name
659                           into an internal buffer */
660                        pathPos++;      /* Skip the $ */
661                        for( i = 0; !isEnvTerminator( pathBuffer[ pathPos + i ] ); i++ );
662                        memcpy( envName, pathBuffer + pathPos, i );
663                        envName[ i ] = '\0';
664
665                        /* Get the env.string and copy it over */
666                        if( ( envString = getenv( envName ) ) != NULL )
667                                {
668                                const int envStrLen = strlen( envString );
669
670                                if( newPathPos + envStrLen < FILENAME_MAX - 2 )
671                                        {
672                                        memcpy( newPath + newPathPos, envString, envStrLen );
673                                        newPathPos += envStrLen;
674                                        }
675                                }
676                        pathPos += i;
677                        }
678                }
679        newPath[ newPathPos ] = '\0';   /* Add der terminador */
680
681        /* Copy the new path to the output */
682        strcpy( path, newPath );
683        }
684
685/* Read the global config file */
686
687static int readGlobalConfig( const char *path )
688        {
689        char buffer[ FILENAME_MAX ], *namePos;
690        int i;
691
692        /* First, try and find the config file in the same directory as the
693           executable.  This requires that argv[0] be set up properly, which
694           isn't the case if Unix search paths are being used, and seems to be
695           pretty broken under Windows */
696        namePos = strstr( path, "dumpasn1" );
697        if( namePos == NULL )
698                namePos = strstr( path, "DUMPASN1" );
699        if( strlen( path ) < FILENAME_MAX - 13 && namePos != NULL )
700                {
701                strcpy( buffer, path );
702                strcpy( buffer + ( int ) ( namePos - ( char * ) path ), CONFIG_NAME );
703                if( testConfigPath( buffer ) )
704                        return( readConfig( buffer, TRUE ) );
705                }
706
707        /* Now try each of the possible absolute locations for the config file */
708        for( i = 0; configPaths[ i ] != NULL; i++ )
709                {
710                buildConfigPath( buffer, configPaths[ i ] );
711                if( testConfigPath( buffer ) )
712                        return( readConfig( buffer, TRUE ) );
713                }
714
715        /* Default out to just the config name (which should fail as it was the
716           first entry in configPaths[]).  readConfig() will display the
717           appropriate warning */
718        return( readConfig( CONFIG_NAME, TRUE ) );
719        }
720
721/****************************************************************************
722*                                                                                                                                                       *
723*                                                       Output/Formatting Routines                                              *
724*                                                                                                                                                       *
725****************************************************************************/
726
727/* Indent a string by the appropriate amount */
728
729static void doIndent( const int level )
730        {
731        int i;
732
733        for( i = 0; i < level; i++ )
734                fprintf( output, ( printDots ) ? ". " : "  " );
735        }
736
737/* Complain about an error in the ASN.1 object */
738
739static void complain( const char *message, const int level )
740        {
741        if( !doPure )
742                fprintf( output, "            : " );
743        doIndent( level + 1 );
744        fprintf( output, "Error: %s.\n", message );
745        noErrors++;
746        }
747
748/* Dump data as a string of hex digits up to a maximum of 128 bytes */
749
750static void dumpHex( FILE *inFile, long length, int level, int isInteger )
751        {
752        const int lineLength = ( dumpText ) ? 8 : 16;
753        char printable[ 9 ];
754        long noBytes = length;
755        int zeroPadded = FALSE, warnPadding = FALSE, warnNegative = isInteger;
756        int maxLevel = ( doPure ) ? 15 : 8, i;
757
758        if( noBytes > 128 && !printAllData )
759                noBytes = 128;  /* Only output a maximum of 128 bytes */
760        if( level > maxLevel )
761                level = maxLevel;       /* Make sure we don't go off edge of screen */
762        printable[ 8 ] = printable[ 0 ] = '\0';
763        for( i = 0; i < noBytes; i++ )
764                {
765                int ch;
766
767                if( !( i % lineLength ) )
768                        {
769                        if( dumpText )
770                                {
771                                /* If we're dumping text alongside the hex data, print the
772                                   accumulated text string */
773                                fputs( "    ", output );
774                                fputs( printable, output );
775                                }
776                        fputc( '\n', output );
777                        if( !doPure )
778                                fprintf( output, "            : " );
779                        doIndent( level + 1 );
780                        }
781                ch = getc( inFile );
782                fprintf( output, "%s%02X", i % lineLength ? " " : "", ch );
783                printable[ i % 8 ] = ( ch >= ' ' && ch < 127 ) ? ch : '.';
784                fPos++;
785
786                /* If we need to check for negative values and zero padding, check
787                   this now */
788                if( !i )
789                        {
790                        if( !ch )
791                                zeroPadded = TRUE;
792                        if( !( ch & 0x80 ) )
793                                warnNegative = FALSE;
794                        }
795                if( i == 1 && zeroPadded && ch < 0x80 )
796                        warnPadding = TRUE;
797                }
798        if( dumpText )
799                {
800                /* Print any remaining text */
801                i %= lineLength;
802                printable[ i ] = '\0';
803                while( i < lineLength )
804                        {
805                        fprintf( output, "   " );
806                        i++;
807                        }
808                fputs( "    ", output );
809                fputs( printable, output );
810                }
811        if( length > 128 && !printAllData )
812                {
813                length -= 128;
814                fputc( '\n', output );
815                if( !doPure )
816                        fprintf( output, "            : " );
817                doIndent( level + 5 );
818                fprintf( output, "[ Another %ld bytes skipped ]", length );
819                if( useStdin )
820                        {
821                        while( length-- )
822                                getc( inFile );
823                        }
824                else
825                        fseek( inFile, length, SEEK_CUR );
826                fPos += length;
827                }
828        fputs( "\n", output );
829
830        if( isInteger )
831                {
832                if( warnPadding )
833                        complain( "Integer has non-DER encoding", level );
834                if( warnNegative )
835                        complain( "Integer has a negative value", level );
836                }
837        }
838
839/* Dump a bitstring, reversing the bits into the standard order in the
840   process */
841
842static void dumpBitString( FILE *inFile, const int length, const int unused,
843                                                   const int level )
844        {
845        unsigned int bitString = 0, currentBitMask = 0x80, remainderMask = 0xFF;
846        int bitFlag, value = 0, noBits, bitNo = -1, i;
847        char *errorStr = NULL;
848
849        if( unused < 0 || unused > 7 )
850                complain( "Invalid number of unused bits", level );
851        noBits = ( length * 8 ) - unused;
852
853        /* ASN.1 bitstrings start at bit 0, so we need to reverse the order of
854           the bits */
855        if( length )
856                {
857                bitString = fgetc( inFile );
858                fPos++;
859                }
860        for( i = noBits - 8; i > 0; i -= 8 )
861                {
862                bitString = ( bitString << 8 ) | fgetc( inFile );
863                currentBitMask <<= 8;
864                remainderMask = ( remainderMask << 8 ) | 0xFF;
865                fPos++;
866                }
867        for( i = 0, bitFlag = 1; i < noBits; i++ )
868                {
869                if( bitString & currentBitMask )
870                        value |= bitFlag;
871                if( !( bitString & remainderMask ) )
872                        /* The last valid bit should be a one bit */
873                        errorStr = "Spurious zero bits in bitstring";
874                bitFlag <<= 1;
875                bitString <<= 1;
876                }
877        if( ( remainderMask << noBits ) & value )
878                /* There shouldn't be any bits set after the last valid one */
879                errorStr = "Spurious one bits in bitstring";
880
881        /* Now that it's in the right order, dump it.  If there's only one
882           bit set (which is often the case for bit flags) we also print the
883           bit number to save users having to count the zeroes to figure out
884           which flag is set */
885        fputc( '\n', output );
886        if( !doPure )
887                fprintf( output, "            : " );
888        doIndent( level + 1 );
889        fputc( '\'', output );
890        currentBitMask = 1 << ( noBits - 1 );
891        for( i = 0; i < noBits; i++ )
892                {
893                if( value & currentBitMask )
894                        {
895                        bitNo = ( bitNo == -1 ) ? ( noBits - 1 ) - i : -2;
896                        fputc( '1', output );
897                        }
898                else
899                        fputc( '0', output );
900                currentBitMask >>= 1;
901                }
902        if( bitNo >= 0 )
903                fprintf( output, "'B (bit %d)\n", bitNo );
904        else
905                fputs( "'B\n", output );
906
907        if( errorStr != NULL )
908                complain( errorStr, level );
909        }
910
911/* Display data as a text string up to a maximum of 240 characters (8 lines
912   of 48 chars to match the hex limit of 8 lines of 16 bytes) with special
913   treatement for control characters and other odd things which can turn up
914   in BMPString and UniversalString types.
915
916   If the string is less than 40 chars in length, we try to print it on the
917   same line as the rest of the text (even if it wraps), otherwise we break
918   it up into 48-char chunks in a somewhat less nice text-dump format */
919
920static void displayString( FILE *inFile, long length, int level,
921                                                   STR_OPTION strOption )
922        {
923        long noBytes = ( length > 384 ) ? 384 : length;
924        int lineLength = 48;
925        int maxLevel = ( doPure ) ? 15 : 8, firstTime = TRUE, i;
926        int warnIA5 = FALSE, warnPrintable = FALSE, warnUTC = FALSE;
927        int warnBMP = FALSE;
928
929        if( strOption == STR_UTCTIME && length != 13 )
930                warnUTC = TRUE;
931        if( length <= 40 )
932                fprintf( output, " '" );                /* Print string on same line */
933        if( level > maxLevel )
934                level = maxLevel;       /* Make sure we don't go off edge of screen */
935        for( i = 0; i < noBytes; i++ )
936                {
937                int ch;
938
939                /* If the string is longer than 40 chars, break it up into multiple
940                   sections */
941                if( length > 40 && !( i % lineLength ) )
942                        {
943                        if( !firstTime )
944                                fputc( '\'', output );
945                        fputc( '\n', output );
946                        if( !doPure )
947                                fprintf( output, "            : " );
948                        doIndent( level + 1 );
949                        fputc( '\'', output );
950                        firstTime = FALSE;
951                        }
952                ch = getc( inFile );
953#ifdef __WIN32__
954                if( strOption == STR_BMP )
955                        {
956                        if( i == noBytes - 1 && ( noBytes & 1 ) )
957                                /* Odd-length BMP string, complain */
958                                warnBMP = TRUE;
959                        else
960                                {
961                                wchar_t wCh = ( ch << 8 ) | getc( inFile );
962                                unsigned char outBuf[ 8 ];
963                                int outLen;
964
965                                /* Attempting to display Unicode characters is pretty hit and
966                                   miss, and if it fails nothing is displayed.  To try and
967                                   detect this we use wcstombs() to see if anything can be
968                                   displayed, if it can't we drop back to trying to display
969                                   the data as non-Unicode */
970                                outLen = wcstombs( outBuf, &wCh, 1 );
971                                if( outLen < 1 )
972                                        {
973                                        /* Can't be displayed as Unicode, fall back to
974                                           displaying it as normal text */
975                                        ungetc( wCh & 0xFF, inFile );
976                                        }
977                                else
978                                        {
979                                        lineLength++;
980                                        i++;    /* We've read two characters for a wchar_t */
981                                        wprintf( L"%c", wCh );
982                                        fPos += 2;
983                                        continue;
984                                        }
985                                }
986                        }
987#endif /* __WIN32__ */
988                if( strOption == STR_PRINTABLE || strOption == STR_IA5 )
989                        {
990                        if( strOption == STR_PRINTABLE && !isPrintable( ch ) )
991                                warnPrintable = TRUE;
992                        if( strOption == STR_IA5 && !isIA5( ch ) )
993                                warnIA5 = TRUE;
994                        if( ch < ' ' || ch >= 0x7F )
995                                ch = '.';               /* Convert non-ASCII to placeholders */
996                        }
997                else
998                        if( strOption == STR_UTCTIME )
999                                {
1000                                if( !isdigit( ch ) && ch != 'Z' )
1001                                        {
1002                                        warnUTC = TRUE;
1003                                        ch = '.';       /* Convert non-numeric to placeholders */
1004                                        }
1005                                }
1006                        else
1007                                if( ( ch & 0x7F ) < ' ' || ch == 0xFF )
1008                                        ch = '.';       /* Convert control chars to placeholders */
1009                fputc( ch, output );
1010                fPos++;
1011                }
1012        if( length > 384 )
1013                {
1014                length -= 384;
1015                fprintf( output, "'\n" );
1016                if( !doPure )
1017                        fprintf( output, "            : " );
1018                doIndent( level + 5 );
1019                fprintf( output, "[ Another %ld characters skipped ]", length );
1020                fPos += length;
1021                while( length-- )
1022                        {
1023                        int ch = getc( inFile );
1024
1025                        if( strOption == STR_PRINTABLE && !isPrintable( ch ) )
1026                                warnPrintable = TRUE;
1027                        if( strOption == STR_IA5 && !isIA5( ch ) )
1028                                warnIA5 = TRUE;
1029                        }
1030                }
1031        else
1032                fputc( '\'', output );
1033        fputc( '\n', output );
1034
1035        /* Display any problems we encountered */
1036        if( warnPrintable )
1037                complain( "PrintableString contains illegal character(s)", level );
1038        if( warnIA5 )
1039                complain( "IA5String contains illegal character(s)", level );
1040        if( warnUTC )
1041                complain( "UTCTime is encoded incorrectly", level );
1042        if( warnBMP )
1043                complain( "BMPString has missing final byte/half character", level );
1044        }
1045
1046/****************************************************************************
1047*                                                                                                                                                       *
1048*                                                               ASN.1 Parsing Routines                                          *
1049*                                                                                                                                                       *
1050****************************************************************************/
1051
1052/* Get an integer value */
1053
1054static long getValue( FILE *inFile, const long length )
1055        {
1056        long value;
1057        char ch;
1058        int i;
1059
1060        ch = getc( inFile );
1061        value = ch;
1062        for( i = 0; i < length - 1; i++ )
1063                value = ( value << 8 ) | getc( inFile );
1064        fPos += length;
1065
1066        return( value );
1067        }
1068
1069/* Get an ASN.1 objects tag and length */
1070
1071int getItem( FILE *inFile, ASN1_ITEM *item )
1072        {
1073        int tag, length, index = 0;
1074
1075        memset( item, 0, sizeof( ASN1_ITEM ) );
1076        item->indefinite = FALSE;
1077        tag = item->header[ index++ ] = fgetc( inFile );
1078        item->id = tag & ~TAG_MASK;
1079        tag &= TAG_MASK;
1080        if( tag == TAG_MASK )
1081                {
1082                int value;
1083
1084                /* Long tag encoded as sequence of 7-bit values.  This doesn't try to
1085                   handle tags > INT_MAX, it'd be pretty peculiar ASN.1 if it had to
1086                   use tags this large */
1087                tag = 0;
1088                do
1089                        {
1090                        value = fgetc( inFile );
1091                        tag = ( tag << 7 ) | ( value & 0x7F );
1092                        item->header[ index++ ] = value;
1093                        fPos++;
1094                        }
1095                while( value & LEN_XTND && !feof( inFile ) );
1096                }
1097        item->tag = tag;
1098        if( feof( inFile ) )
1099                {
1100                fPos++;
1101                return( FALSE );
1102                }
1103        fPos += 2;                      /* Tag + length */
1104        length = item->header[ index++ ] = fgetc( inFile );
1105        item->headerSize = index;
1106        if( length & LEN_XTND )
1107                {
1108                int i;
1109
1110                length &= LEN_MASK;
1111                if( length > 4 )
1112                        /* Impossible length value, probably because we've run into
1113                           the weeds */
1114                        return( -1 );
1115                item->headerSize += length;
1116                item->length = 0;
1117                if( !length )
1118                        item->indefinite = TRUE;
1119                for( i = 0; i < length; i++ )
1120                        {
1121                        int ch = fgetc( inFile );
1122
1123                        item->length = ( item->length << 8 ) | ch;
1124                        item->header[ i + index ] = ch;
1125                        }
1126                fPos += length;
1127                }
1128        else
1129                item->length = length;
1130
1131        return( TRUE );
1132        }
1133
1134/* Check whether a BIT STRING or OCTET STRING encapsulates another object */
1135
1136static int checkEncapsulate( FILE *inFile, const int tag, const int length )
1137        {
1138        ASN1_ITEM nestedItem;
1139        const int currentPos = fPos;
1140        int diffPos;
1141
1142        /* If we're not looking for encapsulated objects, return */
1143        if( !checkEncaps )
1144                return( FALSE );
1145
1146#if 1
1147        /* Read the details of the next item in the input stream */
1148        getItem( inFile, &nestedItem );
1149        diffPos = fPos - currentPos;
1150        fPos = currentPos;
1151        fseek( inFile, -diffPos, SEEK_CUR );
1152
1153        /* If it fits exactly within the current item and has a valid-looking
1154           tag, treat it as nested data */
1155        if( ( ( nestedItem.id & CLASS_MASK ) == UNIVERSAL || \
1156                  ( nestedItem.id & CLASS_MASK ) == CONTEXT ) && \
1157                ( nestedItem.tag > 0 && nestedItem.tag <= 0x31 ) && \
1158                nestedItem.length == length - diffPos )
1159                return( TRUE );
1160#else
1161        /* Older code which used heuristics but was actually less accurate than
1162           the above code */
1163        int ch;
1164
1165        /* Get the first character and see if it's an INTEGER or SEQUENCE */
1166        ch = getc( inFile );
1167        ungetc( ch, inFile );
1168        if( ch == INTEGER || ch == ( SEQUENCE | CONSTRUCTED ) )
1169                return( TRUE );
1170
1171        /* All sorts of weird things get bundled up in octet strings in
1172           certificate extensions */
1173        if( tag == OCTETSTRING && ch == BITSTRING )
1174                return( TRUE );
1175
1176        /* If we're looking for all sorts of things which might be encapsulated,
1177           check for these as well.  At the moment we only check for a small
1178           number of possibilities, this list will probably change as more
1179           oddities are discovered, the idea is to keep the amount of burrowing
1180           we do to a minimum in order to reduce problems with false positives */
1181        if( level > 1 && tag == OCTETSTRING )
1182                {
1183                int length;
1184
1185                if( ch == IA5STRING )
1186                        /* Verisign extensions */
1187                        return( TRUE );
1188
1189                /* For the following possibilities we have to look ahead a bit
1190                   further and check the length as well */
1191                getc( inFile );
1192                length = getc( inFile );
1193                fseek( inFile, -2, SEEK_CUR );
1194                if( ( ch == OID && length < 9 ) || \
1195                        ( ch == ENUMERATED && length == 1 ) || \
1196                        ( ch == GENERALIZEDTIME && length == 15 ) )
1197                        /* CRL per-entry extensions */
1198                        return( TRUE );
1199                }
1200#endif /* 0 */
1201
1202        return( FALSE );
1203        }
1204
1205/* Check whether a zero-length item is OK */
1206
1207int zeroLengthOK( const ASN1_ITEM *item )
1208        {
1209        /* If we can't recognise the type from the tag, reject it */
1210        if( ( item->id & CLASS_MASK ) != UNIVERSAL )
1211                return( FALSE );
1212
1213        /* The following types are zero-length by definition */
1214        if( item->tag == EOC || item->tag == NULLTAG )
1215                return( TRUE );
1216
1217        /* A real with a value of zero has zero length */
1218        if( item->tag == REAL )
1219                return( TRUE );
1220
1221        /* Everything after this point requires input from the user to say that
1222           zero-length data is OK (usually it's not, so we flag it as a
1223           problem) */
1224        if( !zeroLengthAllowed )
1225                return( FALSE );
1226
1227        /* String types can have zero length except for the Unrestricted
1228           Character String type ([UNIVERSAL 29]) which has to have at least one
1229           octet for the CH-A/CH-B index */
1230        if( item->tag == OCTETSTRING || item->tag == NUMERICSTRING || \
1231                item->tag == PRINTABLESTRING || item->tag == T61STRING || \
1232                item->tag == VIDEOTEXSTRING || item->tag == VISIBLESTRING || \
1233                item->tag == IA5STRING || item->tag == GRAPHICSTRING || \
1234                item->tag == GENERALSTRING || item->tag == UNIVERSALSTRING || \
1235                item->tag == BMPSTRING || item->tag == UTF8STRING || \
1236                item->tag == OBJDESCRIPTOR )
1237                return( TRUE );
1238
1239        /* SEQUENCE and SET can be zero if there are absent optional/default
1240           components */
1241        if( item->tag == SEQUENCE || item->tag == SET )
1242                return( TRUE );
1243
1244        return( FALSE );
1245        }
1246
1247/* Check whether the next item looks like text */
1248
1249static int looksLikeText( FILE *inFile, const int length )
1250        {
1251        char buffer[ 16 ];
1252        int sampleLength = min( length, 16 ), i;
1253
1254        /* If the sample size is too small, don't try anything */
1255        if( sampleLength < 4 )
1256                return( FALSE );
1257
1258        /* Check for ASCII-looking text */
1259        sampleLength = fread( buffer, 1, sampleLength, inFile );
1260        fseek( inFile, -sampleLength, SEEK_CUR );
1261        for( i = 0; i < sampleLength; i++ )
1262                {
1263                if( !( i & 1 ) && !buffer[ i ] )
1264                        /* If even bytes are zero, it could be a BMPString */
1265                        continue;
1266                if( buffer[ i ] < 0x20 || buffer[ i ] > 0x7E )
1267                        return( FALSE );
1268                }
1269
1270        /* It looks like a text string */
1271        return( TRUE);
1272        }
1273
1274/* Dump the header bytes for an object, useful for vgrepping the original
1275   object from a hex dump */
1276
1277static void dumpHeader( FILE *inFile, const ASN1_ITEM *item )
1278        {
1279        int extraLen = 24 - item->headerSize, i;
1280
1281        /* Dump the tag and length bytes */
1282        if( !doPure )
1283                fprintf( output, "    " );
1284        fprintf( output, "<%02X", *item->header );
1285        for( i = 1; i < item->headerSize; i++ )
1286                fprintf( output, " %02X", item->header[ i ] );
1287
1288        /* If we're asked for more, dump enough extra data to make up 24 bytes.
1289           This is somewhat ugly since it assumes we can seek backwards over the
1290           data, which means it won't always work on streams */
1291        if( extraLen > 0 && doDumpHeader > 1 )
1292                {
1293                /* Make sure we don't print too much data.  This doesn't work for
1294                   indefinite-length data, we don't try and guess the length with
1295                   this since it involves picking apart what we're printing */
1296                if( extraLen > item->length && !item->indefinite )
1297                        extraLen = ( int ) item->length;
1298
1299                for( i = 0; i < extraLen; i++ )
1300                        {
1301                        int ch = fgetc( inFile );
1302
1303                        if( feof( inFile ) )
1304                                extraLen = i;   /* Exit loop and get fseek() correct */
1305                        else
1306                                fprintf( output, " %02X", ch );
1307                        }
1308                fseek( inFile, -extraLen, SEEK_CUR );
1309                }
1310
1311        fputs( ">\n", output );
1312        }
1313
1314/* Print a constructed ASN.1 object */
1315
1316int printAsn1( FILE *inFile, const int level, long length, const int isIndefinite );
1317
1318static void printConstructed( FILE *inFile, int level, const ASN1_ITEM *item )
1319        {
1320        int result;
1321
1322        /* Special case for zero-length objects */
1323        if( !item->length && !item->indefinite )
1324                {
1325                fputs( " {}\n", output );
1326                return;
1327                }
1328
1329        fputs( " {\n", output );
1330        result = printAsn1( inFile, level + 1, item->length, item->indefinite );
1331        if( result )
1332                {
1333                fprintf( output, "Error: Inconsistent object length, %d byte%s "
1334                                 "difference.\n", result, ( result > 1 ) ? "s" : "" );
1335                noErrors++;
1336                }
1337        if( !doPure )
1338                fprintf( output, "            : " );
1339        fprintf( output, ( printDots ) ? ". " : "  " );
1340        doIndent( level );
1341        fputs( "}\n", output );
1342        }
1343
1344/* Print a single ASN.1 object */
1345
1346void printASN1object( FILE *inFile, ASN1_ITEM *item, int level )
1347        {
1348        OIDINFO *oidInfo;
1349        char buffer[ MAX_OID_SIZE ];
1350        long value;
1351        int x, y;
1352
1353        if( ( item->id & CLASS_MASK ) != UNIVERSAL )
1354                {
1355                static const char *const classtext[] =
1356                        { "UNIVERSAL ", "APPLICATION ", "", "PRIVATE " };
1357
1358                /* Print the object type */
1359                fprintf( output, "[%s%d]",
1360                                 classtext[ ( item->id & CLASS_MASK ) >> 6 ], item->tag );
1361
1362                /* Perform a sanity check */
1363                if( ( item->tag != NULLTAG ) && ( item->length < 0 ) )
1364                        {
1365                        int i;
1366
1367                        fprintf( stderr, "\nError: Object has bad length field, tag = %02X, "
1368                                         "length = %lX, value =", item->tag, item->length );
1369                        fprintf( stderr, "<%02X", *item->header );
1370                        for( i = 1; i < item->headerSize; i++ )
1371                                fprintf( stderr, " %02X", item->header[ i ] );
1372                        fputs( ">.\n", stderr );
1373                        exit( EXIT_FAILURE );
1374                        }
1375
1376                if( !item->length && !item->indefinite )
1377                        {
1378                        fputc( '\n', output );
1379                        complain( "Object has zero length", level );
1380                        return;
1381                        }
1382
1383                /* If it's constructed, print the various fields in it */
1384                if( ( item->id & FORM_MASK ) == CONSTRUCTED )
1385                        {
1386                        printConstructed( inFile, level, item );
1387                        return;
1388                        }
1389
1390                /* It's primitive, if it's a seekable stream try and determine
1391                   whether it's text so we can display it as such */
1392                if( !useStdin && looksLikeText( inFile, item->length ) )
1393                        {
1394                        /* It looks like a text string, dump it as text */
1395                        displayString( inFile, item->length, level, STR_NONE );
1396                        return;
1397                        }
1398
1399                /* This could be anything, dump it as hex data */
1400                dumpHex( inFile, item->length, level, FALSE );
1401
1402                return;
1403                }
1404
1405        /* Print the object type */
1406        fprintf( output, "%s", idstr( item->tag ) );
1407
1408        /* Perform a sanity check */
1409        if( ( item->tag != NULLTAG ) && ( item->length < 0 ) )
1410                {
1411                int i;
1412
1413                fprintf( stderr, "\nError: Object has bad length field, tag = %02X, "
1414                                 "length = %lX, value =", item->tag, item->length );
1415                fprintf( stderr, "<%02X", *item->header );
1416                for( i = 1; i < item->headerSize; i++ )
1417                        fprintf( stderr, " %02X", item->header[ i ] );
1418                fputs( ">.\n", stderr );
1419                exit( EXIT_FAILURE );
1420                }
1421
1422        /* If it's constructed, print the various fields in it */
1423        if( ( item->id & FORM_MASK ) == CONSTRUCTED )
1424                {
1425                printConstructed( inFile, level, item );
1426                return;
1427                }
1428
1429        /* It's primitive */
1430        if( !item->length && !zeroLengthOK( item ) )
1431                {
1432                fputc( '\n', output );
1433                complain( "Object has zero length", level );
1434                return;
1435                }
1436        switch( item->tag )
1437                {
1438                case BOOLEAN:
1439                        x = getc( inFile );
1440                        fprintf( output, " %s\n", x ? "TRUE" : "FALSE" );
1441                        if( x != 0 && x != 0xFF )
1442                                complain( "BOOLEAN has non-DER encoding", level );
1443                        fPos++;
1444                        break;
1445
1446                case INTEGER:
1447                case ENUMERATED:
1448                        if( item->length > 4 )
1449                                dumpHex( inFile, item->length, level, TRUE );
1450                        else
1451                                {
1452                                value = getValue( inFile, item->length );
1453                                fprintf( output, " %ld\n", value );
1454                                if( value < 0 )
1455                                        complain( "Integer has a negative value", level );
1456                                }
1457                        break;
1458
1459                case BITSTRING:
1460                        fprintf( output, " %d unused bits", x = getc( inFile ) );
1461                        fPos++;
1462                        if( !--item->length && !x )
1463                                {
1464                                fputc( '\n', output );
1465                                complain( "Object has zero length", level );
1466                                return;
1467                                }
1468                        if( item->length <= sizeof( int ) )
1469                                {
1470                                /* It's short enough to be a bit flag, dump it as a sequence
1471                                   of bits */
1472                                dumpBitString( inFile, ( int ) item->length, x, level );
1473                                break;
1474                                }
1475                case OCTETSTRING:
1476                        if( checkEncapsulate( inFile, item->tag, item->length ) )
1477                                {
1478                                /* It's something encapsulated inside the string, print it as
1479                                   a constructed item */
1480                                fprintf( output, ", encapsulates" );
1481                                printConstructed( inFile, level + 1, item );
1482                                break;
1483                                }
1484                        if( !useStdin && !dumpText && \
1485                                looksLikeText( inFile, item->length ) )
1486                                {
1487                                /* If we'd be doing a straight hex dump and it looks like
1488                                   encapsulated text, display it as such */
1489                                displayString( inFile, item->length, level, STR_NONE );
1490                                return;
1491                                }
1492                        dumpHex( inFile, item->length, level, FALSE );
1493                        break;
1494
1495                case OID:
1496                        /* Hierarchical Object Identifier: The first two levels are
1497                           encoded into one byte, since the root level has only 3 nodes
1498                           (40*x + y).  However if x = joint-iso-itu-t(2) then y may be
1499                           > 39, so we have to add special-case handling for this */
1500                        if( item->length > MAX_OID_SIZE )
1501                                {
1502                                fprintf( stderr, "\nError: Object identifier length %ld too "
1503                                                 "large.\n", item->length );
1504                                exit( EXIT_FAILURE );
1505                                }
1506                        fread( buffer, 1, ( size_t ) item->length, inFile );
1507                        fPos += item->length;
1508                        if( ( oidInfo = getOIDinfo( buffer, ( int ) item->length ) ) != NULL )
1509                                {
1510                                int lhsSize = ( doPure ) ? 0 : 14;
1511
1512                                /* Check if LHS status info + indent + "OID " string + oid
1513                                   name will wrap */
1514                                if( lhsSize + ( level * 2 ) + 18 + strlen( oidInfo->description ) >= 80 )
1515                                        {
1516                                        fputc( '\n', output );
1517                                        if( !doPure )
1518                                                fprintf( output, "            : " );
1519                                        doIndent( level + 1 );
1520                                        }
1521                                else
1522                                        fputc( ' ', output );
1523                                fprintf( output, "%s\n", oidInfo->description );
1524
1525                                /* Display extra comments about the OID if required */
1526                                if( extraOIDinfo && oidInfo->comment != NULL )
1527                                        {
1528                                        if( !doPure )
1529                                                fprintf( output, "            : " );
1530                                        doIndent( level + 1 );
1531                                        fprintf( output, "(%s)\n", oidInfo->comment );
1532                                        }
1533
1534                                /* If there's a warning associated with this OID, remember
1535                                   that there was a problem */
1536                                if( oidInfo->warn )
1537                                        noWarnings++;
1538
1539                                break;
1540                                }
1541
1542                        /* Pick apart the OID */
1543                        x = ( unsigned char ) buffer[ 0 ] / 40;
1544                        y = ( unsigned char ) buffer[ 0 ] % 40;
1545                        if( x > 2 )
1546                                {
1547                                /* Handle special case for large y if x = 2 */
1548                                y += ( x - 2 ) * 40;
1549                                x = 2;
1550                                }
1551                        fprintf( output, " '%d %d", x, y );
1552                        value = 0;
1553                        for( x = 1; x < item->length; x++ )
1554                                {
1555                                value = ( value << 7 ) | ( buffer[ x ] & 0x7F );
1556                                if( !( buffer[ x ] & 0x80 ) )
1557                                        {
1558                                        fprintf( output, " %ld", value );
1559                                        value = 0;
1560                                        }
1561                                }
1562                        fprintf( output, "'\n" );
1563                        break;
1564
1565                case EOC:
1566                case NULLTAG:
1567                        fputc( '\n', output );
1568                        break;
1569
1570                case OBJDESCRIPTOR:
1571                case GENERALIZEDTIME:
1572                case GRAPHICSTRING:
1573                case VISIBLESTRING:
1574                case GENERALSTRING:
1575                case UNIVERSALSTRING:
1576                case NUMERICSTRING:
1577                case T61STRING:
1578                case VIDEOTEXSTRING:
1579                case UTF8STRING:
1580                        displayString( inFile, item->length, level, STR_NONE );
1581                        break;
1582                case PRINTABLESTRING:
1583                        displayString( inFile, item->length, level, STR_PRINTABLE );
1584                        break;
1585                case BMPSTRING:
1586                        displayString( inFile, item->length, level, STR_BMP );
1587                        break;
1588                case UTCTIME:
1589                        displayString( inFile, item->length, level, STR_UTCTIME );
1590                        break;
1591                case IA5STRING:
1592                        displayString( inFile, item->length, level, STR_IA5 );
1593                        break;
1594
1595                default:
1596                        fputc( '\n', output );
1597                        if( !doPure )
1598                                fprintf( output, "            : " );
1599                        doIndent( level + 1 );
1600                        fprintf( output, "Unrecognised primitive, hex value is:");
1601                        dumpHex( inFile, item->length, level, FALSE );
1602                        noErrors++;             /* Treat it as an error */
1603                }
1604        }
1605
1606/* Print a complex ASN.1 object */
1607
1608int printAsn1( FILE *inFile, const int level, long length,
1609                           const int isIndefinite )
1610        {
1611        ASN1_ITEM item;
1612        long lastPos = fPos;
1613        int seenEOC = FALSE, status;
1614
1615        /* Special-case for zero-length objects */
1616        if( !length && !isIndefinite )
1617                return( 0 );
1618
1619        while( ( status = getItem( inFile, &item ) ) > 0 )
1620                {
1621                /* If the length isn't known and the item has a definite length, set
1622                   the length to the items length */
1623                if( length == LENGTH_MAGIC && !item.indefinite )
1624                        length = item.headerSize + item.length;
1625
1626                /* Dump the header as hex data if requested */
1627                if( doDumpHeader )
1628                        dumpHeader( inFile, &item );
1629
1630                /* Print offset into buffer, tag, and length */
1631                if( !doPure )
1632                        if( item.indefinite )
1633                                fprintf( output, ( doHexValues ) ? "%04lX %02X NDEF: " :
1634                                                 "%4ld %02X NDEF: ", lastPos, item.id | item.tag );
1635                        else
1636                                if( ( item.id | item.tag ) == EOC )
1637                                        seenEOC = TRUE;
1638                                else
1639                                        fprintf( output, ( doHexValues ) ? "%04lX %02X %4lX: " :
1640                                                         "%4ld %02X %4ld: ", lastPos, item.id | item.tag,
1641                                                         item.length );
1642
1643                /* Print details on the item */
1644                if( !seenEOC )
1645                        {
1646                        doIndent( level );
1647                        printASN1object( inFile, &item, level );
1648                        }
1649
1650                /* If it was an indefinite-length object (no length was ever set) and
1651                   we've come back to the top level, exit */
1652                if( length == LENGTH_MAGIC )
1653                        return( 0 );
1654
1655                length -= fPos - lastPos;
1656                lastPos = fPos;
1657                if( isIndefinite )
1658                        {
1659                        if( seenEOC )
1660                                return( 0 );
1661                        }
1662                else
1663                        if( length <= 0 )
1664                                {
1665                                if( length < 0 )
1666                                        return( ( int ) -length );
1667                                return( 0 );
1668                                }
1669                        else
1670                                if( length == 1 )
1671                                        {
1672                                        const int ch = fgetc( inFile );
1673
1674                                        /* No object can be one byte long, try and recover.  This
1675                                           only works sometimes because it can be caused by
1676                                           spurious data in an OCTET STRING hole or an incorrect
1677                                           length encoding.  The following workaround tries to
1678                                           recover from spurious data by skipping the byte if
1679                                           it's zero or a non-basic-ASN.1 tag, but keeping it if
1680                                           it could be valid ASN.1 */
1681                                        if( ch && ch <= 0x31 )
1682                                                ungetc( ch, inFile );
1683                                        else
1684                                                {
1685                                                fPos++;
1686                                                return( 1 );
1687                                                }
1688                                        }
1689                }
1690        if( status == -1 )
1691                {
1692                fprintf( stderr, "\nError: Invalid data encountered at position "
1693                                 "%d.\n", fPos );
1694                exit( EXIT_FAILURE );
1695                }
1696
1697        /* If we see an EOF and there's supposed to be more data present,
1698           complain */
1699        if( length && length != LENGTH_MAGIC )
1700                {
1701                fprintf( output, "Error: Inconsistent object length, %ld byte%s "
1702                                 "difference.\n", length, ( length > 1 ) ? "s" : "" );
1703                noErrors++;
1704                }
1705        return( 0 );
1706        }
1707
1708/* Show usage and exit */
1709
1710void usageExit( void )
1711        {
1712        puts( "DumpASN1 - ASN.1 object dump/syntax check program." );
1713        puts( "Copyright Peter Gutmann 1997 - 2000.  Last updated 21 November 2000." );
1714        puts( "" );
1715        puts( "Usage: dumpasn1 [-acdefhlpsxz] <file>" );
1716        puts( "       - = Take input from stdin (some options may not work properly)" );
1717        puts( "       -<number> = Start <number> bytes into the file" );
1718        puts( "       -- = End of arg list" );
1719        puts( "       -a = Print all data in long data blocks, not just the first 128 bytes" );
1720        puts( "       -c<file> = Read Object Identifier info from alternate config file" );
1721        puts( "            (values will override equivalents in global config file)" );
1722        puts( "       -d = Print dots to show column alignment" );
1723        puts( "       -e = Don't print encapsulated data inside OCTET/BIT STRINGs" );
1724        puts( "       -f<file> = Dump object at offset -<number> to file (allows data to be" );
1725        puts( "            extracted from encapsulating objects)" );
1726        puts( "       -h = Hex dump object header (tag+length) before the decoded output" );
1727        puts( "       -hh = Same as -h but display more of the object as hex data" );
1728        puts( "       -l = Long format, display extra info about Object Identifiers" );
1729        puts( "       -p = Pure ASN.1 output without encoding information" );
1730        puts( "       -s = Syntax check only, don't dump ASN.1 structures" );
1731        puts( "       -t = Display text values next to hex dump of data" );
1732        puts( "       -x = Display size and offset in hex not decimal" );
1733        puts( "       -z = Allow zero-length items" );
1734        puts( "" );
1735        puts( "Warnings generated by deprecated OIDs require the use of '-l' to be displayed." );
1736        puts( "Program return code is the number of errors found or EXIT_SUCCESS." );
1737        exit( EXIT_FAILURE );
1738        }
1739
1740int main( int argc, char *argv[] )
1741        {
1742        FILE *inFile, *outFile = NULL;
1743        char *pathPtr = argv[ 0 ];
1744        long offset = 0;
1745        int moreArgs = TRUE, doCheckOnly = FALSE;
1746
1747        /* Skip the program name */
1748        argv++; argc--;
1749
1750        /* Display usage if no args given */
1751        if( argc < 1 )
1752                usageExit();
1753        output = stdout;        /* Needs to be assigned at runtime */
1754
1755        /* Check for arguments */
1756        while( argc && *argv[ 0 ] == '-' && moreArgs )
1757                {
1758                char *argPtr = argv[ 0 ] + 1;
1759
1760                if( !*argPtr )
1761                        useStdin = TRUE;
1762                while( *argPtr )
1763                        {
1764                        if( isdigit( *argPtr ) )
1765                                {
1766                                offset = atol( argPtr );
1767                                break;
1768                                }
1769                        switch( toupper( *argPtr ) )
1770                                {
1771                                case '-':
1772                                        moreArgs = FALSE;       /* GNU-style end-of-args flag */
1773                                        break;
1774
1775                                case 'A':
1776                                        printAllData = TRUE;
1777                                        break;
1778
1779                                case 'C':
1780                                        if( !readConfig( argPtr + 1, FALSE ) )
1781                                                exit( EXIT_FAILURE );
1782                                        while( argPtr[ 1 ] )
1783                                                argPtr++;       /* Skip rest of arg */
1784                                        break;
1785
1786                                case 'D':
1787                                        printDots = TRUE;
1788                                        break;
1789
1790                                case 'E':
1791                                        checkEncaps = FALSE;
1792                                        break;
1793
1794                                case 'F':
1795                                        if( ( outFile = fopen( argPtr + 1, "wb" ) ) == NULL )
1796                                                {
1797                                                perror( argPtr + 1 );
1798                                                exit( EXIT_FAILURE );
1799                                                }
1800                                        while( argPtr[ 1 ] )
1801                                                argPtr++;       /* Skip rest of arg */
1802                                        break;
1803
1804                                case 'L':
1805                                        extraOIDinfo = TRUE;
1806                                        break;
1807
1808                                case 'H':
1809                                        doDumpHeader++;
1810                                        break;
1811
1812                                case 'P':
1813                                        doPure = TRUE;
1814                                        break;
1815
1816                                case 'S':
1817                                        doCheckOnly = TRUE;
1818#ifdef __WIN32__
1819                                        /* Under Windows we can't fclose( stdout ) because the
1820                                           VC++ runtime reassigns the stdout handle to the next
1821                                           open file (which is valid) but then scribbles stdout
1822                                           garbage all over it for files larger than about 16K
1823                                           (which isn't), so we have to make sure that the
1824                                           stdout is handle pointed to something somewhere */
1825                                        freopen( "nul", "w", stdout );
1826#else
1827                                        /* If we know we're running under Unix we can also
1828                                           freopen( "/dev/null", "w", stdout ); */
1829                                        fclose( stdout );
1830#endif /* __WIN32__ */
1831                                        break;
1832
1833                                case 'T':
1834                                        dumpText = TRUE;
1835                                        break;
1836
1837                                case 'X':
1838                                        doHexValues = TRUE;
1839                                        break;
1840
1841                                case 'Z':
1842                                        zeroLengthAllowed = TRUE;
1843                                        break;
1844
1845                                default:
1846                                        printf( "Unknown argument '%c'.\n", *argPtr );
1847                                        return( EXIT_SUCCESS );
1848                                }
1849                        argPtr++;
1850                        }
1851                argv++;
1852                argc--;
1853                }
1854
1855        /* We can't use options which perform an fseek() if reading from stdin */
1856        if( useStdin && ( doDumpHeader || outFile != NULL ) )
1857                {
1858                puts( "Can't use -f or -h when taking input from stdin" );
1859                exit( EXIT_FAILURE );
1860                }
1861
1862        /* Check args and read the config file.  We don't bother weeding out
1863           dups during the read because (a) the linear search would make the
1864           process n^2, (b) during the dump process the search will terminate on
1865           the first match so dups aren't that serious, and (c) there should be
1866           very few dups present */
1867        if( argc != 1 && !useStdin )
1868                usageExit();
1869        if( !readGlobalConfig( pathPtr ) )
1870                exit( EXIT_FAILURE );
1871
1872        /* Dump the given file */
1873        if( useStdin )
1874                inFile = stdin;
1875        else
1876                if( ( inFile = fopen( argv[ 0 ], "rb" ) ) == NULL )
1877                        {
1878                        perror( argv[ 0 ] );
1879                        exit( EXIT_FAILURE );
1880                        }
1881        if( useStdin )
1882                {
1883                while( offset-- )
1884                        getc( inFile );
1885                }
1886        else
1887                fseek( inFile, offset, SEEK_SET );
1888        if( outFile != NULL )
1889                {
1890                ASN1_ITEM item;
1891                long length;
1892                int i, status;
1893
1894                /* Make sure there's something there, and that it has a definite
1895                   length */
1896                status = getItem( inFile, &item );
1897                if( status == -1 )
1898                        {
1899                        puts( "Non-ASN.1 data encountered." );
1900                        exit( EXIT_FAILURE );
1901                        }
1902                if( status == 0 )
1903                        {
1904                        puts( "Nothing to read." );
1905                        exit( EXIT_FAILURE );
1906                        }
1907                if( item.indefinite )
1908                        {
1909                        puts( "Cannot process indefinite-length item." );
1910                        exit( EXIT_FAILURE );
1911                        }
1912
1913                /* Copy the item across, first the header and then the data */
1914                for( i = 0; i < item.headerSize; i++ )
1915                        putc( item.header[ i ], outFile );
1916                for( length = 0; length < item.length && !feof( inFile ); length++ )
1917                        putc( getc( inFile ), outFile );
1918                fclose( outFile );
1919
1920                fseek( inFile, offset, SEEK_SET );
1921                }
1922        printAsn1( inFile, 0, LENGTH_MAGIC, 0 );
1923        fclose( inFile );
1924
1925        /* Print a summary of warnings/errors if it's required or appropriate */
1926        if( !doPure )
1927                {
1928                if( !doCheckOnly )
1929                        fputc( '\n', stderr );
1930                fprintf( stderr, "%d warning%s, %d error%s.\n", noWarnings,
1931                                ( noWarnings != 1 ) ? "s" : "", noErrors,
1932                                ( noErrors != 1 ) ? "s" : "" );
1933                }
1934
1935        return( ( noErrors ) ? noErrors : EXIT_SUCCESS );
1936        }
Note: See TracBrowser for help on using the repository browser.