/*- * Copyright (c) 2004, 2005 Lev Walkin . All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id$ */ #include "sys-common.h" #include /* For static string tables */ #include #include #include #include #include #include #include #undef COPYRIGHT #define COPYRIGHT \ "Copyright (c) 2004, 2005 Lev Walkin \n" static void usage(const char *av0); /* Print the Usage screen and exit */ static int process(const char *fname); /* Perform the BER decoding */ static int decode_tlv_from_string(const char *datastring); static int single_type_decoding = 0; /* -1 enables that */ static int minimalistic = 0; /* -m enables that */ static int pretty_printing = 1; /* -p disables that */ static int skip_bytes = 0; /* -s controls that */ static char *indent_buffer = " "; /* -i controls that */ int main(int ac, char **av) { int ch; /* Command line character */ int i; /* Index in some loops */ /* * Process command-line options. */ while((ch = getopt(ac, av, "1hi:mps:t:v")) != -1) switch(ch) { case '1': single_type_decoding = 1; break; case 'i': i = atoi(optarg); if(i >= 0 && i < 16) { indent_buffer = alloca(i + 1); memset(indent_buffer, ' ', i); indent_buffer[i] = '\0'; } else { fprintf(stderr, "-i %s: Invalid indent value\n",optarg); exit(EX_USAGE); } break; case 'm': minimalistic = 1; break; case 'p': pretty_printing = 0; break; case 's': skip_bytes = atoi(optarg); if(skip_bytes < 0) { fprintf(stderr, "-s %s: positive value expected\n", optarg); exit(EX_USAGE); } break; case 't': if(decode_tlv_from_string(optarg)) exit(EX_DATAERR); exit(0); case 'v': fprintf(stderr, "ASN.1 BER Decoder, v" VERSION "\n" COPYRIGHT); exit(0); break; case 'h': default: usage(av[0]); } /* * Ensure that there are some input files present. */ if(ac > optind) { ac -= optind; av += optind; } else { fprintf(stderr, "%s: No input files specified\n", av[0]); exit(1); } setvbuf(stdout, 0, _IOLBF, 0); /* * Iterate over input files and parse each. * All syntax trees from all files will be bundled together. */ for(i = 0; i < ac; i++) { if(process(av[i])) exit(EX_DATAERR); } return 0; } /* * Print the usage screen and exit(EX_USAGE). */ static void usage(const char *av0) { fprintf(stderr, "ASN.1 BER Decoder, v" VERSION "\n" COPYRIGHT "Usage: %s [options] [-] [file ...]\n" "Options:\n" " -1 Decode only the first BER structure (otherwise, until EOF)\n" " -i Amount of spaces for output indentation (default is 4)\n" " -m Minimalistic mode: print as little as possible\n" " -p Do not attempt pretty-printing of known ASN.1 types\n" " -s Ignore first bytes of input\n" " -t Decode the given tag[/length] sequence (e.g. -t \"bf20\")\n" "\n" "The XML opening tag format is as follows:\n" " \n" "Where:\n" " tform Which form the value is in: constructed (\"C\", \"I\") or primitive (\"P\")\n" " off Offset of the encoded element in the unber input stream\n" " tag The tag class and value in human readable form\n" " tl_len The length of the TL (BER Tag and Length) encoding\n" " v_len The length of the value (V, encoded by the L), may be \"Indefinite\"\n" " type Likely name of the underlying ASN.1 type (for [UNIVERSAL n] tags)\n" " [F] Indicates that the value was reformatted (pretty-printed)\n" "See the manual page for details\n" , av0); exit(EX_USAGE); } typedef enum pd_code { PD_FAILED = -1, PD_FINISHED = 0, PD_EOF = 1, } pd_code_e; static pd_code_e process_deeper(const char *fname, FILE *fp, asn1c_integer_t *offset, int level, ssize_t limit, ber_tlv_len_t *frame_size, ber_tlv_len_t effective_size, int expect_eoc); static void print_TL(int fin, asn1c_integer_t offset, int level, int constr, ssize_t tlen, ber_tlv_tag_t, ber_tlv_len_t, ber_tlv_len_t effective_frame_size); static int print_V(const char *fname, FILE *fp, ber_tlv_tag_t, ber_tlv_len_t); /* * Open the file and initiate recursive processing. */ static int process(const char *fname) { FILE *fp; pd_code_e pdc; asn1c_integer_t offset = 0; /* Stream decoding position */ ber_tlv_len_t frame_size = 0; /* Single frame size */ if(strcmp(fname, "-")) { fp = fopen(fname, "r"); if(!fp) { perror(fname); return -1; } } else { fp = stdin; } /* * Skip the requested amount of bytes. */ for(; offset < skip_bytes; offset++) { if(fgetc(fp) == -1) { fprintf(stderr, "%s: input source (%" PRIdASN " bytes) " "has less data than \"-s %d\" switch " "wants to skip\n", fname, offset, skip_bytes); if(fp != stdin) fclose(fp); return -1; } } /* * Fetch out BER-encoded data until EOF or error. */ do { pdc = process_deeper(fname, fp, &offset, 0, -1, &frame_size, 0, 0); } while(pdc == PD_FINISHED && !single_type_decoding); if(fp != stdin) fclose(fp); if(pdc == PD_FAILED) return -1; return 0; } /* * Process the TLV recursively. */ static pd_code_e process_deeper(const char *fname, FILE *fp, asn1c_integer_t *offset, int level, ssize_t limit, ber_tlv_len_t *frame_size, ber_tlv_len_t effective_size, int expect_eoc) { unsigned char tagbuf[32]; ssize_t tblen = 0; pd_code_e pdc = PD_FINISHED; ber_tlv_tag_t tlv_tag; ber_tlv_len_t tlv_len; ssize_t t_len; ssize_t l_len; for(;;) { ber_tlv_len_t local_esize = 0; int constr; int ch; if(limit == 0) return PD_FINISHED; if(limit >= 0 && tblen >= limit) { fprintf(stderr, "%s: Too long TL sequence (%ld >= %ld)" " at %" PRIdASN ". " "Broken or maliciously constructed file\n", fname, (long)tblen, (long)limit, *offset); return PD_FAILED; } /* Get the next byte from the input stream */ ch = fgetc(fp); if(ch == -1) { if(limit > 0 || expect_eoc) { fprintf(stderr, "%s: Unexpected end of file (TL)" " at %" PRIdASN "\n", fname, *offset); return PD_FAILED; } else { return PD_EOF; } } tagbuf[tblen++] = ch; /* * Decode the TLV tag. */ t_len = ber_fetch_tag(tagbuf, tblen, &tlv_tag); switch(t_len) { case -1: fprintf(stderr, "%s: Fatal error decoding tag" " at %" PRIdASN "+%ld\n", fname, *offset, (long)tblen); return PD_FAILED; case 0: /* More data expected */ continue; } /* * Decode the TLV length. */ constr = BER_TLV_CONSTRUCTED(tagbuf); l_len = ber_fetch_length(constr, tagbuf + t_len, tblen - t_len, &tlv_len); switch(l_len) { case -1: fprintf(stderr, "%s: Fatal error decoding value length" " at %" PRIdASN "\n", fname, *offset + t_len); return PD_FAILED; case 0: /* More data expected */ continue; } /* Make sure the T & L decoders took exactly the whole buffer */ assert((t_len + l_len) == tblen); if(!expect_eoc || tagbuf[0] || tagbuf[1]) print_TL(0, *offset, level, constr, tblen, tlv_tag, tlv_len, effective_size); if(limit != -1) { /* If limit is set, account for the TL sequence */ limit -= (t_len + l_len); assert(limit >= 0); if(tlv_len > limit) { fprintf(stderr, "%s: Structure advertizes length (%ld) " "greater than of a parent container (%ld)\n", fname, (long)tlv_len, (long)limit); return PD_FAILED; } } *offset += t_len + l_len; *frame_size += t_len + l_len; effective_size += t_len + l_len; local_esize += t_len + l_len; if(expect_eoc && tagbuf[0] == '\0' && tagbuf[1] == '\0') { /* End of content octets */ print_TL(1, *offset - 2, level - 1, 1, 2, 0, -1, effective_size); return PD_FINISHED; } if(constr) { ber_tlv_len_t dec = 0; /* * This is a constructed type. Process recursively. */ printf(">\n"); /* Close the opening tag */ if(tlv_len != -1 && limit != -1) { assert(limit >= tlv_len); } pdc = process_deeper(fname, fp, offset, level + 1, tlv_len == -1 ? limit : tlv_len, &dec, t_len + l_len, tlv_len == -1); if(pdc == PD_FAILED) return pdc; if(limit != -1) { assert(limit >= dec); limit -= dec; } *frame_size += dec; effective_size += dec; local_esize += dec; if(tlv_len == -1) { tblen = 0; if(pdc == PD_FINISHED && limit < 0) return pdc; continue; } } else { assert(tlv_len >= 0); if(print_V(fname, fp, tlv_tag, tlv_len)) return PD_FAILED; if(limit != -1) { assert(limit >= tlv_len); limit -= tlv_len; } *offset += tlv_len; *frame_size += tlv_len; effective_size += tlv_len; local_esize += tlv_len; } print_TL(1, *offset, level, constr, tblen, tlv_tag, tlv_len, local_esize); tblen = 0; /* Report success for a single top level TLV */ if(level == 0 && limit == -1 && !expect_eoc) break; } /* for(;;) */ return pdc; } static void print_TL(int fin, asn1c_integer_t offset, int level, int constr, ssize_t tlen, ber_tlv_tag_t tlv_tag, ber_tlv_len_t tlv_len, ber_tlv_len_t effective_size) { if(fin && !constr) { printf("

