/* 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>
#include <stdio.h>

#if defined(WOLFSSL_ASN_PRINT) && !defined(NO_FILESYSTEM)

/* 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 asn1App_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;

            if (ferror(fp)) {
                free(data);
                return IO_FAILED_E;
            }

            /* 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;
        }
    }

    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 (asn1App_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;
}

#ifndef NO_CODING
/* 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 (asn1App_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 = 0;
    word32 j = 0;

    /* 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 indices. */
        *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 (asn1App_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;
}
#endif

/* Usage lines to show. */
const char* usage[] = {
    "asn1 [OPTION]... [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",
#ifndef NO_CODING
    "  -B, --base64         file contents are Base64 encoded",
#endif
    "  -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",
#ifndef NO_CODING
    "  -p, --pem            file contents are PEM",
#endif
    "  -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 arguments.
 * @param [in] argv  Command line arguments.
 * @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;
    word32 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);
        }
#ifndef NO_CODING
        /* File is Base64 encoded data. */
        else if ((strcmp(argv[0], "-b64") == 0) ||
                 (strcmp(argv[0], "--base64") == 0)) {
            file_format = FORMAT_BASE64;
        }
#endif
        /* 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,
                (word32)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,
                (word32)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);
        }
#ifndef NO_CODING
        /* File contains PEM blocks. */
        else if ((strcmp(argv[0], "-p") == 0) ||
                 (strcmp(argv[0], "--pem") == 0)) {
            file_format = FORMAT_PEM;
        }
#endif
        /* 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 detection. */
        else if (argv[0][0] == '-') {
            fprintf(stderr, "Bad option: %s\n", argv[0]);
            Usage();
            return 1;
        }
        else {
            if (fp != stdin) {
                fprintf(stderr, "At most one input file can be supplied.\n");
                return 1;
            }
            /* 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);
    }
#ifndef NO_CODING
    else if (file_format == FORMAT_BASE64) {
        ret = PrintBase64(fp);
    }
#endif
#ifndef NO_CODING
    else if (file_format == FORMAT_PEM) {
        ret = PrintPem(fp, pem_skip);
    }
#endif

    if (ret != 0) {
        fprintf(stderr, "%s\n", wc_GetErrorString(ret));
    }

    if (fp != stdin) {
        fclose(fp);
    }
    return (ret == 0) ? 0 : 1;
}

#else

/* Main entry of ASN.1 printing program.
 *
 * @param [in] argc  Count of command line arguments.
 * @param [in] argv  Command line arguments.
 * @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 or file system not compiled"
                    " in.\n");
    return 0;
}

#endif /* WOLFSSL_ASN_PRINT && !defined(NO_FILESYSTEM)*/
