Browse Source

ASN.1 print: implementation to parse and print added

New API to parse and print DER/BER data from a buffer.
Add an example to parse DER, Base64 and PEM files and print out ASN.1 items.
Sean Parkinson 1 year ago
parent
commit
9cdee20a7d

+ 1 - 0
.gitignore

@@ -73,6 +73,7 @@ examples/sctp/sctp-server
 examples/sctp/sctp-server-dtls
 examples/sctp/sctp-client
 examples/sctp/sctp-client-dtls
+examples/asn1/asn1
 server_ready
 snifftest
 output

+ 21 - 0
configure.ac

@@ -4016,6 +4016,26 @@ else
     ENABLED_BIGNUM="yes"
 fi
 
+case $host_os in
+*linux* | *darwin* | *freebsd*)
+    DEF_ASN_PRINT="yes"
+    ;;
+*)
+    DEF_ASN_PRINT="no"
+    ;;
+esac
+
+AC_ARG_ENABLE([asn-print],
+    [AS_HELP_STRING([--enable-asn-print],[Enable ASN Print API (default: enabled)])],
+    [ ENABLED_ASN_PRINT=$enableval ],
+    [ ENABLED_ASN_PRINT=$DEF_ASN_PRINT ]
+    )
+
+if test "$ENABLED_ASN_PRINT" = "yes"
+then
+    AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ASN_PRINT"
+fi
+
 
 # AES
 AC_ARG_ENABLE([aes],
@@ -8496,6 +8516,7 @@ AM_CONDITIONAL([BUILD_FASTMATH],[test "x$ENABLED_FASTMATH" = "xyes" || test "x$E
 AM_CONDITIONAL([BUILD_HEAPMATH],[test "x$ENABLED_HEAPMATH" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
 AM_CONDITIONAL([BUILD_EXAMPLE_SERVERS],[test "x$ENABLED_EXAMPLES" = "xyes" && test "x$ENABLED_LEANTLS" = "xno"])
 AM_CONDITIONAL([BUILD_EXAMPLE_CLIENTS],[test "x$ENABLED_EXAMPLES" = "xyes"])
+AM_CONDITIONAL([BUILD_EXAMPLE_ASN1],[test "x$ENABLED_EXAMPLES" = "xyes"] && [test "x$ENABLED_ASN_PRINT" = "xyes"] && [test "x$ENABLED_ASN" = "xyes"])
 AM_CONDITIONAL([BUILD_TESTS],[test "x$ENABLED_EXAMPLES" = "xyes"])
 AM_CONDITIONAL([BUILD_THREADED_EXAMPLES],[test "x$ENABLED_SINGLETHREADED" = "xno" && test "x$ENABLED_EXAMPLES" = "xyes" && test "x$ENABLED_LEANTLS" = "xno"])
 AM_CONDITIONAL([BUILD_WOLFCRYPT_TESTS],[test "x$ENABLED_CRYPT_TESTS" = "xyes"])

+ 145 - 0
doc/dox_comments/header_files/asn_public.h

@@ -2167,3 +2167,148 @@ int wc_SetUnknownExtCallback(DecodedCert* cert,
 int wc_CheckCertSigPubKey(const byte* cert, word32 certSz,
                                       void* heap, const byte* pubKey,
                                       word32 pubKeySz, int pubKeyOID);
+
+/*!
+    \ingroup ASN
+
+    \brief This function initializes the ASN.1 print options.
+
+    \return  0 on success.
+    \return  BAD_FUNC_ARG when asn1 is NULL.
+
+    \param opts  The ASN.1 options for printing.
+
+    _Example_
+    \code
+    Asn1PrintOptions opt;
+
+    // Initialize ASN.1 print options before use.
+    wc_Asn1PrintOptions_Init(&opt);
+    \endcode
+
+    \sa wc_Asn1PrintOptions_Set
+    \sa wc_Asn1_PrintAll
+*/
+int wc_Asn1PrintOptions_Init(Asn1PrintOptions* opts);
+
+/*!
+    \ingroup ASN
+
+    \brief This function sets a print option into an ASN.1 print options object.
+
+    \return  0 on success.
+    \return  BAD_FUNC_ARG when asn1 is NULL.
+    \return  BAD_FUNC_ARG when val is out of range for option.
+ 
+    \param opts  The ASN.1 options for printing.
+    \param opt   An option to set value for.
+    \param val   The value to set.
+
+    _Example_
+    \code
+    Asn1PrintOptions opt;
+
+    // Initialize ASN.1 print options before use.
+    wc_Asn1PrintOptions_Init(&opt);
+    // Set the number of indents when printing tag name to be 1.
+    wc_Asn1PrintOptions_Set(&opt, ASN1_PRINT_OPT_INDENT, 1);
+    \endcode
+
+    \sa wc_Asn1PrintOptions_Init
+    \sa wc_Asn1_PrintAll
+*/
+int wc_Asn1PrintOptions_Set(Asn1PrintOptions* opts, enum Asn1PrintOpt opt,
+    word32 val);
+
+/*!
+    \ingroup ASN
+
+    \brief This function initializes an ASN.1 parsing object.
+
+    \return  0 on success.
+    \return  BAD_FUNC_ARG when asn1 is NULL.
+
+    \param asn1  ASN.1 parse object.
+
+    _Example_
+    \code
+    Asn1 asn1;
+
+    // Initialize ASN.1 parse object before use.
+    wc_Asn1_Init(&asn1);
+    \endcode
+
+    \sa wc_Asn1_SetFile
+    \sa wc_Asn1_PrintAll
+ */
+int wc_Asn1_Init(Asn1* asn1);
+
+/*!
+    \ingroup ASN
+
+    \brief This function sets the file to use when printing into an ASN.1
+    parsing object.
+
+    \return  0 on success.
+    \return  BAD_FUNC_ARG when asn1 is NULL.
+    \return  BAD_FUNC_ARG when file is XBADFILE.
+
+    \param asn1  The ASN.1 parse object.
+    \param file  File to print to.
+
+    _Example_
+    \code
+    Asn1 asn1;
+
+    // Initialize ASN.1 parse object before use.
+    wc_Asn1_Init(&asn1);
+    // Set standard out to be the file descriptor to write to.
+    wc_Asn1_SetFile(&asn1, stdout);
+    \endcode
+
+    \sa wc_Asn1_Init
+    \sa wc_Asn1_PrintAll
+ */
+int wc_Asn1_SetFile(Asn1* asn1, XFILE file);
+
+/*!
+    \ingroup ASN
+
+    \brief Print all ASN.1 items.
+
+    \return  0 on success.
+    \return  BAD_FUNC_ARG when asn1 or opts is NULL.
+    \return  ASN_LEN_E when ASN.1 item's length too long.
+    \return  ASN_DEPTH_E when end offset invalid.
+    \return  ASN_PARSE_E when not all of an ASN.1 item parsed.
+
+    \param asn1  The ASN.1 parse object.
+    \param opts  The ASN.1 print options.
+    \param data  Buffer containing BER/DER data to print.
+    \param len   Length of data to print in bytes.
+
+    \code
+    Asn1PrintOptions opts;
+    Asn1 asn1;
+    unsigned char data[] = { Initialize with DER/BER data };
+    word32 len = sizeof(data);
+
+    // Initialize ASN.1 print options before use.
+    wc_Asn1PrintOptions_Init(&opt);
+    // Set the number of indents when printing tag name to be 1.
+    wc_Asn1PrintOptions_Set(&opt, ASN1_PRINT_OPT_INDENT, 1);
+
+    // Initialize ASN.1 parse object before use.
+    wc_Asn1_Init(&asn1);
+    // Set standard out to be the file descriptor to write to.
+    wc_Asn1_SetFile(&asn1, stdout);
+    // Print all ASN.1 items in buffer with the specified print options.
+    wc_Asn1_PrintAll(&asn1, &opts, data, len);
+    \endcode
+
+    \sa wc_Asn1_Init
+    \sa wc_Asn1_SetFile
+ */
+int wc_Asn1_PrintAll(Asn1* asn1, Asn1PrintOptions* opts, unsigned char* data,
+    word32 len);
+

+ 494 - 0
examples/asn1/asn1.c

@@ -0,0 +1,494 @@
+/* asn1.c
+ *
+ * Copyright (C) 2006-2023 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#ifdef HAVE_CONFIG_H
+    #include <config.h>
+#endif
+
+#ifndef WOLFSSL_USER_SETTINGS
+    #include <wolfssl/options.h>
+#endif
+#include <wolfssl/wolfcrypt/settings.h>
+#include <wolfssl/wolfcrypt/asn_public.h>
+#include <wolfssl/wolfcrypt/coding.h>
+#include <wolfssl/wolfcrypt/error-crypt.h>
+
+#ifdef WOLFSSL_ASN_PRINT
+
+/* Increment allocated data by this much. */
+#define DATA_INC_LEN    256
+
+
+/* File format is DER/BER. */
+#define FORMAT_DER     0
+/* File format is BASE64. */
+#define FORMAT_BASE64  1
+/* File format is PEM. */
+#define FORMAT_PEM     2
+
+/* ASN.1 print options. */
+static Asn1PrintOptions opts;
+/* ASN.1 parsing state. */
+static Asn1 asn1;
+
+/* Read the contents of a file into a dynamically allocated buffer.
+ *
+ * Uses realloc as input may be stdin.
+ *
+ * @param [in]  fp     File pointer to read from.
+ * @param [out] pdata  Pointer to data.
+ * @param [out] plen   Pointer to length.
+ * @return  0 on success.
+ * @return  1 on failure.
+ */
+static int ReadFile(FILE* fp, unsigned char** pdata, word32* plen)
+{
+    int ret = 0;
+    word32 len = 0;
+    size_t read_len;
+    /* Allocate a minimum amount. */
+    unsigned char* data = (unsigned char*)malloc(DATA_INC_LEN);
+
+    if (data != NULL) {
+        /* Read more data. */
+        while ((read_len = fread(data + len, 1, DATA_INC_LEN, fp)) != 0) {
+            unsigned char* p;
+
+            /* Add read data amount to length. */
+            len += (word32)read_len;
+
+            /* Stop if we are at end-of-file. */
+            if (feof(fp)) {
+                break;
+            }
+
+            /* Make space for more data to be added to buffer. */
+            p = (unsigned char*)realloc(data, len + DATA_INC_LEN);
+            if (p == NULL) {
+                /* Reallocation failed - free current buffer. */
+                free(data);
+                data = NULL;
+                break;
+            }
+            /* Set data to new pointer. */
+            data = p;
+        }
+        /* Done with file. */
+        fclose(fp);
+    }
+
+    if (data != NULL) {
+        /* Return data and length. */
+        *pdata = data;
+        *plen = len;
+    }
+    else {
+        /* Failed to allocate data. */
+        ret = MEMORY_E;
+    }
+    return ret;
+}
+
+/* Print ASN.1 of a file containing BER/DER data.
+ *
+ * @param [in] fp  File pointer to read from.
+ * @return  0 on success.
+ * @return  1 on failure.
+ */
+static int PrintDer(FILE* fp)
+{
+    int ret = 0;
+    word32 len = 0;
+    unsigned char* data = NULL;
+
+    /* Load DER/BER file. */
+    if (ReadFile(fp, &data, &len) != 0) {
+        ret = 1;
+    }
+
+    if ((ret == 0) && (data != NULL)) {
+        /* Print DER/BER. */
+        ret = wc_Asn1_PrintAll(&asn1, &opts, data, len);
+        /* Dispose of buffer. */
+        free(data);
+    }
+
+    return ret;
+}
+
+/* Print ASN.1 of a file containing Base64 encoding of BER/DER data.
+ *
+ * @param [in] fp  File pointer to read from.
+ * @return  0 on success.
+ * @return  1 on failure.
+ */
+static int PrintBase64(FILE* fp)
+{
+    int ret = 0;
+    word32 len = 0;
+    unsigned char* data = NULL;
+
+    /* Load Base64 encoded file. */
+    if (ReadFile(fp, &data, &len) != 0) {
+        ret = 1;
+    }
+
+    if ((ret == 0) && (data != NULL)) {
+        /* Decode Base64. */
+        if (Base64_Decode(data, len, data, &len) != 0) {
+            fprintf(stderr, "Invalid Base64 encoding\n");
+            ret = 1;
+        }
+
+        if (ret == 0) {
+            /* Print DER/BER. */
+            ret = wc_Asn1_PrintAll(&asn1, &opts, data, len);
+        }
+        /* Dispose of buffer. */
+        free(data);
+    }
+
+    return ret;
+}
+
+/* Find the next PEM block.
+ *
+ * @param [in]  data    PEM data.
+ * @param [in]  offset  Offset into data to start looking.
+ * @param [in]  len     Length of PEM data.
+ * @param [out] start   Start of Base64 encoding.
+ * @param [out] end     End of Base64 encoding.
+ */
+static int FindPem(unsigned char* data, word32 offset, word32 len,
+    word32* start, word32* end)
+{
+    int ret = 0;
+    word32 i;
+    word32 j;
+
+    /* Find header. */
+    for (i = offset; i < len; i++) {
+        if ((data[i] == '-') &&
+                (strncmp((char*)data + i, "-----BEGIN", 10) == 0)) {
+            break;
+        }
+    }
+    if (i == len) {
+        /* Got to end without finding PEM header. */
+        fprintf(stderr, "No PEM header found\n");
+        ret = 1;
+    }
+    if (ret == 0) {
+        /* Confirm header. */
+        for (i += 10; i < len; i++) {
+            if ((data[i] == '-') &&
+                    (strncmp((char*)data + i, "-----", 5) == 0)) {
+                break;
+            }
+        }
+        if (i == len) {
+            /* Got to end without finding rest of PEM header. */
+            fprintf(stderr, "Invalid PEM header\n");
+            ret = 1;
+        }
+    }
+    if (ret == 0) {
+        /* Find footer. */
+        i += 6;
+        for (j = i + 1; j < len; j++) {
+            if ((data[j] == '-') &&
+                    (strncmp((char*)data + j, "-----END", 8) == 0)) {
+                break;
+            }
+        }
+        if (j == len) {
+            /* Got to end without finding PEM footer. */
+            fprintf(stderr, "No PEM footer found\n");
+            ret = 1;
+        }
+    }
+
+    if (ret == 0) {
+        /* Return start and end indeces. */
+        *start = i;
+        *end = j;
+    }
+    return ret;
+}
+
+/* Print ASN.1 of file containing PEM.
+ *
+ * Only one block is printed.
+ *
+ * @param [in] fp        File pointer to read from.
+ * @param [in] pem_skip  Number of PEM blocks to skip.
+ * @return  0 on success.
+ * @return  1 on failure.
+ */
+static int PrintPem(FILE* fp, int pem_skip)
+{
+    int ret = 0;
+    unsigned char* data = NULL;
+    word32 len = 0;
+
+    /* Load PEM file. */
+    if (ReadFile(fp, &data, &len) != 0) {
+        ret = 1;
+    }
+
+    if ((ret == 0) && (data != NULL)) {
+        word32 i = 0;
+        word32 j = 0;
+
+        /* Find PEM blocks and skip number requested. */
+        do {
+            /* Find start and end of PEM Base64 data. */
+            ret = FindPem(data, j, len, &i, &j);
+        } while ((ret == 0) && ((pem_skip--) != 0));
+
+        /* Decode data between header and footer. */
+        if ((ret == 0) && (Base64_Decode(data + i, j - i, data, &len) != 0)) {
+            fprintf(stderr, "Invalid Base64 encoding\n");
+            ret = 1;
+        }
+
+        if (ret == 0) {
+            /* Print DER/BER. */
+            ret = wc_Asn1_PrintAll(&asn1, &opts, data, len);
+        }
+        /* Dispose of buffer. */
+        free(data);
+    }
+
+    return ret;
+}
+
+/* Usage lines to show. */
+const char* usage[] = {
+    "asn1 [OPTOIN]... [FILE]",
+    "Display a human-readable version of a DER/BER encoding.",
+    "",
+    "Options:",
+    "  -?, --help           display this help and exit",
+    "  -b, --branch         draw branches before tag name",
+    "  -B, --base64         file contents are Base64 encoded",
+    "  -d, --dump           show all ASN.1 item data as a hex dump",
+    "  -h, --headers        show all ASN.1 item headers as a hex dump",
+    "  -i, --indent         indent tag name with depth",
+    "  -l, --length LEN     display length bytes of data",
+    "  -n, --no-text        do not show data as text",
+    "  -N, --no-dump-text   do not show data as a hex dump text",
+    "  -o, --offset OFFSET  start decoding from offset",
+    "  -O, --oid            show wolfSSL OID value in text",
+    "  -p, --pem            file contents are PEM",
+    "  -s, --skip-pem NUM   number of PEM blocks to skip",
+};
+/* Number of usage lines. */
+#define USAGE_SZ  ((int)(sizeof(usage) / sizeof(*usage)))
+
+/* Print out usage lines.
+ */
+static void Usage(void)
+{
+    int i;
+
+    for (i = 0; i < USAGE_SZ; i++) {
+        printf("%s\n", usage[i]);
+    }
+}
+
+/* Main entry of ASN.1 printing program.
+ *
+ * @param [in] argc  Count of command line argements.
+ * @param [in] argv  Command line argements.
+ * @return  0 on success.
+ * @return  1 on failure.
+ */
+int main(int argc, char* argv[])
+{
+    int ret = 0;
+    /* Default to reading STDIN. */
+    FILE* fp = stdin;
+    int file_format = FORMAT_DER;
+    int indent = 0;
+    int pem_skip = 0;
+
+    /* Reset options. */
+    (void)wc_Asn1PrintOptions_Init(&opts);
+
+    /* Skip over program name. */
+    argc--;
+    argv++;
+    while (argc > 0) {
+        /* Show branches instead of indenting. */
+        if ((strcmp(argv[0], "-b") == 0) ||
+            (strcmp(argv[0], "--branch") == 0)) {
+            wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_DRAW_BRANCH, 1);
+        }
+        /* File is Base64 encoded data. */
+        else if ((strcmp(argv[0], "-b64") == 0) ||
+                 (strcmp(argv[0], "--base64") == 0)) {
+            file_format = FORMAT_BASE64;
+        }
+        /* Dump all ASN.1 item data. */
+        else if ((strcmp(argv[0], "-d") == 0) ||
+                 (strcmp(argv[0], "--dump") == 0)) {
+            wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_SHOW_DATA, 1);
+        }
+        /* Dump ASN.1 item headers. */
+        else if ((strcmp(argv[0], "-h") == 0) ||
+                 (strcmp(argv[0], "--headers") == 0)) {
+            wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_SHOW_HEADER_DATA, 1);
+        }
+        /* Indent to text to indicate depth. */
+        else if ((strcmp(argv[0], "-i") == 0) ||
+                 (strcmp(argv[0], "--indent") == 0)) {
+            indent++;
+            if (indent > 15) {
+            }
+        }
+        /* Only parse the specified length of DER/BER data. */
+        else if ((strcmp(argv[0], "-l") == 0) ||
+                 (strcmp(argv[0], "--length") == 0)) {
+            if (argc == 1) {
+                printf("Missing length value\n");
+                return 1;
+            }
+            argc--;
+            argv++;
+            wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_LENGTH,
+                atoi(argv[0]));
+        }
+        /* Do not show text representations of ASN.1 item data. */
+        else if ((strcmp(argv[0], "-n") == 0) ||
+                 (strcmp(argv[0], "--no-text") == 0)) {
+            wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_SHOW_NO_TEXT, 1);
+        }
+        /* Do not show hex dump text representations of ASN.1 item data. */
+        else if ((strcmp(argv[0], "-N") == 0) ||
+                 (strcmp(argv[0], "--no-dump-text") == 0)) {
+            wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_SHOW_NO_DUMP_TEXT, 1);
+        }
+        /* Offset into DER/BER to start decoding from. */
+        else if ((strcmp(argv[0], "-o") == 0) ||
+                 (strcmp(argv[0], "--offset") == 0)) {
+            if (argc == 1) {
+                fprintf(stderr, "Missing offset value\n");
+                return 1;
+            }
+            argc--;
+            argv++;
+            wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_OFFSET,
+                atoi(argv[0]));
+        }
+        /* Show wolfSSL OID value for all OBJECT_IDs. */
+        else if ((strcmp(argv[0], "-O") == 0) ||
+                 (strcmp(argv[0], "--oid") == 0)) {
+            wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_SHOW_OID, 1);
+        }
+        /* File contains PEM blocks. */
+        else if ((strcmp(argv[0], "-p") == 0) ||
+                 (strcmp(argv[0], "--pem") == 0)) {
+            file_format = FORMAT_PEM;
+        }
+        /* Skip a number of PEM blocks. */
+        else if ((strcmp(argv[0], "-s") == 0) ||
+                 (strcmp(argv[0], "--skip-pem") == 0)) {
+            if (argc == 1) {
+                fprintf(stderr, "Missing number of PEM blocks to skip\n");
+                return 1;
+            }
+            argc--;
+            argv++;
+            pem_skip = atoi(argv[0]);
+            if ((pem_skip < 0) || (pem_skip > 15)) {
+                fprintf(stderr, "Skip value out of range: %d\n", pem_skip);
+                return 1;
+            }
+        }
+        /* Display help/usage. */
+        else if ((strcmp(argv[0], "-?") == 0) ||
+                 (strcmp(argv[0], "--help") == 0)) {
+            Usage();
+            return 0;
+        }
+        /* Unknown option dectection. */
+        else if (argv[0][0] == '-') {
+            fprintf(stderr, "Bad option: %s\n", argv[0]);
+            Usage();
+            return 1;
+        }
+        else {
+            /* Name of file to read. */
+            fp = fopen(argv[0], "r");
+            if (fp == NULL) {
+                fprintf(stderr, "File not able to be read: %s\n", argv[0]);
+                return 1;
+            }
+        }
+
+        /* Move on to next command line argument. */
+        argc--;
+        argv++;
+    }
+
+    wc_Asn1PrintOptions_Set(&opts, ASN1_PRINT_OPT_INDENT, indent);
+
+    (void)wc_Asn1_Init(&asn1);
+    (void)wc_Asn1_SetFile(&asn1, stdout);
+
+    /* Process file based on type. */
+    if (file_format == FORMAT_DER) {
+        ret = PrintDer(fp);
+    }
+    else if (file_format == FORMAT_BASE64) {
+        ret = PrintBase64(fp);
+    }
+    else if (file_format == FORMAT_PEM) {
+        ret = PrintPem(fp, pem_skip);
+    }
+
+    if (ret != 0) {
+        fprintf(stderr, "%s\n", wc_GetErrorString(ret));
+    }
+    return (ret == 0) ? 0 : 1;
+}
+
+#else
+
+/* Main entry of ASN.1 printing program.
+ *
+ * @param [in] argc  Count of command line argements.
+ * @param [in] argv  Command line argements.
+ * @return  0 on success.
+ * @return  1 on failure.
+ */
+int main(int argc, char* argv[])
+{
+    (void)argc;
+    (void)argv;
+    fprintf(stderr, "ASN.1 Parsing and Printing not compiled in.\n");
+    return 0;
+}
+
+#endif
+
+

