/* 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 client-side OCSP.
 */

#include "secutil.h"
#include "nspr.h"
#include "plgetopt.h"
#include "nss.h"
#include "cert.h"
#include "ocsp.h"
#include "xconst.h" /*
                      * XXX internal header file; needed to get at
                      * cert_DecodeAuthInfoAccessExtension -- would be
                      * nice to not need this, but that would require
                      * better/different APIs.
                      */

#ifndef NO_PP       /*                                               \
                     * Compile with this every once in a while to be \
                     * sure that no dependencies on it get added     \
                     * outside of the pretty-printing routines.      \
                     */
#include "ocspti.h" /* internals for pretty-printing routines *only* */
#endif              /* NO_PP */

#if defined(_WIN32)
#include "fcntl.h"
#include "io.h"
#endif

#define DEFAULT_DB_DIR "~/.netscape"

/* global */
char *program_name;

static void
synopsis(char *progname)
{
    PRFileDesc *pr_stderr;

    pr_stderr = PR_STDERR;
    PR_fprintf(pr_stderr, "Usage:");
    PR_fprintf(pr_stderr,
               "\t%s -p [-d <dir>]\n",
               progname);
    PR_fprintf(pr_stderr,
               "\t%s -P [-d <dir>]\n",
               progname);
    PR_fprintf(pr_stderr,
               "\t%s -r <name> [-a] [-L] [-s <name>] [-d <dir>]\n",
               progname);
    PR_fprintf(pr_stderr,
               "\t%s -R <name> [-a] [-l <location>] [-s <name>] [-d <dir>]\n",
               progname);
    PR_fprintf(pr_stderr,
               "\t%s -S <name> [-a] [-l <location> -t <name>]\n",
               progname);
    PR_fprintf(pr_stderr,
               "\t\t [-s <name>] [-w <time>] [-d <dir>]\n");
    PR_fprintf(pr_stderr,
               "\t%s -V <name> [-a] -u <usage> [-l <location> -t <name>]\n",
               progname);
    PR_fprintf(pr_stderr,
               "\t\t [-s <name>] [-w <time>] [-d <dir>]\n");
}

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

static void
long_usage(char *progname)
{
    PRFileDesc *pr_stderr;

    pr_stderr = PR_STDERR;
    synopsis(progname);
    PR_fprintf(pr_stderr, "\nCommands (must specify exactly one):\n");
    PR_fprintf(pr_stderr,
               "  %-13s Pretty-print a binary request read from stdin\n",
               "-p");
    PR_fprintf(pr_stderr,
               "  %-13s Pretty-print a binary response read from stdin\n",
               "-P");
    PR_fprintf(pr_stderr,
               "  %-13s Create a request for cert \"nickname\" on stdout\n",
               "-r nickname");
    PR_fprintf(pr_stderr,
               "  %-13s Get response for cert \"nickname\", dump to stdout\n",
               "-R nickname");
    PR_fprintf(pr_stderr,
               "  %-13s Get status for cert \"nickname\"\n",
               "-S nickname");
    PR_fprintf(pr_stderr,
               "  %-13s Fully verify cert \"nickname\", w/ status check\n",
               "-V nickname");
    PR_fprintf(pr_stderr,
               "\n     %-10s also can be the name of the file with DER or\n"
               "  %-13s PEM(use -a option) cert encoding\n",
               "nickname", "");
    PR_fprintf(pr_stderr, "Options:\n");
    PR_fprintf(pr_stderr,
               "  %-13s Decode input cert from PEM format. DER is default\n",
               "-a");
    PR_fprintf(pr_stderr,
               "  %-13s Add the service locator extension to the request\n",
               "-L");
    PR_fprintf(pr_stderr,
               "  %-13s Find security databases in \"dbdir\" (default %s)\n",
               "-d dbdir", DEFAULT_DB_DIR);
    PR_fprintf(pr_stderr,
               "  %-13s Use \"location\" as URL of responder\n",
               "-l location");
    PR_fprintf(pr_stderr,
               "  %-13s Trust cert \"nickname\" as response signer\n",
               "-t nickname");
    PR_fprintf(pr_stderr,
               "  %-13s Sign requests with cert \"nickname\"\n",
               "-s nickname");
    PR_fprintf(pr_stderr,
               "  %-13s Type of certificate usage for verification:\n",
               "-u usage");
    PR_fprintf(pr_stderr,
               "%-17s c   SSL Client\n", "");
    PR_fprintf(pr_stderr,
               "%-17s s   SSL Server\n", "");
    PR_fprintf(pr_stderr,
               "%-17s I   IPsec\n", "");
    PR_fprintf(pr_stderr,
               "%-17s e   Email Recipient\n", "");
    PR_fprintf(pr_stderr,
               "%-17s E   Email Signer\n", "");
    PR_fprintf(pr_stderr,
               "%-17s S   Object Signer\n", "");
    PR_fprintf(pr_stderr,
               "%-17s C   CA\n", "");
    PR_fprintf(pr_stderr,
               "  %-13s Validity time (default current time), one of:\n",
               "-w time");
    PR_fprintf(pr_stderr,
               "%-17s %-25s (GMT)\n", "", "YYMMDDhhmm[ss]Z");
    PR_fprintf(pr_stderr,
               "%-17s %-25s (later than GMT)\n", "", "YYMMDDhhmm[ss]+hhmm");
    PR_fprintf(pr_stderr,
               "%-17s %-25s (earlier than GMT)\n", "", "YYMMDDhhmm[ss]-hhmm");
}

