diff options
Diffstat (limited to 'security/nss/cmd/ocspclnt/ocspclnt.c')
-rw-r--r-- | security/nss/cmd/ocspclnt/ocspclnt.c | 1248 |
1 files changed, 1248 insertions, 0 deletions
diff --git a/security/nss/cmd/ocspclnt/ocspclnt.c b/security/nss/cmd/ocspclnt/ocspclnt.c new file mode 100644 index 0000000000..69d48b4987 --- /dev/null +++ b/security/nss/cmd/ocspclnt/ocspclnt.c @@ -0,0 +1,1248 @@ +/* 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_PPFunc)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; +} |