/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Test program for SDR (Secret Decoder Ring) functions.
 */

#include "nspr.h"
#include "pkcs11t.h"
#include "string.h"
#include "nss.h"
#include "secutil.h"
#include "cert.h"
#include "pk11func.h"

#include "plgetopt.h"
#include "pk11sdr.h"
#include "nssb64.h"

#define DEFAULT_VALUE "Test"
static const char default_value[] = { DEFAULT_VALUE };

PRFileDesc *pr_stderr;
PRBool verbose = PR_FALSE;

static void
synopsis(char *program_name)
{
    PR_fprintf(pr_stderr,
               "Usage: %s [<common>] -i <input-file>\n"
               "       %s [<common>]  -o <output-file>\n"
               "       <common> [-d dir] [-v] [-t text] [-a] [-f pwfile | -p pwd] [-m aes|des3]\n",
               program_name, program_name);
}

static void
short_usage(char *program_name)
{
    PR_fprintf(pr_stderr,
               "Type %s -H for more detailed descriptions\n",
               program_name);
    synopsis(program_name);
}

static void
long_usage(char *program_name)
{
    synopsis(program_name);
    PR_fprintf(pr_stderr, "\nSecret Decoder Test:\n");
    PR_fprintf(pr_stderr,
               "  %-13s Read encrypted data from \"file\"\n",
               "-i file");
    PR_fprintf(pr_stderr,
               "  %-13s Write newly generated encrypted data to \"file\"\n",
               "-o file");
    PR_fprintf(pr_stderr,
               "  %-13s Use \"text\" as the plaintext for encryption and verification\n",
               "-t text");
    PR_fprintf(pr_stderr,
               "  %-13s Find security databases in \"dbdir\"\n",
               "-d dbdir");
    PR_fprintf(pr_stderr,
               "  %-13s read the password from \"pwfile\"\n",
               "-f pwfile");
    PR_fprintf(pr_stderr,
               "  %-13s supply \"password\" on the command line\n",
               "-p password");
    PR_fprintf(pr_stderr,
               "  %-13s Mechanism to use for encryption (aes or des3)\n",
               "-m mechanism");
}

int
readStdin(SECItem *result)
{
    unsigned int bufsize = 0;
    int cc;
    unsigned int wanted = 8192U;

    result->len = 0;
    result->data = NULL;
    do {
        if (bufsize < wanted) {
            unsigned char *tmpData = (unsigned char *)PR_Realloc(result->data, wanted);
            if (!tmpData) {
                if (verbose)
                    PR_fprintf(pr_stderr, "Allocation of buffer failed\n");
                return -1;
            }
            result->data = tmpData;
            bufsize = wanted;
        }
        cc = PR_Read(PR_STDIN, result->data + result->len, bufsize - result->len);
        if (cc > 0) {
            result->len += (unsigned)cc;
            if (result->len >= wanted)
                wanted *= 2;
        }
    } while (cc > 0);
    return cc;
}

int
readInputFile(const char *filename, SECItem *result)
{
    PRFileDesc *file /* = PR_OpenFile(input_file, 0) */;
    PRFileInfo info;
    PRStatus s;
    PRInt32 count;
    int retval = -1;

    file = PR_Open(filename, PR_RDONLY, 0);
    if (!file) {
        if (verbose)
            PR_fprintf(pr_stderr, "Open of file %s failed\n", filename);
        goto loser;
    }

    s = PR_GetOpenFileInfo(file, &info);
    if (s != PR_SUCCESS) {
        if (verbose)
            PR_fprintf(pr_stderr, "File info operation failed\n");
        goto file_loser;
    }

    result->len = info.size;
    result->data = (unsigned char *)PR_Malloc(result->len);
    if (!result->data) {
        if (verbose)
            PR_fprintf(pr_stderr, "Allocation of buffer failed\n");
        goto file_loser;
    }

    count = PR_Read(file, result->data, result->len);
    if (count != result->len) {
        if (verbose)
            PR_fprintf(pr_stderr, "Read failed\n");
        goto file_loser;
    }
    retval = 0;

file_loser:
    PR_Close(file);
loser:
    return retval;
}

