netscape-revival
security/cmd/certutil.c
/*
** certutil.c
**
** utility for managing certificates and the cert database
**
*/
#include "secutil.h"
#include "pk11func.h"
#include "prtypes.h"
#include "prtime.h"
#include "prlong.h"
extern void SEC_Init(void);
#if defined(__sun) && !defined(SVR4)
extern int fwrite(char *, size_t, size_t, FILE*);
extern int getopt(int, char**, char*);
extern int fprintf(FILE *, char *, ...);
extern char *optarg;
#endif
#if defined(LINUX)
#include <getopt.h>
#endif
#define SEC_CERT_DB_EXISTS 0
#define SEC_CREATE_CERT_DB 1
#define GEN_BREAK(e) rv=e; break;
static char *progName;
static CERTGeneralName **GetGeneralName (PRArenaPool *arena)
{
CERTGeneralName **namesList = NULL;
CERTGeneralName *current;
SECStatus rv = SECSuccess;
int count = 0, intValue;
char buffer[512];
void *mark;
PORT_Assert (arena);
mark = PORT_ArenaMark (arena);
do {
fflush (stdin);
puts ("\nSelect one of the following general name type: \n");
puts ("\t1 - instance of other name\n\t2 - rfc822Name\n\t3 - dnsName\n");
puts ("\t4 - x400Address\n\t5 - directoryName\n\t6 - ediPartyName\n");
puts ("\t7 - uniformResourceidentifier\n\t8 - ipAddress\n\t9 - registerID\n");
puts ("\tOther - omit\n\t\tChoice:");
scanf ("%d", &intValue);
if (intValue < certOtherName || intValue > certRegisterID) {
/* add NULL in the last entry to mark end of data */
namesList = PORT_ArenaGrow (arena, namesList,
sizeof (*namesList) * count,
sizeof (*namesList) * (count + 1));
if (namesList == NULL) {
GEN_BREAK (SECFailure);
}
namesList[count] = NULL;
break;
}
current = PORT_ArenaZAlloc (arena, sizeof (*current));
if (current == NULL){
GEN_BREAK (SECFailure);
}
current->type = intValue;
puts ("\nEnter data:");
fflush (stdin);
fflush (stdout);
gets (buffer);
switch (current->type) {
case certURI:
current->name.uri.data = PORT_ArenaAlloc (arena, strlen (buffer));
if (current->name.uri.data == NULL) {
GEN_BREAK (SECFailure);
}
PORT_Memcpy
(current->name.uri.data, buffer, current->name.uri.len = strlen(buffer));
break;
case certDNSName:
case certEDIPartyName:
case certIPAddress:
case certOtherName:
case certRegisterID:
case certRFC822Name:
case certX400Address: {
SECItem temp;
current->name.other.data = PORT_ArenaAlloc (arena, strlen (buffer) + 2);
if (current->name.other.data == NULL) {
GEN_BREAK (SECFailure);
}
PORT_Memcpy (current->name.other.data + 2, buffer, strlen (buffer));
/* This may not be accurate for all cases. For now, use this tag type */
current->name.other.data[0] = (char)(((current->type - 1) & 0x1f)| 0x80);
current->name.other.data[1] = (char)strlen (buffer);
current->name.other.len = strlen (buffer) + 2;
break;
}
case certDirectoryName: {
CERTName *directoryName = NULL;
directoryName = CERT_AsciiToName (buffer);
if (!directoryName) {
fprintf(stderr, "certutil: improperly formatted name: \"%s\"\n", buffer);
break;
}
rv = CERT_CopyName (arena, ¤t->name.directoryName, directoryName);
CERT_DestroyName (directoryName);
break;
}
}
if (rv != SECSuccess)
break;
namesList = PORT_ArenaGrow (arena, namesList,
sizeof (*namesList) * count,
sizeof (*namesList) * (count + 1));
if (namesList == NULL) {
GEN_BREAK (SECFailure);
}
namesList[count] = current;
++count;
}while (1);
if (rv != SECSuccess) {
PORT_SetError (rv);
PORT_ArenaRelease (arena, mark);
namesList = NULL;
}
return (namesList);
}
static SECStatus GetString (PRArenaPool *arena, char *prompt, SECItem *value)
{
char buffer[251];
value->data = NULL;
value->len = 0;
puts (prompt);
fflush (stdin);
gets (buffer);
if (strlen (buffer) > 0) {
value->data = PORT_ArenaAlloc (arena, strlen (buffer));
if (value->data == NULL) {
PORT_SetError (SEC_ERROR_NO_MEMORY);
return (SECFailure);
}
PORT_Memcpy (value->data, buffer, value->len = strlen(buffer));
}
return (SECSuccess);
}
static CERTCertificateRequest *GetCertRequest (FILE *inFile)
{
CERTCertificateRequest *certReq = NULL;
CERTSignedData signedData;
PRArenaPool *arena = NULL;
SECItem reqDER;
SECStatus rv;
reqDER.data = NULL;
do {
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (arena == NULL) {
GEN_BREAK (SEC_ERROR_NO_MEMORY);
}
rv = SECU_DER_Read (&reqDER, inFile);
if (rv)
break;
certReq = (CERTCertificateRequest*) PORT_ArenaZAlloc
(arena, sizeof(CERTCertificateRequest));
if (!certReq)
break;
certReq->arena = arena;
/* Since cert request is a signed data, must decode to get the inner
data
*/
PORT_Memset(&signedData, 0, sizeof(signedData));
rv = DER_Decode(arena, &signedData, CERTSignedDataTemplate, &reqDER);
if (rv)
break;
rv = DER_Decode(arena, certReq, CERTCertificateRequestTemplate, &signedData.data);
} while (0);
if (rv) {
fprintf(stderr, "%s: unable to create cert request (%s)\n", progName,
SECU_ErrorString(PORT_GetError()));
return (NULL);
}
return (certReq);
}
static int GetYesNo (char *prompt)
{
char charValue;
fflush (stdin);
puts (prompt);
scanf ("%c", &charValue);
if (charValue != 'y' && charValue != 'Y')
return (0);
return (1);
}
static CERTCertDBHandle
*OpenCertDB(void)
/* NOTE: This routine has been modified to allow the libsec/pcertdb.c
* routines to automatically find and convert the old cert database
* into the new v3.0 format (cert db version 5).
*/
{
CERTCertDBHandle *certHandle;
SECStatus rv;
struct stat stat_buf;
int ret;
/* Allocate a handle to fill with CERT_OpenCertDB below */
certHandle = (CERTCertDBHandle *)PORT_ZAlloc(sizeof(CERTCertDBHandle));
if (!certHandle) {
SECU_PrintError(progName, "unable to get database handle");
return NULL;
}
rv = CERT_OpenCertDB(certHandle, FALSE, SECU_CertDBNameCallback, NULL);
if (rv) {
SECU_PrintError(progName, "could not open certificate database");
if (certHandle) free (certHandle); /* we don't want to leave anything behind... */
return NULL;
} else {
CERT_SetDefaultCertDB(certHandle);
}
return certHandle;
}
static SECStatus
AddCert(CERTCertDBHandle *handle, char *name, char *trusts, FILE *inFile,
int ascii, PRBool emailcert)
{
CERTCertTrust *trust = NULL;
CERTCertificate *cert = NULL, *tempCert = NULL;
SECItem certDER;
SECStatus rv;
certDER.data = NULL;
do {
/* Read in the entire file specified with the -f argument */
if (ascii) {
certDER.data = SECU_FileToString(inFile);
if (!certDER.data) {
SECU_PrintError(progName, "unable to read input file");
GEN_BREAK(SECFailure);
}
certDER.len = PORT_Strlen (certDER.data);
}
else {
rv = SECU_DER_Read(&certDER, inFile);
if (rv != SECSuccess) {
SECU_PrintError(progName, "unable to read input file");
rv = -1;
break;
}
}
/* Read in an ASCII cert and return a CERTCertificate */
cert = CERT_DecodeCertFromPackage(certDER.data, certDER.len);
if (!cert) {
SECU_PrintError(progName, "could not obtain certificate from file");
GEN_BREAK(SECFailure);
}
/* Create a cert trust to pass to SEC_AddPermCertificate */
trust = (CERTCertTrust *)PORT_ZAlloc(sizeof(CERTCertTrust));
if (!trust) {
SECU_PrintError(progName, "unable to allocate cert trust");
GEN_BREAK(SECFailure);
}
rv = CERT_DecodeTrustString(trust, trusts);
if (rv) {
GEN_BREAK(SECFailure);
}
if ( emailcert ) {
CERTIssuerAndSN *issuerAndSN;
SECItem *derCert;
issuerAndSN = CERT_GetCertIssuerAndSN(NULL, cert);
derCert = &cert->derCert;
#ifdef NOTDEF /* take this out for now */
tempCert = CERT_ImportCertsFromSMIME(handle, 1, &derCert,
issuerAndSN, NULL, NULL,
NULL, PR_TRUE);
#else
tempCert = NULL;
#endif
if (tempCert == NULL) {
SECU_PrintError(progName,
"unable to add email cert (%s) to the temp database",
SECU_ErrorString(PORT_GetError()));
GEN_BREAK(SECFailure);
}
} else {
/* Make sure there isn't a nickname conflict */
if (SEC_CertNicknameConflict(name, &cert->derSubject, handle)) {
SECU_PrintError(progName, "certificate '%s' conflicts", name);
GEN_BREAK(SECFailure);
}
/* CERT_ImportCert only collects certificates and returns the
* first certficate. It does not insert these certificates into
* the dbase. For now, just call CERT_NewTempCertificate.
* This will result in decoding the der twice. This have to
* be handle properly.
*/
tempCert = CERT_NewTempCertificate(handle, &cert->derCert, NULL,
PR_FALSE, PR_TRUE);
if (tempCert == NULL) {
SECU_PrintError(progName,
"unable to add cert (%s) to the temp database",
SECU_ErrorString(PORT_GetError()));
GEN_BREAK(SECFailure);
}
rv = CERT_AddTempCertToPerm(tempCert, name, trust);
if (rv) {
SECU_PrintError(progName, "could not add certificate to database");
GEN_BREAK(SECFailure);
}
}
} while (0);
CERT_DestroyCertificate (tempCert);
CERT_DestroyCertificate (cert);
PORT_Free(trust);
PORT_Free(certDER.data);
return rv;
}
static SECStatus CertReq(char *name, CERTName *subject, char *phone, int ascii)
{
SECKEYKeyDBHandle *keydb;
SECKEYPrivateKey *privk;
SECKEYPublicKey *pubk;
CERTSubjectPublicKeyInfo *spki;
CERTCertificateRequest *cr;
SECItem der, result;
SECStatus rv;
PRArenaPool *arena;
/* Open the key.db database */
keydb = SECU_OpenKeyDB();
if (keydb == NULL) {
SECU_PrintError(progName, "could not open key database");
return SECFailure;
}
privk = SECU_FindPrivateKeyFromNickname(name);
/* Get public key from private key */
pubk = SECKEY_ConvertToPublicKey(privk);
if (!pubk) {
SECU_PrintError(progName, "unable to extract public key");
return SECFailure;
}
/* Create info about public key */
spki = SECKEY_CreateSubjectPublicKeyInfo(pubk);
if (!spki) {
SECU_PrintError(progName, "unable to create subject public key");
return SECFailure;
}
/* Generate certificate request */
cr = CERT_CreateCertificateRequest(subject, spki, 0);
if (!cr) {
SECU_PrintError(progName, "unable to make certificate request");
return SECFailure;
}
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if ( !arena ) {
SECU_PrintError(progName, "out of memory");
return SECFailure;
}
/* Der encode the request */
rv = DER_Encode(arena, &der, CERTCertificateRequestTemplate, cr);
if (rv) {
SECU_PrintError(progName, "der encoding of request failed");
return SECFailure;
}
/* Sign the request */
rv = SEC_DerSignData(arena, &result, der.data, der.len, privk,
SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION);
if (rv) {
SECU_PrintError(progName, "signing of data failed");
return SECFailure;
}
/* Encode request in specified format */
if (ascii) {
unsigned char *obuf;
char *name, *email, *org, *state, *country;
BTOAContext *cvt;
unsigned total, part1, part2, len;
SECItem *it;
it = &result;
len = BTOA_GetLength(it->len);
obuf = (unsigned char*) PORT_Alloc(len);
cvt = BTOA_NewContext();
if (!obuf || !cvt) {
SECU_PrintError(progName, "out of memory");
return SECFailure;
}
BTOA_Begin(cvt);
BTOA_Update(cvt, obuf, &part1, len, it->data, it->len);
BTOA_End(cvt, obuf + part1, &part2, len - part1);
total = part1 + part2;
name = CERT_GetCommonName(subject);
if (!name) {
fprintf(stdout, "You must specify a common name\n");
return SECFailure;
}
if (!phone)
phone = strdup("(not specified)");
email = CERT_GetCertEmailAddress(subject);
if (!email)
email = strdup("(not specified)");
org = CERT_GetOrgName(subject);
if (!org)
org = strdup("(not specified)");
state = CERT_GetStateName(subject);
if (!state)
state = strdup("(not specified)");
country = CERT_GetCountryName(subject);
if (!country)
country = strdup("(not specified)");
fprintf(stdout, "\nCertificate request generated by Netscape certutil\n");
fprintf(stdout, "Phone: %s\n\n", phone);
fprintf(stdout, "Common Name: %s\n", name);
fprintf(stdout, "Email: %s\n", email);
fprintf(stdout, "Organization: %s\n", org);
fprintf(stdout, "State: %s\n", state);
fprintf(stdout, "Country: %s\n\n", country);
fprintf(stdout, "%s\n", NS_CERTREQ_HEADER);
rv = fwrite(obuf, 1, total, stdout);
if (rv != total) {
SECU_PrintSystemError(progName, "write error");
return SECFailure;
}
fprintf(stdout, "%s\n", NS_CERTREQ_TRAILER);
} else {
rv = fwrite(result.data, 1, result.len, stdout);
rv = (rv != (int)result.len) ? -1 : 0;
if (rv) {
SECU_PrintSystemError(progName, "write error");
return SECFailure;
}
}
return SECSuccess;
}
static SECStatus ChangeTrustAttributes(CERTCertDBHandle *handle,
char *name, char *trusts)
{
SECStatus rv;
CERTCertificate *cert;
CERTCertTrust *trust;
cert = CERT_FindCertByNicknameOrEmailAddr(handle, name);
if (!cert) {
SECU_PrintError(progName, "could not find certificate named %s",
name);
return SECFailure;
}
trust = (CERTCertTrust *)PORT_ZAlloc(sizeof(CERTCertTrust));
if (!trust) {
SECU_PrintError(progName, "unable to allocate cert trust");
return SECFailure;
}
rv = CERT_DecodeTrustString(trust, trusts);
if (rv) {
return SECFailure;
}
rv = CERT_ChangeCertTrust(handle, cert, trust);
if (rv) {
SECU_PrintError(progName, "unable to modify trust attributes");
return SECFailure;
}
return SECSuccess;
}
static SECStatus
printCertCB(CERTCertificate *cert, void *arg)
{
SECStatus rv;
SECItem data;
data.data = cert->derCert.data;
data.len = cert->derCert.len;
rv = SECU_PrintSignedData(stdout, &data, "Certificate", 0,
SECU_PrintCertificate);
if (rv) {
SECU_PrintError(progName, "problem printing certificate");
return(SECFailure);
}
SECU_PrintTrustFlags(stdout, &cert->dbEntry->trust,
"Certificate Trust Flags", 1);
return(SECSuccess);
}
static SECStatus
ListCerts(CERTCertDBHandle *handle, char *name, int raw, int ascii)
{
SECStatus rv;
CERTCertificate *cert;
SECItem data;
int ret;
if (name == NULL) {
rv = SECU_PrintCertificateNames(handle, stdout);
if (rv) {
SECU_PrintError(progName, "problem printing certificate nicknames");
return SECFailure;
}
} else {
if ( raw || ascii ) {
cert = CERT_FindCertByNicknameOrEmailAddr(handle, name);
if (!cert) {
SECU_PrintError(progName, "could not find certificate named %s", name);
return SECFailure;
}
data.data = cert->derCert.data;
data.len = cert->derCert.len;
if (ascii) {
fprintf(stdout, "%s\n%s\n%s\n", NS_CERT_HEADER,
BTOA_DataToAscii(data.data, data.len), NS_CERT_TRAILER);
} else if (raw) {
ret = fwrite(data.data, data.len, 1, stdout);
if (ret != 1) {
SECU_PrintSystemError(progName, "error writing raw cert");
return SECFailure;
}
}
} else {
rv = CERT_TraversePermCertsForNickname(handle, name, printCertCB,
NULL);
}
}
return SECSuccess;
}
static SECStatus DeleteCert(CERTCertDBHandle *handle, char *name)
{
SECStatus rv;
CERTCertificate *cert;
cert = CERT_FindCertByNicknameOrEmailAddr(handle, name);
if (!cert) {
SECU_PrintError(progName, "could not find certificate named %s", name);
return SECFailure;
}
rv = SEC_DeletePermCertificate(cert);
if (rv) {
SECU_PrintError(progName, "unable to delete certificate");
return SECFailure;
}
return SECSuccess;
}
static SECStatus
ValidateCert(CERTCertDBHandle *handle, char *name, char *date,
char *certUsage, char *checkSignature, PRBool logit)
{
SECStatus rv;
CERTCertificate *cert;
int64 timeBoundary;
SECCertUsage usage;
PRBool chkSign;
CERTVerifyLog reallog;
CERTVerifyLog *log = NULL;
switch (*certUsage) {
case 'C':
usage = certUsageSSLClient;
break;
case 'V':
usage = certUsageSSLServer;
break;
case 'S':
usage = certUsageEmailSigner;
break;
case 'R':
usage = certUsageEmailRecipient;
break;
default:
PORT_SetError (SEC_ERROR_INVALID_ARGS);
return (SECFailure);
}
chkSign = *checkSignature == 'Y' ? PR_TRUE : PR_FALSE;
do {
cert = CERT_FindCertByNicknameOrEmailAddr(handle, name);
if (!cert) {
SECU_PrintError(progName, "could not find certificate named %s", name);
GEN_BREAK (SECFailure)
}
rv = DER_AsciiToTime(&timeBoundary, date);
if (rv) {
SECU_PrintError(progName, "invalid input date");
GEN_BREAK (SECFailure)
}
if ( logit ) {
log = &reallog;
log->count = 0;
log->head = NULL;
log->tail = NULL;
log->arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if ( log->arena == NULL ) {
SECU_PrintError(progName, "out of memory");
GEN_BREAK (SECFailure)
}
}
/*rv = CERT_CheckCertValidTimes(cert, timeBoundary); */
rv = CERT_VerifyCert(handle, cert, chkSign, usage,
timeBoundary, NULL, log);
if ( log ) {
if ( log->head == NULL ) {
SECU_PrintError(progName, "certificate is valid");
GEN_BREAK (SECSuccess)
} else {
char *name;
CERTVerifyLogNode *node;
node = log->head;
while ( node ) {
if ( node->cert->nickname != NULL ) {
name = node->cert->nickname;
} else {
name = node->cert->subjectName;
}
fprintf(stderr, "%s : %s\n", name, SECU_ErrorString(node->error));
CERT_DestroyCertificate(node->cert);
node = node->next;
}
}
} else {
if (rv == SECSuccess) {
SECU_PrintError(progName, "certificate is valid");
GEN_BREAK (SECSuccess)
}
SECU_PrintError(progName, "certificate is invalid");
GEN_BREAK (SECFailure)
}
} while (0);
free (name);
free (date);
return (rv);
}
static void Usage(char *progName)
{
fprintf(stderr, "Type %s -H for more detailed descriptions\n", progName);
fprintf(stderr,
"Usage: %s -R -n key-name -s subj [-d keydir] [-p phone] [-a]\n",
progName);
fprintf(stderr,
" %s -N [-d certdir]\n", progName);
fprintf(stderr,
" %s -A -n cert-name -t trustargs [-d certdir] [-i input]\n", progName);
fprintf(stderr,
" %s -D -n cert-name [-d certdir]\n", progName);
fprintf(stderr,
" %s -L [-n cert-name] [-d certdir] [-r] [-a]\n", progName);
fprintf(stderr,
" %s -M -n cert-name -t trustargs [-d certdir]\n",
progName);
fprintf(stderr,
" %s -V -n cert-name -b time -u usage -e checksignature [-d certdir]\n",
progName);
fprintf(stderr,
" %s -C -n issuer-name -i cert-request -o output-cert -x [-m serial-number] [-w warp-months] [-d certdir]\n"
" [-1 keyusage extension] \n"
" [-2 basicConstraint extension] \n"
" [-3 authKeyID extension]\n"
" [-4 crlDistributionPoints extension]\n",
progName);
exit(-1);
}
static void LongUsage(char *progName)
{
fprintf(stderr, "%-15s Generate a certificate request (stdout)\n",
"-R");
fprintf(stderr, "%-20s Specify the nickname of the key to use\n",
" -n key-name");
fprintf(stderr, "%-20s Specify the subject name (using RFC1485)\n",
" -s subject");
fprintf(stderr,"%-20s Key database directory (default is ~/.netscape)\n",
" -d keydir");
fprintf(stderr, "%-20s Specify the contact phone number (\"123-456-7890\")\n",
" -p phone");
fprintf(stderr,"%-20s Output the certificate request in ascii (RFC1113)\n",
" -a");
fprintf(stderr, "\n");
fprintf(stderr, "%-15s Create a new certificate database\n",
"-N");
fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
fprintf(stderr, "\n");
fprintf(stderr, "%-15s Add a certificate to the database (create if needed)\n",
"-A");
fprintf(stderr, "%-20s Specify the nickname of the certificate to add\n",
" -n cert-name");
fprintf(stderr, "%-20s Set the certificate trust attributes:\n",
" -t trustargs");
fprintf(stderr, "%-25s p \t valid peer\n", "");
fprintf(stderr, "%-25s P \t trusted peer (implies p)\n", "");
fprintf(stderr, "%-25s c \t valid CA\n", "");
fprintf(stderr, "%-25s C \t trusted CA to issue client certs (implies c)\n", "");
fprintf(stderr, "%-25s S \t trusted CA to issue server certs (implies c)\n", "");
fprintf(stderr, "%-25s u \t user cert\n", "");
fprintf(stderr, "%-25s w \t send warning\n", "");
fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
fprintf(stderr, "%-20s Specify the ASCII certificate file (default is stdin)\n",
" -i input");
fprintf(stderr, "\n");
fprintf(stderr, "%-15s Delete a certificate from the database\n",
"-D");
fprintf(stderr, "%-20s The nickname of the cert to delete\n",
" -n cert-name");
fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
fprintf(stderr, "\n");
fprintf(stderr, "%-15s List all certs, or print out a single named cert\n",
"-L");
fprintf(stderr, "%-20s Pretty print named cert (list all if unspecified)\n",
" -n cert-name");
fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
fprintf(stderr, "%-20s For single cert, print binary DER encoding\n",
" -r");
fprintf(stderr, "%-20s For single cert, print ascii encoding (RFC1113)\n",
" -a");
fprintf(stderr, "\n");
fprintf(stderr, "%-15s Modify trust attributes of certificate\n",
"-M");
fprintf(stderr, "%-20s The nickname of the cert to modify\n",
" -n cert-name");
fprintf(stderr, "%-20s Set the certificate trust attributes (see -A above)\n",
" -t trustargs");
fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
fprintf(stderr, "\n");
fprintf(stderr, "%-15s Validate a certificate\n",
"-V");
fprintf(stderr, "%-20s The nickname of the cert to modify\n",
" -n cert-name");
fprintf(stderr, "%-20s Specify the time boundary (\"YYMMDDHHMMSS[[+][-][HH][MM]][Z]\")\n",
" -b time");
fprintf(stderr, "%-20s Check certificate signature (Y/N)\n",
" -e checksignature");
fprintf(stderr, "%-20s Specify certificate usage:\n", " -u certusage");
fprintf(stderr, "%-25s C \t SSL Client\n", "");
fprintf(stderr, "%-25s V \t SSL Server\n", "");
fprintf(stderr, "%-25s S \t Email signer\n", "");
fprintf(stderr, "%-25s R \t Email Recipient\n", "");
fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
fprintf(stderr, "\n");
fprintf(stderr, "%-15s Create a new certificate\n",
"-C");
fprintf(stderr, "%-20s \tThe nickname of the issuer cert\n",
" -n issuer-name");
fprintf(stderr, "%-20s \tThe certificate request\n",
" -i cert-request file");
fprintf(stderr, "%-20s Define an output file to use (default is stdout)\n",
" -o output");
fprintf(stderr, "%-20s \tSelf Sign\n",
" -x");
fprintf(stderr, "%-20s \tSerial Number\n",
" -m serial-number");
fprintf(stderr, "%-20s \tTime Warp\n",
" -w warp-months");
fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
fprintf(stderr, "%-20s \tThe certificate key usage\n",
" -1 create key usage extension");
fprintf(stderr, "%-20s \tThe certificate basic constraint\n",
" -2 create basicConstraint extension");
fprintf(stderr, "%-20s \tThe certificate authority key ID\n",
" -3 create authKeyID extension");
fprintf(stderr, "%-20s \tThe certificate crl distribution point\n",
" -4 create crlDistributionPoints extension");
fprintf(stderr, "\n");
exit(-1);
}
static CERTCertificate
*MakeV1Cert(CERTCertDBHandle *handle, CERTCertificateRequest *req,
char *issuerNickName, PRBool selfsign, int serialNumber,
int warpmonths)
{
CERTCertificate *issuerCert = NULL;
CERTValidity *validity;
CERTCertificate *cert = NULL;
#ifndef NSPR20
PRTime printableTime;
int64 now, after;
#else
PRExplodedTime printableTime;
PRTime now, after;
#endif
if ( !selfsign ) {
issuerCert = CERT_FindCertByNicknameOrEmailAddr(handle, issuerNickName);
if (!issuerCert) {
SECU_PrintError(progName, "could not find certificate named %s", issuerNickName);
return NULL;
}
}
now = PR_Now();
#ifndef NSPR20
PR_ExplodeGMTTime (&printableTime, now);
#else
PR_ExplodeTime (&printableTime, PR_GMTParameters, now);
#endif
if ( warpmonths ) {
printableTime.tm_mon += warpmonths;
#ifndef NSPR20
now = PR_ImplodeTime (&printableTime, 0, 0);
PR_ExplodeGMTTime (&printableTime, now);
#else
now = PR_ImplodeTime (printableTime);
PR_ExplodeTime (&printableTime, PR_GMTParameters, now);
#endif
}
printableTime.tm_mon += 3;
#ifndef NSPR20
after = PR_ImplodeTime (&printableTime, 0, 0);
#else
after = PR_ImplodeTime (printableTime);
#endif
/* note that the time is now in micro-second unit */
validity = CERT_CreateValidity (now, after);
if ( selfsign ) {
cert = CERT_CreateCertificate
(serialNumber,&(req->subject), validity, req);
} else {
cert = CERT_CreateCertificate
(serialNumber,&(issuerCert->subject), validity, req);
}
CERT_DestroyValidity(validity);
if ( issuerCert ) {
CERT_DestroyCertificate (issuerCert);
}
return(cert);
}
static SECStatus AddKeyUsage (void *extHandle)
{
SECStatus rv;
SECItem bitStringValue;
char keyUsage = 0x0, buffer[5];
int value;
int i;
while (1) {
fprintf(stdout, "%-25s 0 - Digital Signature\n", "");
fprintf(stdout, "%-25s 1 - Non-repudiation\n", "");
fprintf(stdout, "%-25s 2 - Key encipherment\n", "");
fprintf(stdout, "%-25s 3 - Data encipherment\n", "");
fprintf(stdout, "%-25s 4 - Key agreement\n", "");
fprintf(stdout, "%-25s 5 - Cert signning key\n", "");
fprintf(stdout, "%-25s 6 - CRL signning key\n", "");
fprintf(stdout, "%-25s Other to finish\n", "");
gets (buffer);
value = atoi (buffer);
if (value < 0 || value > 6)
break;
keyUsage |= (0x80 >> value);
}
bitStringValue.data = &keyUsage;
bitStringValue.len = 1;
buffer[0] = 'n';
puts ("Is this a critical extension [y/n]? ");
gets (buffer);
return (CERT_EncodeAndAddBitStrExtension
(extHandle, SEC_OID_X509_KEY_USAGE, &bitStringValue,
(buffer[0] == 'y' || buffer[0] == 'Y') ? PR_TRUE : PR_FALSE));
}
typedef SECStatus (* EXTEN_VALUE_ENCODER)
(PRArenaPool *extHandle, void *value, SECItem *encodedValue);
static SECStatus EncodeAndAddExtensionValue
(PRArenaPool *arena, void *extHandle, void *value, PRBool criticality,
int extenType, EXTEN_VALUE_ENCODER EncodeValueFn)
{
SECItem encodedValue;
SECStatus rv;
encodedValue.data = NULL;
encodedValue.len = 0;
do {
rv = (*EncodeValueFn)(arena, value, &encodedValue);
if (rv != SECSuccess)
break;
rv = CERT_AddExtension
(extHandle, extenType, &encodedValue, criticality,PR_TRUE);
} while (0);
return (rv);
}
static SECStatus AddBasicConstraint (void *extHandle)
{
CERTBasicConstraints basicConstraint;
SECItem encodedValue;
SECStatus rv;
char buffer[10];
encodedValue.data = NULL;
encodedValue.len = 0;
do {
basicConstraint.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT;
puts ("Is this a CA certificate [y/n]?");
fflush (stdin);
gets (buffer);
basicConstraint.isCA = (buffer[0] == 'Y' || buffer[0] == 'y') ?
PR_TRUE : PR_FALSE;
puts ("Enter the path length constraint, enter to skip [<0 for unlimited path]:");
gets (buffer);
if (PORT_Strlen (buffer) > 0)
basicConstraint.pathLenConstraint = atoi (buffer);
rv = CERT_EncodeBasicConstraintValue (NULL, &basicConstraint, &encodedValue);
if (rv)
return (rv);
buffer[0] = 'n';
puts ("Is this a critical extension [y/n]? ");
gets (buffer);
rv = CERT_AddExtension
(extHandle, SEC_OID_X509_BASIC_CONSTRAINTS,
&encodedValue, (buffer[0] == 'y' || buffer[0] == 'Y') ?
PR_TRUE : PR_FALSE ,PR_TRUE);
} while (0);
PORT_Free (encodedValue.data);
return (rv);
}
static SECItem *
SignCert(CERTCertificate *cert, char *issuerNickName)
{
SECItem der;
SECItem *result = NULL;
SECKEYPrivateKey *caPrivateKey = NULL;
SECStatus rv;
PRArenaPool *arena;
SECOidTag algID;
caPrivateKey = SECU_FindPrivateKeyFromNickname(issuerNickName);
if (caPrivateKey == NULL) {
SECU_PrintError(progName, "unable to retrieve key %s", issuerNickName);
return NULL;
}
arena = cert->arena;
switch(caPrivateKey->keyType) {
case rsaKey:
algID = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION;
break;
case dsaKey:
algID = SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST;
break;
default:
fprintf(stderr, "Unknown key type for issuer.");
goto done;
break;
}
rv = SECOID_SetAlgorithmID(arena, &cert->signature, algID, 0);
if (rv != SECSuccess) {
fprintf(stderr, "Could not set signature algorithm id.");
goto done;
}
/* we only deal with cert v3 here */
*(cert->version.data) = 2;
cert->version.len = 1;
rv = DER_Encode (arena, &der, CERTCertificateTemplate, cert);
if (rv != SECSuccess) {
fprintf (stderr, "Could not encode certificate.\n");
goto done;
}
result = (SECItem *) PORT_ArenaZAlloc (arena, sizeof (SECItem));
if (result == NULL) {
fprintf (stderr, "Could not allocate item for certificate data.\n");
goto done;
}
rv = SEC_DerSignData (arena, result, der.data, der.len, caPrivateKey,
algID);
if (rv != SECSuccess) {
fprintf (stderr, "Could not sign encoded certificate data.\n");
PORT_Free(result);
result = NULL;
goto done;
}
cert->derCert = *result;
done:
SECKEY_DestroyPrivateKey(caPrivateKey);
return result;
}
static SECStatus AddAuthKeyID (void *extHandle)
{
CERTAuthKeyID *authKeyID = NULL;
PRArenaPool *arena = NULL;
SECStatus rv = SECSuccess;
char charValue;
char buffer[512];
do {
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if ( !arena ) {
SECU_PrintError(progName, "out of memory");
GEN_BREAK (SECFailure);
}
if (GetYesNo ("Enter value for the authKeyID extension [y/n]?\n") == 0)
break;
authKeyID = PORT_ArenaZAlloc (arena, sizeof (CERTAuthKeyID));
if (authKeyID == NULL) {
GEN_BREAK (SECFailure);
}
fflush (stdin);
rv = GetString (arena, "Enter value for the key identifier fields, enter to omit:",
&authKeyID->keyID);
if (rv != SECSuccess)
break;
authKeyID->authCertIssuer = GetGeneralName (arena);
if (authKeyID->authCertIssuer == NULL && SECFailure == PORT_GetError ())
break;
rv = GetString (arena, "Enter value for the authCertSerial field, enter to omit:",
&authKeyID->authCertSerialNumber);
buffer[0] = 'n';
puts ("Is this a critical extension [y/n]? ");
gets (buffer);
rv = EncodeAndAddExtensionValue
(arena, extHandle, authKeyID,
(buffer[0] == 'y' || buffer[0] == 'Y') ? PR_TRUE : PR_FALSE,
SEC_OID_X509_AUTH_KEY_ID, CERT_EncodeAuthKeyID);
if (rv)
break;
} while (0);
if (arena)
PORT_FreeArena (arena, PR_FALSE);
return (rv);
}
static SECStatus AddCrlDistPoint (void *extHandle)
{
PRArenaPool *arena = NULL;
CERTCrlDistributionPoints *crlDistPoints = NULL;
CRLDistributionPoint *current;
SECStatus rv = SECSuccess;
int count = 0, intValue;
char charValue, buffer[5];
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if ( !arena )
return (SECFailure);
do {
current = NULL;
current = PORT_ArenaZAlloc (arena, sizeof (*current));
if (current == NULL) {
GEN_BREAK (SECFailure);
}
fflush (stdin);
/* Get the distributionPointName fields - this field is optional */
puts ("Enter the type of the distribution point name:\n");
puts ("\t1 - Full Name\n\t2 - Relative Name\n\tOther - omit\n\t\tChoice: ");
scanf ("%d", &intValue);
switch (intValue) {
case generalName:
current->distPointType = intValue;
current->distPoint.fullName = GetGeneralName (arena);
rv = PORT_GetError();
break;
case relativeDistinguishedName: {
CERTName *name;
char buffer[512];
current->distPointType = intValue;
puts ("Enter the relative name: ");
fflush (stdout);
fflush (stdin);
gets (buffer);
/* For simplicity, use CERT_AsciiToName to converse from a string
to NAME, but we only interest in the first RDN */
name = CERT_AsciiToName (buffer);
if (!name) {
GEN_BREAK (SECFailure);
}
rv = CERT_CopyRDN (arena, ¤t->distPoint.relativeName, name->rdns[0]);
CERT_DestroyName (name);
break;
}
}
if (rv != SECSuccess)
break;
/* Get the reason flags */
puts ("\nSelect one of the following for the reason flags\n");
puts ("\t0 - unused\n\t1 - keyCompromise\n\t2 - caCompromise\n\t3 - affiliationChanged\n");
puts ("\t4 - superseded\n\t5 - cessationOfOperation\n\t6 - certificateHold\n");
puts ("\tother - omit\t\tChoice: ");
scanf ("%d", &intValue);
if (intValue >= 0 && intValue <8) {
current->reasons.data = PORT_ArenaAlloc (arena, sizeof(char));
if (current->reasons.data == NULL) {
GEN_BREAK (SECFailure);
}
*current->reasons.data = (char)(0x80 >> intValue);
current->reasons.len = 1;
}
puts ("Enter value for the CRL Issuer name:\n");
current->crlIssuer = GetGeneralName (arena);
if (current->crlIssuer == NULL && (rv = PORT_GetError()) == SECFailure)
break;
if (crlDistPoints == NULL) {
crlDistPoints = PORT_ArenaZAlloc (arena, sizeof (*crlDistPoints));
if (crlDistPoints == NULL) {
GEN_BREAK (SECFailure);
}
}
crlDistPoints->distPoints = PORT_ArenaGrow
(arena, crlDistPoints->distPoints,
sizeof (*crlDistPoints->distPoints) * count,
sizeof (*crlDistPoints->distPoints) *(count + 1));
if (crlDistPoints->distPoints == NULL) {
GEN_BREAK (SECFailure);
}
crlDistPoints->distPoints[count] = current;
++count;
if (GetYesNo ("Enter more value for the CRL distribution point extension [y/n]\n") == 0) {
/* Add null to the end of the crlDistPoints to mark end of data */
crlDistPoints->distPoints = PORT_ArenaGrow
(arena, crlDistPoints->distPoints,
sizeof (*crlDistPoints->distPoints) * count,
sizeof (*crlDistPoints->distPoints) *(count + 1));
crlDistPoints->distPoints[count] = NULL;
break;
}
} while (1);
if (rv == SECSuccess) {
fflush (stdin);
buffer[0] = 'n';
puts ("Is this a critical extension [y/n]? ");
gets (buffer);
rv = EncodeAndAddExtensionValue
(arena, extHandle, crlDistPoints,
(buffer[0] == 'Y' || buffer[0] == 'y') ? PR_TRUE : PR_FALSE,
SEC_OID_X509_CRL_DIST_POINTS, CERT_EncodeCRLDistributionPoints);
}
if (arena)
PORT_FreeArena (arena, PR_FALSE);
return (rv);
}
static SECStatus
CreateCert(CERTCertDBHandle *handle, char *issuerNickName, FILE *inFile,
PRBool keyUsage, PRBool basicConstraint, PRBool authKeyID,
PRBool crlDistPoints, FILE *outFile, PRBool selfsign,
int serialNumber, int warpmonths)
{
void *extHandle;
SECItem *certDER;
SECItem reqDER;
SECStatus rv = SECSuccess;
PRArenaPool *arena = NULL;
CERTCertificate *subjectCert = NULL, *issuerCert = NULL;
CERTCertificateRequest *certReq = NULL;
CERTCertDBHandle *certHandle;
reqDER.data = NULL;
do {
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena) {
GEN_BREAK (SEC_ERROR_NO_MEMORY);
}
/* Create a certrequest object from the input cert request der */
certReq = GetCertRequest (inFile);
if (certReq == NULL) {
GEN_BREAK (SECFailure)
}
subjectCert = MakeV1Cert (handle, certReq, issuerNickName, selfsign,
serialNumber, warpmonths);
if (subjectCert == NULL) {
GEN_BREAK (SECFailure)
}
extHandle = CERT_StartCertExtensions (subjectCert);
if (extHandle == NULL) {
GEN_BREAK (SECFailure)
}
/* Add key usage extension */
if (keyUsage) {
rv = AddKeyUsage(extHandle);
if (rv)
break;
}
/* Add basic constraint extension */
if (basicConstraint) {
rv = AddBasicConstraint(extHandle);
if (rv)
break;
}
if (authKeyID) {
rv = AddAuthKeyID (extHandle);
if (rv)
break;
}
if (crlDistPoints) {
rv = AddCrlDistPoint (extHandle);
if (rv)
break;
}
CERT_FinishExtensions(extHandle);
certDER = SignCert (subjectCert, issuerNickName);
if (certDER)
fwrite (certDER->data, 1, certDER->len, outFile);
} while (0);
CERT_DestroyCertificateRequest (certReq);
CERT_DestroyCertificate (subjectCert);
PORT_FreeArena (arena, PR_FALSE);
if (rv != SECSuccess) {
fprintf(stderr, "%s: unable to create cert (%s)\n", progName,
SECU_ErrorString(PORT_GetError()));
}
return (rv);
}
int main(int argc, char **argv)
{
PRBool authKeyID, basicConstraint, crlDistPoints, keyUsage;
CERTName *subject;
FILE *inFile, *outFile;
SECStatus rv = SECSuccess;
char *name;
char *trusts, *phone, *date, *certUsage, *checkSignature;
int o, i;
int ascii, newdb, addcert, certreq, listcerts, delete,
modify, raw, validateCert, createCert;
static int genNameCount = 0, genNameTypeCount = 0;
int currentDistPoint;
PRBool selfsign = PR_FALSE;
int serialNumber = 0;
CERTCertDBHandle *handle;
PRBool emailcert = PR_FALSE;
int warpmonths = 0;
PRBool logit = PR_FALSE;
progName = strrchr(argv[0], '/');
progName = progName ? progName+1 : argv[0];
authKeyID = basicConstraint = crlDistPoints = keyUsage = PR_FALSE;
name = trusts = phone = date = certUsage = checkSignature = NULL;
inFile = outFile = NULL;
subject = 0;
ascii = newdb = addcert = certreq = listcerts =
delete = modify = raw = validateCert = createCert = 0;
/* Parse command line arguments */
while ((o = getopt
(argc, argv, "HNAEn:d:t:i:Rs:p:aVb:u:e:C1234o:LrDMxm:w:l")) != -1) {
switch (o) {
case '?':
Usage(progName);
break;
case 'H':
LongUsage(progName);
break;
case 'N':
newdb = 1;
break;
case 'A':
addcert = 1;
break;
case 'E':
addcert = 1;
emailcert = PR_TRUE;
break;
case 'R':
certreq = 1;
break;
case 'L':
listcerts = 1;
break;
case 'D':
delete = 1;
break;
case 'M':
modify = 1;
break;
case 'V':
validateCert = 1;
break;
case 'C':
createCert = 1;
break;
case 'd':
SECU_ConfigDirectory(optarg);
break;
case 'i':
inFile = fopen(optarg, "r");
if (!inFile) {
fprintf(stderr, "%s: unable to open \"%s\" for reading\n",
progName, optarg);
rv = -1;
}
break;
case 'n':
name = strdup(optarg);
break;
case 'e':
checkSignature = strdup(optarg);
break;
case 'u':
certUsage = strdup(optarg);
break;
case 't':
trusts = strdup(optarg);
break;
case 'p':
phone = strdup(optarg);
break;
case 's':
subject = CERT_AsciiToName(optarg);
if (!subject) {
fprintf(stderr, "%s: improperly formatted name: \"%s\"\n",
progName, optarg);
return -1;
}
break;
case 'a':
ascii = 1;
break;
case 'r':
raw = 1;
break;
case 'b':
date = strdup(optarg);
break;
case 'o':
outFile = fopen(optarg, "wb");
if (!outFile) {
fprintf(stderr, "%s: unable to open \"%s\" for writing\n",
progName, optarg);
rv = -1;
}
break;
case '1':
/* key usage extension */
keyUsage = PR_TRUE;
break;
case '2':
basicConstraint = PR_TRUE;
break;
case '3':
authKeyID = PR_TRUE;
break;
case '4':
crlDistPoints = 1;
break;
case 'x':
selfsign = PR_TRUE;
break;
case 'm':
serialNumber = atoi(optarg);
break;
case 'w': /* timewarp */
warpmonths = atoi(optarg);
break;
case 'l': /* log on verify */
logit = PR_TRUE;
break;
}
if (rv)
return (rv);
}
/* check command line arguments */
if (addcert || certreq || delete || modify || validateCert || createCert)
if (!name) Usage(progName);
if ((addcert || modify) && !trusts) Usage(progName);
if (newdb+certreq+addcert+listcerts+delete+modify+validateCert+createCert != 1)
Usage(progName);
if ((listcerts && (raw || ascii)) && !name)
Usage(progName);
if (listcerts && raw && ascii)
Usage(progName);
if (certreq) {
if (!subject) Usage(progName);
} else if (addcert) {
if (!inFile) inFile = stdin;
}
if (validateCert && !(date && name && certUsage && checkSignature))
Usage(progName);
if (createCert && (!(name && inFile && outFile) || (genNameCount != genNameTypeCount)) )
Usage (progName);
PR_Init("certutil", 1, 1, 0);
SECU_PKCS11Init();
SEC_Init();
handle = OpenCertDB();
if ( handle == NULL ) {
SECU_PrintError(progName, "unable to open cert database");
return(-1);
}
if (addcert) {
rv = AddCert(handle, name, trusts, inFile, ascii, emailcert);
} else
if (certreq) {
rv = CertReq(name, subject, phone, ascii);
} else
if (listcerts) {
rv = ListCerts(handle, name, raw, ascii);
} else
if (delete) {
rv = DeleteCert(handle, name);
} else
if (modify) {
rv = ChangeTrustAttributes(handle, name, trusts);
} else
if (validateCert) {
rv = ValidateCert (handle, name, date, certUsage, checkSignature,
logit);
} else
if (createCert) {
rv = CreateCert(handle, name, inFile, keyUsage, basicConstraint,
authKeyID, crlDistPoints, outFile, selfsign,
serialNumber, warpmonths);
}
if ( handle ) {
CERT_ClosePermCertDB(handle);
}
return rv;
}