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

/* Cert-O-Matic CGI */

#include "nspr.h"
#include "prtypes.h"
#include "prtime.h"
#include "prlong.h"

#include "pk11func.h"
#include "cert.h"
#include "cryptohi.h"
#include "secoid.h"
#include "secder.h"
#include "genname.h"
#include "xconst.h"
#include "secutil.h"
#include "pk11pqg.h"
#include "certxutl.h"
#include "nss.h"

/* #define TEST           1 */
/* #define FILEOUT        1 */
/* #define OFFLINE        1 */
#define START_FIELDS 100
#define PREFIX_LEN 6
#define SERIAL_FILE "../serial"
#define DB_DIRECTORY ".."

static char *progName;

typedef struct PairStr Pair;

struct PairStr {
    char *name;
    char *data;
};

char prefix[PREFIX_LEN];

const SEC_ASN1Template CERTIA5TypeTemplate[] = {
    { SEC_ASN1_IA5_STRING }
};

SECKEYPrivateKey *privkeys[9] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                                  NULL, NULL };

#ifdef notdef
const SEC_ASN1Template CERT_GeneralNameTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, 0, SEC_AnyTemplate }
};
#endif

static void
error_out(char *error_string)
{
    printf("Content-type: text/plain\n\n");
    printf("%s", error_string);
    fflush(stderr);
    fflush(stdout);
    exit(1);
}

static void
error_allocate(void)
{
    error_out("ERROR: Unable to allocate memory");
}

static char *
make_copy_string(char *read_pos,
                 int length,
                 char sentinal_value)
/* copys string from to a new string it creates and
       returns a pointer to the new string */
{
    int remaining = length;
    char *write_pos;
    char *new;

    new = write_pos = (char *)PORT_Alloc(length);
    if (new == NULL) {
        error_allocate();
    }
    while (*read_pos != sentinal_value) {
        if (remaining == 1) {
            remaining += length;
            length = length * 2;
            new = PORT_Realloc(new, length);
            if (new == NULL) {
                error_allocate();
            }
            write_pos = new + length - remaining;
        }
        *write_pos = *read_pos;
        ++write_pos;
        ++read_pos;
        remaining = remaining - 1;
    }
    *write_pos = '\0';
    return new;
}

static SECStatus
clean_input(Pair *data)
/* converts the non-alphanumeric characters in a form post
       from hex codes back to characters */
{
    int length;
    int hi_digit;
    int low_digit;
    char character;
    char *begin_pos;
    char *read_pos;
    char *write_pos;
    PRBool name = PR_TRUE;

    begin_pos = data->name;
    while (begin_pos != NULL) {
        length = strlen(begin_pos);
        read_pos = write_pos = begin_pos;
        while ((read_pos - begin_pos) < length) {
            if (*read_pos == '+') {
                *read_pos = ' ';
            }
            if (*read_pos == '%') {
                hi_digit = *(read_pos + 1);
                low_digit = *(read_pos + 2);
                read_pos += 3;
                if (isdigit(hi_digit)) {
                    hi_digit = hi_digit - '0';
                } else {
                    hi_digit = toupper(hi_digit);
                    if (isxdigit(hi_digit)) {
                        hi_digit = (hi_digit - 'A') + 10;
                    } else {
                        error_out("ERROR: Form data incorrectly formated");
                    }
                }
                if (isdigit(low_digit)) {
                    low_digit = low_digit - '0';
                } else {
                    low_digit = toupper(low_digit);
                    if ((low_digit >= 'A') && (low_digit <= 'F')) {
                        low_digit = (low_digit - 'A') + 10;
                    } else {
                        error_out("ERROR: Form data incorrectly formated");
                    }
                }
                character = (hi_digit << 4) | low_digit;
                if (character != 10) {
                    *write_pos = character;
                    ++write_pos;
                }
            } else {
                *write_pos = *read_pos;
                ++write_pos;
                ++read_pos;
            }
        }
        *write_pos = '\0';
        if (name == PR_TRUE) {
            begin_pos = data->data;
            name = PR_FALSE;
        } else {
            data++;
            begin_pos = data->name;
            name = PR_TRUE;
        }
    }
    return SECSuccess;
}

static char *
make_name(char *new_data)
/* gets the next field name in the input string and returns
       a pointer to a string containing a copy of it */
{
    int length = 20;
    char *name;

    name = make_copy_string(new_data, length, '=');
    return name;
}

static char *
make_data(char *new_data)
/* gets the data for the next field in the input string
       and returns a pointer to a string containing it */
{
    int length = 100;
    char *data;
    char *read_pos;

    read_pos = new_data;
    while (*(read_pos - 1) != '=') {
        ++read_pos;
    }
    data = make_copy_string(read_pos, length, '&');
    return data;
}

static Pair
make_pair(char *new_data)
/* makes a pair name/data pair from the input string */
{
    Pair temp;

    temp.name = make_name(new_data);
    temp.data = make_data(new_data);
    return temp;
}

static Pair *
make_datastruct(char *data, int len)
/* parses the input from the form post into a data
       structure of field name/data pairs */
{
    Pair *datastruct;
    Pair *current;
    char *curr_pos;
    int fields = START_FIELDS;
    int remaining = START_FIELDS;

    curr_pos = data;
    datastruct = current = (Pair *)PORT_Alloc(fields * sizeof(Pair));
    if (datastruct == NULL) {
        error_allocate();
    }
    while (curr_pos - data < len) {
        if (remaining == 1) {
            remaining += fields;
            fields = fields * 2;
            datastruct = (Pair *)PORT_Realloc(datastruct, fields *
                                                              sizeof(Pair));
            if (datastruct == NULL) {
                error_allocate();
            }
            current = datastruct + (fields - remaining);
        }
        *current = make_pair(curr_pos);
        while (*curr_pos != '&') {
            ++curr_pos;
        }
        ++curr_pos;
        ++current;
        remaining = remaining - 1;
    }
    current->name = NULL;
    return datastruct;
}

static char *
return_name(Pair *data_struct,
            int n)
/* returns a pointer to the name of the nth
       (starting from 0) item in the data structure */
{
    char *name;

    if ((data_struct + n)->name != NULL) {
        name = (data_struct + n)->name;
        return name;
    } else {
        return NULL;
    }
}

static char *
return_data(Pair *data_struct, int n)
/* returns a pointer to the data of the nth (starting from 0)
       itme in the data structure */
{
    char *data;

    data = (data_struct + n)->data;
    return data;
}

static char *
add_prefix(char *field_name)
{
    extern char prefix[PREFIX_LEN];
    int i = 0;
    char *rv;
    char *write;

    rv = write = PORT_Alloc(PORT_Strlen(prefix) + PORT_Strlen(field_name) + 1);
    for (i = 0; i < PORT_Strlen(prefix); i++) {
        *write = prefix[i];
        write++;
    }
    *write = '\0';
    rv = PORT_Strcat(rv, field_name);
    return rv;
}

static char *
find_field(Pair *data,
           char *field_name,
           PRBool add_pre)
/* returns a pointer to the data of the first pair
       thats name matches the string it is passed */
{
    int i = 0;
    char *retrieved;
    int found = 0;

    if (add_pre) {
        field_name = add_prefix(field_name);
    }
    while (return_name(data, i) != NULL) {
        if (PORT_Strcmp(return_name(data, i), field_name) == 0) {
            retrieved = return_data(data, i);
            found = 1;
            break;
        }
        i++;
    }
    if (!found) {
        retrieved = NULL;
    }
    return retrieved;
}

static PRBool
find_field_bool(Pair *data,
                char *fieldname,
                PRBool add_pre)
{
    char *rv;

    rv = find_field(data, fieldname, add_pre);

    if ((rv != NULL) && (PORT_Strcmp(rv, "true")) == 0) {
        return PR_TRUE;
    } else {
        return PR_FALSE;
    }
}

static CERTCertificateRequest *
makeCertReq(Pair *form_data,
            int which_priv_key)
