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

/*
** dbtool.c
**
** tool to dump the underlying encoding of a database. This tool duplicates
**  some private functions in softoken. It uses libsec and libutil, but no
**  other portions of NSS. It currently only works on sqlite databases. For
**  an even more primitive dump, use sqlite3 on the individual files.
**
**   TODO: dump the meta data for the databases.
**         optionally dump more PKCS5 information (KDF/salt/iterations)
**         take a password and decode encrypted attributes/verify signed
**             attributes.
*/
#include <stdio.h>
#include <string.h>

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

/*#include "secutil.h" */
/*#include "pk11pub.h" */

#if defined(XP_UNIX)
#include <unistd.h>
#endif

#include "nspr.h"
#include "prtypes.h"
#include "nss.h"
#include "secasn1.h"
#include "secder.h"
#include "pk11table.h"
#include "sftkdbt.h"
#include "sdb.h"
#include "secoid.h"

#include "plgetopt.h"

static char *progName;

char *dbDir = NULL;

static void
Usage()
{
    printf("Usage:  %s [-c certprefix] [-k keyprefix] "
           "[-V certversion] [-v keyversion]\n"
           "           [-d dbdir]\n",
           progName);
    printf("%-20s Directory with cert database (default is .)\n",
           "-d certdir");
    printf("%-20s prefix for the cert database (default is \"\")\n",
           "-c certprefix");
    printf("%-20s prefix for the key database (default is \"\")\n",
           "-k keyprefix");
    printf("%-20s version of the cert database (default is 9)\n",
           "-V certversion");
    printf("%-20s version of the key database (default is 4)\n",
           "-v keyversion");
    exit(1);
}
#define SFTK_KEYDB_TYPE 0x40000000
#define SFTK_TOKEN_TYPE 0x80000000

/*
 * known attributes
 */
static const CK_ATTRIBUTE_TYPE known_attributes[] = {
    CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_APPLICATION,
    CKA_VALUE, CKA_OBJECT_ID, CKA_CERTIFICATE_TYPE, CKA_ISSUER,
    CKA_SERIAL_NUMBER, CKA_AC_ISSUER, CKA_OWNER, CKA_ATTR_TYPES, CKA_TRUSTED,
    CKA_CERTIFICATE_CATEGORY, CKA_JAVA_MIDP_SECURITY_DOMAIN, CKA_URL,
    CKA_HASH_OF_SUBJECT_PUBLIC_KEY, CKA_HASH_OF_ISSUER_PUBLIC_KEY,
    CKA_CHECK_VALUE, CKA_KEY_TYPE, CKA_SUBJECT, CKA_ID, CKA_SENSITIVE,
    CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, CKA_SIGN, CKA_SIGN_RECOVER,
    CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_DERIVE, CKA_START_DATE, CKA_END_DATE,
    CKA_MODULUS, CKA_MODULUS_BITS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT,
    CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT,
    CKA_PRIME, CKA_SUBPRIME, CKA_BASE, CKA_PRIME_BITS,
    CKA_SUB_PRIME_BITS, CKA_VALUE_BITS, CKA_VALUE_LEN, CKA_EXTRACTABLE,
    CKA_LOCAL, CKA_NEVER_EXTRACTABLE, CKA_ALWAYS_SENSITIVE,
    CKA_KEY_GEN_MECHANISM, CKA_MODIFIABLE, CKA_EC_PARAMS,
    CKA_EC_POINT, CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS,
    CKA_ALWAYS_AUTHENTICATE, CKA_WRAP_WITH_TRUSTED, CKA_WRAP_TEMPLATE,
    CKA_UNWRAP_TEMPLATE, CKA_HW_FEATURE_TYPE, CKA_RESET_ON_INIT,
    CKA_HAS_RESET, CKA_PIXEL_X, CKA_PIXEL_Y, CKA_RESOLUTION, CKA_CHAR_ROWS,
    CKA_CHAR_COLUMNS, CKA_COLOR, CKA_BITS_PER_PIXEL, CKA_CHAR_SETS,
    CKA_ENCODING_METHODS, CKA_MIME_TYPES, CKA_MECHANISM_TYPE,
    CKA_REQUIRED_CMS_ATTRIBUTES, CKA_DEFAULT_CMS_ATTRIBUTES,
    CKA_SUPPORTED_CMS_ATTRIBUTES, CKA_NSS_URL, CKA_NSS_EMAIL,
    CKA_NSS_SMIME_INFO, CKA_NSS_SMIME_TIMESTAMP,
    CKA_NSS_PKCS8_SALT, CKA_NSS_PASSWORD_CHECK, CKA_NSS_EXPIRES,
    CKA_NSS_KRL, CKA_NSS_PQG_COUNTER, CKA_NSS_PQG_SEED,
    CKA_NSS_PQG_H, CKA_NSS_PQG_SEED_BITS, CKA_NSS_MODULE_SPEC,
    CKA_TRUST_DIGITAL_SIGNATURE, CKA_TRUST_NON_REPUDIATION,
    CKA_TRUST_KEY_ENCIPHERMENT, CKA_TRUST_DATA_ENCIPHERMENT,
    CKA_TRUST_KEY_AGREEMENT, CKA_TRUST_KEY_CERT_SIGN, CKA_TRUST_CRL_SIGN,
    CKA_TRUST_SERVER_AUTH, CKA_TRUST_CLIENT_AUTH, CKA_TRUST_CODE_SIGNING,
    CKA_TRUST_EMAIL_PROTECTION, CKA_TRUST_IPSEC_END_SYSTEM,
    CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER, CKA_TRUST_TIME_STAMPING,
    CKA_TRUST_STEP_UP_APPROVED, CKA_CERT_SHA1_HASH, CKA_CERT_MD5_HASH,
    CKA_NSS_DB, CKA_NSS_TRUST, CKA_NSS_OVERRIDE_EXTENSIONS,
    CKA_PUBLIC_KEY_INFO
};

