/* ASN.1 to C-lookalike text decoder. * * Copyright (C) 2015 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public Licence * as published by the Free Software Foundation; either version * 2 of the Licence, or (at your option) any later version. * * * Pipe in BER-encoded ASN.1 and the program will dump structured output that * looks like: * * SEQUENCE { * CONT_3 { * INTEGER(1234); * } * } * * Where: * * (*) "tag { ... }" denotes a constructed form containing some other objects. * * (*) "tag.indef { ... }" denotes an indefinite-length constructed form * containing some other objects. * * (*) "tag(xxxxxx);" denotes a primitive form, with the contents in hex. * * The tags are encoded thus: * * (*) UNIV_n, APPL_n, CONT_n, PRIV_n - Class given before the underscore, * numeric tag value after. * * (*) SEQUENCE, SET, BOOLEAN, INTEGER, ... - Universal tag aliases. These * are emitted in preference to UNIV_n where available. * * The output can be passed through the asn1encode counterpart program to turn * it back into DER-encoded ASN.1. */ #include #include #include #include /* Class */ enum asn1_class { ASN1_UNIV = 0, /* Universal */ ASN1_APPL = 1, /* Application */ ASN1_CONT = 2, /* Context */ ASN1_PRIV = 3 /* Private */ }; #define ASN1_CLASS_BITS 0xc0 enum asn1_method { ASN1_PRIM = 0, /* Primitive */ ASN1_CONS = 1 /* Constructed */ }; #define ASN1_CONS_BIT 0x20 /* Tag */ enum asn1_tag { ASN1_EOC = 0, /* End Of Contents or N/A */ ASN1_BOOL = 1, /* Boolean */ ASN1_INT = 2, /* Integer */ ASN1_BTS = 3, /* Bit String */ ASN1_OTS = 4, /* Octet String */ ASN1_NULL = 5, /* Null */ ASN1_OID = 6, /* Object Identifier */ ASN1_ODE = 7, /* Object Description */ ASN1_EXT = 8, /* External */ ASN1_REAL = 9, /* Real float */ ASN1_ENUM = 10, /* Enumerated */ ASN1_EPDV = 11, /* Embedded PDV */ ASN1_UTF8STR = 12, /* UTF8 String */ ASN1_RELOID = 13, /* Relative OID */ /* 14 - Reserved */ /* 15 - Reserved */ ASN1_SEQ = 16, /* Sequence and Sequence of */ ASN1_SET = 17, /* Set and Set of */ ASN1_NUMSTR = 18, /* Numerical String */ ASN1_PRNSTR = 19, /* Printable String */ ASN1_TEXSTR = 20, /* T61 String / Teletext String */ ASN1_VIDSTR = 21, /* Videotex String */ ASN1_IA5STR = 22, /* IA5 String */ ASN1_UNITIM = 23, /* Universal Time */ ASN1_GENTIM = 24, /* General Time */ ASN1_GRASTR = 25, /* Graphic String */ ASN1_VISSTR = 26, /* Visible String */ ASN1_GENSTR = 27, /* General String */ ASN1_UNISTR = 28, /* Universal String */ ASN1_CHRSTR = 29, /* Character String */ ASN1_BMPSTR = 30, /* BMP String */ ASN1_LONG_TAG = 31 /* Long form tag */ }; #define ASN1_INDEFINITE_LENGTH 0x80 typedef unsigned char octet; static const char *const asn1_universal_tags[32] = { "EOC", "BOOLEAN", "INTEGER", "BIT_STRING", "OCTET_STRING", "NULL", "OBJECT", "OBJECT_DESC", "EXTERNAL", "REAL", "ENUM", "EPDV", "UTF8STRING", "RELOID", "UNIV_14", "UNIV_15", "SEQUENCE", "SET", "NUMSTR", "PRNSTR", "TEXSTR", "VIDSTR", "IA5STR", "UNITIM", "GENTIM", "GRASTR", "VISSTR", "GENSTR", "UNISTR", "CHRSTR", "BMPSTR", NULL /* LONG TAG */ }; static unsigned fpos = 0; #define error(FMT, ...) \ do {\ printf("\n"); \ fflush(stdout); \ fprintf(stderr, "Error: octet %u: "FMT, fpos, ## __VA_ARGS__); \ exit(1); \ } while (0) static inline octet get_octet(void) { int c; fpos++; c = fgetc(stdin); if (c == EOF) error("Reading octet: %m\n"); return c; } static void indent(int level) { int i; for (i = 0; i < level; i++) printf(" "); } static unsigned decode_one_object(unsigned limit, int level); /* * Dump a primitive object. */ static void dump_primitive(unsigned size, int level) { unsigned i, seg; if (size == 0) { printf("();\n"); return; } if (size <= 32) { printf("("); for (i = 0; i < size; i++) printf("%02x", get_octet()); printf(");\n"); return; } printf("(\n"); while (size > 0) { indent(level + 1); seg = (size > 32) ? 32 : size; size -= seg; for (i = 0; i < seg; i++) printf("%02x", get_octet()); if (size > 0) printf(",\n"); else printf("\n"); } indent(level); printf(");\n"); } /* * Decode a constructed object. */ static unsigned decode_cons(unsigned size, bool indef, unsigned limit, int level) { unsigned total = 0; octet c, l; if (indef) { for (;;) { if (limit < UINT_MAX && limit - size < 2) error("Indef Cons object does not end in EOC\n"); c = get_octet(); if (c == ASN1_EOC) { l = get_octet(); if (l != 0) error("Corrupt ASN.1 EOC tag (%02x:%02x)\n", c, l); total += 2; return total; } ungetc(c, stdin); } } else { while (total < size) { if (size - total < 2) error("Cons object with short content\n"); total += decode_one_object(size - total, level); } if (size != total) error("Cons object overran (%u != %u)\n", size, total); return total; } } /* * Decode one ASN.1 object. */ static unsigned decode_one_object(unsigned limit, int level) { unsigned size, hdrsize = 2; octet tag, len; bool indef = false; int i; indent(level); tag = get_octet(); len = get_octet(); if ((tag & 0x1f) == ASN1_LONG_TAG) error("ASN.1 Long Tag not supported\n"); switch ((enum asn1_class)(tag >> 6)) { case ASN1_UNIV: printf("%s", asn1_universal_tags[tag & 0x1f]); break; case ASN1_APPL: printf("APPL_%u", tag & 0x1f); break; case ASN1_CONT: printf("CONT_%u", tag & 0x1f); break; case ASN1_PRIV: printf("PRIV_%u", tag & 0x1f); break; } size = 0; switch (len) { case 0 ... 0x7f: size = len; break; case ASN1_INDEFINITE_LENGTH: if (!(tag & ASN1_CONS_BIT)) error("ASN.1 Indef length with Prim object (%02x:%02x) not supported\n", tag, len); printf(".indef"); indef = true; size = 0; break; case 0x81 ... 0x84: hdrsize += len - 0x80; for (i = len - 0x80; i > 0; i--){ size <<= 8; size |= get_octet(); } break; case 0x85 ... 0xfe: error("ASN.1 Length > 4 octets (%02x:%02x) not supported\n", tag, len); case 0xff: error("ASN.1 Reserved Length (%02x:%02x) not supported\n", tag, len); } if (hdrsize + size > limit) error("ASN.1 object (%02x:%02x) exceeds parent frame size (%u>%u)\n", tag, len, hdrsize + size, limit); if (tag & ASN1_CONS_BIT) { /* Constructed */ unsigned cons_limit; if (!indef) { cons_limit = size; } else { if (limit == UINT_MAX) cons_limit = limit; else cons_limit = limit - hdrsize; } printf(" {\n"); size = decode_cons(size, indef, cons_limit, level + 1); indent(level); printf("}\n"); } else { /* Primitive */ dump_primitive(size, level); } return hdrsize + size; } /* * */ int main(int argc, char **argv) { int c; if (argc != 1) { fprintf(stderr, "Format: %s c-file\n", argv[0]); exit(2); } for (;;) { decode_one_object(UINT_MAX, 0); c = fgetc(stdin); if (c == EOF) { if (feof(stdin)) return 0; error("Reading next octet: %m\n"); } ungetc(c, stdin); } }