/* makes and encodes a certrequest */
{

    PK11SlotInfo *slot;
    CERTCertificateRequest *certReq = NULL;
    CERTSubjectPublicKeyInfo *spki;
    SECKEYPrivateKey *privkey = NULL;
    SECKEYPublicKey *pubkey = NULL;
    CERTName *name;
    char *key;
    extern SECKEYPrivateKey *privkeys[9];
    int keySizeInBits;
    char *challenge = "foo";
    SECStatus rv = SECSuccess;
    PQGParams *pqgParams = NULL;
    PQGVerify *pqgVfy = NULL;

    name = CERT_AsciiToName(find_field(form_data, "subject", PR_TRUE));
    if (name == NULL) {
        error_out("ERROR: Unable to create Subject Name");
    }
    key = find_field(form_data, "key", PR_TRUE);
    if (key == NULL) {
        switch (*find_field(form_data, "keysize", PR_TRUE)) {
            case '0':
                keySizeInBits = 2048;
                break;
            case '1':
                keySizeInBits = 1024;
                break;
            case '2':
                keySizeInBits = 512;
                break;
            default:
                error_out("ERROR: Unsupported Key length selected");
        }
        if (find_field_bool(form_data, "keyType-dsa", PR_TRUE)) {
            rv = PK11_PQG_ParamGen(keySizeInBits, &pqgParams, &pqgVfy);
            if (rv != SECSuccess) {
                error_out("ERROR: Unable to generate PQG parameters");
            }
            slot = PK11_GetBestSlot(CKM_DSA_KEY_PAIR_GEN, NULL);
            privkey = PK11_GenerateKeyPair(slot, CKM_DSA_KEY_PAIR_GEN,
                                           pqgParams, &pubkey, PR_FALSE,
                                           PR_TRUE, NULL);
        } else {
            privkey = SECKEY_CreateRSAPrivateKey(keySizeInBits, &pubkey, NULL);
        }
        privkeys[which_priv_key] = privkey;
        spki = SECKEY_CreateSubjectPublicKeyInfo(pubkey);
    } else {
        spki = SECKEY_ConvertAndDecodePublicKeyAndChallenge(key, challenge,
                                                            NULL);
        if (spki == NULL) {
            error_out("ERROR: Unable to decode Public Key and Challenge String");
        }
    }
    certReq = CERT_CreateCertificateRequest(name, spki, NULL);
    if (certReq == NULL) {
        error_out("ERROR: Unable to create Certificate Request");
    }
    if (pubkey != NULL) {
        SECKEY_DestroyPublicKey(pubkey);
    }
    if (spki != NULL) {
        SECKEY_DestroySubjectPublicKeyInfo(spki);
    }
    if (pqgParams != NULL) {
        PK11_PQG_DestroyParams(pqgParams);
    }
    if (pqgVfy != NULL) {
        PK11_PQG_DestroyVerify(pqgVfy);
    }
    return certReq;
}

static CERTCertificate *
MakeV1Cert(CERTCertDBHandle *handle,
           CERTCertificateRequest *req,
           char *issuerNameStr,
           PRBool selfsign,
           int serialNumber,
           int warpmonths,
           Pair *data)
{
    CERTCertificate *issuerCert = NULL;
    CERTValidity *validity;
    CERTCertificate *cert = NULL;
    PRExplodedTime printableTime;
    PRTime now,
        after;
    if (!selfsign) {
        issuerCert = CERT_FindCertByNameString(handle, issuerNameStr);
        if (!issuerCert) {
            error_out("ERROR: Could not find issuer's certificate");
            return NULL;
        }
    }
    if (find_field_bool(data, "manValidity", PR_TRUE)) {
        (void)DER_AsciiToTime(&now, find_field(data, "notBefore", PR_TRUE));
    } else {
        now = PR_Now();
    }
    PR_ExplodeTime(now, PR_GMTParameters, &printableTime);
    if (warpmonths) {
        printableTime.tm_month += warpmonths;
        now = PR_ImplodeTime(&printableTime);
        PR_ExplodeTime(now, PR_GMTParameters, &printableTime);
    }
    if (find_field_bool(data, "manValidity", PR_TRUE)) {
        (void)DER_AsciiToTime(&after, find_field(data, "notAfter", PR_TRUE));
        PR_ExplodeTime(after, PR_GMTParameters, &printableTime);
    } else {
        printableTime.tm_month += 3;
        after = PR_ImplodeTime(&printableTime);
    }
    /* 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 int
get_serial_number(Pair *data)
{
    int serial = 0;
    int error;
    char *filename = SERIAL_FILE;
    char *SN;
    FILE *serialFile;

    if (find_field_bool(data, "serial-auto", PR_TRUE)) {
        serialFile = fopen(filename, "r");
        if (serialFile != NULL) {
            size_t nread = fread(&serial, sizeof(int), 1, serialFile);
            if (ferror(serialFile) != 0 || nread != 1) {
                error_out("Error: Unable to read serial number file");
            }
            if (serial == -1) {
                serial = 21;
            }
            fclose(serialFile);
            ++serial;
            serialFile = fopen(filename, "w");
            if (serialFile == NULL) {
                error_out("ERROR: Unable to open serial number file for writing");
            }
            fwrite(&serial, sizeof(int), 1, serialFile);
            if (ferror(serialFile) != 0) {
                error_out("Error: Unable to write to serial number file");
            }
        } else {
            fclose(serialFile);
            serialFile = fopen(filename, "w");
            if (serialFile == NULL) {
                error_out("ERROR: Unable to open serial number file");
            }
            serial = 21;
            fwrite(&serial, sizeof(int), 1, serialFile);
            if (ferror(serialFile) != 0) {
                error_out("Error: Unable to write to serial number file");
            }
            error = ferror(serialFile);
            if (error != 0) {
                error_out("ERROR: Unable to write to serial file");
            }
        }
        fclose(serialFile);
    } else {
        SN = find_field(data, "serial_value", PR_TRUE);
        while (*SN != '\0') {
            serial = serial * 16;
            if ((*SN >= 'A') && (*SN <= 'F')) {
                serial += *SN - 'A' + 10;
            } else {
                if ((*SN >= 'a') && (*SN <= 'f')) {
                    serial += *SN - 'a' + 10;
                } else {
                    serial += *SN - '0';
                }
            }
            ++SN;
        }
    }
    return serial;
}

typedef SECStatus (*EXTEN_VALUE_ENCODER)(PLArenaPool *extHandle, void *value, SECItem *encodedValue);

static SECStatus
EncodeAndAddExtensionValue(
    PLArenaPool *arena,
    void *extHandle,
    void *value,
    PRBool criticality,
    int extenType,
    EXTEN_VALUE_ENCODER EncodeValueFn)
{
    SECItem encodedValue;
    SECStatus rv;

    encodedValue.data = NULL;
    encodedValue.len = 0;
    rv = (*EncodeValueFn)(arena, value, &encodedValue);
    if (rv != SECSuccess) {
        error_out("ERROR: Unable to encode extension value");
    }
    rv = CERT_AddExtension(extHandle, extenType, &encodedValue, criticality, PR_TRUE);
    return (rv);
}

static SECStatus
AddKeyUsage(void *extHandle,
            Pair *data)
{
    SECItem bitStringValue;
    unsigned char keyUsage = 0x0;

    if (find_field_bool(data, "keyUsage-digitalSignature", PR_TRUE)) {
        keyUsage |= (0x80 >> 0);
    }
    if (find_field_bool(data, "keyUsage-nonRepudiation", PR_TRUE)) {
        keyUsage |= (0x80 >> 1);
    }
    if (find_field_bool(data, "keyUsage-keyEncipherment", PR_TRUE)) {
        keyUsage |= (0x80 >> 2);
    }
    if (find_field_bool(data, "keyUsage-dataEncipherment", PR_TRUE)) {
        keyUsage |= (0x80 >> 3);
    }
    if (find_field_bool(data, "keyUsage-keyAgreement", PR_TRUE)) {
        keyUsage |= (0x80 >> 4);
    }
    if (find_field_bool(data, "keyUsage-keyCertSign", PR_TRUE)) {
        keyUsage |= (0x80 >> 5);
    }
    if (find_field_bool(data, "keyUsage-cRLSign", PR_TRUE)) {
        keyUsage |= (0x80 >> 6);
    }

    bitStringValue.data = &keyUsage;
    bitStringValue.len = 1;

    return (CERT_EncodeAndAddBitStrExtension(extHandle, SEC_OID_X509_KEY_USAGE, &bitStringValue,
                                             (find_field_bool(data, "keyUsage-crit", PR_TRUE))));
}

static CERTOidSequence *
CreateOidSequence(void)
{
    CERTOidSequence *rv = (CERTOidSequence *)NULL;
    PLArenaPool *arena = (PLArenaPool *)NULL;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if ((PLArenaPool *)NULL == arena) {
        goto loser;
    }

    rv = (CERTOidSequence *)PORT_ArenaZAlloc(arena, sizeof(CERTOidSequence));
    if ((CERTOidSequence *)NULL == rv) {
        goto loser;
    }

    rv->oids = (SECItem **)PORT_ArenaZAlloc(arena, sizeof(SECItem *));
    if ((SECItem **)NULL == rv->oids) {
        goto loser;
    }

    rv->arena = arena;
    return rv;

loser:
    if ((PLArenaPool *)NULL != arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (CERTOidSequence *)NULL;
}

static SECStatus
AddOidToSequence(CERTOidSequence *os, SECOidTag oidTag)
{
    SECItem **oids;
    PRUint32 count = 0;
    SECOidData *od;

    od = SECOID_FindOIDByTag(oidTag);
    if ((SECOidData *)NULL == od) {
        return SECFailure;
    }

    for (oids = os->oids; (SECItem *)NULL != *oids; oids++) {
        count++;
    }

    /* ArenaZRealloc */

    {
        PRUint32 i;

        oids = (SECItem **)PORT_ArenaZAlloc(os->arena, sizeof(SECItem *) * (count + 2));
        if ((SECItem **)NULL == oids) {
            return SECFailure;
        }

        for (i = 0; i < count; i++) {
            oids[i] = os->oids[i];
        }

        /* ArenaZFree(os->oids); */
    }

    os->oids = oids;
    os->oids[count] = &od->oid;

    return SECSuccess;
}