#if defined(WIN32)
/* We're going to write binary data to stdout, or read binary from stdin.
 * We must put stdout or stdin into O_BINARY mode or else
   outgoing \n's will become \r\n's, and incoming \r\n's will become \n's.
*/
static SECStatus
make_file_binary(FILE *binfile)
{
    int smrv = _setmode(_fileno(binfile), _O_BINARY);
    if (smrv == -1) {
        fprintf(stderr, "%s: Cannot change stdout to binary mode.\n",
                program_name);
    }
    return smrv;
}
#define MAKE_FILE_BINARY make_file_binary
#else
#define MAKE_FILE_BINARY(file)
#endif

/*
 * XXX This is a generic function that would probably make a good
 * replacement for SECU_DER_Read (which is not at all specific to DER,
 * despite its name), but that requires fixing all of the tools...
 * Still, it should be done, whenenver I/somebody has the time.
 * (Also, consider whether this actually belongs in the security
 * library itself, not just in the command library.)
 *
 * This function takes an open file (a PRFileDesc *) and reads the
 * entire file into a SECItem.  (Obviously, the file is intended to
 * be small enough that such a thing is advisable.)  Both the SECItem
 * and the buffer it points to are allocated from the heap; the caller
 * is expected to free them.  ("SECITEM_FreeItem(item, PR_TRUE)")
 */
