/* 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/. */

#include "secutil.h"
#include "secmod.h"
#include "cert.h"
#include "secoid.h"
#include "nss.h"

/* NSPR 2.0 header files */
#include "prinit.h"
#include "prprf.h"
#include "prsystem.h"
#include "prmem.h"
/* Portable layer header files */
#include "plstr.h"
#include "sechash.h" /* for HASH_GetHashObject() */

static PRBool debugInfo;
static PRBool verbose;
static PRBool doVerify;
static PRBool displayAll;

static const char *const usageInfo[] = {
    "signver - verify a detached PKCS7 signature - Version " NSS_VERSION,
    "Commands:",
    " -A                    display all information from pkcs #7",
    " -V                    verify the signed object and display result",
    "Options:",
    " -a                    signature file is ASCII",
    " -d certdir            directory containing cert database",
    " -i dataFileName       input file containing signed data (default stdin)",
    " -o outputFileName     output file name, default stdout",
    " -s signatureFileName  input file for signature (default stdin)",
    " -v                    display verbose reason for failure"
};
static int nUsageInfo = sizeof(usageInfo) / sizeof(char *);

extern int SV_PrintPKCS7ContentInfo(FILE *, SECItem *);

static void
Usage(char *progName, FILE *outFile)
{
    int i;
    fprintf(outFile, "Usage:  %s [ commands ] options\n", progName);
    for (i = 0; i < nUsageInfo; i++)
        fprintf(outFile, "%s\n", usageInfo[i]);
    exit(-1);
}

static HASH_HashType
AlgorithmToHashType(SECAlgorithmID *digestAlgorithms)
{
    SECOidTag tag = SECOID_GetAlgorithmTag(digestAlgorithms);
    HASH_HashType hash = HASH_GetHashTypeByOidTag(tag);
    return hash;
}

static SECStatus
DigestContent(SECItem *digest, SECItem *content, HASH_HashType hashType)
{
    unsigned int maxLen = digest->len;
    unsigned int len = HASH_ResultLen(hashType);
    SECStatus rv;

    if (len > maxLen) {
        PORT_SetError(SEC_ERROR_OUTPUT_LEN);
        return SECFailure;
    }

    rv = HASH_HashBuf(hashType, digest->data, content->data, content->len);
    if (rv == SECSuccess)
        digest->len = len;
    return rv;
}

enum {
    cmd_DisplayAllPCKS7Info = 0,
    cmd_VerifySignedObj
};

enum {
    opt_ASCII,
    opt_CertDir,
    opt_InputDataFile,
    opt_OutputFile,
    opt_InputSigFile,
    opt_PrintWhyFailure,
    opt_DebugInfo
};

static secuCommandFlag signver_commands[] =
    {
      { /* cmd_DisplayAllPCKS7Info*/ 'A', PR_FALSE, 0, PR_FALSE },
      { /* cmd_VerifySignedObj  */ 'V', PR_FALSE, 0, PR_FALSE }
    };

static secuCommandFlag signver_options[] =
    {
      { /* opt_ASCII            */ 'a', PR_FALSE, 0, PR_FALSE },
      { /* opt_CertDir          */ 'd', PR_TRUE, 0, PR_FALSE },
      { /* opt_InputDataFile    */ 'i', PR_TRUE, 0, PR_FALSE },
      { /* opt_OutputFile       */ 'o', PR_TRUE, 0, PR_FALSE },
      { /* opt_InputSigFile     */ 's', PR_TRUE, 0, PR_FALSE },
      { /* opt_PrintWhyFailure  */ 'v', PR_FALSE, 0, PR_FALSE },
      { /* opt_DebugInfo                */ 0, PR_FALSE, 0, PR_FALSE, "debug" }
    };