static SECItem *
EncodeOidSequence(CERTOidSequence *os)
{
    SECItem *rv;
    extern const SEC_ASN1Template CERT_OidSeqTemplate[];

    rv = (SECItem *)PORT_ArenaZAlloc(os->arena, sizeof(SECItem));
    if ((SECItem *)NULL == rv) {
        goto loser;
    }

    if (!SEC_ASN1EncodeItem(os->arena, rv, os, CERT_OidSeqTemplate)) {
        goto loser;
    }

    return rv;

loser:
    return (SECItem *)NULL;
}

static SECStatus
AddExtKeyUsage(void *extHandle, Pair *data)
{
    SECStatus rv;
    CERTOidSequence *os;
    SECItem *value;
    PRBool crit;

    os = CreateOidSequence();
    if ((CERTOidSequence *)NULL == os) {
        return SECFailure;
    }

    if (find_field_bool(data, "extKeyUsage-serverAuth", PR_TRUE)) {
        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_SERVER_AUTH);
        if (SECSuccess != rv)
            goto loser;
    }

    if (find_field_bool(data, "extKeyUsage-msTrustListSign", PR_TRUE)) {
        rv = AddOidToSequence(os, SEC_OID_MS_EXT_KEY_USAGE_CTL_SIGNING);
        if (SECSuccess != rv)
            goto loser;
    }

    if (find_field_bool(data, "extKeyUsage-clientAuth", PR_TRUE)) {
        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH);
        if (SECSuccess != rv)
            goto loser;
    }

    if (find_field_bool(data, "extKeyUsage-codeSign", PR_TRUE)) {
        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_CODE_SIGN);
        if (SECSuccess != rv)
            goto loser;
    }

    if (find_field_bool(data, "extKeyUsage-emailProtect", PR_TRUE)) {
        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT);
        if (SECSuccess != rv)
            goto loser;
    }

    if (find_field_bool(data, "extKeyUsage-timeStamp", PR_TRUE)) {
        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_TIME_STAMP);
        if (SECSuccess != rv)
            goto loser;
    }

    if (find_field_bool(data, "extKeyUsage-ocspResponder", PR_TRUE)) {
        rv = AddOidToSequence(os, SEC_OID_OCSP_RESPONDER);
        if (SECSuccess != rv)
            goto loser;
    }

    if (find_field_bool(data, "extKeyUsage-NS-govtApproved", PR_TRUE)) {
        rv = AddOidToSequence(os, SEC_OID_NS_KEY_USAGE_GOVT_APPROVED);
        if (SECSuccess != rv)
            goto loser;
    }

    value = EncodeOidSequence(os);

    crit = find_field_bool(data, "extKeyUsage-crit", PR_TRUE);

    rv = CERT_AddExtension(extHandle, SEC_OID_X509_EXT_KEY_USAGE, value,
                           crit, PR_TRUE);
/*FALLTHROUGH*/
loser:
    CERT_DestroyOidSequence(os);
    return rv;
}

static SECStatus
AddSubKeyID(void *extHandle,
            Pair *data,
            CERTCertificate *subjectCert)
{
    SECItem encodedValue;
    SECStatus rv;
    char *read;
    char *write;
    char *first;
    char character;
    int high_digit = 0,
        low_digit = 0;
    int len;
    PRBool odd = PR_FALSE;

    encodedValue.data = NULL;
    encodedValue.len = 0;
    first = read = write = find_field(data, "subjectKeyIdentifier-text",
                                      PR_TRUE);
    len = PORT_Strlen(first);
    odd = ((len % 2) != 0) ? PR_TRUE : PR_FALSE;
    if (find_field_bool(data, "subjectKeyIdentifier-radio-hex", PR_TRUE)) {
        if (odd) {
            error_out("ERROR: Improperly formated subject key identifier, hex values must be expressed as an octet string");
        }
        while (*read != '\0') {
            if (!isxdigit(*read)) {
                error_out("ERROR: Improperly formated subject key identifier");
            }
            *read = toupper(*read);
            if ((*read >= 'A') && (*read <= 'F')) {
                high_digit = *read - 'A' + 10;
            } else {
                high_digit = *read - '0';
            }
            ++read;
            if (!isxdigit(*read)) {
                error_out("ERROR: Improperly formated subject key identifier");
            }
            *read = toupper(*read);
            if ((*read >= 'A') && (*read <= 'F')) {
                low_digit = *(read) - 'A' + 10;
            } else {
                low_digit = *(read) - '0';
            }
            character = (high_digit << 4) | low_digit;
            *write = character;
            ++write;
            ++read;
        }
        *write = '\0';
        len = write - first;
    }
    subjectCert->subjectKeyID.data = (unsigned char *)find_field(data, "subjectKeyIdentifier-text", PR_TRUE);
    subjectCert->subjectKeyID.len = len;
    rv = CERT_EncodeSubjectKeyID(NULL, &subjectCert->subjectKeyID, &encodedValue);
    if (rv) {
        return (rv);
    }
    return (CERT_AddExtension(extHandle, SEC_OID_X509_SUBJECT_KEY_ID,
                              &encodedValue, PR_FALSE, PR_TRUE));
}

static SECStatus
AddAuthKeyID(void *extHandle,
             Pair *data,
             char *issuerNameStr,
             CERTCertDBHandle *handle)
{
    CERTAuthKeyID *authKeyID = NULL;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;
    CERTCertificate *issuerCert = NULL;
    CERTGeneralName *genNames;
    CERTName *directoryName = NULL;

    issuerCert = CERT_FindCertByNameString(handle, issuerNameStr);
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!arena) {
        error_allocate();
    }

    authKeyID = PORT_ArenaZAlloc(arena, sizeof(CERTAuthKeyID));
    if (authKeyID == NULL) {
        error_allocate();
    }
    if (find_field_bool(data, "authorityKeyIdentifier-radio-keyIdentifier",
                        PR_TRUE)) {
        authKeyID->keyID.data = PORT_ArenaAlloc(arena, PORT_Strlen((char *)issuerCert->subjectKeyID.data));
        if (authKeyID->keyID.data == NULL) {
            error_allocate();
        }
        PORT_Memcpy(authKeyID->keyID.data, issuerCert->subjectKeyID.data,
                    authKeyID->keyID.len =
                        PORT_Strlen((char *)issuerCert->subjectKeyID.data));
    } else {

        PORT_Assert(arena);
        genNames = (CERTGeneralName *)PORT_ArenaZAlloc(arena, (sizeof(CERTGeneralName)));
        if (genNames == NULL) {
            error_allocate();
        }
        genNames->l.next = genNames->l.prev = &(genNames->l);
        genNames->type = certDirectoryName;

        directoryName = CERT_AsciiToName(issuerCert->subjectName);
        if (!directoryName) {
            error_out("ERROR: Unable to create Directory Name");
        }
        rv = CERT_CopyName(arena, &genNames->name.directoryName,
                           directoryName);
        CERT_DestroyName(directoryName);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to copy Directory Name");
        }
        authKeyID->authCertIssuer = genNames;
        if (authKeyID->authCertIssuer == NULL && SECFailure == PORT_GetError()) {
            error_out("ERROR: Unable to get Issuer General Name for Authority Key ID Extension");
        }
        authKeyID->authCertSerialNumber = issuerCert->serialNumber;
    }
    rv = EncodeAndAddExtensionValue(arena, extHandle, authKeyID, PR_FALSE,
                                    SEC_OID_X509_AUTH_KEY_ID,
                                    (EXTEN_VALUE_ENCODER)
                                        CERT_EncodeAuthKeyID);
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return (rv);
}

