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 | |
---|
148 | typedef 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 | |
---|
158 | typedef 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 | |
---|
169 | static int printDots = FALSE; /* Whether to print dots to align columns */ |
---|
170 | static int doPure = FALSE; /* Print data without LHS info column */ |
---|
171 | static int doDumpHeader = FALSE; /* Dump tag+len in hex (level = 0, 1, 2) */ |
---|
172 | static int extraOIDinfo = FALSE; /* Print extra information about OIDs */ |
---|
173 | static int doHexValues = FALSE; /* Display size, offset in hex not dec.*/ |
---|
174 | static int useStdin = FALSE; /* Take input from stdin */ |
---|
175 | static int zeroLengthAllowed = FALSE;/* Zero-length items allowed */ |
---|
176 | static int dumpText = FALSE; /* Dump text alongside hex data */ |
---|
177 | static int printAllData = FALSE; /* Whether to print all data in long blocks */ |
---|
178 | static int checkEncaps = TRUE; /* Print encaps.data in BIT/OCTET STRINGs */ |
---|
179 | |
---|
180 | /* Error and warning information */ |
---|
181 | |
---|
182 | static int noErrors = 0; /* Number of errors found */ |
---|
183 | static int noWarnings = 0; /* Number of warnings */ |
---|
184 | |
---|
185 | /* Position in the input stream */ |
---|
186 | |
---|
187 | static int fPos = 0; /* Absolute position in data */ |
---|
188 | |
---|
189 | /* The output stream */ |
---|
190 | |
---|
191 | static FILE *output; /* Output stream */ |
---|
192 | |
---|
193 | /* Information on an ASN.1 Object Identifier */ |
---|
194 | |
---|
195 | #define MAX_OID_SIZE 32 |
---|
196 | |
---|
197 | typedef 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 | |
---|
204 | static 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 | |
---|
213 | static 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 | |
---|
247 | char *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 | |
---|
312 | static 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 | |
---|
327 | static 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 | |
---|
344 | static 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 | |
---|
363 | static int isPrintable( int ch ) |
---|
364 | { |
---|
365 | if( ch >= 128 || !( charFlags[ ch ] & P ) ) |
---|
366 | return( FALSE ); |
---|
367 | return( TRUE ); |
---|
368 | } |
---|
369 | |
---|
370 | static 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 | |
---|
394 | static int lineNo; |
---|
395 | |
---|
396 | static 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 | |
---|
461 | static 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 | |
---|
492 | static 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 | |
---|
612 | static 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 | |
---|
626 | static 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 | |
---|
687 | static 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 | |
---|
729 | static 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 | |
---|
739 | static 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 | |
---|
750 | static 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 | |
---|
842 | static 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 | |
---|
920 | static 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 | |
---|
1054 | static 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 | |
---|
1071 | int 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 | |
---|
1136 | static 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 | |
---|
1207 | int 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 | |
---|
1249 | static 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 | |
---|
1277 | static 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 | |
---|
1316 | int printAsn1( FILE *inFile, const int level, long length, const int isIndefinite ); |
---|
1317 | |
---|
1318 | static 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 | |
---|
1346 | void 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 | |
---|
1608 | int 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 | |
---|
1710 | void 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 | |
---|
1740 | int 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 | } |
---|