static unsigned int known_attributes_size = sizeof(known_attributes) /
                                            sizeof(known_attributes[0]);

PRBool
isULONGAttribute(CK_ATTRIBUTE_TYPE type)
{
    switch (type) {
        case CKA_CERTIFICATE_CATEGORY:
        case CKA_CERTIFICATE_TYPE:
        case CKA_CLASS:
        case CKA_JAVA_MIDP_SECURITY_DOMAIN:
        case CKA_KEY_GEN_MECHANISM:
        case CKA_KEY_TYPE:
        case CKA_MECHANISM_TYPE:
        case CKA_MODULUS_BITS:
        case CKA_PRIME_BITS:
        case CKA_SUBPRIME_BITS:
        case CKA_VALUE_BITS:
        case CKA_VALUE_LEN:

        case CKA_TRUST_DIGITAL_SIGNATURE:
        case CKA_TRUST_NON_REPUDIATION:
        case CKA_TRUST_KEY_ENCIPHERMENT:
        case CKA_TRUST_DATA_ENCIPHERMENT:
        case CKA_TRUST_KEY_AGREEMENT:
        case CKA_TRUST_KEY_CERT_SIGN:
        case CKA_TRUST_CRL_SIGN:

        case CKA_TRUST_SERVER_AUTH:
        case CKA_TRUST_CLIENT_AUTH:
        case CKA_TRUST_CODE_SIGNING:
        case CKA_TRUST_EMAIL_PROTECTION:
        case CKA_TRUST_IPSEC_END_SYSTEM:
        case CKA_TRUST_IPSEC_TUNNEL:
        case CKA_TRUST_IPSEC_USER:
        case CKA_TRUST_TIME_STAMPING:
        case CKA_TRUST_STEP_UP_APPROVED:
            return PR_TRUE;
        default:
            break;
    }
    return PR_FALSE;
}

/* are the attributes private? */
static PRBool
isPrivateAttribute(CK_ATTRIBUTE_TYPE type)
{
    switch (type) {
        case CKA_VALUE:
        case CKA_PRIVATE_EXPONENT:
        case CKA_PRIME_1:
        case CKA_PRIME_2:
        case CKA_EXPONENT_1:
        case CKA_EXPONENT_2:
        case CKA_COEFFICIENT:
            return PR_TRUE;
        default:
            break;
    }
    return PR_FALSE;
}

