diff --git a/.gitignore b/.gitignore index 09275afe..337f040d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,8 @@ stamp-h* /asn1c/asn1c # /asn1c-tools -/asn1-tools/enber -/asn1-tools/unber +/asn1-tools/enber/enber +/asn1-tools/unber/unber # /skeletons /skeletons/check-* diff --git a/asn1-tools/unber/Makefile.am b/asn1-tools/unber/Makefile.am index 938bea1d..465b169e 100644 --- a/asn1-tools/unber/Makefile.am +++ b/asn1-tools/unber/Makefile.am @@ -6,6 +6,12 @@ AM_CPPFLAGS = \ -I${top_srcdir}/libasn1parser \ -I${top_srcdir}/skeletons -unber_LDADD = $(top_builddir)/libasn1common/libasn1common.la +noinst_LTLIBRARIES = libasn1-unber-tool.la + +libasn1_unber_tool_la_SOURCES = \ + libasn1_unber_tool.c libasn1_unber_tool.h + +unber_LDADD = libasn1-unber-tool.la \ + $(top_builddir)/libasn1common/libasn1common.la bin_PROGRAMS = unber diff --git a/asn1-tools/unber/libasn1_unber_tool.c b/asn1-tools/unber/libasn1_unber_tool.c new file mode 100644 index 00000000..d9567726 --- /dev/null +++ b/asn1-tools/unber/libasn1_unber_tool.c @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2004, 2005, 2006 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. + * + */ +#include "asn1_common.h" +#include "libasn1_unber_tool.h" + +#define ASN_DISABLE_PER_SUPPORT 1 +#define ASN_DISABLE_OER_SUPPORT 1 + +#include /* For static string tables */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 long skip_bytes = 0; /* -s controls that */ +static char indent_bytes[16] = " "; /* -i controls that */ + +void set_minimalistic_output(int v) { minimalistic = v; } +void set_single_type_decoding(int v) { single_type_decoding = v; } +void set_pretty_printing(int v) { pretty_printing = v; } +int set_skip_bytes(long v) { if(v < 0) return -1; skip_bytes = v; return 0; } +int set_indent_size(int indent_size) { + if(indent_size < 0 || indent_size >= (int)sizeof(indent_bytes)) { + return -1; + } + memset(indent_bytes, ' ', indent_size); + indent_bytes[indent_size] = '\0'; + return 0; +} + +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, + size_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, size_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 +unber_stream(const char *fname, FILE *fp) { + pd_code_e pdc; + size_t offset = 0; /* Stream decoding position */ + ber_tlv_len_t frame_size = 0; /* Single frame size */ + + /* + * Skip the requested amount of bytes. + */ + for(; offset < (size_t)skip_bytes; offset++) { + if(fgetc(fp) == -1) { + fprintf(stderr, "%s: input source (%zu bytes) " + "has less data than \"-s %ld\" switch " + "wants to skip\n", + fname, offset, skip_bytes); + 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(pdc == PD_FAILED) return -1; + return 0; +} + +/* + * Process the TLV recursively. + */ +static pd_code_e +process_deeper(const char *fname, FILE *fp, size_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 %zu. " + "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 %zu\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 %zu+%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 %zu\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] && !tagbuf[1]) { + /* 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 && !expect_eoc) 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, size_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) fputs(indent_bytes, stdout); /* 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) { + asn_oid_arc_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) FREEMEM(vbuf); + if(arcs) FREEMEM(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("%s", asn1p_itoa(collector)); + break; + case ASN_BASIC_OBJECT_IDENTIFIER: + if(vbuf) { + OBJECT_IDENTIFIER_t oid = {0, 0}; + ssize_t arcno; + + oid.buf = vbuf; + oid.size = tlv_len; + + arcno = OBJECT_IDENTIFIER_get_arcs(&oid, arcs, tlv_len + 1); + if(arcno >= 0) { + assert(arcno <= (tlv_len + 1)); + printf(" F>"); + for(i = 0; i < arcno; i++) { + if(i) printf("."); + printf("%" PRIu32, arcs[i]); + } + FREEMEM(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, tlv_len); + if(arcno >= 0) { + assert(arcno <= tlv_len); + printf(" F>"); + for(i = 0; i < arcno; i++) { + if(i) printf("."); + printf("%" PRIu32, arcs[i]); + } + FREEMEM(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]); + } + FREEMEM(vbuf); + } + + if(arcs) FREEMEM(arcs); + return 0; +} + +int +unber_file(const char *fname) { + FILE *fp; + + if(strcmp(fname, "-")) { + fp = fopen(fname, "rb"); + if(!fp) { + perror(fname); + return -1; + } + } else { + fp = stdin; + } + + int ret = unber_stream(fname, fp); + + if(fp != stdin) fclose(fp); + + return ret; +} + +int +decode_tlv_from_hex_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++) { + /* clang-format off */ + 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; + } + /* clang-format on */ + 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(const asn_codec_ctx_t *opt_codec_ctx, + const 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(const 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(const 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; +} + +size_t +xer_whitespace_span(const void *chunk_buf, size_t chunk_size) { + (void)chunk_buf; + (void)chunk_size; + return 0; +} + +int +OCTET_STRING_compare(const asn_TYPE_descriptor_t *td, const void *a, + const void *b) { + (void)td; + (void)a; + (void)b; + return 0; +} + +intmax_t +asn_random_between(intmax_t a, intmax_t b) { + (void)b; + return a; +}; + diff --git a/asn1-tools/unber/libasn1_unber_tool.h b/asn1-tools/unber/libasn1_unber_tool.h new file mode 100644 index 00000000..316db82f --- /dev/null +++ b/asn1-tools/unber/libasn1_unber_tool.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2004-2019 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. + * + */ + +/* Set global options. */ +void set_minimalistic_output(int v); +void set_single_type_decoding(int v); +void set_pretty_printing(int v); +int set_skip_bytes(long v); +int set_indent_size(int indent_size); + +/* + * Convert BER-encoded file into the low level non-standard XML-like structure. + * Primarily used for manual debugging. + */ +int unber_file(const char *fname); + +/* + * Decode the TLV given by the given string. + */ +int decode_tlv_from_hex_string(const char *datastring); + diff --git a/asn1-tools/unber/unber.c b/asn1-tools/unber/unber.c index c707c983..6126b154 100644 --- a/asn1-tools/unber/unber.c +++ b/asn1-tools/unber/unber.c @@ -25,73 +25,45 @@ * */ #include "asn1_common.h" - -#define ASN_DISABLE_PER_SUPPORT 1 -#define ASN_DISABLE_OER_SUPPORT 1 - -#include /* For static string tables */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "libasn1_unber_tool.h" #undef COPYRIGHT -#define COPYRIGHT "Copyright (c) 2004, 2005 Lev Walkin \n" +#define COPYRIGHT "Copyright (c) 2004-2019 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 long skip_bytes = 0; /* -s controls that */ -static char indent_bytes[16] = " "; /* -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) { + while((ch = getopt(ac, av, "1hi:mps:t:v")) != -1) { + switch(ch) { case '1': - single_type_decoding = 1; + set_single_type_decoding(1); break; case 'i': - i = atoi(optarg); - if(i >= 0 && i < (int)sizeof(indent_bytes)) { - memset(indent_bytes, ' ', i); - indent_bytes[i] = '\0'; - } else { + if(set_indent_size(atoi(optarg)) != 0) { fprintf(stderr, "-i %s: Invalid indent value\n", optarg); exit(EX_USAGE); } break; case 'm': - minimalistic = 1; + set_minimalistic_output(1); break; case 'p': - pretty_printing = 0; + set_pretty_printing(0); break; case 's': - skip_bytes = atol(optarg); - if(skip_bytes < 0) { + if(set_skip_bytes(atol(optarg)) != 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); + if(decode_tlv_from_hex_string(optarg)) exit(EX_DATAERR); exit(0); case 'v': fprintf(stderr, "ASN.1 BER Decoder, v" VERSION "\n" COPYRIGHT); @@ -101,6 +73,7 @@ main(int ac, char **av) { default: usage(av[0]); } + } /* * Ensure that there are some input files present. @@ -119,8 +92,8 @@ main(int ac, char **av) { * 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); + for(int i = 0; i < ac; i++) { + if(unber_file(av[i])) exit(EX_DATAERR); } return 0; @@ -159,687 +132,3 @@ usage(const char *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, - size_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, size_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; - size_t offset = 0; /* Stream decoding position */ - ber_tlv_len_t frame_size = 0; /* Single frame size */ - - if(strcmp(fname, "-")) { - fp = fopen(fname, "rb"); - if(!fp) { - perror(fname); - return -1; - } - } else { - fp = stdin; - } - - /* - * Skip the requested amount of bytes. - */ - for(; offset < (size_t)skip_bytes; offset++) { - if(fgetc(fp) == -1) { - fprintf(stderr, "%s: input source (%zu bytes) " - "has less data than \"-s %ld\" 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, size_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 %zu. " - "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 %zu\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 %zu+%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 %zu\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] && !tagbuf[1]) { - /* 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 && !expect_eoc) 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, size_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) fputs(indent_bytes, stdout); /* 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) { - asn_oid_arc_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) FREEMEM(vbuf); - if(arcs) FREEMEM(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("%s", asn1p_itoa(collector)); - break; - case ASN_BASIC_OBJECT_IDENTIFIER: - if(vbuf) { - OBJECT_IDENTIFIER_t oid = {0, 0}; - ssize_t arcno; - - oid.buf = vbuf; - oid.size = tlv_len; - - arcno = OBJECT_IDENTIFIER_get_arcs(&oid, arcs, tlv_len + 1); - if(arcno >= 0) { - assert(arcno <= (tlv_len + 1)); - printf(" F>"); - for(i = 0; i < arcno; i++) { - if(i) printf("."); - printf("%" PRIu32, arcs[i]); - } - FREEMEM(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, tlv_len); - if(arcno >= 0) { - assert(arcno <= tlv_len); - printf(" F>"); - for(i = 0; i < arcno; i++) { - if(i) printf("."); - printf("%" PRIu32, arcs[i]); - } - FREEMEM(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]); - } - FREEMEM(vbuf); - } - - if(arcs) FREEMEM(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++) { - /* clang-format off */ - 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; - } - /* clang-format on */ - 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(const asn_codec_ctx_t *opt_codec_ctx, - const 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(const 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(const 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; -} - -size_t -xer_whitespace_span(const void *chunk_buf, size_t chunk_size) { - (void)chunk_buf; - (void)chunk_size; - return 0; -} - -int -OCTET_STRING_compare(const asn_TYPE_descriptor_t *td, const void *a, - const void *b) { - (void)td; - (void)a; - (void)b; - return 0; -} - -intmax_t -asn_random_between(intmax_t a, intmax_t b) { - (void)b; - return a; -}; -