static SECItem *
read_file_into_item(PRFileDesc *in_file, SECItemType si_type)
{
    PRStatus prv;
    SECItem *item;
    PRFileInfo file_info;
    PRInt32 bytes_read;

    prv = PR_GetOpenFileInfo(in_file, &file_info);
    if (prv != PR_SUCCESS)
        return NULL;

    if (file_info.size == 0) {
        /* XXX Need a better error; just grabbed this one for expediency. */
        PORT_SetError(SEC_ERROR_INPUT_LEN);
        return NULL;
    }

    if (file_info.size > 0xffff) { /* I think this is too big. */
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    item = PORT_Alloc(sizeof(SECItem));
    if (item == NULL)
        return NULL;

    item->type = si_type;
    item->len = (unsigned int)file_info.size;
    item->data = PORT_Alloc((size_t)item->len);
    if (item->data == NULL)
        goto loser;

    bytes_read = PR_Read(in_file, item->data, (PRInt32)item->len);
    if (bytes_read < 0) {
        /* Something went wrong; error is already set for us. */
        goto loser;
    } else if (bytes_read == 0) {
        /* Something went wrong; we read nothing.  But no system/nspr error. */
        /* XXX Need to set an error here. */
        goto loser;
    } else if (item->len != (unsigned int)bytes_read) {
        /* Something went wrong; we read less (or more!?) than we expected. */
        /* XXX Need to set an error here. */
        goto loser;
    }

    return item;

loser:
    SECITEM_FreeItem(item, PR_TRUE);
    return NULL;
}

/*
 * Create a DER-encoded OCSP request (for the certificate whose nickname
 * is "name") and dump it out.
 */
static SECStatus
create_request(FILE *out_file, CERTCertDBHandle *handle, CERTCertificate *cert,
               PRBool add_service_locator, PRBool add_acceptable_responses)
{
    CERTCertList *certs = NULL;
    CERTCertificate *myCert = NULL;
    CERTOCSPRequest *request = NULL;
    PRTime now = PR_Now();
    SECItem *encoding = NULL;
    SECStatus rv = SECFailure;

    if (handle == NULL || cert == NULL)
        return rv;

    myCert = CERT_DupCertificate(cert);
    if (myCert == NULL)
        goto loser;

    /*
     * We need to create a list of one.
     */
    certs = CERT_NewCertList();
    if (certs == NULL)
        goto loser;

    if (CERT_AddCertToListTail(certs, myCert) != SECSuccess)
        goto loser;

    /*
     * Now that cert is included in the list, we need to be careful
     * that we do not try to destroy it twice.  This will prevent that.
     */
    myCert = NULL;

    request = CERT_CreateOCSPRequest(certs, now, add_service_locator, NULL);
    if (request == NULL)
        goto loser;

    if (add_acceptable_responses) {
        rv = CERT_AddOCSPAcceptableResponses(request,
                                             SEC_OID_PKIX_OCSP_BASIC_RESPONSE);
        if (rv != SECSuccess)
            goto loser;
    }

    encoding = CERT_EncodeOCSPRequest(NULL, request, NULL);
    if (encoding == NULL)
        goto loser;

    MAKE_FILE_BINARY(out_file);
    if (fwrite(encoding->data, encoding->len, 1, out_file) != 1)
        goto loser;

    rv = SECSuccess;

loser:
    if (encoding != NULL)
        SECITEM_FreeItem(encoding, PR_TRUE);
    if (request != NULL)
        CERT_DestroyOCSPRequest(request);
    if (certs != NULL)
        CERT_DestroyCertList(certs);
    if (myCert != NULL)
        CERT_DestroyCertificate(myCert);

    return rv;
}

/*
 * Create a DER-encoded OCSP request (for the certificate whose nickname is
 * "cert_name"), then get and dump a corresponding response.  The responder
 * location is either specified explicitly (as "responder_url") or found
 * via the AuthorityInfoAccess URL in the cert.
 */
static SECStatus
dump_response(FILE *out_file, CERTCertDBHandle *handle, CERTCertificate *cert,
              const char *responder_url)
{
    CERTCertList *certs = NULL;
    CERTCertificate *myCert = NULL;
    char *loc = NULL;
    PRTime now = PR_Now();
    SECItem *response = NULL;
    SECStatus rv = SECFailure;
    PRBool includeServiceLocator;

    if (handle == NULL || cert == NULL)
        return rv;

    myCert = CERT_DupCertificate(cert);
    if (myCert == NULL)
        goto loser;

    if (responder_url != NULL) {
        loc = (char *)responder_url;
        includeServiceLocator = PR_TRUE;
    } else {
        loc = CERT_GetOCSPAuthorityInfoAccessLocation(cert);
        if (loc == NULL)
            goto loser;
        includeServiceLocator = PR_FALSE;
    }

    /*
     * We need to create a list of one.
     */
    certs = CERT_NewCertList();
    if (certs == NULL)
        goto loser;

    if (CERT_AddCertToListTail(certs, myCert) != SECSuccess)
        goto loser;

    /*
     * Now that cert is included in the list, we need to be careful
     * that we do not try to destroy it twice.  This will prevent that.
     */
    myCert = NULL;

    response = CERT_GetEncodedOCSPResponse(NULL, certs, loc, now,
                                           includeServiceLocator,
                                           NULL, NULL, NULL);
    if (response == NULL)
        goto loser;

    MAKE_FILE_BINARY(out_file);
    if (fwrite(response->data, response->len, 1, out_file) != 1)
        goto loser;

    rv = SECSuccess;

loser:
    if (response != NULL)
        SECITEM_FreeItem(response, PR_TRUE);
    if (certs != NULL)
        CERT_DestroyCertList(certs);
    if (myCert != NULL)
        CERT_DestroyCertificate(myCert);
    if (loc != NULL && loc != responder_url)
        PORT_Free(loc);

    return rv;
}

/*
 * Get the status for the specified certificate (whose nickname is "cert_name").
 * Directly use the OCSP function rather than doing a full verification.
 */
static SECStatus
get_cert_status(FILE *out_file, CERTCertDBHandle *handle,
                CERTCertificate *cert, const char *cert_name,
                PRTime verify_time)
{
    SECStatus rv = SECFailure;

    if (handle == NULL || cert == NULL)
        goto loser;

    rv = CERT_CheckOCSPStatus(handle, cert, verify_time, NULL);

    fprintf(out_file, "Check of certificate \"%s\" ", cert_name);
    if (rv == SECSuccess) {
        fprintf(out_file, "succeeded.\n");
    } else {
        const char *error_string = SECU_Strerror(PORT_GetError());
        fprintf(out_file, "failed.  Reason:\n");
        if (error_string != NULL && PORT_Strlen(error_string) > 0)
            fprintf(out_file, "%s\n", error_string);
        else
            fprintf(out_file, "Unknown\n");
    }

    rv = SECSuccess;

loser:

    return rv;
}

/*
 * Verify the specified certificate (whose nickname is "cert_name").
 * OCSP is already turned on, so we just need to call the standard
 * certificate verification API and let it do all the work.
 */
static SECStatus
verify_cert(FILE *out_file, CERTCertDBHandle *handle, CERTCertificate *cert,
            const char *cert_name, SECCertUsage cert_usage, PRTime verify_time)
{
    SECStatus rv = SECFailure;

    if (handle == NULL || cert == NULL)
        return rv;

    rv = CERT_VerifyCert(handle, cert, PR_TRUE, cert_usage, verify_time,
                         NULL, NULL);

    fprintf(out_file, "Verification of certificate \"%s\" ", cert_name);
    if (rv == SECSuccess) {
        fprintf(out_file, "succeeded.\n");
    } else {
        const char *error_string = SECU_Strerror(PORT_GetError());
        fprintf(out_file, "failed.  Reason:\n");
        if (error_string != NULL && PORT_Strlen(error_string) > 0)
            fprintf(out_file, "%s\n", error_string);
        else
            fprintf(out_file, "Unknown\n");
    }

    rv = SECSuccess;

    return rv;
}

CERTCertificate *
find_certificate(CERTCertDBHandle *handle, const char *name, PRBool ascii)
{
    CERTCertificate *cert = NULL;
    SECItem der;
    PRFileDesc *certFile;

    if (handle == NULL || name == NULL)
        return NULL;

    if (ascii == PR_FALSE) {
        /* by default need to check if there is cert nick is given */
        cert = CERT_FindCertByNicknameOrEmailAddr(handle, (char *)name);
        if (cert != NULL)
            return cert;
    }

    certFile = PR_Open(name, PR_RDONLY, 0);
    if (certFile == NULL) {
        return NULL;
    }

    if (SECU_ReadDERFromFile(&der, certFile, ascii, PR_FALSE) == SECSuccess) {
        cert = CERT_DecodeCertFromPackage((char *)der.data, der.len);
        SECITEM_FreeItem(&der, PR_FALSE);
    }
    PR_Close(certFile);

    return cert;
}

#ifdef NO_PP

static SECStatus
print_request(FILE *out_file, SECItem *data)
{
    fprintf(out_file, "Cannot pretty-print request compiled with NO_PP.\n");
    return SECSuccess;
}

static SECStatus
print_response(FILE *out_file, SECItem *data, CERTCertDBHandle *handle)
{
    fprintf(out_file, "Cannot pretty-print response compiled with NO_PP.\n");
    return SECSuccess;
}

#else /* NO_PP */

static void
print_ocsp_version(FILE *out_file, SECItem *version, int level)
{
    if (version->len > 0) {
        SECU_PrintInteger(out_file, version, "Version", level);
    } else {
        SECU_Indent(out_file, level);
        fprintf(out_file, "Version: DEFAULT\n");
    }
}

static void
print_ocsp_cert_id(FILE *out_file, CERTOCSPCertID *cert_id, int level)
{
    SECU_Indent(out_file, level);
    fprintf(out_file, "Cert ID:\n");
    level++;

    SECU_PrintAlgorithmID(out_file, &(cert_id->hashAlgorithm),
                          "Hash Algorithm", level);
    SECU_PrintAsHex(out_file, &(cert_id->issuerNameHash),
                    "Issuer Name Hash", level);
    SECU_PrintAsHex(out_file, &(cert_id->issuerKeyHash),
                    "Issuer Key Hash", level);
    SECU_PrintInteger(out_file, &(cert_id->serialNumber),
                      "Serial Number", level);
    /* XXX lookup the cert; if found, print something nice (nickname?) */
}

static void
print_raw_certificates(FILE *out_file, SECItem **raw_certs, int level)
{
    SECItem *raw_cert;
    int i = 0;
    char cert_label[50];

    SECU_Indent(out_file, level);

    if (raw_certs == NULL) {
        fprintf(out_file, "No Certificates.\n");
        return;
    }

    fprintf(out_file, "Certificate List:\n");
    while ((raw_cert = raw_certs[i++]) != NULL) {
        snprintf(cert_label, sizeof(cert_label), "Certificate (%d)", i);
        (void)SECU_PrintSignedData(out_file, raw_cert, cert_label, level + 1,
                                   SECU_PrintCertificate);
    }
}

static void
print_ocsp_extensions(FILE *out_file, CERTCertExtension **extensions,
                      char *msg, int level)
{
    if (extensions) {
        SECU_PrintExtensions(out_file, extensions, msg, level);
    } else {
        SECU_Indent(out_file, level);
        fprintf(out_file, "No %s\n", msg);
    }
}

static void
print_single_request(FILE *out_file, ocspSingleRequest *single, int level)
{
    print_ocsp_cert_id(out_file, single->reqCert, level);
    print_ocsp_extensions(out_file, single->singleRequestExtensions,
                          "Single Request Extensions", level);
}

/*
 * Decode the DER/BER-encoded item "data" as an OCSP request
 * and pretty-print the subfields.
 */
static SECStatus
print_request(FILE *out_file, SECItem *data)
{
    CERTOCSPRequest *request;
    ocspTBSRequest *tbsRequest;
    int level = 0;

    PORT_Assert(out_file != NULL);
    PORT_Assert(data != NULL);
    if (out_file == NULL || data == NULL) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    request = CERT_DecodeOCSPRequest(data);
    if (request == NULL || request->tbsRequest == NULL)
        return SECFailure;

    tbsRequest = request->tbsRequest;

    fprintf(out_file, "TBS Request:\n");
    level++;

    print_ocsp_version(out_file, &(tbsRequest->version), level);

    /*
     * XXX Probably should be an interface to get the signer name
     * without looking inside the tbsRequest at all.
     */
    if (tbsRequest->requestorName != NULL) {
        SECU_Indent(out_file, level);
        fprintf(out_file, "XXX print the requestorName\n");
    } else {
        SECU_Indent(out_file, level);
        fprintf(out_file, "No Requestor Name.\n");
    }

    if (tbsRequest->requestList != NULL) {
        int i;

        for (i = 0; tbsRequest->requestList[i] != NULL; i++) {
            SECU_Indent(out_file, level);
            fprintf(out_file, "Request %d:\n", i);
            print_single_request(out_file, tbsRequest->requestList[i],
                                 level + 1);
        }
    } else {
        fprintf(out_file, "Request list is empty.\n");
    }

    print_ocsp_extensions(out_file, tbsRequest->requestExtensions,
                          "Request Extensions", level);

    if (request->optionalSignature != NULL) {
        ocspSignature *whole_sig;
        SECItem rawsig;

        fprintf(out_file, "Signature:\n");

        whole_sig = request->optionalSignature;
        SECU_PrintAlgorithmID(out_file, &(whole_sig->signatureAlgorithm),
                              "Signature Algorithm", level);

        rawsig = whole_sig->signature;
        DER_ConvertBitString(&rawsig);
        SECU_PrintAsHex(out_file, &rawsig, "Signature", level);

        print_raw_certificates(out_file, whole_sig->derCerts, level);

        fprintf(out_file, "XXX verify the sig and print result\n");
    } else {
        fprintf(out_file, "No Signature\n");
    }

    CERT_DestroyOCSPRequest(request);
    return SECSuccess;
}

static void
print_revoked_info(FILE *out_file, ocspRevokedInfo *revoked_info, int level)
{
    SECU_PrintGeneralizedTime(out_file, &(revoked_info->revocationTime),
                              "Revocation Time", level);

    if (revoked_info->revocationReason != NULL) {
        SECU_PrintAsHex(out_file, revoked_info->revocationReason,
                        "Revocation Reason", level);
    } else {
        SECU_Indent(out_file, level);
        fprintf(out_file, "No Revocation Reason.\n");
    }
}

static void
print_cert_status(FILE *out_file, ocspCertStatus *status, int level)
{
    SECU_Indent(out_file, level);
    fprintf(out_file, "Status: ");

    switch (status->certStatusType) {
        case ocspCertStatus_good:
            fprintf(out_file, "Cert is good.\n");
            break;
        case ocspCertStatus_revoked:
            fprintf(out_file, "Cert has been revoked.\n");
            print_revoked_info(out_file, status->certStatusInfo.revokedInfo,
                               level + 1);
            break;
        case ocspCertStatus_unknown:
            fprintf(out_file, "Cert is unknown to responder.\n");
            break;
        default:
            fprintf(out_file, "Unrecognized status.\n");
            break;
    }
}

static void
print_single_response(FILE *out_file, CERTOCSPSingleResponse *single,
                      int level)
{
    print_ocsp_cert_id(out_file, single->certID, level);

    print_cert_status(out_file, single->certStatus, level);

    SECU_PrintGeneralizedTime(out_file, &(single->thisUpdate),
                              "This Update", level);

    if (single->nextUpdate != NULL) {
        SECU_PrintGeneralizedTime(out_file, single->nextUpdate,
                                  "Next Update", level);
    } else {
        SECU_Indent(out_file, level);
        fprintf(out_file, "No Next Update\n");
    }

    print_ocsp_extensions(out_file, single->singleExtensions,
                          "Single Response Extensions", level);
}

static void
print_responder_id(FILE *out_file, ocspResponderID *responderID, int level)
{
    SECU_Indent(out_file, level);
    fprintf(out_file, "Responder ID ");

    switch (responderID->responderIDType) {
        case ocspResponderID_byName:
            fprintf(out_file, "(byName):\n");
            SECU_PrintName(out_file, &(responderID->responderIDValue.name),
                           "Name", level + 1);
            break;
        case ocspResponderID_byKey:
            fprintf(out_file, "(byKey):\n");
            SECU_PrintAsHex(out_file, &(responderID->responderIDValue.keyHash),
                            "Key Hash", level + 1);
            break;
        default:
            fprintf(out_file, "Unrecognized Responder ID Type\n");
            break;
    }
}

static void
print_response_data(FILE *out_file, ocspResponseData *responseData, int level)
{
    SECU_Indent(out_file, level);
    fprintf(out_file, "Response Data:\n");
    level++;

    print_ocsp_version(out_file, &(responseData->version), level);

    print_responder_id(out_file, responseData->responderID, level);

    SECU_PrintGeneralizedTime(out_file, &(responseData->producedAt),
                              "Produced At", level);

    if (responseData->responses != NULL) {
        int i;

        for (i = 0; responseData->responses[i] != NULL; i++) {
            SECU_Indent(out_file, level);
            fprintf(out_file, "Response %d:\n", i);
            print_single_response(out_file, responseData->responses[i],
                                  level + 1);
        }
    } else {
        fprintf(out_file, "Response list is empty.\n");
    }

    print_ocsp_extensions(out_file, responseData->responseExtensions,
                          "Response Extensions", level);
}

static void
print_basic_response(FILE *out_file, ocspBasicOCSPResponse *basic, int level)
{
    SECItem rawsig;

    SECU_Indent(out_file, level);
    fprintf(out_file, "Basic OCSP Response:\n");
    level++;

    print_response_data(out_file, basic->tbsResponseData, level);

    SECU_PrintAlgorithmID(out_file,
                          &(basic->responseSignature.signatureAlgorithm),
                          "Signature Algorithm", level);

    rawsig = basic->responseSignature.signature;
    DER_ConvertBitString(&rawsig);
    SECU_PrintAsHex(out_file, &rawsig, "Signature", level);

    print_raw_certificates(out_file, basic->responseSignature.derCerts, level);
}

/*
 * Note this must match (exactly) the enumeration ocspResponseStatus.
 */
static char *responseStatusNames[] = {
    "successful (Response has valid confirmations)",
    "malformedRequest (Illegal confirmation request)",
    "internalError (Internal error in issuer)",
    "tryLater (Try again later)",
    "unused ((4) is not used)",
    "sigRequired (Must sign the request)",
    "unauthorized (Request unauthorized)"
};

/*
 * Decode the DER/BER-encoded item "data" as an OCSP response
 * and pretty-print the subfields.
 */
static SECStatus
print_response(FILE *out_file, SECItem *data, CERTCertDBHandle *handle)
{
    CERTOCSPResponse *response;
    int level = 0;

    PORT_Assert(out_file != NULL);
    PORT_Assert(data != NULL);
    if (out_file == NULL || data == NULL) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    response = CERT_DecodeOCSPResponse(data);
    if (response == NULL)
        return SECFailure;

    if (response->statusValue >= ocspResponse_min &&
        response->statusValue <= ocspResponse_max) {
        fprintf(out_file, "Response Status: %s\n",
                responseStatusNames[response->statusValue]);
    } else {
        fprintf(out_file,
                "Response Status: other (Status value %d out of defined range)\n",
                (int)response->statusValue);
    }

    if (response->statusValue == ocspResponse_successful) {
        ocspResponseBytes *responseBytes = response->responseBytes;
        SECStatus sigStatus;
        CERTCertificate *signerCert = NULL;

        PORT_Assert(responseBytes != NULL);

        level++;
        fprintf(out_file, "Response Bytes:\n");
        SECU_PrintObjectID(out_file, &(responseBytes->responseType),
                           "Response Type", level);
        switch (response->responseBytes->responseTypeTag) {
            case SEC_OID_PKIX_OCSP_BASIC_RESPONSE:
                print_basic_response(out_file,
                                     responseBytes->decodedResponse.basic,
                                     level);
                break;
            default:
                SECU_Indent(out_file, level);
                fprintf(out_file, "Unknown response syntax\n");
                break;
        }

        sigStatus = CERT_VerifyOCSPResponseSignature(response, handle,
                                                     NULL, &signerCert, NULL);
        SECU_Indent(out_file, level);
        fprintf(out_file, "Signature verification ");
        if (sigStatus != SECSuccess) {
            fprintf(out_file, "failed: %s\n", SECU_Strerror(PORT_GetError()));
        } else {
            fprintf(out_file, "succeeded.\n");
            if (signerCert != NULL) {
                SECU_PrintName(out_file, &signerCert->subject, "Signer",
                               level);
                CERT_DestroyCertificate(signerCert);
            } else {
                SECU_Indent(out_file, level);
                fprintf(out_file, "No signer cert returned?\n");
            }
        }
    } else {
        SECU_Indent(out_file, level);
        fprintf(out_file, "Unsuccessful response, no more information.\n");
    }

    CERT_DestroyOCSPResponse(response);
    return SECSuccess;
}

#endif /* NO_PP */

static SECStatus
cert_usage_from_char(const char *cert_usage_str, SECCertUsage *cert_usage)
{
    PORT_Assert(cert_usage_str != NULL);
    PORT_Assert(cert_usage != NULL);

    if (PORT_Strlen(cert_usage_str) != 1)
        return SECFailure;

    switch (*cert_usage_str) {
        case 'c':
            *cert_usage = certUsageSSLClient;
            break;
        case 's':
            *cert_usage = certUsageSSLServer;
            break;
        case 'I':
            *cert_usage = certUsageIPsec;
            break;
        case 'e':
            *cert_usage = certUsageEmailRecipient;
            break;
        case 'E':
            *cert_usage = certUsageEmailSigner;
            break;
        case 'S':
            *cert_usage = certUsageObjectSigner;
            break;
        case 'C':
            *cert_usage = certUsageVerifyCA;
            break;
        default:
            return SECFailure;
    }

    return SECSuccess;
}

int
main(int argc, char **argv)
{
    int retval;
    PRFileDesc *in_file;
    FILE *out_file; /* not PRFileDesc until SECU accepts it */
    int crequest, dresponse;
    int prequest, presponse;
    int ccert, vcert;
    const char *db_dir, *date_str, *cert_usage_str, *name;
    const char *responder_name, *responder_url, *signer_name;
    PRBool add_acceptable_responses, add_service_locator;
    SECItem *data = NULL;
    PLOptState *optstate;
    SECStatus rv;
    CERTCertDBHandle *handle = NULL;
    SECCertUsage cert_usage = certUsageSSLClient;
    PRTime verify_time;
    CERTCertificate *cert = NULL;
    PRBool ascii = PR_FALSE;

    retval = -1; /* what we return/exit with on error */

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

    in_file = PR_STDIN;
    out_file = stdout;

    crequest = 0;
    dresponse = 0;
    prequest = 0;
    presponse = 0;
    ccert = 0;
    vcert = 0;

    db_dir = NULL;
    date_str = NULL;
    cert_usage_str = NULL;
    name = NULL;
    responder_name = NULL;
    responder_url = NULL;
    signer_name = NULL;

    add_acceptable_responses = PR_FALSE;
    add_service_locator = PR_FALSE;

    optstate = PL_CreateOptState(argc, argv, "AHLPR:S:V:d:l:pr:s:t:u:w:");
    if (optstate == NULL) {
        SECU_PrintError(program_name, "PL_CreateOptState failed");
        return retval;
    }

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

            case 'A':
                add_acceptable_responses = PR_TRUE;
                break;

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

            case 'L':
                add_service_locator = PR_TRUE;
                break;

            case 'P':
                presponse = 1;
                break;

            case 'R':
                dresponse = 1;
                name = optstate->value;
                break;

            case 'S':
                ccert = 1;
                name = optstate->value;
                break;

            case 'V':
                vcert = 1;
                name = optstate->value;
                break;

            case 'a':
                ascii = PR_TRUE;
                break;

            case 'd':
                db_dir = optstate->value;
                break;

            case 'l':
                responder_url = optstate->value;
                break;

            case 'p':
                prequest = 1;
                break;

            case 'r':
                crequest = 1;
                name = optstate->value;
                break;

            case 's':
                signer_name = optstate->value;
                break;

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

            case 'u':
                cert_usage_str = optstate->value;
                break;

            case 'w':
                date_str = optstate->value;
                break;
        }
    }

    PL_DestroyOptState(optstate);

    if ((crequest + dresponse + prequest + presponse + ccert + vcert) != 1) {
        PR_fprintf(PR_STDERR, "%s: must specify exactly one command\n\n",
                   program_name);
        short_usage(program_name);
        return retval;
    }

    if (vcert) {
        if (cert_usage_str == NULL) {
            PR_fprintf(PR_STDERR, "%s: verification requires cert usage\n\n",
                       program_name);
            short_usage(program_name);
            return retval;
        }

        rv = cert_usage_from_char(cert_usage_str, &cert_usage);
        if (rv != SECSuccess) {
            PR_fprintf(PR_STDERR, "%s: invalid cert usage (\"%s\")\n\n",
                       program_name, cert_usage_str);
            long_usage(program_name);
            return retval;
        }
    }

    if (ccert + vcert) {
        if (responder_url != NULL || responder_name != NULL) {
            /*
             * To do a full status check, both the URL and the cert name
             * of the responder must be specified if either one is.
             */
            if (responder_url == NULL || responder_name == NULL) {
                if (responder_url == NULL)
                    PR_fprintf(PR_STDERR,
                               "%s: must also specify responder location\n\n",
                               program_name);
                else
                    PR_fprintf(PR_STDERR,
                               "%s: must also specify responder name\n\n",
                               program_name);
                short_usage(program_name);
                return retval;
            }
        }

        if (date_str != NULL) {
            rv = DER_AsciiToTime(&verify_time, (char *)date_str);
            if (rv != SECSuccess) {
                SECU_PrintError(program_name, "error converting time string");
                PR_fprintf(PR_STDERR, "\n");
                long_usage(program_name);
                return retval;
            }
        } else {
            verify_time = PR_Now();
        }
    }

    retval = -2; /* errors change from usage to runtime */

    /*
     * Initialize the NSPR and Security libraries.
     */
    PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
    db_dir = SECU_ConfigDirectory(db_dir);
    rv = NSS_Init(db_dir);
    if (rv != SECSuccess) {
        SECU_PrintError(program_name, "NSS_Init failed");
        goto prdone;
    }
    SECU_RegisterDynamicOids();

    if (prequest + presponse) {
        MAKE_FILE_BINARY(stdin);
        data = read_file_into_item(in_file, siBuffer);
        if (data == NULL) {
            SECU_PrintError(program_name, "problem reading input");
            goto nssdone;
        }
    }

    if (crequest + dresponse + presponse + ccert + vcert) {
        handle = CERT_GetDefaultCertDB();
        if (handle == NULL) {
            SECU_PrintError(program_name, "problem getting certdb handle");
            goto nssdone;
        }

        /*
         * It would be fine to do the enable for all of these commands,
         * but this way we check that everything but an overall verify
         * can be done without it.  That is, that the individual pieces
         * work on their own.
         */
        if (vcert) {
            rv = CERT_EnableOCSPChecking(handle);
            if (rv != SECSuccess) {
                SECU_PrintError(program_name, "error enabling OCSP checking");
                goto nssdone;
            }
        }

        if ((ccert + vcert) && (responder_name != NULL)) {
            rv = CERT_SetOCSPDefaultResponder(handle, responder_url,
                                              responder_name);
            if (rv != SECSuccess) {
                SECU_PrintError(program_name,
                                "error setting default responder");
                goto nssdone;
            }

            rv = CERT_EnableOCSPDefaultResponder(handle);
            if (rv != SECSuccess) {
                SECU_PrintError(program_name,
                                "error enabling default responder");
                goto nssdone;
            }
        }
    }

