/* C-lookalike text to ASN.1 BER encoder * * 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 structured input like the following: * * SEQUENCE { * CONT_3 { * INTEGER(1234); * } * } * * and the program will emit BER-encoded ASN.1 output. * * 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 #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; struct segment { struct segment *next; struct segment *children; struct segment **children_pp; unsigned asn1_len; unsigned size; octet data[]; }; static const char *const asn1_universal_tags[32] = { NULL, // 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 line = 1, col = -1; #define error(FMT, ...) \ do {\ fprintf(stderr, "Error: %u:%u: "FMT, line, col, ## __VA_ARGS__); \ exit(1); \ } while (0) static inline void unget_char(char c) { col--; ungetc(c, stdin); } static inline char get_char(bool skip_space) { int c; col++; c = fgetc(stdin); if (c == EOF) error("Reading char: %m\n"); if (!isspace(c)) return c; /* Condense whitespace here */ for (;;) { if (c == '\n') { line++; col = -1; } col++; c = fgetc(stdin); if (c == EOF) { if (!skip_space && feof(stdin)) return ' '; error("Reading char: %m\n"); } if (!isspace(c)) { if (skip_space) return c; unget_char(c); return ' '; } } } unsigned get_word(char *buf, size_t max) { char *p = buf, *end = buf + max - 1; char c; c = get_char(true); if (!isalpha(c)) error("Expected word, got '%c'\n", c); *p++ = c; for (;;) { c = get_char(false); if (isalnum(c) || c == '_') { if (p >= end) error("Word too large\n"); *p++ = c; continue; } if (!isspace(c)) unget_char(c); *p = 0; return p - buf; } } /* * Encode the data of a primitive. */ struct segment *encode_primitive(void) { struct segment *asn1 = NULL; octet x, y; char c; int cap = 0, n = 0; for (;;) { c = get_char(true); if (c == ')') break; if (c == ',') continue; if (!isxdigit(c)) error("Expected hex digit in primitive (got '%c')\n", c); c = tolower(c); x = c <= '9' ? c - '0' : c - 'a' + 0xa; /* Allocate a segment to hold the ASN.1 header */ if (n >= cap) { cap += 32; asn1 = realloc(asn1, sizeof(struct segment) + cap); if (!asn1) perror(NULL); if (n == 0) { asn1->next = NULL; asn1->children = NULL; asn1->children_pp = &asn1->children; asn1->asn1_len = 0; asn1->size = 0; } } c = get_char(false); if (!isxdigit(c)) error("Odd number of hex digits"); c = tolower(c); y = c <= '9' ? c - '0' : c - 'a' + 0xa; asn1->data[n++] = (x << 4) | y; } if (asn1) asn1->asn1_len = asn1->size = n; return asn1; } /* * Encode a single object. */ struct segment *encode_one_object(void) { struct segment *asn1, *eoc, *child; unsigned tag, len; char buf[128], c; bool indef = false; int i; /* Allocate a segment to hold the ASN.1 header */ asn1 = malloc(sizeof(struct segment) + 6); if (!asn1) perror(NULL); asn1->next = NULL; asn1->children = NULL; asn1->children_pp = &asn1->children; asn1->asn1_len = 0; asn1->size = 0; /* Start off by finding the tag */ get_word(buf, sizeof(buf)); if (sscanf(buf, "PRIV_%u", &tag) == 1) { if (tag >= ASN1_LONG_TAG) error("PRIV long tag (%u)\n", tag); tag |= ASN1_PRIV << 6; } else if (sscanf(buf, "APPL_%u", &tag) == 1) { if (tag >= ASN1_LONG_TAG) error("APPL long tag (%u)\n", tag); tag |= ASN1_APPL << 6; } else if (sscanf(buf, "CONT_%u", &tag) == 1) { if (tag >= ASN1_LONG_TAG) error("CONT long tag (%u)\n", tag); tag |= ASN1_CONT << 6; } else if (sscanf(buf, "UNIV_%u", &tag) == 1) { if (tag >= ASN1_LONG_TAG) error("UNIV long tag (%u)\n", tag); tag |= ASN1_UNIV << 6; } else { for (i = 0; i < 32; i++) { if (!asn1_universal_tags[i]) continue; if (strcmp(asn1_universal_tags[i], buf) != 0) continue; break; } if (i >= 32) error("Unrecognised tag (%s)\n", buf); tag = ASN1_UNIV | i; } c = get_char(true); if (c == '.') { get_word(buf, sizeof(buf)); if (strcmp(buf, "indef") != 0) error("Expected '.indef', got '.%s'\n", buf); indef = true; c = get_char(true); } if (c == '(') { /* Primitive */ tag |= ASN1_PRIM << 5; if (indef) error("Indefinite primitive is not allowed\n"); } else if (c == '{') { /* Constructed */ tag |= ASN1_CONS << 5; } else { error("Expected '(' or '{' after tag\n"); } asn1->data[0] = tag; if (tag & ASN1_CONS_BIT) { len = 0; for (;;) { c = get_char(true); if (c == '}') break; unget_char(c); child = encode_one_object(); len += child->asn1_len; *asn1->children_pp = child; asn1->children_pp = &child->next; } } else { asn1->children = encode_primitive(); c = get_char(true); if (c != ';') error("Expected semicolon at end of primitive (got '%c')\n", c); len = asn1->children ? asn1->children->asn1_len : 0; } if (!indef) { if (len <= 0x7f) { asn1->data[1] = len; asn1->size = 2; } else if (len <= 0xff) { asn1->data[1] = 0x81; asn1->data[2] = len; asn1->size = 3; } else if (len <= 0xffff) { asn1->data[1] = 0x82; asn1->data[2] = (len >> 8) & 0xff; asn1->data[3] = (len >> 0) & 0xff; asn1->size = 4; } else if (len <= 0xffffff) { asn1->data[1] = 0x83; asn1->data[2] = (len >> 16) & 0xff; asn1->data[3] = (len >> 8) & 0xff; asn1->data[4] = (len >> 0) & 0xff; asn1->size = 5; } else { asn1->data[1] = 0x84; asn1->data[2] = (len >> 24) & 0xff; asn1->data[3] = (len >> 16) & 0xff; asn1->data[4] = (len >> 8) & 0xff; asn1->data[5] = (len >> 0) & 0xff; asn1->size = 6; } } else { asn1->data[1] = ASN1_INDEFINITE_LENGTH; asn1->size = 2; /* Allocate an EOC segment */ eoc = malloc(sizeof(struct segment) + 2); if (!eoc) perror(NULL); eoc->next = NULL; eoc->children = NULL; eoc->asn1_len = eoc->size = 2; eoc->data[0] = ASN1_EOC; eoc->data[1] = 0; *asn1->children_pp = eoc; asn1->children_pp = &eoc->next; } asn1->asn1_len = asn1->size + len; return asn1; } /* * Turn the segment tree into ASN.1 output. */ void render(const struct segment *asn1) { const struct segment *p; fwrite(asn1->data, asn1->size, 1, stdout); for (p = asn1->children; p; p = p->next) render(p); } /* * */ int main(int argc, char **argv) { struct segment *asn1 = NULL, *p, **pp = &asn1; int c; if (argc != 1) { fprintf(stderr, "Format: %s der-file\n", argv[0]); exit(2); } for (;;) { p = encode_one_object(); *pp = p; pp = &p->next; while (c = fgetc(stdin), c != EOF && isspace(c)) {} if (c == EOF) { if (feof(stdin)) break; error("Reading next char: %m\n"); } unget_char(c); } render(asn1); return 0; }