From 9cdee20a7d8452333cde90fdc0e9303eb712def0 Mon Sep 17 00:00:00 2001 From: Sean Parkinson Date: Fri, 28 Apr 2023 12:10:05 +1000 Subject: [PATCH] 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. --- .gitignore | 1 + configure.ac | 21 + doc/dox_comments/header_files/asn_public.h | 145 ++++ examples/asn1/asn1.c | 494 +++++++++++ examples/asn1/include.am | 12 + examples/include.am | 1 + wolfcrypt/src/asn.c | 964 +++++++++++++++++++-- wolfcrypt/src/error.c | 6 + wolfssl/wolfcrypt/asn.h | 13 +- wolfssl/wolfcrypt/asn_public.h | 100 +++ wolfssl/wolfcrypt/error-crypt.h | 5 +- 11 files changed, 1679 insertions(+), 83 deletions(-) create mode 100644 examples/asn1/asn1.c create mode 100644 examples/asn1/include.am diff --git a/.gitignore b/.gitignore index ca5861e8a..d6e72cb55 100644 --- a/.gitignore +++ b/.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 diff --git a/configure.ac b/configure.ac index ef7290a04..fbd2ac329 100644 --- a/configure.ac +++ b/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"]) diff --git a/doc/dox_comments/header_files/asn_public.h b/doc/dox_comments/header_files/asn_public.h index 06d29fe4b..882ab89cf 100644 --- a/doc/dox_comments/header_files/asn_public.h +++ b/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); + diff --git a/examples/asn1/asn1.c b/examples/asn1/asn1.c new file mode 100644 index 000000000..0397a1426 --- /dev/null +++ b/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 +#endif + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include +#include + +#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 + + diff --git a/examples/asn1/include.am b/examples/asn1/include.am new file mode 100644 index 000000000..9406211e5 --- /dev/null +++ b/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 + diff --git a/examples/include.am b/examples/include.am index afde43057..76f48161a 100644 --- a/examples/include.am +++ b/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 diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 2ddcbb17d..7a47e6c09 100644 --- a/wolfcrypt/src/asn.c +++ b/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 @@ -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 diff --git a/wolfcrypt/src/error.c b/wolfcrypt/src/error.c index ca3d2dcac..27bf65389 100644 --- a/wolfcrypt/src/error.c +++ b/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"; diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 3794acde6..78bff4e19 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/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 diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h index cbe60b8f0..de779286b 100644 --- a/wolfssl/wolfcrypt/asn_public.h +++ b/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 */ diff --git a/wolfssl/wolfcrypt/error-crypt.h b/wolfssl/wolfcrypt/error-crypt.h index 7f0db6aa8..5c062efd4 100644 --- a/wolfssl/wolfcrypt/error-crypt.h +++ b/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