static SECStatus
AddPrivKeyUsagePeriod(void *extHandle,
                      Pair *data,
                      CERTCertificate *cert)
{
    char *notBeforeStr;
    char *notAfterStr;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;
    CERTPrivKeyUsagePeriod *pkup;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!arena) {
        error_allocate();
    }
    pkup = PORT_ArenaZNew(arena, CERTPrivKeyUsagePeriod);
    if (pkup == NULL) {
        error_allocate();
    }
    notBeforeStr = (char *)PORT_Alloc(16);
    if (notBeforeStr == NULL) {
        error_allocate();
    }
    notAfterStr = (char *)PORT_Alloc(16);
    if (notAfterStr == NULL) {
        error_allocate();
    }
    *notBeforeStr = '\0';
    *notAfterStr = '\0';
    pkup->arena = arena;
    pkup->notBefore.len = 0;
    pkup->notBefore.data = NULL;
    pkup->notAfter.len = 0;
    pkup->notAfter.data = NULL;
    if (find_field_bool(data, "privKeyUsagePeriod-radio-notBefore", PR_TRUE) ||
        find_field_bool(data, "privKeyUsagePeriod-radio-both", PR_TRUE)) {
        pkup->notBefore.len = 15;
        pkup->notBefore.data = (unsigned char *)notBeforeStr;
        if (find_field_bool(data, "privKeyUsagePeriod-notBefore-radio-manual",
                            PR_TRUE)) {
            PORT_Strcat(notBeforeStr, find_field(data,
                                                 "privKeyUsagePeriod-notBefore-year",
                                                 PR_TRUE));
            PORT_Strcat(notBeforeStr, find_field(data,
                                                 "privKeyUsagePeriod-notBefore-month",
                                                 PR_TRUE));
            PORT_Strcat(notBeforeStr, find_field(data,
                                                 "privKeyUsagePeriod-notBefore-day",
                                                 PR_TRUE));
            PORT_Strcat(notBeforeStr, find_field(data,
                                                 "privKeyUsagePeriod-notBefore-hour",
                                                 PR_TRUE));
            PORT_Strcat(notBeforeStr, find_field(data,
                                                 "privKeyUsagePeriod-notBefore-minute",
                                                 PR_TRUE));
            PORT_Strcat(notBeforeStr, find_field(data,
                                                 "privKeyUsagePeriod-notBefore-second",
                                                 PR_TRUE));
            if ((*(notBeforeStr + 14) != '\0') ||
                (!isdigit(*(notBeforeStr + 13))) ||
                (*(notBeforeStr + 12) >= '5' && *(notBeforeStr + 12) <= '0') ||
                (!isdigit(*(notBeforeStr + 11))) ||
                (*(notBeforeStr + 10) >= '5' && *(notBeforeStr + 10) <= '0') ||
                (!isdigit(*(notBeforeStr + 9))) ||
                (*(notBeforeStr + 8) >= '2' && *(notBeforeStr + 8) <= '0') ||
                (!isdigit(*(notBeforeStr + 7))) ||
                (*(notBeforeStr + 6) >= '3' && *(notBeforeStr + 6) <= '0') ||
                (!isdigit(*(notBeforeStr + 5))) ||
                (*(notBeforeStr + 4) >= '1' && *(notBeforeStr + 4) <= '0') ||
                (!isdigit(*(notBeforeStr + 3))) ||
                (!isdigit(*(notBeforeStr + 2))) ||
                (!isdigit(*(notBeforeStr + 1))) ||
                (!isdigit(*(notBeforeStr + 0))) ||
                (*(notBeforeStr + 8) == '2' && *(notBeforeStr + 9) >= '4') ||
                (*(notBeforeStr + 6) == '3' && *(notBeforeStr + 7) >= '1') ||
                (*(notBeforeStr + 4) == '1' && *(notBeforeStr + 5) >= '2')) {
                error_out("ERROR: Improperly formated private key usage period");
            }
            *(notBeforeStr + 14) = 'Z';
            *(notBeforeStr + 15) = '\0';
        } else {
            if ((*(cert->validity.notBefore.data) > '5') ||
                ((*(cert->validity.notBefore.data) == '5') &&
                 (*(cert->validity.notBefore.data + 1) != '0'))) {
                PORT_Strcat(notBeforeStr, "19");
            } else {
                PORT_Strcat(notBeforeStr, "20");
            }
            PORT_Strcat(notBeforeStr, (char *)cert->validity.notBefore.data);
        }
    }
    if (find_field_bool(data, "privKeyUsagePeriod-radio-notAfter", PR_TRUE) ||
        find_field_bool(data, "privKeyUsagePeriod-radio-both", PR_TRUE)) {
        pkup->notAfter.len = 15;
        pkup->notAfter.data = (unsigned char *)notAfterStr;
        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-year",
                                            PR_TRUE));
        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-month",
                                            PR_TRUE));
        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-day",
                                            PR_TRUE));
        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-hour",
                                            PR_TRUE));
        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-minute",
                                            PR_TRUE));
        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-second",
                                            PR_TRUE));
        if ((*(notAfterStr + 14) != '\0') ||
            (!isdigit(*(notAfterStr + 13))) ||
            (*(notAfterStr + 12) >= '5' && *(notAfterStr + 12) <= '0') ||
            (!isdigit(*(notAfterStr + 11))) ||
            (*(notAfterStr + 10) >= '5' && *(notAfterStr + 10) <= '0') ||
            (!isdigit(*(notAfterStr + 9))) ||
            (*(notAfterStr + 8) >= '2' && *(notAfterStr + 8) <= '0') ||
            (!isdigit(*(notAfterStr + 7))) ||
            (*(notAfterStr + 6) >= '3' && *(notAfterStr + 6) <= '0') ||
            (!isdigit(*(notAfterStr + 5))) ||
            (*(notAfterStr + 4) >= '1' && *(notAfterStr + 4) <= '0') ||
            (!isdigit(*(notAfterStr + 3))) ||
            (!isdigit(*(notAfterStr + 2))) ||
            (!isdigit(*(notAfterStr + 1))) ||
            (!isdigit(*(notAfterStr + 0))) ||
            (*(notAfterStr + 8) == '2' && *(notAfterStr + 9) >= '4') ||
            (*(notAfterStr + 6) == '3' && *(notAfterStr + 7) >= '1') ||
            (*(notAfterStr + 4) == '1' && *(notAfterStr + 5) >= '2')) {
            error_out("ERROR: Improperly formated private key usage period");
        }
        *(notAfterStr + 14) = 'Z';
        *(notAfterStr + 15) = '\0';
    }

    PORT_Assert(arena);

    rv = EncodeAndAddExtensionValue(arena, extHandle, pkup,
                                    find_field_bool(data,
                                                    "privKeyUsagePeriod-crit",
                                                    PR_TRUE),
                                    SEC_OID_X509_PRIVATE_KEY_USAGE_PERIOD,
                                    (EXTEN_VALUE_ENCODER)
                                        CERT_EncodePrivateKeyUsagePeriod);
    PORT_FreeArena(arena, PR_FALSE);
    PORT_Free(notBeforeStr);
    PORT_Free(notAfterStr);
    return (rv);
}

static SECStatus
AddBasicConstraint(void *extHandle,
                   Pair *data)
{
    CERTBasicConstraints basicConstraint;
    SECItem encodedValue;
    SECStatus rv;

    encodedValue.data = NULL;
    encodedValue.len = 0;
    basicConstraint.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT;
    basicConstraint.isCA = (find_field_bool(data, "basicConstraints-cA-radio-CA",
                                            PR_TRUE));
    if (find_field_bool(data, "basicConstraints-pathLengthConstraint", PR_TRUE)) {
        basicConstraint.pathLenConstraint = atoi(find_field(data, "basicConstraints-pathLengthConstraint-text",
                                                            PR_TRUE));
    }

    rv = CERT_EncodeBasicConstraintValue(NULL, &basicConstraint,
                                         &encodedValue);
    if (rv)
        return (rv);
    rv = CERT_AddExtension(extHandle, SEC_OID_X509_BASIC_CONSTRAINTS,
                           &encodedValue,
                           (find_field_bool(data, "basicConstraints-crit",
                                            PR_TRUE)),
                           PR_TRUE);

    PORT_Free(encodedValue.data);
    return (rv);
}

static SECStatus
AddNscpCertType(void *extHandle,
                Pair *data)
{
    SECItem bitStringValue;
    unsigned char CertType = 0x0;

    if (find_field_bool(data, "netscape-cert-type-ssl-client", PR_TRUE)) {
        CertType |= (0x80 >> 0);
    }
    if (find_field_bool(data, "netscape-cert-type-ssl-server", PR_TRUE)) {
        CertType |= (0x80 >> 1);
    }
    if (find_field_bool(data, "netscape-cert-type-smime", PR_TRUE)) {
        CertType |= (0x80 >> 2);
    }
    if (find_field_bool(data, "netscape-cert-type-object-signing", PR_TRUE)) {
        CertType |= (0x80 >> 3);
    }
    if (find_field_bool(data, "netscape-cert-type-reserved", PR_TRUE)) {
        CertType |= (0x80 >> 4);
    }
    if (find_field_bool(data, "netscape-cert-type-ssl-ca", PR_TRUE)) {
        CertType |= (0x80 >> 5);
    }
    if (find_field_bool(data, "netscape-cert-type-smime-ca", PR_TRUE)) {
        CertType |= (0x80 >> 6);
    }
    if (find_field_bool(data, "netscape-cert-type-object-signing-ca", PR_TRUE)) {
        CertType |= (0x80 >> 7);
    }

    bitStringValue.data = &CertType;
    bitStringValue.len = 1;

    return (CERT_EncodeAndAddBitStrExtension(extHandle, SEC_OID_NS_CERT_EXT_CERT_TYPE, &bitStringValue,
                                             (find_field_bool(data, "netscape-cert-type-crit", PR_TRUE))));
}

static SECStatus
add_IA5StringExtension(void *extHandle,
                       char *string,
                       PRBool crit,
                       int idtag)
{
    SECItem encodedValue;
    SECStatus rv;

    encodedValue.data = NULL;
    encodedValue.len = 0;

    rv = CERT_EncodeIA5TypeExtension(NULL, string, &encodedValue);
    if (rv) {
        return (rv);
    }
    return (CERT_AddExtension(extHandle, idtag, &encodedValue, crit, PR_TRUE));
}