\n"); return; } while(level-- > 0) printf(indent_buffer); /* Print indent */ printf(fin ? "\n"); } } /* * Print the value in binary form, or reformat for pretty-printing. */ static int print_V(const char *fname, FILE *fp, ber_tlv_tag_t tlv_tag, ber_tlv_len_t tlv_len) { asn1c_integer_t *arcs = 0; /* Object identifier arcs */ unsigned char *vbuf = 0; asn1p_expr_type_e etype = 0; asn1c_integer_t collector = 0; int special_format = 0; ssize_t i; /* Figure out what type is it */ if(BER_TAG_CLASS(tlv_tag) == ASN_TAG_CLASS_UNIVERSAL && pretty_printing) { ber_tlv_tag_t tvalue = BER_TAG_VALUE(tlv_tag); etype = ASN_UNIVERSAL_TAG2TYPE(tvalue); } /* * Determine how to print the value, either in its native binary form, * encoded with &xNN characters, or using pretty-printing. * The basic string types (including "useful types", like UTCTime) * are excempt from this determination logic, because their alphabets * are subsets of the XML's native UTF-8 encoding. */ switch(etype) { case ASN_BASIC_BOOLEAN: if(tlv_len == 1) special_format = 1; else etype = 0; break; case ASN_BASIC_INTEGER: case ASN_BASIC_ENUMERATED: if((size_t)tlv_len <= sizeof(collector)) special_format = 1; else etype = 0; break; case ASN_BASIC_OBJECT_IDENTIFIER: case ASN_BASIC_RELATIVE_OID: if(tlv_len > 0 && tlv_len < 128*1024 /* VERY long OID! */) { arcs = malloc(sizeof(*arcs) * (tlv_len + 1)); if(arcs) { vbuf = malloc(tlv_len + 1); /* Not checking is intentional */ } } case ASN_BASIC_UTCTime: case ASN_BASIC_GeneralizedTime: case ASN_STRING_NumericString: case ASN_STRING_PrintableString: case ASN_STRING_VisibleString: case ASN_STRING_IA5String: case ASN_STRING_UTF8String: break; /* Directly compatible with UTF-8 */ case ASN_STRING_BMPString: case ASN_STRING_UniversalString: break; /* Not directly compatible with UTF-8 */ default: /* Conditionally compatible with UTF-8 */ if(( (etype & ASN_STRING_MASK) || (etype == ASN_BASIC_OCTET_STRING) || /* * AUTOMATIC TAGS or IMPLICIT TAGS in effect, * Treat this primitive type as OCTET_STRING. */ (BER_TAG_CLASS(tlv_tag) != ASN_TAG_CLASS_UNIVERSAL && pretty_printing) ) && (tlv_len > 0 && tlv_len < 128 * 1024)) { vbuf = malloc(tlv_len + 1); /* Not checking is intentional */ } break; } /* If collection vbuf is present, defer printing the F flag. */ if(!vbuf) printf(special_format ? " F>" : ">"); /* * Print the value in binary or text form, * or collect the bytes into vbuf. */ for(i = 0; i < tlv_len; i++) { int ch = fgetc(fp); if(ch == -1) { fprintf(stderr, "%s: Unexpected end of file (V)\n", fname); if(vbuf) free(vbuf); if(arcs) free(arcs); return -1; } switch(etype) { case ASN_BASIC_UTCTime: case ASN_BASIC_GeneralizedTime: case ASN_STRING_NumericString: case ASN_STRING_PrintableString: case ASN_STRING_VisibleString: case ASN_STRING_IA5String: case ASN_STRING_UTF8String: switch(ch) { default: if(((etype == ASN_STRING_UTF8String) || !(ch & 0x80)) && (ch >= 0x20) ) { printf("%c", ch); break; } /* Fall through */ case 0x3c: case 0x3e: case 0x26: printf("&#x%02x;", ch); } break; case ASN_BASIC_BOOLEAN: switch(ch) { case 0: printf(""); break; case 0xff: printf(""); break; default: printf("", ch); } break; case ASN_BASIC_INTEGER: case ASN_BASIC_ENUMERATED: if(i) collector = collector * 256 + ch; else collector = (int)(signed char)ch; break; default: if(vbuf) { vbuf[i] = ch; } else { printf("&#x%02x;", ch); } } } /* Do post-processing */ switch(etype) { case ASN_BASIC_INTEGER: case ASN_BASIC_ENUMERATED: printf("%" PRIdASN, collector); break; case ASN_BASIC_OBJECT_IDENTIFIER: if(vbuf) { OBJECT_IDENTIFIER_t oid; int arcno; oid.buf = vbuf; oid.size = tlv_len; arcno = OBJECT_IDENTIFIER_get_arcs(&oid, arcs, sizeof(*arcs), tlv_len + 1); if(arcno >= 0) { assert(arcno <= (tlv_len + 1)); printf(" F>"); for(i = 0; i < arcno; i++) { if(i) printf("."); printf("%" PRIuASN, arcs[i]); } free(vbuf); vbuf = 0; } } break; case ASN_BASIC_RELATIVE_OID: if(vbuf) { RELATIVE_OID_t oid; int arcno; oid.buf = vbuf; oid.size = tlv_len; arcno = RELATIVE_OID_get_arcs(&oid, arcs, sizeof(*arcs), tlv_len); if(arcno >= 0) { assert(arcno <= (tlv_len + 1)); printf(" F>"); for(i = 0; i < arcno; i++) { if(i) printf("."); printf("%" PRIuASN, arcs[i]); } free(vbuf); vbuf = 0; } } break; default: break; } /* * If the buffer was not consumed, print it out. * It might be an OCTET STRING or other primitive type, * which might actually be printable, but we need to figure it out. */ if(vbuf) { int binary; /* * Check whether the data could be represented as text */ binary = -1 * (tlv_len >> 3); /* Threshold is 12.5% binary */ for(i = 0; i < tlv_len; i++) { switch(vbuf[i]) { case 0x1b: binary = 1; break; case 0x09: case 0x0a: case 0x0d: continue; default: if(vbuf[i] < 0x20 || vbuf[i] >= 0x7f) if(++binary > 0) /* Way too many */ break; continue; } break; } printf(">"); for(i = 0; i < tlv_len; i++) { if(binary > 0 || vbuf[i] < 0x20 || vbuf[i] >= 0x7f || vbuf[i] == 0x26 /* '&' */ || vbuf[i] == 0x3c /* '<' */ || vbuf[i] == 0x3e /* '>' */ ) printf("&#x%02x;", vbuf[i]); else printf("%c", vbuf[i]); } free(vbuf); } if(arcs) free(arcs); return 0; } static int decode_tlv_from_string(const char *datastring) { unsigned char *data, *dp; size_t dsize; /* Data size */ ssize_t len; ber_tlv_tag_t tlv_tag; ber_tlv_len_t tlv_len; const char *p; int half; dsize = strlen(datastring) + 1; dp = data = calloc(1, dsize); assert(data); for(half = 0, p = datastring; *p; p++) { switch(*p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': *dp |= *p - '0'; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': *dp |= *p - 'A' + 10; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': *dp |= *p - 'a' + 10; break; case ' ': case '\t': case '\r': case '\n': continue; default: fprintf(stderr, "Unexpected symbols in data string:\n"); fprintf(stderr, "%s\n", datastring); for(dp = data; datastring < p; datastring++, dp++) *dp = ' '; *dp = '\0'; fprintf(stderr, "%s^ <- here\n", (char *)data); return -1; } if(half) dp++; else (*dp) <<= 4; half = !half; } assert((size_t)(dp - data) <= dsize); dsize = dp - data; printf("BER: "); for(dp = data; dp < data + dsize; dp++) printf("%02X", *dp); printf("\n"); len = ber_fetch_tag(data, dsize, &tlv_tag); switch(len) { case -1: fprintf(stderr, "TAG: Fatal error decoding tag\n"); return -1; case 0: fprintf(stderr, "TAG: More data expected\n"); return -1; default: printf("TAG: "); ber_tlv_tag_fwrite(tlv_tag, stdout); if(BER_TLV_CONSTRUCTED(data)) { printf(" (constructed)"); } else if(dsize >= 2 && data[0] == 0 && data[1] == 0) { printf(" (end-of-content)"); } else { printf(" (primitive)"); } if(BER_TAG_CLASS(tlv_tag) == ASN_TAG_CLASS_UNIVERSAL) { const char *str; ber_tlv_tag_t tvalue = BER_TAG_VALUE(tlv_tag); str = ASN_UNIVERSAL_TAG2STR(tvalue); if(str) printf(" \"%s\"", str); } printf("\n"); } if(dsize > (size_t)len) { len = ber_fetch_length(BER_TLV_CONSTRUCTED(data), data + len, dsize - len, &tlv_len); switch(len) { case -1: fprintf(stderr, "LEN: Fatal error decoding length\n"); return -1; case 0: fprintf(stderr, "LEN: More data expected\n"); return -1; default: if(tlv_len == (ber_tlv_len_t)-1) printf("LEN: Indefinite length encoding\n"); else printf("LEN: %ld bytes\n", (long)tlv_len); } } return 0; } /* * Dummy functions. */ asn_dec_rval_t ber_check_tags(asn_codec_ctx_t *opt_codec_ctx, asn_TYPE_descriptor_t *td, asn_struct_ctx_t *opt_ctx, const void *ptr, size_t size, int tag_mode, int last_tag_form, ber_tlv_len_t *last_length, int *opt_tlv_form) { asn_dec_rval_t rv = { 0, 0 }; (void)opt_codec_ctx; (void)td; (void)opt_ctx; (void)ptr; (void)size; (void)tag_mode; (void)last_tag_form; (void)last_length; (void)opt_tlv_form; return rv; } ssize_t der_write_tags(asn_TYPE_descriptor_t *td, size_t slen, int tag_mode, int last_tag_form, ber_tlv_tag_t tag, asn_app_consume_bytes_f *cb, void *app_key) { (void)td; (void)slen; (void)tag_mode; (void)last_tag_form; (void)tag; (void)cb; (void)app_key; return -1; } asn_dec_rval_t xer_decode_general(asn_codec_ctx_t *opt_codec_ctx, asn_struct_ctx_t *ctx, void *struct_key, const char *xml_tag, const void *buf_ptr, size_t size, int (*otd)(void *struct_key, const void *chunk_buf, size_t chunk_size), ssize_t (*br)(void *struct_key, const void *chunk_buf, size_t chunk_size, int have_more)) { asn_dec_rval_t rv = { 0, 0 }; (void)opt_codec_ctx; (void)ctx; (void)struct_key; (void)xml_tag; (void)buf_ptr; (void)size; (void)otd; (void)br; return rv; } int xer_is_whitespace(const void *b, size_t s) { (void)b; (void)s; return 0; }