+ 12 - 0
examples/asn1/include.am

@@ -0,0 +1,12 @@
+# vim:ft=automake
+# included from Top Level Makefile.am
+# All paths should be given relative to the root
+
+
+if BUILD_EXAMPLE_ASN1
+noinst_PROGRAMS += examples/asn1/asn1
+examples_asn1_asn1_SOURCES = examples/asn1/asn1.c
+examples_asn1_asn1_LDADD        = src/libwolfssl@LIBSUFFIX@.la $(LIB_STATIC_ADD)
+examples_asn1_asn1_DEPENDENCIES = src/libwolfssl@LIBSUFFIX@.la
+endif
+

+ 1 - 0
examples/include.am

@@ -8,4 +8,5 @@ include examples/echoserver/include.am
 include examples/server/include.am
 include examples/sctp/include.am
 include examples/configs/include.am
+include examples/asn1/include.am
 EXTRA_DIST += examples/README.md

+ 883 - 81
wolfcrypt/src/asn.c

@@ -218,6 +218,88 @@ extern int wc_InitRsaHw(RsaKey* key);
     #endif /* HAVE_SELFTEST */
 #endif
 
+#if defined(WOLFSSL_ASN_PRINT) || defined(WOLFSSL_DEBUG_ASN_TEMPLATE)
+
+/* String representations of tags. */
+static const char* tagString[4][32] = {
+    /* Universal */
+    {
+        "EOC",
+        "BOOLEAN",
+        "INTEGER",
+        "BIT STRING",
+        "OCTET STRING",
+        "NULL",
+        "OBJECT ID",
+        "ObjectDescriptor",
+        "INSTANCE OF",
+        "REAL",
+        "ENUMERATED",
+        "EMBEDDED PDV",
+        "UT8String",
+        "RELATIVE-OID",
+        "(0x0e) 14",
+        "(0x0f) 15",
+        "SEQUENCE",
+        "SET",
+        "NumericString",
+        "PrintableString",
+        "T61String",
+        "VideotexString",
+        "IA5String",
+        "UTCTime",
+        "GeneralizedTime",
+        "GraphicString",
+        "ISO646String",
+        "GeneralString",
+        "UniversalString",
+        "CHARACTER STRING",
+        "BMPString",
+        "(0x1f) 31",
+    },
+    /* Application */
+    {
+         "[A 0]",  "[A 1]",  "[A 2]",  "[A 3]",
+         "[A 4]",  "[A 5]",  "[A 6]",  "[A 7]",
+         "[A 8]",  "[A 9]", "[A 10]", "[A 11]",
+        "[A 12]", "[A 13]", "[A 14]", "[A 15]",
+        "[A 16]", "[A 17]", "[A 18]", "[A 19]",
+        "[A 20]", "[A 21]", "[A 22]", "[A 23]",
+        "[A 24]", "[A 25]", "[A 26]", "[A 27]",
+        "[A 28]", "[A 20]", "[A 30]", "[A 31]"
+    },
+    /* Context-Specific */
+    {
+         "[0]",  "[1]",  "[2]",  "[3]",  "[4]",  "[5]",  "[6]",  "[7]",
+         "[8]",  "[9]", "[10]", "[11]", "[12]", "[13]", "[14]", "[15]",
+        "[16]", "[17]", "[18]", "[19]", "[20]", "[21]", "[22]", "[23]",
+        "[24]", "[25]", "[26]", "[27]", "[28]", "[20]", "[30]", "[31]"
+    },
+    /* Private */
+    {
+         "[P 0]",  "[P 1]",  "[P 2]",  "[P 3]",
+         "[P 4]",  "[P 5]",  "[P 6]",  "[P 7]",
+         "[P 8]",  "[P 9]", "[P 10]", "[P 11]",
+        "[P 12]", "[P 13]", "[P 14]", "[P 15]",
+        "[P 16]", "[P 17]", "[P 18]", "[P 19]",
+        "[P 20]", "[P 21]", "[P 22]", "[P 23]",
+        "[P 24]", "[P 25]", "[P 26]", "[P 27]",
+        "[P 28]", "[P 20]", "[P 30]", "[P 31]"
+    }
+};
+
+/* Converts a tag byte to string.
+ *
+ * @param [in] tag  BER tag value to interpret.
+ * @return  String corresponding to tag.
+ */
+static const char* TagString(byte tag)
+{
+    return tagString[tag >> 6][tag & ASN_TYPE_MASK];
+}
+
+#endif
+
 
 /* Calculates the minimum number of bytes required to encode the value.
  *
@@ -482,83 +564,6 @@ static word32 SizeASNLength(word32 length)
 #endif
 
 #ifdef WOLFSSL_DEBUG_ASN_TEMPLATE
-/* String representations of tags. */
-static const char* tagString[4][32] = {
-    /* Universal */
-    {
-        "EOC",
-        "BOOLEAN",
-        "INTEGER",
-        "BIT STRING",
-        "OCTET STRING",
-        "NULL",
-        "OBJECT ID",
-        "ObjectDescriptor",
-        "INSTANCE OF",
-        "REAL",
-        "ENUMERATED",
-        "EMBEDDED PDV",
-        "UT8String",
-        "RELATIVE-OID",
-        "(0x0e) 14",
-        "(0x0f) 15",
-        "SEQUENCE",
-        "SET",
-        "NumericString",
-        "PrintableString",
-        "T61String",
-        "VideotexString",
-        "IA5String",
-        "UTCTime",
-        "GeneralizedTime",
-        "GraphicString",
-        "ISO646String",
-        "GeneralString",
-        "UniversalString",
-        "CHARACTER STRING",
-        "BMPString",
-        "(0x1f) 31",
-    },
-    /* Application */
-    {
-         "[A 0]",  "[A 1]",  "[A 2]",  "[A 3]",
-         "[A 4]",  "[A 5]",  "[A 6]",  "[A 7]",
-         "[A 8]",  "[A 9]", "[A 10]", "[A 11]",
-        "[A 12]", "[A 13]", "[A 14]", "[A 15]",
-        "[A 16]", "[A 17]", "[A 18]", "[A 19]",
-        "[A 20]", "[A 21]", "[A 22]", "[A 23]",
-        "[A 24]", "[A 25]", "[A 26]", "[A 27]",
-        "[A 28]", "[A 20]", "[A 30]", "[A 31]"
-    },
-    /* Context-Specific */
-    {
-         "[0]",  "[1]",  "[2]",  "[3]",  "[4]",  "[5]",  "[6]",  "[7]",
-         "[8]",  "[9]", "[10]", "[11]", "[12]", "[13]", "[14]", "[15]",
-        "[16]", "[17]", "[18]", "[19]", "[20]", "[21]", "[22]", "[23]",
-        "[24]", "[25]", "[26]", "[27]", "[28]", "[20]", "[30]", "[31]"
-    },
-    /* Private */
-    {
-         "[P 0]",  "[P 1]",  "[P 2]",  "[P 3]",
-         "[P 4]",  "[P 5]",  "[P 6]",  "[P 7]",
-         "[P 8]",  "[P 9]", "[P 10]", "[P 11]",
-        "[P 12]", "[P 13]", "[P 14]", "[P 15]",
-        "[P 16]", "[P 17]", "[P 18]", "[P 19]",
-        "[P 20]", "[P 21]", "[P 22]", "[P 23]",
-        "[P 24]", "[P 25]", "[P 26]", "[P 27]",
-        "[P 28]", "[P 20]", "[P 30]", "[P 31]"
-    }
-};
-
-/* Converts a tag byte to string.
- *
- * @param [in] tag  BER tag value to interpret.
- * @return  String corresponding to tag.
- */
-static const char* TagString(byte tag)
-{
-    return tagString[tag >> 6][tag & ASN_TYPE_MASK];
-}
 
 #include <stdarg.h>
 