static SECItem *
string_to_oid(char *string)
{
    int i;
    int length = 20;
    int remaining;
    int first_value;
    int second_value;
    int value;
    int oidLength;
    unsigned char *oidString;
    unsigned char *write;
    unsigned char *read;
    unsigned char *temp;
    SECItem *oid;

    remaining = length;
    i = 0;
    while (*string == ' ') {
        string++;
    }
    while (isdigit(*(string + i))) {
        i++;
    }
    if (*(string + i) == '.') {
        *(string + i) = '\0';
    } else {
        error_out("ERROR: Improperly formated OID");
    }
    first_value = atoi(string);
    if (first_value < 0 || first_value > 2) {
        error_out("ERROR: Improperly formated OID");
    }
    string += i + 1;
    i = 0;
    while (isdigit(*(string + i))) {
        i++;
    }
    if (*(string + i) == '.') {
        *(string + i) = '\0';
    } else {
        error_out("ERROR: Improperly formated OID");
    }
    second_value = atoi(string);
    if (second_value < 0 || second_value > 39) {
        error_out("ERROR: Improperly formated OID");
    }
    oidString = PORT_ZAlloc(2);
    *oidString = (first_value * 40) + second_value;
    *(oidString + 1) = '\0';
    oidLength = 1;
    string += i + 1;
    i = 0;
    temp = write = PORT_ZAlloc(length);
    while (*string != '\0') {
        value = 0;
        while (isdigit(*(string + i))) {
            i++;
        }
        if (*(string + i) == '\0') {
            value = atoi(string);
            string += i;
        } else {
            if (*(string + i) == '.') {
                *(string + i) = '\0';
                value = atoi(string);
                string += i + 1;
            } else {
                *(string + i) = '\0';
                i++;
                value = atoi(string);
                while (*(string + i) == ' ')
                    i++;
                if (*(string + i) != '\0') {
                    error_out("ERROR: Improperly formated OID");
                }
            }
        }
        i = 0;
        while (value != 0) {
            if (remaining < 1) {
                remaining += length;
                length = length * 2;
                temp = PORT_Realloc(temp, length);
                write = temp + length - remaining;
            }
            *write = (value & 0x7f) | (0x80);
            write++;
            remaining--;
            value = value >> 7;
        }
        *temp = *temp & (0x7f);
        oidLength += write - temp;
        oidString = PORT_Realloc(oidString, (oidLength + 1));
        read = write - 1;
        write = oidLength + oidString - 1;
        for (i = 0; i < (length - remaining); i++) {
            *write = *read;
            write--;
            read++;
        }
        write = temp;
        remaining = length;
    }
    *(oidString + oidLength) = '\0';
    oid = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    oid->data = oidString;
    oid->len = oidLength;
    PORT_Free(temp);
    return oid;
}

static SECItem *
string_to_ipaddress(char *string)
{
    int i = 0;
    int value;
    int j = 0;
    SECItem *ipaddress;

    while (*string == ' ') {
        string++;
    }
    ipaddress = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    ipaddress->data = PORT_ZAlloc(9);
    while (*string != '\0' && j < 8) {
        while (isdigit(*(string + i))) {
            i++;
        }
        if (*(string + i) == '.') {
            *(string + i) = '\0';
            value = atoi(string);
            string = string + i + 1;
            i = 0;
        } else {
            if (*(string + i) == '\0') {
                value = atoi(string);
                string = string + i;
                i = 0;
            } else {
                *(string + i) = '\0';
                while (*(string + i) == ' ') {
                    i++;
                }
                if (*(string + i) == '\0') {
                    value = atoi(string);
                    string = string + i;
                    i = 0;
                } else {
                    error_out("ERROR: Improperly formated IP Address");
                }
            }
        }
        if (value >= 0 && value < 256) {
            *(ipaddress->data + j) = value;
        } else {
            error_out("ERROR: Improperly formated IP Address");
        }
        j++;
    }
    *(ipaddress->data + j) = '\0';
    if (j != 4 && j != 8) {
        error_out("ERROR: Improperly formated IP Address");
    }
    ipaddress->len = j;
    return ipaddress;
}

static int
chr_to_hex(char c)
{
    if (isdigit(c)) {
        return c - '0';
    }
    if (isxdigit(c)) {
        return toupper(c) - 'A' + 10;
    }
    return -1;
}

static SECItem *
string_to_binary(char *string)
{
    SECItem *rv;

    rv = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if (rv == NULL) {
        error_allocate();
    }
    rv->data = (unsigned char *)PORT_ZAlloc((PORT_Strlen(string)) / 3 + 2);
    rv->len = 0;
    while (*string && !isxdigit(*string)) {
        string++;
    }
    while (*string) {
        int high, low;
        high = chr_to_hex(*string++);
        low = chr_to_hex(*string++);
        if (high < 0 || low < 0) {
            error_out("ERROR: Improperly formated binary encoding");
        }
        rv->data[(rv->len)++] = high << 4 | low;
        if (*string != ':') {
            break;
        }
        ++string;
    }
    while (*string == ' ') {
        ++string;
    }
    if (*string) {
        error_out("ERROR: Junk after binary encoding");
    }

    return rv;
}

static SECStatus
MakeGeneralName(char *name,
                CERTGeneralName *genName,
                PLArenaPool *arena)
{
    SECItem *oid;
    SECOidData *oidData;
    SECItem *ipaddress;
    SECItem *temp = NULL;
    int i;
    int nameType;
    PRBool binary = PR_FALSE;
    SECStatus rv = SECSuccess;
    PRBool nickname = PR_FALSE;

    PORT_Assert(genName);
    PORT_Assert(arena);
    nameType = *(name + PORT_Strlen(name) - 1) - '0';
    if (nameType == 0 && *(name + PORT_Strlen(name) - 2) == '1') {
        nickname = PR_TRUE;
        nameType = certOtherName;
    }
    if (nameType < 1 || nameType > 9) {
        error_out("ERROR: Unknown General Name Type");
    }
    *(name + PORT_Strlen(name) - 4) = '\0';
    genName->type = nameType;

    switch (genName->type) {
        case certURI:
        case certRFC822Name:
        case certDNSName: {
            genName->name.other.data = (unsigned char *)name;
            genName->name.other.len = PORT_Strlen(name);
            break;
        }

        case certIPAddress: {
            ipaddress = string_to_ipaddress(name);
            genName->name.other.data = ipaddress->data;
            genName->name.other.len = ipaddress->len;
            break;
        }

        case certRegisterID: {
            oid = string_to_oid(name);
            genName->name.other.data = oid->data;
            genName->name.other.len = oid->len;
            break;
        }

        case certEDIPartyName:
        case certX400Address: {

            genName->name.other.data = PORT_ArenaAlloc(arena,
                                                       PORT_Strlen(name) + 2);
            if (genName->name.other.data == NULL) {
                error_allocate();
            }

            PORT_Memcpy(genName->name.other.data + 2, name, PORT_Strlen(name));
            /* This may not be accurate for all cases.
             For now, use this tag type */
            genName->name.other.data[0] = (char)(((genName->type - 1) &
                                                  0x1f) |
                                                 0x80);
            genName->name.other.data[1] = (char)PORT_Strlen(name);
            genName->name.other.len = PORT_Strlen(name) + 2;
            break;
        }

        case certOtherName: {
            i = 0;
            if (!nickname) {
                while (!isdigit(*(name + PORT_Strlen(name) - i))) {
                    i++;
                }
                if (*(name + PORT_Strlen(name) - i) == '1') {
                    binary = PR_TRUE;
                } else {
                    binary = PR_FALSE;
                }
                while (*(name + PORT_Strlen(name) - i) != '-') {
                    i++;
                }
                *(name + PORT_Strlen(name) - i - 1) = '\0';
                i = 0;
                while (*(name + i) != '-') {
                    i++;
                }
                *(name + i - 1) = '\0';
                oid = string_to_oid(name + i + 2);
            } else {
                oidData = SECOID_FindOIDByTag(SEC_OID_NETSCAPE_NICKNAME);
                oid = &oidData->oid;
                while (*(name + PORT_Strlen(name) - i) != '-') {
                    i++;
                }
                *(name + PORT_Strlen(name) - i) = '\0';
            }
            genName->name.OthName.oid.data = oid->data;
            genName->name.OthName.oid.len = oid->len;
            if (binary) {
                temp = string_to_binary(name);
                genName->name.OthName.name.data = temp->data;
                genName->name.OthName.name.len = temp->len;
            } else {
                temp = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
                if (temp == NULL) {
                    error_allocate();
                }
                temp->data = (unsigned char *)name;
                temp->len = PORT_Strlen(name);
                SEC_ASN1EncodeItem(arena, &(genName->name.OthName.name), temp,
                                   CERTIA5TypeTemplate);
            }
            PORT_Free(temp);
            break;
        }

        case certDirectoryName: {
            CERTName *directoryName = NULL;

            directoryName = CERT_AsciiToName(name);
            if (!directoryName) {
                error_out("ERROR: Improperly formated alternative name");
                break;
            }
            rv = CERT_CopyName(arena, &genName->name.directoryName,
                               directoryName);
            CERT_DestroyName(directoryName);

            break;
        }
    }
    genName->l.next = &(genName->l);
    genName->l.prev = &(genName->l);
    return rv;
}