int
main(int argc, char **argv)
{
    PRFileDesc *contentFile = NULL;
    PRFileDesc *signFile = PR_STDIN;
    FILE *outFile = stdout;
    char *progName;
    SECStatus rv;
    int result = 1;
    SECItem pkcs7der, content;
    secuCommand signver;

    pkcs7der.data = NULL;
    content.data = NULL;

    signver.numCommands = sizeof(signver_commands) / sizeof(secuCommandFlag);
    signver.numOptions = sizeof(signver_options) / sizeof(secuCommandFlag);
    signver.commands = signver_commands;
    signver.options = signver_options;

#ifdef XP_PC
    progName = strrchr(argv[0], '\\');
#else
    progName = strrchr(argv[0], '/');
#endif
    progName = progName ? progName + 1 : argv[0];

    rv = SECU_ParseCommandLine(argc, argv, progName, &signver);
    if (SECSuccess != rv) {
        Usage(progName, outFile);
    }
    debugInfo = signver.options[opt_DebugInfo].activated;
    verbose = signver.options[opt_PrintWhyFailure].activated;
    doVerify = signver.commands[cmd_VerifySignedObj].activated;
    displayAll = signver.commands[cmd_DisplayAllPCKS7Info].activated;
    if (!doVerify && !displayAll)
        doVerify = PR_TRUE;

    /*  Set the certdb directory (default is ~/.netscape) */
    rv = NSS_Init(SECU_ConfigDirectory(signver.options[opt_CertDir].arg));
    if (rv != SECSuccess) {
        SECU_PrintPRandOSError(progName);
        return result;
    }
    /* below here, goto cleanup */
    SECU_RegisterDynamicOids();

    /*  Open the input content file. */
    if (signver.options[opt_InputDataFile].activated &&
        signver.options[opt_InputDataFile].arg) {
        if (PL_strcmp("-", signver.options[opt_InputDataFile].arg)) {
            contentFile = PR_Open(signver.options[opt_InputDataFile].arg,
                                  PR_RDONLY, 0);
            if (!contentFile) {
                PR_fprintf(PR_STDERR,
                           "%s: unable to open \"%s\" for reading.\n",
                           progName, signver.options[opt_InputDataFile].arg);
                goto cleanup;
            }
        } else
            contentFile = PR_STDIN;
    }

    /*  Open the input signature file.  */
    if (signver.options[opt_InputSigFile].activated &&
        signver.options[opt_InputSigFile].arg) {
        if (PL_strcmp("-", signver.options[opt_InputSigFile].arg)) {
            signFile = PR_Open(signver.options[opt_InputSigFile].arg,
                               PR_RDONLY, 0);
            if (!signFile) {
                PR_fprintf(PR_STDERR,
                           "%s: unable to open \"%s\" for reading.\n",
                           progName, signver.options[opt_InputSigFile].arg);
                goto cleanup;
            }
        }
    }

    if (contentFile == PR_STDIN && signFile == PR_STDIN && doVerify) {
        PR_fprintf(PR_STDERR,
                   "%s: cannot read both content and signature from standard input\n",
                   progName);
        goto cleanup;
    }

    /*  Open|Create the output file.  */
    if (signver.options[opt_OutputFile].activated) {
        outFile = fopen(signver.options[opt_OutputFile].arg, "w");
        if (!outFile) {
            PR_fprintf(PR_STDERR, "%s: unable to open \"%s\" for writing.\n",
                       progName, signver.options[opt_OutputFile].arg);
            goto cleanup;
        }
    }

    /* read in the input files' contents */
    rv = SECU_ReadDERFromFile(&pkcs7der, signFile,
                              signver.options[opt_ASCII].activated, PR_FALSE);
    if (signFile != PR_STDIN)
        PR_Close(signFile);
    if (rv != SECSuccess) {
        SECU_PrintError(progName, "problem reading PKCS7 input");
        goto cleanup;
    }
    if (contentFile) {
        rv = SECU_FileToItem(&content, contentFile);
        if (contentFile != PR_STDIN)
            PR_Close(contentFile);
        if (rv != SECSuccess)
            content.data = NULL;
    }

    /* Signature Verification */
    if (doVerify) {
        SEC_PKCS7ContentInfo *cinfo;
        SEC_PKCS7SignedData *signedData;
        HASH_HashType digestType;
        PRBool contentIsSigned;

        cinfo = SEC_PKCS7DecodeItem(&pkcs7der, NULL, NULL, NULL, NULL,
                                    NULL, NULL, NULL);
        if (cinfo == NULL) {
            PR_fprintf(PR_STDERR, "Unable to decode PKCS7 data\n");
            goto cleanup;
        }
        /* below here, goto done */

        contentIsSigned = SEC_PKCS7ContentIsSigned(cinfo);
        if (debugInfo) {
            PR_fprintf(PR_STDERR, "Content is%s encrypted.\n",
                       SEC_PKCS7ContentIsEncrypted(cinfo) ? "" : " not");
        }
        if (debugInfo || !contentIsSigned) {
            PR_fprintf(PR_STDERR, "Content is%s signed.\n",
                       contentIsSigned ? "" : " not");
        }

        if (!contentIsSigned)
            goto done;

        signedData = cinfo->content.signedData;

        /* assume that there is only one digest algorithm for now */
        digestType = AlgorithmToHashType(signedData->digestAlgorithms[0]);
        if (digestType == HASH_AlgNULL) {
            PR_fprintf(PR_STDERR, "Invalid hash algorithmID\n");
            goto done;
        }
        if (content.data) {
            SECCertUsage usage = certUsageEmailSigner;
            SECItem digest;
            unsigned char digestBuffer[HASH_LENGTH_MAX];

            if (debugInfo)
                PR_fprintf(PR_STDERR, "contentToVerify=%s\n", content.data);

            digest.data = digestBuffer;
            digest.len = sizeof digestBuffer;

            if (DigestContent(&digest, &content, digestType)) {
                SECU_PrintError(progName, "Message digest computation failure");
                goto done;
            }

            if (debugInfo) {
                unsigned int i;
                PR_fprintf(PR_STDERR, "Data Digest=:");
                for (i = 0; i < digest.len; i++)
                    PR_fprintf(PR_STDERR, "%02x:", digest.data[i]);
                PR_fprintf(PR_STDERR, "\n");
            }

            fprintf(outFile, "signatureValid=");
            PORT_SetError(0);
            if (SEC_PKCS7VerifyDetachedSignature(cinfo, usage,
                                                 &digest, digestType, PR_FALSE)) {
                fprintf(outFile, "yes");
            } else {
                fprintf(outFile, "no");
                if (verbose) {
                    fprintf(outFile, ":%s",
                            SECU_Strerror(PORT_GetError()));
                }
            }
            fprintf(outFile, "\n");
            result = 0;
        }
    done:
        SEC_PKCS7DestroyContentInfo(cinfo);
    }

    if (displayAll) {
        if (SV_PrintPKCS7ContentInfo(outFile, &pkcs7der))
            result = 1;
    }

cleanup:
    SECITEM_FreeItem(&pkcs7der, PR_FALSE);
    SECITEM_FreeItem(&content, PR_FALSE);

    if (NSS_Shutdown() != SECSuccess) {
        result = 1;
    }

    return result;
}