/* These attributes must be authenticated with an hmac. */
static PRBool
isAuthenticatedAttribute(CK_ATTRIBUTE_TYPE type)
{
    switch (type) {
        case CKA_MODULUS:
        case CKA_PUBLIC_EXPONENT:
        case CKA_CERT_SHA1_HASH:
        case CKA_CERT_MD5_HASH:
        case CKA_TRUST_SERVER_AUTH:
        case CKA_TRUST_CLIENT_AUTH:
        case CKA_TRUST_EMAIL_PROTECTION:
        case CKA_TRUST_CODE_SIGNING:
        case CKA_TRUST_STEP_UP_APPROVED:
        case CKA_NSS_OVERRIDE_EXTENSIONS:
            return PR_TRUE;
        default:
            break;
    }
    return PR_FALSE;
}

/*
 * convert a database ulong back to a native ULONG. (reverse of the above
 * function.
 */
static CK_ULONG
sdbULong2ULong(unsigned char *data)
{
    int i;
    CK_ULONG value = 0;

    for (i = 0; i < SDB_ULONG_SIZE; i++) {
        value |= (((CK_ULONG)data[i]) << (SDB_ULONG_SIZE - 1 - i) * PR_BITS_PER_BYTE);
    }
    return value;
}

/* PBE defines and functions */
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)

typedef struct EncryptedDataInfoStr {
    SECAlgorithmID algorithm;
    SECItem encryptedData;
} EncryptedDataInfo;

static const SEC_ASN1Template encryptedDataInfoTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(EncryptedDataInfo) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(EncryptedDataInfo, algorithm),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_OCTET_STRING,
      offsetof(EncryptedDataInfo, encryptedData) },
    { 0 }
};

typedef struct PBEParameterStr {
    SECAlgorithmID prfAlg;
    SECItem salt;
    SECItem iteration;
    SECItem keyLength;
} PBEParameter;

static const SEC_ASN1Template pkcs5V1PBEParameterTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(PBEParameter) },
    { SEC_ASN1_OCTET_STRING,
      offsetof(PBEParameter, salt) },
    { SEC_ASN1_INTEGER,
      offsetof(PBEParameter, iteration) },
    { 0 }
};

static const SEC_ASN1Template pkcs12V2PBEParameterTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(PBEParameter) },
    { SEC_ASN1_OCTET_STRING, offsetof(PBEParameter, salt) },
    { SEC_ASN1_INTEGER, offsetof(PBEParameter, iteration) },
    { 0 }
};

static const SEC_ASN1Template pkcs5V2PBEParameterTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(PBEParameter) },
    /* this is really a choice, but since we don't understand any other
       * choice, just inline it. */
    { SEC_ASN1_OCTET_STRING, offsetof(PBEParameter, salt) },
    { SEC_ASN1_INTEGER, offsetof(PBEParameter, iteration) },
    { SEC_ASN1_INTEGER, offsetof(PBEParameter, keyLength) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(PBEParameter, prfAlg),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { 0 }
};

typedef struct Pkcs5v2PBEParameterStr {
    SECAlgorithmID keyParams; /* parameters of the key generation */
    SECAlgorithmID algParams; /* parameters for the encryption or mac op */
} Pkcs5v2PBEParameter;

static const SEC_ASN1Template pkcs5v2PBES2ParameterTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(Pkcs5v2PBEParameter) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(Pkcs5v2PBEParameter, keyParams),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(Pkcs5v2PBEParameter, algParams),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { 0 }
};

static inline PRBool
isPKCS12PBE(SECOidTag alg)
{
    switch (alg) {
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4:
            return PR_TRUE;
        default:
            break;
    }
    return PR_FALSE;
}

/* helper functions */

/* output an NSS specific attribute or name that wasn't found in our
 * pkcs #11 table */
const char *
makeNSSVendorName(CK_ATTRIBUTE_TYPE attribute, const char *nameType)
{
    static char nss_name[256];
    const char *name = NULL;
    if ((attribute >= CKA_NSS) && (attribute < 0xffffffffUL)) {
        sprintf(nss_name, "%s+%d", nameType, (int)(attribute - CKA_NSS));
        name = nss_name;
    }
    return name;
}

/*  turn and attribute into a name */
const char *
AttributeName(CK_ATTRIBUTE_TYPE attribute)
{
    const char *name = getNameFromAttribute(attribute);
    if (!name) {
        name = makeNSSVendorName(attribute, "CKA_NSS");
    }

    return name ? name : "UNKNOWN_ATTRIBUTE_TYPE";
}