static CERTGeneralName *
MakeAltName(Pair *data,
            char *which,
            PLArenaPool *arena)
{
    CERTGeneralName *SubAltName;
    CERTGeneralName *current;
    CERTGeneralName *newname;
    char *name = NULL;
    SECStatus rv = SECSuccess;
    int len;

    len = PORT_Strlen(which);
    name = find_field(data, which, PR_TRUE);
    SubAltName = current = (CERTGeneralName *)PORT_ZAlloc(sizeof(CERTGeneralName));
    if (current == NULL) {
        error_allocate();
    }
    while (name != NULL) {

        rv = MakeGeneralName(name, current, arena);

        if (rv != SECSuccess) {
            break;
        }
        if (*(which + len - 1) < '9') {
            *(which + len - 1) = *(which + len - 1) + 1;
        } else {
            if (isdigit(*(which + len - 2))) {
                *(which + len - 2) = *(which + len - 2) + 1;
                *(which + len - 1) = '0';
            } else {
                *(which + len - 1) = '1';
                *(which + len) = '0';
                *(which + len + 1) = '\0';
                len++;
            }
        }
        len = PORT_Strlen(which);
        name = find_field(data, which, PR_TRUE);
        if (name != NULL) {
            newname = (CERTGeneralName *)PORT_ZAlloc(sizeof(CERTGeneralName));
            if (newname == NULL) {
                error_allocate();
            }
            current->l.next = &(newname->l);
            newname->l.prev = &(current->l);
            current = newname;
            newname = NULL;
        } else {
            current->l.next = &(SubAltName->l);
            SubAltName->l.prev = &(current->l);
        }
    }
    if (rv == SECFailure) {
        return NULL;
    }
    return SubAltName;
}

static CERTNameConstraints *
MakeNameConstraints(Pair *data,
                    PLArenaPool *arena)
{
    CERTNameConstraints *NameConstraints;
    CERTNameConstraint *current = NULL;
    CERTNameConstraint *last_permited = NULL;
    CERTNameConstraint *last_excluded = NULL;
    char *constraint = NULL;
    char *which;
    SECStatus rv = SECSuccess;
    int len;
    int i;
    long max;
    long min;
    PRBool permited;

    NameConstraints = (CERTNameConstraints *)PORT_ZAlloc(sizeof(CERTNameConstraints));
    which = make_copy_string("NameConstraintSelect0", 25, '\0');
    len = PORT_Strlen(which);
    constraint = find_field(data, which, PR_TRUE);
    NameConstraints->permited = NameConstraints->excluded = NULL;
    while (constraint != NULL) {
        current = (CERTNameConstraint *)PORT_ZAlloc(sizeof(CERTNameConstraint));
        if (current == NULL) {
            error_allocate();
        }
        i = 0;
        while (*(constraint + PORT_Strlen(constraint) - i) != '-') {
            i++;
        }
        *(constraint + PORT_Strlen(constraint) - i - 1) = '\0';
        max = (long)atoi(constraint + PORT_Strlen(constraint) + 3);
        if (max > 0) {
            (void)SEC_ASN1EncodeInteger(arena, &current->max, max);
        }
        i = 0;
        while (*(constraint + PORT_Strlen(constraint) - i) != '-') {
            i++;
        }
        *(constraint + PORT_Strlen(constraint) - i - 1) = '\0';
        min = (long)atoi(constraint + PORT_Strlen(constraint) + 3);
        (void)SEC_ASN1EncodeInteger(arena, &current->min, min);
        while (*(constraint + PORT_Strlen(constraint) - i) != '-') {
            i++;
        }
        *(constraint + PORT_Strlen(constraint) - i - 1) = '\0';
        if (*(constraint + PORT_Strlen(constraint) + 3) == 'p') {
            permited = PR_TRUE;
        } else {
            permited = PR_FALSE;
        }
        rv = MakeGeneralName(constraint, &(current->name), arena);

        if (rv != SECSuccess) {
            break;
        }
        if (*(which + len - 1) < '9') {
            *(which + len - 1) = *(which + len - 1) + 1;
        } else {
            if (isdigit(*(which + len - 2))) {
                *(which + len - 2) = *(which + len - 2) + 1;
                *(which + len - 1) = '0';
            } else {
                *(which + len - 1) = '1';
                *(which + len) = '0';
                *(which + len + 1) = '\0';
                len++;
            }
        }
        len = PORT_Strlen(which);
        if (permited) {
            if (NameConstraints->permited == NULL) {
                NameConstraints->permited = last_permited = current;
            }
            last_permited->l.next = &(current->l);
            current->l.prev = &(last_permited->l);
            last_permited = current;
        } else {
            if (NameConstraints->excluded == NULL) {
                NameConstraints->excluded = last_excluded = current;
            }
            last_excluded->l.next = &(current->l);
            current->l.prev = &(last_excluded->l);
            last_excluded = current;
        }
        constraint = find_field(data, which, PR_TRUE);
        if (constraint != NULL) {
            current = (CERTNameConstraint *)PORT_ZAlloc(sizeof(CERTNameConstraint));
            if (current == NULL) {
                error_allocate();
            }
        }
    }
    if (NameConstraints->permited != NULL) {
        last_permited->l.next = &(NameConstraints->permited->l);
        NameConstraints->permited->l.prev = &(last_permited->l);
    }
    if (NameConstraints->excluded != NULL) {
        last_excluded->l.next = &(NameConstraints->excluded->l);
        NameConstraints->excluded->l.prev = &(last_excluded->l);
    }
    if (which != NULL) {
        PORT_Free(which);
    }
    if (rv == SECFailure) {
        return NULL;
    }
    return NameConstraints;
}

static SECStatus
AddAltName(void *extHandle,
           Pair *data,
           char *issuerNameStr,
           CERTCertDBHandle *handle,
           int type)
{
    PRBool autoIssuer = PR_FALSE;
    PLArenaPool *arena = NULL;
    CERTGeneralName *genName = NULL;
    char *which = NULL;
    char *name = NULL;
    SECStatus rv = SECSuccess;
    SECItem *issuersAltName = NULL;
    CERTCertificate *issuerCert = NULL;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        error_allocate();
    }
    if (type == 0) {
        which = make_copy_string("SubAltNameSelect0", 20, '\0');
        genName = MakeAltName(data, which, arena);
    } else {
        if (autoIssuer) {
            autoIssuer = find_field_bool(data, "IssuerAltNameSourceRadio-auto",
                                         PR_TRUE);
            issuerCert = CERT_FindCertByNameString(handle, issuerNameStr);
            rv = cert_FindExtension((*issuerCert).extensions,
                                    SEC_OID_X509_SUBJECT_ALT_NAME,
                                    issuersAltName);
            if (issuersAltName == NULL) {
                name = PORT_Alloc(PORT_Strlen((*issuerCert).subjectName) + 4);
                PORT_Strcpy(name, (*issuerCert).subjectName);
                PORT_Strcat(name, " - 5");
            }
        } else {
            which = make_copy_string("IssuerAltNameSelect0", 20, '\0');
            genName = MakeAltName(data, which, arena);
        }
    }
    if (type == 0) {
        EncodeAndAddExtensionValue(arena, extHandle, genName,
                                   find_field_bool(data, "SubAltName-crit",
                                                   PR_TRUE),
                                   SEC_OID_X509_SUBJECT_ALT_NAME,
                                   (EXTEN_VALUE_ENCODER)
                                       CERT_EncodeAltNameExtension);

    } else {
        if (autoIssuer && (name == NULL)) {
            rv = CERT_AddExtension(extHandle, SEC_OID_X509_ISSUER_ALT_NAME, issuersAltName,
                                   find_field_bool(data, "IssuerAltName-crit", PR_TRUE), PR_TRUE);
        } else {
            EncodeAndAddExtensionValue(arena, extHandle, genName,
                                       find_field_bool(data,
                                                       "IssuerAltName-crit",
                                                       PR_TRUE),
                                       SEC_OID_X509_ISSUER_ALT_NAME,
                                       (EXTEN_VALUE_ENCODER)
                                           CERT_EncodeAltNameExtension);
        }
    }
    if (which != NULL) {
        PORT_Free(which);
    }
    if (issuerCert != NULL) {
        CERT_DestroyCertificate(issuerCert);
    }
    return rv;
}

static SECStatus
AddNameConstraints(void *extHandle,
                   Pair *data)
{
    PLArenaPool *arena = NULL;
    CERTNameConstraints *constraints = NULL;
    SECStatus rv = SECSuccess;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        error_allocate();
    }
    constraints = MakeNameConstraints(data, arena);
    if (constraints != NULL) {
        EncodeAndAddExtensionValue(arena, extHandle, constraints, PR_TRUE,
                                   SEC_OID_X509_NAME_CONSTRAINTS,
                                   (EXTEN_VALUE_ENCODER)
                                       CERT_EncodeNameConstraintsExtension);
    }
    if (arena != NULL) {
        PORT_ArenaRelease(arena, NULL);
    }
    return rv;
}