int
main(int argc, char **argv)
{
    int retval = 0; /* 0 - test succeeded.  -1 - test failed */
    SECStatus rv;
    PLOptState *optstate;
    PLOptStatus optstatus;
    char *program_name;
    const char *input_file = NULL;     /* read encrypted data from here (or create) */
    const char *output_file = NULL;    /* write new encrypted data here */
    const char *value = default_value; /* Use this for plaintext */
    SECItem data;
    SECItem result = { 0, 0, 0 };
    SECItem text;
    PRBool ascii = PR_FALSE;
    secuPWData pwdata = { PW_NONE, 0 };
    CK_MECHANISM_TYPE mechanism = CKM_AES_CBC;

    pr_stderr = PR_STDERR;
    result.data = 0;
    text.data = 0;
    text.len = 0;

    program_name = PL_strrchr(argv[0], '/');
    program_name = program_name ? (program_name + 1) : argv[0];

    optstate = PL_CreateOptState(argc, argv, "?Had:i:o:t:vf:p:m:");
    if (optstate == NULL) {
        SECU_PrintError(program_name, "PL_CreateOptState failed");
        return -1;
    }

    while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
        switch (optstate->option) {
            case '?':
                short_usage(program_name);
                return retval;

            case 'H':
                long_usage(program_name);
                return retval;

            case 'a':
                ascii = PR_TRUE;
                break;

            case 'd':
                SECU_ConfigDirectory(optstate->value);
                break;

            case 'i':
                input_file = optstate->value;
                break;

            case 'o':
                output_file = optstate->value;
                break;

            case 't':
                value = optstate->value;
                break;

            case 'f':
                if (pwdata.data) {
                    PORT_Free(pwdata.data);
                    short_usage(program_name);
                    return -1;
                }
                pwdata.source = PW_FROMFILE;
                pwdata.data = PORT_Strdup(optstate->value);
                break;

            case 'p':
                if (pwdata.data) {
                    PORT_Free(pwdata.data);
                    short_usage(program_name);
                    return -1;
                }
                pwdata.source = PW_PLAINTEXT;
                pwdata.data = PORT_Strdup(optstate->value);
                break;

            case 'v':
                verbose = PR_TRUE;
                break;

            case 'm':
                if (!strcmp(optstate->value, "des3")) {
                    mechanism = CKM_DES3_CBC;
                } else if (strcmp(optstate->value, "aes")) {
                    short_usage(program_name);
                    return -1;
                }
                break;
        }
    }
    PL_DestroyOptState(optstate);
    if (optstatus == PL_OPT_BAD) {
        short_usage(program_name);
        return -1;
    }
    if (!output_file && !input_file && value == default_value) {
        short_usage(program_name);
        PR_fprintf(pr_stderr, "Must specify at least one of -t, -i or -o \n");
        return -1;
    }

    /*
     * Initialize the Security libraries.
     */
    PK11_SetPasswordFunc(SECU_GetModulePassword);

    if (output_file) {
        rv = NSS_InitReadWrite(SECU_ConfigDirectory(NULL));
    } else {
        rv = NSS_Init(SECU_ConfigDirectory(NULL));
    }
    if (rv != SECSuccess) {
        SECU_PrintError(program_name, "NSS_Init failed");
        retval = -1;
        goto prdone;
    }

    /* Convert value into an item */
    data.data = (unsigned char *)value;
    data.len = strlen(value);

    /* Get the encrypted result, either from the input file
     * or from encrypting the plaintext value
     */
    if (input_file) {
        if (verbose)
            printf("Reading data from %s\n", input_file);

        if (!strcmp(input_file, "-")) {
            retval = readStdin(&result);
            ascii = PR_TRUE;
        } else {
            retval = readInputFile(input_file, &result);
        }
        if (retval != 0)
            goto loser;
        if (ascii) {
            /* input was base64 encoded.  Decode it. */
            SECItem newResult = { 0, 0, 0 };
            SECItem *ok = NSSBase64_DecodeBuffer(NULL, &newResult,
                                                 (const char *)result.data, result.len);
            if (!ok) {
                SECU_PrintError(program_name, "Base 64 decode failed");
                retval = -1;
                goto loser;
            }
            SECITEM_ZfreeItem(&result, PR_FALSE);
            result = *ok;
        }
    } else {
        SECItem keyid = { 0, 0, 0 };
        SECItem outBuf = { 0, 0, 0 };
        PK11SlotInfo *slot = NULL;

        /* sigh, initialize the key database */
        slot = PK11_GetInternalKeySlot();
        if (slot && PK11_NeedUserInit(slot)) {
            switch (pwdata.source) {
                case PW_FROMFILE:
                    rv = SECU_ChangePW(slot, 0, pwdata.data);
                    break;
                case PW_PLAINTEXT:
                    rv = SECU_ChangePW(slot, pwdata.data, 0);
                    break;
                default:
                    rv = SECU_ChangePW(slot, "", 0);
                    break;
            }
            if (rv != SECSuccess) {
                SECU_PrintError(program_name, "Failed to initialize slot \"%s\"",
                                PK11_GetSlotName(slot));
                return SECFailure;
            }
        }

        rv = PK11SDR_EncryptWithMechanism(slot, &keyid, mechanism, &data, &result, &pwdata);
        if (rv != SECSuccess) {
            if (verbose)
                SECU_PrintError(program_name, "Encrypt operation failed\n");
            retval = -1;
            goto loser;
        }

        if (slot) {
            PK11_FreeSlot(slot);
        }

        if (verbose)
            printf("Encrypted result is %d bytes long\n", result.len);

        if (!strcmp(output_file, "-")) {
            ascii = PR_TRUE;
        }

        if (ascii) {
            /* base64 encode output. */
            char *newResult = NSSBase64_EncodeItem(NULL, NULL, 0, &result);
            if (!newResult) {
                SECU_PrintError(program_name, "Base 64 encode failed\n");
                retval = -1;
                goto loser;
            }
            outBuf.data = (unsigned char *)newResult;
            outBuf.len = strlen(newResult);
            if (verbose)
                printf("Base 64 encoded result is %d bytes long\n", outBuf.len);
        } else {
            outBuf = result;
        }

        /* -v printf("Result is %.*s\n", text.len, text.data); */
        if (output_file) {
            PRFileDesc *file;
            PRInt32 count;

            if (verbose)
                printf("Writing result to %s\n", output_file);
            if (!strcmp(output_file, "-")) {
                file = PR_STDOUT;
            } else {
                /* Write to file */
                file = PR_Open(output_file, PR_CREATE_FILE | PR_WRONLY, 0666);
            }
            if (!file) {
                if (verbose)
                    SECU_PrintError(program_name,
                                    "Open of output file %s failed\n",
                                    output_file);
                retval = -1;
                goto loser;
            }

            count = PR_Write(file, outBuf.data, outBuf.len);

            if (file == PR_STDOUT) {
                puts("");
            } else {
                PR_Close(file);
            }

            if (count != outBuf.len) {
                if (verbose)
                    SECU_PrintError(program_name, "Write failed\n");
                retval = -1;
                goto loser;
            }
            if (ascii) {
                free(outBuf.data);
            }
        }
    }

    /* Decrypt the value */
    rv = PK11SDR_Decrypt(&result, &text, &pwdata);
    if (rv != SECSuccess) {
        if (verbose)
            SECU_PrintError(program_name, "Decrypt operation failed\n");
        retval = -1;
        goto loser;
    }

    if (verbose)
        printf("Decrypted result is \"%.*s\"\n", text.len, text.data);

    /* Compare to required value */
    if (text.len != data.len || memcmp(data.data, text.data, text.len) != 0) {
        if (verbose)
            PR_fprintf(pr_stderr, "Comparison failed\n");
        retval = -1;
        goto loser;
    }

loser:
    if (text.data)
        SECITEM_ZfreeItem(&text, PR_FALSE);
    if (result.data)
        SECITEM_ZfreeItem(&result, PR_FALSE);
    if (NSS_Shutdown() != SECSuccess) {
        exit(1);
    }

prdone:
    PR_Cleanup();
    if (pwdata.data) {
        PORT_Free(pwdata.data);
    }
    return retval;
}