/*  turn and error code into a name */
const char *
ErrorName(CK_RV crv)
{
    const char *error = getName(crv, ConstResult);
    if (!error) {
        error = makeNSSVendorName(crv, "CKR_NSS");
    }
    return error ? error : "UNKNOWN_ERROR";
}

/* turn an oud tag into a string */
const char *
oid2string(SECOidTag alg)
{
    const char *oidstring = SECOID_FindOIDTagDescription(alg);
    const char *def = "Invalid oid tag"; /* future build a dotted oid string value here */
    return oidstring ? oidstring : def;
}

/* dump an arbitary data blob. Dump it has hex with ascii on the side */
#define ASCCHAR(val) ((val) >= ' ' && (val) <= 0x7e ? (val) : '.')
#define LINE_LENGTH 16
void
dumpValue(const unsigned char *v, int len)
{
    int i, next = 0;
    char string[LINE_LENGTH + 1];
    char space[LINE_LENGTH * 2 + 1];
    char *nl = "";
    char *sp = "";
    PORT_Memset(string, 0, sizeof(string));

    for (i = 0; i < len; i++) {
        if ((i % LINE_LENGTH) == 0) {
            printf("%s%s%s        ", sp, string, nl);
            PORT_Memset(string, 0, sizeof(string));
            next = 0;
            nl = "\n";
            sp = " ";
        }
        printf("%02x", v[i]);
        string[next++] = ASCCHAR(v[i]);
    }
    PORT_Memset(space, 0, sizeof(space));
    i = LINE_LENGTH - (len % LINE_LENGTH);
    if (i != LINE_LENGTH) {
        int j;
        for (j = 0; j < i; j++) {
            space[j * 2] = ' ';
            space[j * 2 + 1] = ' ';
        }
    }
    printf("%s%s%s%s", space, sp, string, nl);
}

/* dump a PKCS5/12 PBE blob */
void
dumpPKCS(unsigned char *val, CK_ULONG len, PRBool *hasSig)
{
    EncryptedDataInfo edi;
    SECStatus rv;
    SECItem data;
    PLArenaPool *arena;
    SECOidTag alg, prfAlg;
    PBEParameter pbeParam;
    unsigned char zero = 0;
    const SEC_ASN1Template *template = pkcs5V1PBEParameterTemplate;
    int iter, keyLen, i;

    if (hasSig) {
        *hasSig = PR_FALSE;
    }

    data.data = val;
    data.len = len;
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        printf("Couldn't allocate arena\n");
        return;
    }

    /* initialize default values */
    PORT_Memset(&pbeParam, 0, sizeof(pbeParam));
    pbeParam.keyLength.data = &zero;
    pbeParam.keyLength.len = sizeof(zero);
    SECOID_SetAlgorithmID(arena, &pbeParam.prfAlg, SEC_OID_SHA1, NULL);

    /* first crack the encrypted data from the PBE algorithm ID */
    rv = SEC_QuickDERDecodeItem(arena, &edi, encryptedDataInfoTemplate, &data);
    if (rv != SECSuccess) {
        printf("Encrypted Data, failed to decode\n");
        dumpValue(val, len);
        PORT_FreeArena(arena, PR_FALSE);
        return;
    }
    /* now use the pbe secalg to dump info on the pbe */
    alg = SECOID_GetAlgorithmTag(&edi.algorithm);
    if ((alg == SEC_OID_PKCS5_PBES2) || (alg == SEC_OID_PKCS5_PBMAC1)) {
        Pkcs5v2PBEParameter param;
        SECOidTag palg;
        const char *typeName = (alg == SEC_OID_PKCS5_PBES2) ? "Encrypted Data PBES2" : "Mac Data PBMAC1";

        rv = SEC_QuickDERDecodeItem(arena, &param,
                                    pkcs5v2PBES2ParameterTemplate,
                                    &edi.algorithm.parameters);
        if (rv != SECSuccess) {
            printf("%s, failed to decode\n", typeName);
            dumpValue(val, len);
            PORT_FreeArena(arena, PR_FALSE);
            return;
        }
        palg = SECOID_GetAlgorithmTag(&param.algParams);
        printf("%s alg=%s ", typeName, oid2string(palg));
        if (hasSig && palg == SEC_OID_AES_256_CBC) {
            *hasSig = PR_TRUE;
        }
        template = pkcs5V2PBEParameterTemplate;
        edi.algorithm.parameters = param.keyParams.parameters;
    } else {
        printf("Encrypted Data alg=%s ", oid2string(alg));
        if (alg == SEC_OID_PKCS5_PBKDF2) {
            template = pkcs5V2PBEParameterTemplate;
        } else if (isPKCS12PBE(alg)) {
            template = pkcs12V2PBEParameterTemplate;
        } else {
            template = pkcs5V1PBEParameterTemplate;
        }
    }
    rv = SEC_QuickDERDecodeItem(arena, &pbeParam,
                                template,
                                &edi.algorithm.parameters);
    if (rv != SECSuccess) {
        printf("( failed to decode params)\n");
        PORT_FreeArena(arena, PR_FALSE);
        return;
    }
    /* dump the pbe parmeters */
    iter = DER_GetInteger(&pbeParam.iteration);
    keyLen = DER_GetInteger(&pbeParam.keyLength);
    prfAlg = SECOID_GetAlgorithmTag(&pbeParam.prfAlg);
    printf("(prf=%s iter=%d keyLen=%d salt=0x",
           oid2string(prfAlg), iter, keyLen);
    for (i = 0; i < pbeParam.salt.len; i++)
        printf("%02x", pbeParam.salt.data[i]);
    printf(")\n");
    /* finally dump the raw encrypted data */
    dumpValue(edi.encryptedData.data, edi.encryptedData.len);
    PORT_FreeArena(arena, PR_FALSE);
}