static SECStatus
add_extensions(CERTCertificate *subjectCert,
               Pair *data,
               char *issuerNameStr,
               CERTCertDBHandle *handle)
{
    void *extHandle;
    SECStatus rv = SECSuccess;

    extHandle = CERT_StartCertExtensions(subjectCert);
    if (extHandle == NULL) {
        error_out("ERROR: Unable to get certificates extension handle");
    }
    if (find_field_bool(data, "keyUsage", PR_TRUE)) {
        rv = AddKeyUsage(extHandle, data);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Key Usage extension");
        }
    }

    if (find_field_bool(data, "extKeyUsage", PR_TRUE)) {
        rv = AddExtKeyUsage(extHandle, data);
        if (SECSuccess != rv) {
            error_out("ERROR: Unable to add Extended Key Usage extension");
        }
    }

    if (find_field_bool(data, "basicConstraints", PR_TRUE)) {
        rv = AddBasicConstraint(extHandle, data);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Basic Constraint extension");
        }
    }
    if (find_field_bool(data, "subjectKeyIdentifier", PR_TRUE)) {
        rv = AddSubKeyID(extHandle, data, subjectCert);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Subject Key Identifier Extension");
        }
    }
    if (find_field_bool(data, "authorityKeyIdentifier", PR_TRUE)) {
        rv = AddAuthKeyID(extHandle, data, issuerNameStr, handle);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Authority Key Identifier extension");
        }
    }
    if (find_field_bool(data, "privKeyUsagePeriod", PR_TRUE)) {
        rv = AddPrivKeyUsagePeriod(extHandle, data, subjectCert);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Private Key Usage Period extension");
        }
    }
    if (find_field_bool(data, "SubAltName", PR_TRUE)) {
        rv = AddAltName(extHandle, data, NULL, NULL, 0);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Subject Alternative Name extension");
        }
    }
    if (find_field_bool(data, "IssuerAltName", PR_TRUE)) {
        rv = AddAltName(extHandle, data, issuerNameStr, handle, 1);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Issuer Alternative Name Extension");
        }
    }
    if (find_field_bool(data, "NameConstraints", PR_TRUE)) {
        rv = AddNameConstraints(extHandle, data);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Name Constraints Extension");
        }
    }
    if (find_field_bool(data, "netscape-cert-type", PR_TRUE)) {
        rv = AddNscpCertType(extHandle, data);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Netscape Certificate Type Extension");
        }
    }
    if (find_field_bool(data, "netscape-base-url", PR_TRUE)) {
        rv = add_IA5StringExtension(extHandle,
                                    find_field(data, "netscape-base-url-text",
                                               PR_TRUE),
                                    find_field_bool(data,
                                                    "netscape-base-url-crit",
                                                    PR_TRUE),
                                    SEC_OID_NS_CERT_EXT_BASE_URL);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Netscape Base URL Extension");
        }
    }
    if (find_field_bool(data, "netscape-revocation-url", PR_TRUE)) {
        rv = add_IA5StringExtension(extHandle,
                                    find_field(data,
                                               "netscape-revocation-url-text",
                                               PR_TRUE),
                                    find_field_bool(data, "netscape-revocation-url-crit",
                                                    PR_TRUE),
                                    SEC_OID_NS_CERT_EXT_REVOCATION_URL);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Netscape Revocation URL Extension");
        }
    }
    if (find_field_bool(data, "netscape-ca-revocation-url", PR_TRUE)) {
        rv = add_IA5StringExtension(extHandle,
                                    find_field(data,
                                               "netscape-ca-revocation-url-text",
                                               PR_TRUE),
                                    find_field_bool(data, "netscape-ca-revocation-url-crit", PR_TRUE),
                                    SEC_OID_NS_CERT_EXT_CA_REVOCATION_URL);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Netscape CA Revocation URL Extension");
        }
    }
    if (find_field_bool(data, "netscape-cert-renewal-url", PR_TRUE)) {
        rv = add_IA5StringExtension(extHandle,
                                    find_field(data,
                                               "netscape-cert-renewal-url-text",
                                               PR_TRUE),
                                    find_field_bool(data, "netscape-cert-renewal-url-crit",
                                                    PR_TRUE),
                                    SEC_OID_NS_CERT_EXT_CERT_RENEWAL_URL);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Netscape Certificate Renewal URL Extension");
        }
    }
    if (find_field_bool(data, "netscape-ca-policy-url", PR_TRUE)) {
        rv = add_IA5StringExtension(extHandle,
                                    find_field(data,
                                               "netscape-ca-policy-url-text",
                                               PR_TRUE),
                                    find_field_bool(data, "netscape-ca-policy-url-crit",
                                                    PR_TRUE),
                                    SEC_OID_NS_CERT_EXT_CA_POLICY_URL);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Netscape CA Policy URL Extension");
        }
    }
    if (find_field_bool(data, "netscape-ssl-server-name", PR_TRUE)) {
        rv = add_IA5StringExtension(extHandle,
                                    find_field(data,
                                               "netscape-ssl-server-name-text",
                                               PR_TRUE),
                                    find_field_bool(data, "netscape-ssl-server-name-crit",
                                                    PR_TRUE),
                                    SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Netscape SSL Server Name Extension");
        }
    }
    if (find_field_bool(data, "netscape-comment", PR_TRUE)) {
        rv = add_IA5StringExtension(extHandle,
                                    find_field(data, "netscape-comment-text",
                                               PR_TRUE),
                                    find_field_bool(data,
                                                    "netscape-comment-crit",
                                                    PR_TRUE),
                                    SEC_OID_NS_CERT_EXT_COMMENT);
        if (rv != SECSuccess) {
            error_out("ERROR: Unable to add Netscape Comment Extension");
        }
    }
    CERT_FinishExtensions(extHandle);
    return (rv);
}

char *
return_dbpasswd(PK11SlotInfo *slot, PRBool retry, void *data)
{
    char *rv;

    /* don't clobber our poor smart card */
    if (retry == PR_TRUE) {
        return NULL;
    }
    rv = PORT_Alloc(4);
    PORT_Strcpy(rv, "foo");
    return rv;
}

SECKEYPrivateKey *
FindPrivateKeyFromNameStr(char *name,
                          CERTCertDBHandle *certHandle)
{
    SECKEYPrivateKey *key;
    CERTCertificate *cert;
    CERTCertificate *p11Cert;

    /* We don't presently have a PK11 function to find a cert by
    ** subject name.
    ** We do have a function to find a cert in the internal slot's
    ** cert db by subject name, but it doesn't setup the slot info.
    ** So, this HACK works, but should be replaced as soon as we
    ** have a function to search for certs accross slots by subject name.
    */
    cert = CERT_FindCertByNameString(certHandle, name);
    if (cert == NULL || cert->nickname == NULL) {
        error_out("ERROR: Unable to retrieve issuers certificate");
    }
    p11Cert = PK11_FindCertFromNickname(cert->nickname, NULL);
    if (p11Cert == NULL) {
        error_out("ERROR: Unable to retrieve issuers certificate");
    }
    key = PK11_FindKeyByAnyCert(p11Cert, NULL);
    return key;
}

static SECItem *
SignCert(CERTCertificate *cert,
         char *issuerNameStr,
         Pair *data,
         CERTCertDBHandle *handle,
         int which_key)
{
    SECItem der;
    SECKEYPrivateKey *caPrivateKey = NULL;
    SECStatus rv;
    PLArenaPool *arena;
    SECOidTag algID;

    if (which_key == 0) {
        caPrivateKey = FindPrivateKeyFromNameStr(issuerNameStr, handle);
    } else {
        caPrivateKey = privkeys[which_key - 1];
    }
    if (caPrivateKey == NULL) {
        error_out("ERROR: unable to retrieve issuers key");
    }

    arena = cert->arena;

    algID = SEC_GetSignatureAlgorithmOidTag(caPrivateKey->keyType,
                                            SEC_OID_UNKNOWN);
    if (algID == SEC_OID_UNKNOWN) {
        error_out("ERROR: Unknown key type for issuer.");
        goto done;
    }

    rv = SECOID_SetAlgorithmID(arena, &cert->signature, algID, 0);
    if (rv != SECSuccess) {
        error_out("ERROR: Could not set signature algorithm id.");
    }

    if (find_field_bool(data, "ver-1", PR_TRUE)) {
        *(cert->version.data) = 0;
        cert->version.len = 1;
    } else {
        *(cert->version.data) = 2;
        cert->version.len = 1;
    }
    der.data = NULL;
    der.len = 0;
    (void)SEC_ASN1EncodeItem(arena, &der, cert, CERT_CertificateTemplate);
    if (der.data == NULL) {
        error_out("ERROR: Could not encode certificate.\n");
    }
    rv = SEC_DerSignData(arena, &(cert->derCert), der.data, der.len, caPrivateKey,
                         algID);
    if (rv != SECSuccess) {
        error_out("ERROR: Could not sign encoded certificate data.\n");
    }
done:
    SECKEY_DestroyPrivateKey(caPrivateKey);
    return &(cert->derCert);
}