#define NOTYET(opt)                                         \
    {                                                       \
        PR_fprintf(PR_STDERR, "%s not yet working\n", opt); \
        exit(-1);                                           \
    }

    if (name) {
        cert = find_certificate(handle, name, ascii);
    }

    if (crequest) {
        if (signer_name != NULL) {
            NOTYET("-s");
        }
        rv = create_request(out_file, handle, cert, add_service_locator,
                            add_acceptable_responses);
    } else if (dresponse) {
        if (signer_name != NULL) {
            NOTYET("-s");
        }
        rv = dump_response(out_file, handle, cert, responder_url);
    } else if (prequest) {
        rv = print_request(out_file, data);
    } else if (presponse) {
        rv = print_response(out_file, data, handle);
    } else if (ccert) {
        if (signer_name != NULL) {
            NOTYET("-s");
        }
        rv = get_cert_status(out_file, handle, cert, name, verify_time);
    } else if (vcert) {
        if (signer_name != NULL) {
            NOTYET("-s");
        }
        rv = verify_cert(out_file, handle, cert, name, cert_usage, verify_time);
    }

    if (rv != SECSuccess)
        SECU_PrintError(program_name, "error performing requested operation");
    else
        retval = 0;

nssdone:
    if (cert) {
        CERT_DestroyCertificate(cert);
    }

    if (data != NULL) {
        SECITEM_FreeItem(data, PR_TRUE);
    }

    if (handle != NULL) {
        CERT_DisableOCSPDefaultResponder(handle);
        CERT_DisableOCSPChecking(handle);
    }

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

prdone:
    PR_Cleanup();
    return retval;
}