/* dump a long attribute, convert to an unsigned long. PKCS #11 Longs are
 * limited to 32 bits by the spec, even if the CK_ULONG is longer */
void
dumpLongAttribute(CK_ATTRIBUTE_TYPE type, CK_ULONG value)
{
    const char *nameType = "CK_NSS";
    ConstType constType = ConstNone;
    const char *valueName = NULL;

    switch (type) {
        case CKA_CLASS:
            nameType = "CKO_NSS";
            constType = ConstObject;
            break;
        case CKA_CERTIFICATE_TYPE:
            nameType = "CKC_NSS";
            constType = ConstCertType;
            break;
        case CKA_KEY_TYPE:
            nameType = "CKK_NSS";
            constType = ConstKeyType;
            break;
        case CKA_MECHANISM_TYPE:
            nameType = "CKM_NSS";
            constType = ConstMechanism;
            break;
        case CKA_TRUST_SERVER_AUTH:
        case CKA_TRUST_CLIENT_AUTH:
        case CKA_TRUST_CODE_SIGNING:
        case CKA_TRUST_EMAIL_PROTECTION:
        case CKA_TRUST_IPSEC_END_SYSTEM:
        case CKA_TRUST_IPSEC_TUNNEL:
        case CKA_TRUST_IPSEC_USER:
        case CKA_TRUST_TIME_STAMPING:
            nameType = "CKT_NSS";
            constType = ConstTrust;
            break;
        default:
            break;
    }
    /* if value has a symbolic name, use it */
    if (constType != ConstNone) {
        valueName = getName(value, constType);
    }
    if (!valueName) {
        valueName = makeNSSVendorName(value, nameType);
    }
    if (!valueName) {
        printf("%d (0x%08x)\n", (int)value, (int)value);
    } else {
        printf("%s (0x%08x)\n", valueName, (int)value);
    }
}

/* dump a signature for an object */
static const char META_SIG_TEMPLATE[] = "sig_%s_%08x_%08x";
void
dumpSignature(CK_ATTRIBUTE_TYPE attribute, SDB *keydb, PRBool isKey,
              CK_OBJECT_HANDLE objectID, PRBool force)
{
    char id[30];
    CK_RV crv;
    SECItem signText;
    unsigned char signData[SDB_MAX_META_DATA_LEN];

    if (!force && !isAuthenticatedAttribute(attribute)) {
        return;
    }
    sprintf(id, META_SIG_TEMPLATE,
            isKey ? "key" : "cert",
            (unsigned int)objectID, (unsigned int)attribute);
    printf("        Signature %s:", id);
    signText.data = signData;
    signText.len = sizeof(signData);

    crv = (*keydb->sdb_GetMetaData)(keydb, id, &signText, NULL);
    if ((crv != CKR_OK) && isKey) {
        sprintf(id, META_SIG_TEMPLATE,
                isKey ? "key" : "cert", (unsigned int)(objectID | SFTK_KEYDB_TYPE | SFTK_TOKEN_TYPE),
                (unsigned int)attribute);
        crv = (*keydb->sdb_GetMetaData)(keydb, id, &signText, NULL);
    }
    if (crv != CKR_OK) {
        printf(" FAILED %s with %s (0x%08x)\n", id, ErrorName(crv), (int)crv);
        return;
    }
    dumpPKCS(signText.data, signText.len, NULL);
    return;
}

