зеркало из https://github.com/mozilla/pjs.git
1226 строки
32 KiB
C
1226 строки
32 KiB
C
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is the Netscape security libraries.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1994-2000
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/*
|
|
* Test program for client-side OCSP.
|
|
*
|
|
* $Id: ocspclnt.c,v 1.7 2004-04-25 15:02:47 gerv%gerv.net Exp $
|
|
*/
|
|
|
|
#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 */
|
|
|
|
#define DEFAULT_DB_DIR "~/.netscape"
|
|
|
|
|
|
static void
|
|
synopsis (char *program_name)
|
|
{
|
|
PRFileDesc *pr_stderr;
|
|
|
|
pr_stderr = PR_STDERR;
|
|
PR_fprintf (pr_stderr, "Usage:");
|
|
PR_fprintf (pr_stderr,
|
|
"\t%s -p [-d <dir>]\n",
|
|
program_name);
|
|
PR_fprintf (pr_stderr,
|
|
"\t%s -P [-d <dir>]\n",
|
|
program_name);
|
|
PR_fprintf (pr_stderr,
|
|
"\t%s -r <name> [-L] [-s <name>] [-d <dir>]\n",
|
|
program_name);
|
|
PR_fprintf (pr_stderr,
|
|
"\t%s -R <name> [-l <location>] [-s <name>] [-d <dir>]\n",
|
|
program_name);
|
|
PR_fprintf (pr_stderr,
|
|
"\t%s -S <name> [-l <location> -t <name>]\n",
|
|
program_name);
|
|
PR_fprintf (pr_stderr,
|
|
"\t\t [-s <name>] [-w <time>] [-d <dir>]\n");
|
|
PR_fprintf (pr_stderr,
|
|
"\t%s -V <name> -u <usage> [-l <location> -t <name>]\n",
|
|
program_name);
|
|
PR_fprintf (pr_stderr,
|
|
"\t\t [-s <name>] [-w <time>] [-d <dir>]\n");
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
PRFileDesc *pr_stderr;
|
|
|
|
pr_stderr = PR_STDERR;
|
|
synopsis (program_name);
|
|
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, "Options:\n");
|
|
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 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");
|
|
}
|
|
|
|
|
|
/*
|
|
* 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, const char *cert_name,
|
|
PRBool add_service_locator, PRBool add_acceptable_responses)
|
|
{
|
|
CERTCertificate *cert = NULL;
|
|
CERTCertList *certs = NULL;
|
|
CERTOCSPRequest *request = NULL;
|
|
int64 now = PR_Now();
|
|
SECItem *encoding = NULL;
|
|
SECStatus rv = SECFailure;
|
|
|
|
if (handle == NULL || cert_name == NULL)
|
|
goto loser;
|
|
|
|
cert = CERT_FindCertByNicknameOrEmailAddr (handle, (char *) cert_name);
|
|
if (cert == NULL)
|
|
goto loser;
|
|
|
|
/*
|
|
* We need to create a list of one.
|
|
*/
|
|
certs = CERT_NewCertList();
|
|
if (certs == NULL)
|
|
goto loser;
|
|
|
|
if (CERT_AddCertToListTail (certs, cert) != 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.
|
|
*/
|
|
cert = 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;
|
|
|
|
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 (cert != NULL)
|
|
CERT_DestroyCertificate (cert);
|
|
|
|
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, const char *cert_name,
|
|
const char *responder_url)
|
|
{
|
|
CERTCertificate *cert = NULL;
|
|
CERTCertList *certs = NULL;
|
|
char *loc = NULL;
|
|
int64 now = PR_Now();
|
|
SECItem *response = NULL;
|
|
SECStatus rv = SECFailure;
|
|
PRBool includeServiceLocator;
|
|
|
|
if (handle == NULL || cert_name == NULL)
|
|
goto loser;
|
|
|
|
cert = CERT_FindCertByNicknameOrEmailAddr (handle, (char *) cert_name);
|
|
if (cert == 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, cert) != 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.
|
|
*/
|
|
cert = NULL;
|
|
|
|
response = CERT_GetEncodedOCSPResponse (NULL, certs, loc, now,
|
|
includeServiceLocator,
|
|
NULL, NULL, NULL);
|
|
if (response == NULL)
|
|
goto loser;
|
|
|
|
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 (loc != NULL && loc != responder_url)
|
|
PORT_Free (loc);
|
|
if (cert != NULL)
|
|
CERT_DestroyCertificate (cert);
|
|
|
|
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,
|
|
const char *cert_name, int64 verify_time)
|
|
{
|
|
CERTCertificate *cert = NULL;
|
|
SECStatus rv = SECFailure;
|
|
|
|
if (handle == NULL || cert_name == NULL)
|
|
goto loser;
|
|
|
|
cert = CERT_FindCertByNicknameOrEmailAddr (handle, (char *) cert_name);
|
|
if (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:
|
|
if (cert != NULL)
|
|
CERT_DestroyCertificate (cert);
|
|
|
|
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, const char *cert_name,
|
|
SECCertUsage cert_usage, int64 verify_time)
|
|
{
|
|
CERTCertificate *cert = NULL;
|
|
SECStatus rv = SECFailure;
|
|
|
|
if (handle == NULL || cert_name == NULL)
|
|
goto loser;
|
|
|
|
cert = CERT_FindCertByNicknameOrEmailAddr (handle, (char *) cert_name);
|
|
if (cert == NULL)
|
|
goto loser;
|
|
|
|
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;
|
|
|
|
loser:
|
|
if (cert != NULL)
|
|
CERT_DestroyCertificate (cert);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
#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) {
|
|
sprintf (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)",
|
|
"other (Status value out of defined range)"
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
|
|
PORT_Assert (response->statusValue <= ocspResponse_other);
|
|
fprintf (out_file, "Response Status: %s\n",
|
|
responseStatusNames[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 '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;
|
|
char *program_name;
|
|
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;
|
|
int64 verify_time;
|
|
|
|
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 '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;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
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 (crequest) {
|
|
if (signer_name != NULL) {
|
|
NOTYET("-s");
|
|
}
|
|
rv = create_request (out_file, handle, name, add_service_locator,
|
|
add_acceptable_responses);
|
|
} else if (dresponse) {
|
|
if (signer_name != NULL) {
|
|
NOTYET("-s");
|
|
}
|
|
rv = dump_response (out_file, handle, name, 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, name, verify_time);
|
|
} else if (vcert) {
|
|
if (signer_name != NULL) {
|
|
NOTYET("-s");
|
|
}
|
|
rv = verify_cert (out_file, handle, name, cert_usage, verify_time);
|
|
}
|
|
|
|
if (rv != SECSuccess)
|
|
SECU_PrintError (program_name, "error performing requested operation");
|
|
else
|
|
retval = 0;
|
|
|
|
nssdone:
|
|
if (data != NULL) {
|
|
SECITEM_FreeItem (data, PR_TRUE);
|
|
}
|
|
|
|
if (handle != NULL) {
|
|
(void) CERT_DisableOCSPChecking (handle);
|
|
}
|
|
|
|
if (NSS_Shutdown () != SECSuccess) {
|
|
exit(1);
|
|
}
|
|
|
|
prdone:
|
|
PR_Cleanup ();
|
|
return retval;
|
|
}
|