int
main(int argc, char **argv)
{
    int length = 500;
    int remaining = 500;
    int n;
    int i;
    int serial;
    int chainLen;
    int which_key;
    char *pos;
#ifdef OFFLINE
    char *form_output = "key=MIIBPTCBpzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA7"
                        "SLqjWBL9Wl11Vlg%0AaMqZCvcQOL%2FnvSqYPPRP0XZy9SoAeyWzQnBOiCm2t8H5mK7r2"
                        "jnKdAQOmfhjaJil%0A3hNVu3SekHOXF6Ze7bkWa6%2FSGVcY%2FojkydxFSgY43nd1iyd"
                        "zPQDp8WWLL%2BpVpt%2B%2B%0ATRhFtVXbF0fQI03j9h3BoTgP2lkCAwEAARYDZm9vMA0"
                        "GCSqGSIb3DQEBBAUAA4GB%0AAJ8UfRKJ0GtG%2B%2BufCC6tAfTzKrq3CTBHnom55EyXc"
                        "sAsv6WbDqI%2F0rLAPkn2Xo1r%0AnNhtMxIuj441blMt%2Fa3AGLOy5zmC7Qawt8IytvQ"
                        "ikQ1XTpTBCXevytrmLjCmlURr%0ANJryTM48WaMQHiMiJpbXCqVJC1d%2FpEWBtqvALzZ"
                        "aOOIy&subject=CN%3D%22test%22%26serial-auto%3Dtrue%26serial_value%3D%"
                        "26ver-1%3Dtrue%26ver-3%3Dfalse%26caChoiceradio-SignWithDefaultkey%3Dt"
                        "rue%26caChoiceradio-SignWithRandomChain%3Dfalse%26autoCAs%3D%26caChoi"
                        "ceradio-SignWithSpecifiedChain%3Dfalse%26manCAs%3D%26%24";
#else
    char *form_output;
#endif
    char *issuerNameStr;
    char *certName;
    char *DBdir = DB_DIRECTORY;
    char *prefixs[10] = { "CA#1-", "CA#2-", "CA#3-",
                          "CA#4-", "CA#5-", "CA#6-",
                          "CA#7-", "CA#8-", "CA#9-", "" };
    Pair *form_data;
    CERTCertificate *cert;
    CERTCertDBHandle *handle;
    CERTCertificateRequest *certReq = NULL;
    int warpmonths = 0;
    SECItem *certDER;
#ifdef FILEOUT
    FILE *outfile;
#endif
    SECStatus status = SECSuccess;
    extern char prefix[PREFIX_LEN];
    SEC_PKCS7ContentInfo *certChain;
    SECItem *encodedCertChain;
    PRBool UChain = PR_FALSE;

    progName = strrchr(argv[0], '/');
    progName = progName ? progName + 1 : argv[0];

#ifdef TEST
    sleep(20);
#endif
    SECU_ConfigDirectory(DBdir);

    PK11_SetPasswordFunc(return_dbpasswd);
    status = NSS_InitReadWrite(DBdir);
    if (status != SECSuccess) {
        SECU_PrintPRandOSError(progName);
        return -1;
    }
    handle = CERT_GetDefaultCertDB();

    prefix[0] = '\0';
#if !defined(OFFLINE)
    form_output = (char *)PORT_Alloc(length);
    if (form_output == NULL) {
        error_allocate();
    }
    pos = form_output;
    while (feof(stdin) == 0) {
        if (remaining <= 1) {
            remaining += length;
            length = length * 2;
            form_output = PORT_Realloc(form_output, (length));
            if (form_output == NULL) {
                error_allocate();
            }
            pos = form_output + length - remaining;
        }
        n = fread(pos, 1, (size_t)(remaining - 1), stdin);
        pos += n;
        remaining -= n;
    }
    *pos = '&';
    pos++;
    length = pos - form_output;
#else
    length = PORT_Strlen(form_output);
#endif
#ifdef FILEOUT
    printf("Content-type: text/plain\n\n");
    fwrite(form_output, 1, (size_t)length, stdout);
    printf("\n");
#endif
#ifdef FILEOUT
    fwrite(form_output, 1, (size_t)length, stdout);
    printf("\n");
    fflush(stdout);
#endif
    form_data = make_datastruct(form_output, length);
    status = clean_input(form_data);
#if !defined(OFFLINE)
    PORT_Free(form_output);
#endif
#ifdef FILEOUT
    i = 0;
    while (return_name(form_data, i) != NULL) {
        printf("%s", return_name(form_data, i));
        printf("=\n");
        printf("%s", return_data(form_data, i));
        printf("\n");
        i++;
    }
    printf("I got that done, woo hoo\n");
    fflush(stdout);
#endif
    issuerNameStr = PORT_Alloc(200);
    if (find_field_bool(form_data, "caChoiceradio-SignWithSpecifiedChain",
                        PR_FALSE)) {
        UChain = PR_TRUE;
        chainLen = atoi(find_field(form_data, "manCAs", PR_FALSE));
        PORT_Strcpy(prefix, prefixs[0]);
        issuerNameStr = PORT_Strcpy(issuerNameStr,
                                    "CN=Cert-O-Matic II, O=Cert-O-Matic II");
        if (chainLen == 0) {
            UChain = PR_FALSE;
        }
    } else {
        if (find_field_bool(form_data, "caChoiceradio-SignWithRandomChain",
                            PR_FALSE)) {
            PORT_Strcpy(prefix, prefixs[9]);
            chainLen = atoi(find_field(form_data, "autoCAs", PR_FALSE));
            if (chainLen < 1 || chainLen > 18) {
                issuerNameStr = PORT_Strcpy(issuerNameStr,
                                            "CN=CA18, O=Cert-O-Matic II");
            }
            issuerNameStr = PORT_Strcpy(issuerNameStr, "CN=CA");
            issuerNameStr = PORT_Strcat(issuerNameStr,
                                        find_field(form_data, "autoCAs", PR_FALSE));
            issuerNameStr = PORT_Strcat(issuerNameStr, ", O=Cert-O-Matic II");
        } else {
            issuerNameStr = PORT_Strcpy(issuerNameStr,
                                        "CN=Cert-O-Matic II, O=Cert-O-Matic II");
        }
        chainLen = 0;
    }

    i = -1;
    which_key = 0;
    do {
        extern SECStatus cert_GetKeyID(CERTCertificate * cert);
        i++;
        if (i != 0 && UChain) {
            PORT_Strcpy(prefix, prefixs[i]);
        }
        /*        find_field(form_data,"subject", PR_TRUE); */
        certReq = makeCertReq(form_data, which_key);
#ifdef OFFLINE
        serial = 900;
#else
        serial = get_serial_number(form_data);
#endif
        cert = MakeV1Cert(handle, certReq, issuerNameStr, PR_FALSE,
                          serial, warpmonths, form_data);
        if (certReq != NULL) {
            CERT_DestroyCertificateRequest(certReq);
        }
        if (find_field_bool(form_data, "ver-3", PR_TRUE)) {
            status = add_extensions(cert, form_data, issuerNameStr, handle);
            if (status != SECSuccess) {
                error_out("ERROR: Unable to add extensions");
            }
        }
        status = cert_GetKeyID(cert);
        if (status == SECFailure) {
            error_out("ERROR: Unable to get Key ID.");
        }
        certDER = SignCert(cert, issuerNameStr, form_data, handle, which_key);
        CERT_NewTempCertificate(handle, certDER, NULL, PR_FALSE, PR_TRUE);
        issuerNameStr = find_field(form_data, "subject", PR_TRUE);
        /*        SECITEM_FreeItem(certDER, PR_TRUE); */
        CERT_DestroyCertificate(cert);
        if (i == (chainLen - 1)) {
            i = 8;
        }
        ++which_key;
    } while (i < 9 && UChain);

#ifdef FILEOUT
    outfile = fopen("../certout", "wb");
#endif
    certName = find_field(form_data, "subject", PR_FALSE);
    cert = CERT_FindCertByNameString(handle, certName);
    certChain = SEC_PKCS7CreateCertsOnly(cert, PR_TRUE, handle);
    if (certChain == NULL) {
        error_out("ERROR: No certificates in cert chain");
    }
    encodedCertChain = SEC_PKCS7EncodeItem(NULL, NULL, certChain, NULL, NULL,
                                           NULL);
    if (encodedCertChain) {
#if !defined(FILEOUT)
        printf("Content-type: application/x-x509-user-cert\r\n");
        printf("Content-length: %d\r\n\r\n", encodedCertChain->len);
        fwrite(encodedCertChain->data, 1, encodedCertChain->len, stdout);
#else
        fwrite(encodedCertChain->data, 1, encodedCertChain->len, outfile);
#endif

    } else {
        error_out("Error: Unable to DER encode certificate");
    }
#ifdef FILEOUT
    printf("\nI got here!\n");
    fflush(outfile);
    fclose(outfile);
#endif
    fflush(stdout);
    if (NSS_Shutdown() != SECSuccess) {
        exit(1);
    }
    return 0;
}