/* dump an attribute. use the helper functions above */
void
dumpAttribute(CK_ATTRIBUTE *template, SDB *keydb, PRBool isKey,
              CK_OBJECT_HANDLE id)
{
    CK_ATTRIBUTE_TYPE attribute = template->type;
    printf("      %s(0x%08x): ", AttributeName(attribute), (int)attribute);
    if (template->pValue == NULL) {
        printf("NULL (%d)\n", (int)template->ulValueLen);
        return;
    }
    if (template->ulValueLen == SDB_ULONG_SIZE && isULONGAttribute(attribute)) {
        CK_ULONG value = sdbULong2ULong(template->pValue);
        dumpLongAttribute(attribute, value);
        return;
    }
    if (template->ulValueLen == 1) {
        unsigned char val = *(unsigned char *)template->pValue;
        switch (val) {
            case 0:
                printf("CK_FALSE\n");
                break;
            case 1:
                printf("CK_TRUE\n");
                break;
            default:
                printf("%d 0x%02x %c\n", val, val, ASCCHAR(val));
                break;
        }
        return;
    }
    if (isKey && isPrivateAttribute(attribute)) {
        PRBool hasSig = PR_FALSE;
        dumpPKCS(template->pValue, template->ulValueLen, &hasSig);
        if (hasSig) {
            dumpSignature(attribute, keydb, isKey, id, PR_TRUE);
        }
        return;
    }
    if (template->ulValueLen == 0) {
        printf("empty");
    }
    printf("\n");
    dumpValue(template->pValue, template->ulValueLen);
}

/* dump all the attributes in an object */
void
dumpObject(CK_OBJECT_HANDLE id, SDB *db, SDB *keydb, PRBool isKey)
{
    CK_RV crv;
    int i;
    CK_ATTRIBUTE template;
    char buffer[2048];
    char *alloc = NULL;

    printf("  Object 0x%08x:\n", (int)id);
    for (i = 0; i < known_attributes_size; i++) {
        CK_ATTRIBUTE_TYPE attribute = known_attributes[i];
        template.type = attribute;
        template.pValue = NULL;
        template.ulValueLen = 0;
        crv = (*db->sdb_GetAttributeValue)(db, id, &template, 1);

        if (crv != CKR_OK) {
            if (crv != CKR_ATTRIBUTE_TYPE_INVALID) {
                PR_fprintf(PR_STDERR, "    "
                                      "Get Attribute %s (0x%08x):FAILED\"%s\"(0x%08x)\n",
                           AttributeName(attribute), (int)attribute,
                           ErrorName(crv), (int)crv);
            }
            continue;
        }

        if (template.ulValueLen < sizeof(buffer)) {
            template.pValue = buffer;
        } else {
            alloc = PORT_Alloc(template.ulValueLen);
            template.pValue = alloc;
        }
        if (template.pValue == NULL) {
            PR_fprintf(PR_STDERR, "    "
                                  "Could allocate %d bytes for  Attribute %s (0x%08x)\n",
                       (int)template.ulValueLen,
                       AttributeName(attribute), (int)attribute);
            continue;
        }
        crv = (*db->sdb_GetAttributeValue)(db, id, &template, 1);

        if (crv != CKR_OK) {
            if (crv != CKR_ATTRIBUTE_TYPE_INVALID) {
                PR_fprintf(PR_STDERR, "    "
                                      "Get Attribute %s (0x%08x):FAILED\"%s\"(0x%08x)\n",
                           AttributeName(attribute), (int)attribute,
                           ErrorName(crv), (int)crv);
            }
            if (alloc) {
                PORT_Free(alloc);
                alloc = NULL;
            }
            continue;
        }

        dumpAttribute(&template, keydb, isKey, id);
        dumpSignature(template.type, keydb, isKey, id, PR_FALSE);
        if (alloc) {
            PORT_Free(alloc);
            alloc = NULL;
        }
    }
}