@@ -5507,7 +5512,7 @@ int EncodeObjectId(const word16* in, word32 inSz, byte* out, word32* outSz)
 }
 #endif /* HAVE_OID_ENCODING */
 
-#ifdef HAVE_OID_DECODING
+#if defined(HAVE_OID_DECODING) || defined(WOLFSSL_ASN_PRINT)
 /* Encode dotted form of OID into byte array version.
  *
  * @param [in]      in     Byte array containing OID.
@@ -5537,12 +5542,12 @@ int DecodeObjectId(const byte* in, word32 inSz, word16* out, word32* outSz)
                 return BUFFER_E;
             }
             if (y == 0) {
-                out[0] = (t / 40);
-                out[1] = (t % 40);
+                out[0] = (word16)(t / 40);
+                out[1] = (word16)(t % 40);
                 y = 2;
             }
             else {
-                out[y++] = t;
+                out[y++] = (word16)t;
             }
             t = 0; /* reset tmp */
         }
@@ -37020,6 +37025,803 @@ int wc_MIME_free_hdrs(MimeHdr* head)
 
 #undef ERROR_OUT
 
+
+#ifdef WOLFSSL_ASN_PRINT
+
+/*******************************************************************************
+ * ASN.1 Parsing and Printing Implemenation
+ ******************************************************************************/
+
+/* Initialize ASN.1 print options.
+ *
+ * @param [in, out] opts  ASN.1 options for printing.
+ * @return  0 on success.
+ * @return  BAD_FUNC_ARG when asn1 is NULL.
+ */
+int wc_Asn1PrintOptions_Init(Asn1PrintOptions* opts)
+{
+    int ret = 0;
+
+    if (opts == NULL) {
+        ret = BAD_FUNC_ARG;
+    }
+    else {
+        XMEMSET(opts, 0, sizeof(*opts));
+    }
+
+    return ret;
+}
+
+/* Set a print option into Asn1PrintOptions object.
+ *
+ * @param [in, out] opts  ASN.1 options for printing.
+ * @param [in]      opt   Option to set value of.
+ * @param [in]      val   Value to set for option.
+ * @return  0 on success.
+ * @return  BAD_FUNC_ARG when asn1 is NULL.
+ * @return  BAD_FUNC_ARG when val is out of range for option.
+ */
+int wc_Asn1PrintOptions_Set(Asn1PrintOptions* opts, enum Asn1PrintOpt opt,
+    word32 val)
+{
+    int ret = 0;
+
+    /* Validate parameters. */
+    if (opts == NULL) {
+        ret = BAD_FUNC_ARG;
+    }
+
+    if (ret == 0) {
+        switch (opt) {
+        /* Offset into DER/BER data to start decoding from. */
+        case ASN1_PRINT_OPT_OFFSET:
+            opts->offset = val;
+            break;
+        /* Length of DER/BER encoding to parse. */
+        case ASN1_PRINT_OPT_LENGTH:
+            opts->length = val;
+            break;
+        /* Number of spaces to indent for each change in depth. */
+        case ASN1_PRINT_OPT_INDENT:
+            /* Only 4 bits available for value. */
+            if (val >= (1 << 4)) {
+                ret = BAD_FUNC_ARG;
+            }
+            else {
+                opts->indent = val;
+            }
+            break;
+        /* Draw branches instead of indenting. */
+        case ASN1_PRINT_OPT_DRAW_BRANCH:
+            /* Boolean value. */
+            if (val > 1) {
+                ret = BAD_FUNC_ARG;
+            }
+            else {
+                opts->draw_branch = val;
+            }
+            break;
+        /* Show raw data of primitive types as octets. */
+        case ASN1_PRINT_OPT_SHOW_DATA:
+            /* Boolean value. */
+            if (val > 1) {
+                ret = BAD_FUNC_ARG;
+            }
+            else {
+                opts->show_data = val;
+            }
+            break;
+        /* Show header data as octets. */
+        case ASN1_PRINT_OPT_SHOW_HEADER_DATA:
+            /* Boolean value. */
+            if (val > 1) {
+                ret = BAD_FUNC_ARG;
+            }
+            else {
+                opts->show_header_data = val;
+            }
+            break;
+        /* Show the wolfSSL OID value for OBJECT_ID. */
+        case ASN1_PRINT_OPT_SHOW_OID:
+            /* Boolean value. */
+            if (val > 1) {
+                ret = BAD_FUNC_ARG;
+            }
+            else {
+                opts->show_oid = val;
+            }
+            break;
+        /* Don't show text representations of primitive types. */
+        case ASN1_PRINT_OPT_SHOW_NO_TEXT:
+            /* Boolean value. */
+            if (val > 1) {
+                ret = BAD_FUNC_ARG;
+            }
+            else {
+                opts->show_no_text = val;
+            }
+            break;
+        /* Don't show dump text representations of primitive types. */
+        case ASN1_PRINT_OPT_SHOW_NO_DUMP_TEXT:
+            /* Boolean value. */
+            if (val > 1) {
+                ret = BAD_FUNC_ARG;
+            }
+            else {
+                opts->show_no_dump_text = val;
+            }
+            break;
+        }
+    }
+
+    return ret;
+}
+
+/* Initialize an ASN.1 parse object.
+ *
+ * @param [in, out] asn1  ASN.1 parse object.
+ * @return  0 on success.
+ * @return  BAD_FUNC_ARG when asn1 is NULL.
+ */
+int wc_Asn1_Init(Asn1* asn1)
+{
+    int ret = 0;
+
+    if (asn1 == NULL) {
+        ret = BAD_FUNC_ARG;
+    }
+    else {
+        XMEMSET(asn1, 0, sizeof(*asn1));
+        asn1->file = XBADFILE;
+    }
+
+    return ret;
+}
+
+/* Set the file to use when printing.
+ *
+ * @param [in, out] asn1  ASN.1 parse object.
+ * @param [in]      file  File to print to.
+ * @return  0 on success.
+ * @return  BAD_FUNC_ARG when asn1 is NULL.
+ * @return  BAD_FUNC_ARG when file is XBADFILE.
+ */
+int wc_Asn1_SetFile(Asn1* asn1, XFILE file)
+{
+    int ret = 0;
+
+    if ((asn1 == NULL) || (file == XBADFILE)) {
+        ret = BAD_FUNC_ARG;
+    }
+    else {
+        asn1->file = file;
+    }
+
+    return ret;
+}
+
+/* Maximum OID dotted form size. */
+#define ASN1_OID_DOTTED_MAX_SZ         16
+
+/* Print OID in dotted form or as hex bytes.
+ *
+ * @param [in]  file        File pointer to write to.
+ * @param [in]  oid         OBJECT_ID data.
+ * @param [in]  oid_len     Length of OBJECT_ID data.
+ */
+static void PrintObjectIdNum(XFILE file, unsigned char* oid, word32 len)
+{
+    word16 dotted_nums[ASN1_OID_DOTTED_MAX_SZ];
+    word32 num = ASN1_OID_DOTTED_MAX_SZ;
+    word32 i;
+
+    /* Decode OBJECT_ID into dotted form array. */
+    if (DecodeObjectId(oid, len, dotted_nums, &num) == 0) {
+        /* Print out each number of dotted form. */
+        for (i = 0; i < num; i++) {
+            XFPRINTF(file, "%d", dotted_nums[i]);
+            /* Add separetor. */
+            if (i < num - 1) {
+                XFPRINTF(file, ".");
+            }
+        }
+    }
+    else {
+        /* Print out bytes as we couldn't decode. */
+        for (i = 0; i < len; i++) {
+            XFPRINTF(file, "%02x", oid[i]);
+            /* Add separetor. */
+            if (i < len - 1) {
+                XFPRINTF(file, ":");
+            }
+        }
+    }
+}
+
+/* OID value to name mapping. */
+typedef struct OidName {
+    /* wolfSSL OID value. */
+    word32 oid;
+    /* Long name to print when OID seen. */
+    const char* name;
+} OidName;
+
+/* Extra OID to name mappings. */
+static const OidName extraOids[] = {
+    { 0x005c, "commonName" },
+    { 0x005d, "surname" },
+    { 0x005e, "serialNumber" },
+    { 0x005f, "countryName" },
+    { 0x0060, "localityName" },
+    { 0x0061, "stateOrProvinceName" },
+    { 0x0062, "streetAddress" },
+    { 0x0063, "organizationName" },
+    { 0x0064, "organizationUnitName" },
+    { 0x0065, "title" },
+    { 0x0086, "certificateExtension" },
+    { 0x028d, "emailAddress" },
+    { 0x0293, "challengePassword" },
+    { 0x029a, "extensionReq" },
+};
+/* Length of table of extra OID to name mappings. */
+#define EXTRA_OIDS_LEN   ((int)(sizeof(extraOids) / sizeof(*extraOids)))
+
+/* Convert OID value to long name.
+ *
+ * @param [in]  oid   OID value.
+ * @param [out] name  Long name for OID when known.
+ * @return  1 when OID known.
+ * @return  0 when OID not known.
+ */
+static int Oid2LongName(word32 oid, const char** name)
+{
+    int ret = 0;
+    int i;
+
+    /* Step through each entry in table. */
+    for (i = 0; i < EXTRA_OIDS_LEN; i++) {
+        if (extraOids[i].oid == oid) {
+            /* Return the name associated with the OID value. */
+            *name = extraOids[i].name;
+            ret = 1;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+/* Print the text version of the OBJECT_ID.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ * @param [in] opts  ASN.1 options for printing.
+ */
+static void PrintObjectIdText(Asn1* asn1, Asn1PrintOptions* opts)
+{
+    word32 oid = (word32)-1;
+#if !defined(WOLFCRYPT_ONLY) && defined(OPENSSL_EXTRA)
+    word32 nid;
+#endif
+    const char* ln = NULL;
+    word32 i = 0;
+    int known = 1;
+
+    /* Get the OID value for the OBJECT_ID. */
+    GetObjectId(asn1->data + asn1->offset, &i, &oid, oidIgnoreType,
+        asn1->item.len + 2);
+#if !defined(WOLFCRYPT_ONLY) && defined(OPENSSL_EXTRA)
+    /* Lookup NID for OID value. */
+    if ((nid = oid2nid(oid, oidIgnoreType)) != (word32)-1) {
+        /* Lookup long name for NID. */
+        ln = wolfSSL_OBJ_nid2ln(nid);
+    }
+    else
+#endif
+    /* Lookup long name for extra known OID values. */
+    if (!Oid2LongName(oid, &ln)) {
+        /* Unknown OID value. */
+        ln = NULL;
+        known = 0;
+    }
+
+    XFPRINTF(asn1->file, ":");
+    /* Show OID value if not known or asked to. */
+    if ((!known) || opts->show_oid) {
+        XFPRINTF(asn1->file, "(0x%x) ", oid);
+    }
+    if (ln != NULL) {
+        /* Print long name. */
+        XFPRINTF(asn1->file, "%s", ln);
+    }
+    else {
+        /* Print out as numbers - either dotted or hex values. */
+        PrintObjectIdNum(asn1->file, asn1->data + asn1->item.data_idx,
+            asn1->item.len);
+    }
+}
+
+/* Print ASN.1 data as a character string.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ */
+static void PrintText(Asn1* asn1)
+{
+    word32 i;
+
+    XFPRINTF(asn1->file, ":");
+    /* Print all data bytes as characters. */
+    for (i = 0; i < asn1->item.len; i++) {
+        XFPRINTF(asn1->file, "%c", asn1->data[asn1->item.data_idx + i]);
+    }
+}
+
+/* Print data as a hex bytes.
+ *
+ * @param [in] file  File pointer to write to.
+ * @param [in] data  Data to print.
+ * @param [in] len   Number of bytes to print.
+ */
+static void PrintHex(XFILE file, unsigned char* data, word32 len)
+{
+    word32 i;
+
+    /* Print data bytes as hex numbers. */
+    for (i = 0; i < len; i++) {
+        XFPRINTF(file, "%02x", data[i]);
+    }
+}
+
+/* Print ASN.1 data as a hex bytes.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ */
+static void PrintHexText(Asn1* asn1)
+{
+    XFPRINTF(asn1->file, ":");
+    PrintHex(asn1->file, asn1->data + asn1->item.data_idx, asn1->item.len);
+}
+
+/* Print ASN.1 BIT_STRING data as hex bytes noting special first byte.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ */
+static void PrintBitStringText(Asn1* asn1)
+{
+    if (asn1->item.len > 0) {
+        XFPRINTF(asn1->file, ":[%02x]", asn1->data[asn1->item.data_idx]);
+        PrintHex(asn1->file, asn1->data + asn1->item.data_idx + 1,
+            asn1->item.len - 1);
+    }
+}
+
+/* Print ASN.1 BOOLEAN data as text with value.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ */
+static void PrintBooleanText(Asn1* asn1)
+{
+    /* Booleans should be 1 byte of data. */
+    if (asn1->item.len == 1) {
+        XFPRINTF(asn1->file, ":%s (%d)",
+            (asn1->data[asn1->item.data_idx] == 0) ? "FALSE" : "TRUE",
+            asn1->data[asn1->item.data_idx]);
+    }
+}
+
+/* Print ASN.1 data as single byte +/- number.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ */
+static void PrintNumberText(Asn1* asn1)
+{
+    /* Only supporting 1 byte of data for now. */
+    if (asn1->item.len == 1) {
+       int num = asn1->data[asn1->item.data_idx];
+
+       XFPRINTF(asn1->file, ":%d", num >= 0x80 ? num - 0x100 : num);
+    }
+}
+
+/* Print ASN.1 data as a text based on the tag.
+ *
+ * TODO: handle more tags.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ * @param [in] opts  ASN.1 options for printing.
+ */
+static void PrintAsn1Text(Asn1* asn1, Asn1PrintOptions* opts)
+{
+    /* Get the long name for OBJECT_ID where possible. */
+    if (asn1->item.tag == ASN_OBJECT_ID) {
+        PrintObjectIdText(asn1, opts);
+    }
+    /* Data is an array of printable characters. */
+    else if ((asn1->item.tag == ASN_UTF8STRING) ||
+             (asn1->item.tag == ASN_IA5_STRING) ||
+             (asn1->item.tag == ASN_PRINTABLE_STRING) ||
+             (asn1->item.tag == ASN_T61STRING) ||
+             (asn1->item.tag == ASN_BMPSTRING) ||
+             (asn1->item.tag == ASN_UTC_TIME) ||
+             (asn1->item.tag == ASN_GENERALIZED_TIME) ||
+             (asn1->item.tag == ASN_UNIVERSALSTRING) ||
+             (asn1->item.tag == ASN_OBJECT_DESC) ||
+             (asn1->item.tag == ASN_CHARACTER_STRING)) {
+        PrintText(asn1);
+    }
+    /* Show TRUE and FALSE with number. */
+    else if (asn1->item.tag == ASN_BOOLEAN) {
+        PrintBooleanText(asn1);
+    }
+    /* Show number. */
+    else if (asn1->item.tag == ASN_ENUMERATED) {
+        PrintNumberText(asn1);
+    }
+    /* Dumping potentially long string of hex digites. */
+    else if (!opts->show_no_dump_text) {
+        /* Dump all bytes. */
+        if ((asn1->item.tag == ASN_INTEGER) ||
+            (asn1->item.tag == ASN_OCTET_STRING) ||
+            ((asn1->item.tag > ASN_APPLICATION) && (asn1->item.cons))) {
+            PrintHexText(asn1);
+        }
+        /* First byte is number of unused bits in last byte.
+         * Print first specially and dump rest of the bytes. */
+        else if (asn1->item.tag == ASN_BIT_STRING) {
+            PrintBitStringText(asn1);
+        }
+    }
+}
+
+#define HexToChar(n) ((((n) >= 32) && ((n) < 127)) ? (n) : '.')
+
+/* Dump data as hex bytes.
+ *
+ * @param [in] file  File pointer to write to.
+ * @param [in] data  Data to print.
+ * @param [in] len   Number of bytes to print.
+ */
+static void DumpData(XFILE file, unsigned char* data, word32 len)
+{
+    word32 i;
+    word32 j;
+
+    for (i = 0; i < len; i += j) {
+        /* Print offset. */
+        XFPRINTF(file, "       %04x:", i);
+        for (j = 0; (j < 16) && (i + j < len); j++) {
+            /* Print byte as hex number. */
+            XFPRINTF(file, "%s%02x", (j == 8) ? "  " : " ", data[i + j]);
+        }
+        /* Print spaces between hex and characters. */
+        XFPRINTF(file, "   %*s", (16 - j) * 3 + ((j < 8) ? 1 : 0), "");
+        for (j = 0; (j < 16) && (i + j < len); j++) {
+            /* Print byte as hex number. */
+            XFPRINTF(file, "%c", HexToChar(data[i + j]));
+        }
+        XFPRINTF(file, "\n");
+    }
+}
+
+/* Update current depth based on the current position.
+ *
+ * @param [in, out] asn1  ASN.1 parse object.
+ */
+static void UpdateDepth(Asn1* asn1)
+{
+    /* If current index is greater than or equal end index then it is done. */
+    while ((asn1->depth > 0) &&
+           (asn1->end_idx[asn1->depth-1] <= asn1->curr)) {
+        /* Move up a depth. */
+        asn1->depth--;
+    }
+}
+
+/* Check validity of end index of constructed ASN.1 items.
+ *
+ * @param [in, out] asn1  ASN.1 parse object.
+ * @return  0 on success.
+ * @return  ASN_DEPTH_E when end offset invalid.
+ */
+static int CheckDepth(Asn1* asn1)
+{
+    int ret = 0;
+    int i;
+    word32 curr_end = asn1->curr + asn1->item.len;
+
+    for (i = 0; (ret == 0) && (i < asn1->depth); i++) {
+        /* Each end index must be at least as large as the current one. */
+        if (asn1->end_idx[i] < asn1->end_idx[asn1->depth]) {
+            ret = ASN_DEPTH_E;
+        }
+        /* Each end index must be at least as large as current index. */
+        if (asn1->end_idx[i] < curr_end) {
+            ret = ASN_DEPTH_E;
+        }
+    }
+
+    return ret;
+}
+
+/* Draw branching based on depth for an ASN.1 item.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ */
+static void DrawBranch(Asn1* asn1)
+{
+    int i;
+    word32 end = asn1->curr + asn1->item.len;
+
+    /* Write out the character for all depths but current. */
+    for (i = 0; i < asn1->depth; i++) {
+        if (asn1->item.cons || (end < asn1->end_idx[i])) {
+            if (i < asn1->depth - 1) {
+                /* Constructed or not end index and not current depth: | */
+                XFPRINTF(asn1->file, "\xe2\x94\x82");
+            }
+            else {
+                /* Constructed or not end index and current depth: |- */
+                XFPRINTF(asn1->file, "\xe2\x94\x9c");
+            }
+        }
+        else if ((i > 1) && (end >= asn1->end_idx[i-1])) {
+            /* End index for previous: _|_ (in top half) */
+            XFPRINTF(asn1->file, "\xe2\x94\xb4");
+        }
+        else {
+            /* End index but not for previous: L (in top half) */
+            XFPRINTF(asn1->file, "\xe2\x94\x94");
+        }
+    }
+    /* Prefix to tag name. */
+    if (asn1->item.cons) {
+        if (asn1->depth > 0) {
+            /* Have other line to connect to: T (in bottom half) */
+            XFPRINTF(asn1->file, "\xe2\x94\xac");
+        }
+        else {
+            /* Have no other line to connect to: r */
+            XFPRINTF(asn1->file, "\xe2\x94\x8c");
+        }
+    }
+    else {
+        /* In a sequence: - */
+        XFPRINTF(asn1->file, "\xe2\x94\x80");
+    }
+}
+
+/* Print data as hex bytes separated by space.
+ *
+ * @param [in] file  File pointer to write to.
+ * @param [in] data  Data to print.
+ * @param [in] len   Number of bytes to print.
+ */
+static void PrintHexBytes(XFILE file, unsigned char* data, int len)
+{
+    int i;
+
+    for (i = 0; i < len; i++) {
+        XFPRINTF(file, " %02x", data[i]);
+    }
+}
+
+/* Dump header data.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ * @param [in] opts  ASN.1 options for printing.
+ */
+static void DumpHeader(Asn1* asn1, Asn1PrintOptions* opts)
+{
+    /* Put on same line when not showing data too and not showing text data. */
+    if ((!opts->show_data) && opts->show_no_text) {
+        XFPRINTF(asn1->file, "%10s %02x", "", asn1->item.tag);
+    }
+    else {
+        /* Align with start of data. */
+        XFPRINTF(asn1->file, "\n%12s %02x", "", asn1->item.tag);
+    }
+    /* Print the header bytes as hex bytes separated by a space. */
+    PrintHexBytes(asn1->file, asn1->data + asn1->offset + 1,
+        asn1->curr - (asn1->offset + 1));
+}
+
+/* Print ASN.1 item info based on header and indeces.
+ *
+ * @param [in] asn1  ASN.1 parse object.
+ * @param [in] opts  ASN.1 options for printing.
+ */
+static void PrintInfo(Asn1* asn1, Asn1PrintOptions* opts)
+{
+    /* Print offset of this ASN.1 item. */
+    XFPRINTF(asn1->file, "%4d: ", asn1->offset);
+    /* Print length of header. */
+    XFPRINTF(asn1->file, "%1d ", asn1->curr - asn1->offset);
+    /* Print data length. */
+    XFPRINTF(asn1->file, "%c%4d%c", asn1->item.cons ? '[' : '+', asn1->item.len,
+                      asn1->item.cons ? ']' : ' ');
+    /* Print depth. */
+    XFPRINTF(asn1->file, " %s(%d)", (asn1->depth < 10) ? " " : "", asn1->depth);
+    if (!opts->draw_branch) {
+        /* Indent to depth as required. */
+        XFPRINTF(asn1->file, "%*s ", asn1->depth * opts->indent, "");
+        if (!opts->indent) {
+            /* Indicate constructed if no indent. */
+            XFPRINTF(asn1->file, "%c", asn1->item.cons ? '+' : ' ');
+        }
+    }
+    else {
+        /* Draw branch structure for ASN.1 item. */
+        XFPRINTF(asn1->file, " ");
+        DrawBranch(asn1);
+    }
+    /* Print tag name. */
+    XFPRINTF(asn1->file, "%-16s", TagString(asn1->item.tag));
+}
+
+/* Expecting tag part of ASN.1 item. */
+#define ASN_PART_TAG        0
+/* Expecting length part of ASN.1 item. */
+#define ASN_PART_LENGTH     1
+/* Expecting data part of ASN.1 item. */
+#define ASN_PART_DATA       2
+
+/* Print next ASN.1 item.
+ *
+ * @param [in, out] asn1  ASN.1 parse object.
+ * @param [in]      opts  ASN.1 print options.
+ * @return  0 on success.
+ * @return  BAD_FUNC_ARG when asn1 or opts is NULL.
+ * @return  ASN_LEN_E when ASN.1 item's length too long.
+ * @return  ASN_DEPTH_E when end offset invalid.
+ */
+static int wc_Asn1_Print(Asn1* asn1, Asn1PrintOptions* opts)
+{
+    int ret = 0;
+
+    if ((asn1 == NULL) || (opts == NULL)) {
+        ret = BAD_FUNC_ARG;
+    }
+
+    /* Process tag. */
+    if (asn1->part == ASN_PART_TAG) {
+        /* Recalculate which depth we are at. */
+        UpdateDepth(asn1);
+        /* Get tag. */
+        asn1->item.tag = asn1->data[asn1->curr] & ~ASN_CONSTRUCTED;
+        /* Store whether tag indicates constructed. */
+        asn1->item.cons = (asn1->data[asn1->curr] & ASN_CONSTRUCTED) ==
+                     ASN_CONSTRUCTED;
+        /* Start of ASN.1 item is current index. */
+        asn1->offset = asn1->curr;
+        /* Step over tag. */
+        asn1->curr++;
+        /* Next part is length. */
+        asn1->part = ASN_PART_LENGTH;
+    }
+    /* Process length. */
+    if (asn1->part == ASN_PART_LENGTH) {
+        int len;
+
+        /* Decode length and step over it. */
+        if (GetLength(asn1->data, &asn1->curr, &len, asn1->max) < 0) {
+            ret = ASN_LEN_E;
+        }
+        else {
+            /* Store ASN.1 item data offset. */
+            asn1->item.data_idx = asn1->curr;
+            /* Store ASN.1 item data length. */
+            asn1->item.len = len;
+
+            /* Print info about ASN.1 item. */
+            PrintInfo(asn1, opts);
+
+            if (!asn1->item.cons) {
+                /* Move on to print data. */
+                asn1->part = ASN_PART_DATA;
+            }
+            else {
+                /* Print header now if not printing data. */
+                if (opts->show_header_data) {
+                    DumpHeader(asn1, opts);
+                }
+                XFPRINTF(asn1->file, "\n");
+                /* Record end offset for this depth. */
+                asn1->end_idx[asn1->depth++] = asn1->curr + asn1->item.len;
+                /* Done with this ASN.1 item. */
+                asn1->part = ASN_PART_TAG;
+            }
+            /* Check end indeces are valid. */
+            ret = CheckDepth(asn1);
+        }
+    }
+    /* Process data. */
+    if ((ret == 0) && (asn1->part == ASN_PART_DATA)) {
+        if (!opts->show_no_text) {
+            /* Print text representation of data. */
+            PrintAsn1Text(asn1, opts);
+        }
+        if (opts->show_header_data) {
+            /* Dump header bytes. */
+            DumpHeader(asn1, opts);
+        }
+        XFPRINTF(asn1->file, "\n");
+        if (opts->show_data) {
+            /* Dump data bytes. */
+            DumpData(asn1->file, asn1->data + asn1->item.data_idx,
+                asn1->item.len);
+        }
+        /* Step past data to next ASN.1 item. */
+        asn1->curr += asn1->item.len;
+        /* Update the depth based on end indeces. */
+        UpdateDepth(asn1);
+        /* Done with this ASN.1 item. */
+        asn1->part = ASN_PART_TAG;
+    }
+
+    /* Make ASN.1 item printing go out. */
+    fflush(asn1->file);
+
+    return ret;
+}
+
+/* Print all ASN.1 items.
+ *
+ * @param [in, out] asn1  ASN.1 parse object.
+ * @param [in]      opts  ASN.1 print options.
+ * @param [in]      data  BER/DER data to print.
+ * @param [in]      len   Length of data to print in bytes.
+ * @return  0 on success.
+ * @return  BAD_FUNC_ARG when asn1 or opts is NULL.
+ * @return  ASN_LEN_E when ASN.1 item's length too long.
+ * @return  ASN_DEPTH_E when end offset invalid.
+ * @return  ASN_PARSE_E when not all of an ASN.1 item parsed.
+ */
+int wc_Asn1_PrintAll(Asn1* asn1, Asn1PrintOptions* opts, unsigned char* data,
+    word32 len)
+{
+    int ret = 0;
+
+    if (asn1 == NULL) {
+        ret = BAD_FUNC_ARG;
+    }
+
+    if (ret == 0) {
+        /* Initialize start position. */
+        asn1->curr = 0;
+        /* Start parsing at tag. */
+        asn1->part = ASN_PART_TAG;
+        /* Start depth at 0. */
+        asn1->depth = 0;
+
+        /* Store the starting point of the data to parse. */
+        asn1->data = data + opts->offset;
+        if (opts->length > 0) {
+            /* Use user specified maximum length. */
+            asn1->max = opts->length;
+        }
+        else {
+            /* Maximum length is up to end from offset. */
+            asn1->max = len - opts->offset;
+        }
+
+        /* Keep going while no error and have data to parse. */
+        while ((ret == 0) && (asn1->curr < asn1->max)) {
+            /* Print an ASN.1 item. */
+            ret = wc_Asn1_Print(asn1, opts);
+        }
+    }
+    if ((ret == 0) && (asn1->part != ASN_PART_TAG)) {
+        /* Stopped before finishing ASN.1 item. */
+        ret = ASN_PARSE_E;
+    }
+    if ((ret == 0) && (asn1->depth != 0)) {
+        /* Stopped without seeing all items in a constructed item. */
+        ret = ASN_DEPTH_E;
+    }
+
+    return ret;
+}
+
+#endif /* WOLFSSL_ASN_PRINT */
 #endif /* !NO_ASN */
 
 #ifdef WOLFSSL_SEP

+ 6 - 0
wolfcrypt/src/error.c

@@ -583,6 +583,12 @@ const char* wc_GetErrorString(int error)
     case ENTROPY_APT_E:
         return "Entropy Adaptive Proportion Test failed";
 
+    case ASN_DEPTH_E:
+        return "Invalid ASN.1 - depth check";
+
+    case ASN_LEN_E:
+        return "ASN.1 length invalid";
+
     default:
         return "unknown error number";
 

+ 12 - 1
wolfssl/wolfcrypt/asn.h

@@ -95,16 +95,27 @@ enum ASN_Tags {
     ASN_OCTET_STRING      = 0x04,
     ASN_TAG_NULL          = 0x05,
     ASN_OBJECT_ID         = 0x06,
+    ASN_OBJECT_DESC       = 0x07,
+    ASN_INSTANCE_OF       = 0x08,
+    ASN_REAL              = 0x09,
     ASN_ENUMERATED        = 0x0a,
+    ASN_EMBEDDED_PDV      = 0x0b,
     ASN_UTF8STRING        = 0x0c,
+    ASN_RELATIVE_OID      = 0x0d,
     ASN_SEQUENCE          = 0x10,
     ASN_SET               = 0x11,
+    ASN_NUMERICSTRING     = 0x12,
     ASN_PRINTABLE_STRING  = 0x13,
     ASN_T61STRING         = 0x14,
+    ASN_VIDEOTEXSTRING    = 0x15,
     ASN_IA5_STRING        = 0x16,
     ASN_UTC_TIME          = 0x17,
     ASN_GENERALIZED_TIME  = 0x18,
+    ASN_GRAPHICSTRING     = 0x19,
+    ASN_ISO646STRING      = 0x1a,
+    ASN_GENERALSTRING     = 0x1b,
     ASN_UNIVERSALSTRING   = 0x1c,
+    ASN_CHARACTER_STRING  = 0x1d,
     ASN_BMPSTRING         = 0x1e,
     ASN_TYPE_MASK         = 0x1f,
 
@@ -2153,7 +2164,7 @@ WOLFSSL_LOCAL int GetInt(mp_int* mpi, const byte* input, word32* inOutIdx,
     WOLFSSL_LOCAL int EncodeObjectId(const word16* in, word32 inSz,
         byte* out, word32* outSz);
 #endif
-#ifdef HAVE_OID_DECODING
+#if defined(HAVE_OID_DECODING) || defined(WOLFSSL_ASN_PRINT)
     WOLFSSL_LOCAL int DecodeObjectId(const byte* in, word32 inSz,
         word16* out, word32* outSz);
 #endif

+ 100 - 0
wolfssl/wolfcrypt/asn_public.h

@@ -914,4 +914,104 @@ WOLFSSL_API int wc_GetFASCNFromCert(struct DecodedCert* cert,
     } /* extern "C" */
 #endif
 
+#if !defined(XFPRINTF) || defined(NO_FILESYSTEM) || \
+    defined(NO_STDIO_FILESYSTEM) && defined(WOLFSSL_ASN_PRINT)
+#undef WOLFSSL_ASN_PRINT
+#endif
+
+#ifdef WOLFSSL_ASN_PRINT
+
+enum Asn1PrintOpt {
+    /* Offset into DER/BER data to start decoding from. */
+    ASN1_PRINT_OPT_OFFSET,
+    /* Length of DER/BER encoding to parse. */
+    ASN1_PRINT_OPT_LENGTH,
+    /* Number of spaces to indent for each change in depth. */
+    ASN1_PRINT_OPT_INDENT,
+    /* Draw branches instead of indenting. */
+    ASN1_PRINT_OPT_DRAW_BRANCH,
+    /* Show raw data of primitive types as octets. */
+    ASN1_PRINT_OPT_SHOW_DATA,
+    /* Show header data as octets. */
+    ASN1_PRINT_OPT_SHOW_HEADER_DATA,
+    /* Show the wolfSSL OID value for OBJECT_ID. */
+    ASN1_PRINT_OPT_SHOW_OID,
+    /* Don't show text representations of primitive types. */
+    ASN1_PRINT_OPT_SHOW_NO_TEXT,
+    /* Don't show dump text representations of primitive types. */
+    ASN1_PRINT_OPT_SHOW_NO_DUMP_TEXT,
+};
+
+/* ASN.1 print options. */
+typedef struct Asn1PrintOptions {
+    /* Offset into DER/BER encoding to start parsing from. */
+    word32 offset;
+    /* Length of DER/BER encoding to parse. */
+    word32 length;
+    /* Number of spaces to indent for each change in depth. */
+    int indent:4;
+    /* Draw branches instead of indenting. */
+    int draw_branch:1;
+    /* Show raw data of primitive types as octets. */
+    int show_data:1;
+    /* Show header data as octets. */
+    int show_header_data:1;
+    /* Show the wolfSSL OID value for OBJECT_ID. */
+    int show_oid:1;
+    /* Don't show text representations of primitive types. */
+    int show_no_text:1;
+    /* Don't show dump text representations of primitive types. */
+    int show_no_dump_text:1;
+} Asn1PrintOptions;
+
+/* ASN.1 item data. */
+typedef struct Asn1Item {
+    /* Tag of current item. */
+    unsigned char  tag;
+    /* Whether current item is constructed. */
+    unsigned char  cons;
+    /* Length of data in current ASN.1 item. */
+    word32         len;
+    /* Index into data of ASN.1 item data. */
+    word32         data_idx;
+} Asn1Item;
+
+/* Maximum supported depth of ASN.1 items. */
+#define ASN_MAX_DEPTH       16
+
+/* ASN.1 parsing state. */
+typedef struct Asn1 {
+    /* ASN.1 item data. */
+    Asn1Item         item;
+    /* Current depth of ASN.1 item. */
+    unsigned char    depth;
+    /* End indeces of ASN.1 items at different depths. */
+    word32           end_idx[ASN_MAX_DEPTH];
+
+    /* Buffer to print. */
+    unsigned char*   data;
+    /* Maximum number of bytes to process. */
+    word32           max;
+    /* Starting offset of current ASN.1 item. */
+    word32           offset;
+    /* Current offset into ASN.1 data. */
+    word32           curr;
+    /* Next part of ASN.1 item expected. */
+    unsigned char    part;
+
+    /* File pointer to print to. */
+    XFILE            file;
+} Asn1;
+
+WOLFSSL_API int wc_Asn1PrintOptions_Init(Asn1PrintOptions* opts);
+WOLFSSL_API int wc_Asn1PrintOptions_Set(Asn1PrintOptions* opts,
+    enum Asn1PrintOpt opt, word32 val);
+
+WOLFSSL_API int wc_Asn1_Init(Asn1* asn1);
+WOLFSSL_API int wc_Asn1_SetFile(Asn1* asn1, XFILE file);
+WOLFSSL_API int wc_Asn1_PrintAll(Asn1* asn1, Asn1PrintOptions* opts,
+    unsigned char* data, word32 len);
+
+#endif /* WOLFSSL_ASN_PRINT */
+
 #endif /* WOLF_CRYPT_ASN_PUBLIC_H */

+ 4 - 1
wolfssl/wolfcrypt/error-crypt.h

@@ -257,7 +257,10 @@ enum {
     ENTROPY_RT_E        = -294,  /* Entropy Repetition Test failed */
     ENTROPY_APT_E       = -295,  /* Entropy Adaptive Proportion Test failed */
 
-    WC_LAST_E           = -295,  /* Update this to indicate last error */
+    ASN_DEPTH_E         = -296,  /* Invalid ASN.1 - depth check */
+    ASN_LEN_E           = -297,  /* ASN.1 length invalid */
+
+    WC_LAST_E           = -297,  /* Update this to indicate last error */
     MIN_CODE_E          = -300   /* errors -101 - -299 */
 
     /* add new companion error id strings for any new error codes