/* dump all the objects in a database */
void
dumpDB(SDB *db, const char *name, SDB *keydb, PRBool isKey)
{
    SDBFind *findHandle = NULL;
    CK_BBOOL isTrue = 1;
    CK_ATTRIBUTE allObjectTemplate = { CKA_TOKEN, NULL, 1 };
    CK_ULONG allObjectTemplateCount = 1;
    PRBool recordFound = PR_FALSE;
    CK_RV crv = CKR_OK;
    CK_ULONG objectCount = 0;
    printf("%s:\n", name);

    allObjectTemplate.pValue = &isTrue;
    crv = (*db->sdb_FindObjectsInit)(db, &allObjectTemplate,
                                     allObjectTemplateCount, &findHandle);
    do {
        CK_OBJECT_HANDLE id;
        recordFound = PR_FALSE;
        crv = (*db->sdb_FindObjects)(db, findHandle, &id, 1, &objectCount);
        if ((crv == CKR_OK) && (objectCount == 1)) {
            recordFound = PR_TRUE;
            dumpObject(id, db, keydb, isKey);
        }
    } while (recordFound);
    if (crv != CKR_OK) {
        PR_fprintf(PR_STDERR,
                   "Last record return PKCS #11 error = %s (0x%08x)\n",
                   ErrorName(crv), (int)crv);
    }
    (*db->sdb_FindObjectsFinal)(db, findHandle);
}

static char *
secu_ConfigDirectory(const char *base)
{
    static PRBool initted = PR_FALSE;
    const char *dir = ".netscape";
    char *home;
    static char buf[1000];

    if (initted)
        return buf;

    if (base == NULL || *base == 0) {
        home = PR_GetEnvSecure("HOME");
        if (!home)
            home = "";

        if (*home && home[strlen(home) - 1] == '/')
            sprintf(buf, "%.900s%s", home, dir);
        else
            sprintf(buf, "%.900s/%s", home, dir);
    } else {
        sprintf(buf, "%.900s", base);
        if (buf[strlen(buf) - 1] == '/')
            buf[strlen(buf) - 1] = 0;
    }

    initted = PR_TRUE;
    return buf;
}

int
main(int argc, char **argv)
{
    PLOptState *optstate;
    PLOptStatus optstatus;
    char *certPrefix = "", *keyPrefix = "";
    int cert_version = 9;
    int key_version = 4;
    SDB *certdb = NULL;
    SDB *keydb = NULL;
    PRBool isNew = PR_FALSE;

    CK_RV crv;

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

    optstate = PL_CreateOptState(argc, argv, "d:c:k:v:V:h");

    while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
        switch (optstate->option) {
            case 'h':
            default:
                Usage();
                break;

            case 'd':
                dbDir = PORT_Strdup(optstate->value);
                break;

            case 'c':
                certPrefix = PORT_Strdup(optstate->value);
                break;

            case 'k':
                keyPrefix = PORT_Strdup(optstate->value);
                break;

            case 'v':
                key_version = atoi(optstate->value);
                break;

            case 'V':
                cert_version = atoi(optstate->value);
                break;
        }
    }
    PL_DestroyOptState(optstate);
    if (optstatus == PL_OPT_BAD)
        Usage();

    if (dbDir) {
        char *tmp = dbDir;
        dbDir = secu_ConfigDirectory(tmp);
        PORT_Free(tmp);
    } else {
        dbDir = secu_ConfigDirectory(NULL);
    }
    PR_fprintf(PR_STDERR, "dbdir selected is %s\n\n", dbDir);

    if (dbDir[0] == '\0') {
        PR_fprintf(PR_STDERR,
                   "ERROR: Directory \"%s\" does not exist.\n", dbDir);
        return 1;
    }

    PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
    SECOID_Init();

    crv = s_open(dbDir, certPrefix, keyPrefix, cert_version, key_version,
                 SDB_RDONLY, &certdb, &keydb, &isNew);
    if (crv != CKR_OK) {
        PR_fprintf(PR_STDERR,
                   "Couldn't open databased in %s, error=%s (0x%08x)\n",
                   dbDir, ErrorName(crv), (int)crv);
        return 1;
    }

    /* now dump the objects in the cert database */
    dumpDB(certdb, "CertDB", keydb, PR_FALSE);
    dumpDB(keydb, "KeyDB", keydb, PR_TRUE);
    return 0;
}
