root/usr/src/cmd/cmd-crypto/pktool/common.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * This file contains the functions that are shared among
 * the various services this tool will ultimately provide.
 * The functions in this file return PKCS#11 CK_RV errors.
 * Only one session and one login per token is supported
 * at this time.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <tzfile.h>
#include <cryptoutil.h>
#include <security/cryptoki.h>
#include <kmfapi.h>

#include "common.h"

/* Local status variables. */
static boolean_t        initialized = B_FALSE;
static boolean_t        session_opened = B_FALSE;
static boolean_t        logged_in = B_FALSE;

/* Supporting structures and global variables for getopt_av(). */
typedef struct  av_opts_s {
        int             shortnm;        /* short name character */
        char            *longnm;        /* long name string, NOT terminated */
        int             longnm_len;     /* length of long name string */
        boolean_t       has_arg;        /* takes optional argument */
} av_opts;
static av_opts          *opts_av = NULL;
static const char       *_save_optstr = NULL;
static int              _save_numopts = 0;

int                     optind_av = 1;
char                    *optarg_av = NULL;

static void close_sess(CK_SESSION_HANDLE);
static void logout_token(CK_SESSION_HANDLE);

struct oid_table_entry {
        const KMF_OID *oid;
        char *name;
};

struct oid_table_entry oid_table[] = {
        { &KMFOID_ECC_secp112r1, "secp112r1"},
        { &KMFOID_ECC_secp112r2, "secp112r2"},
        { &KMFOID_ECC_secp128r1, "secp128r1"},
        { &KMFOID_ECC_secp128r2, "secp128r2"},
        { &KMFOID_ECC_secp160k1, "secp160k1"},
        { &KMFOID_ECC_secp160r1, "secp160r1"},
        { &KMFOID_ECC_secp160r2, "secp160r2"},
        { &KMFOID_ECC_secp192k1, "secp192k1"},
        { &KMFOID_ECC_secp192r1, "secp192r1"},
        { &KMFOID_ECC_secp224k1, "secp224k1"},
        { &KMFOID_ECC_secp224r1, "secp224r1"},
        { &KMFOID_ECC_secp256k1, "secp256k1"},
        { &KMFOID_ECC_secp256r1, "secp256r1"},
        { &KMFOID_ECC_secp384r1, "secp384r1"},
        { &KMFOID_ECC_secp521r1, "secp521r1"},
        { &KMFOID_ECC_sect113r1, "sect113r1"},
        { &KMFOID_ECC_sect113r2, "sect113r2"},
        { &KMFOID_ECC_sect131r1, "sect131r1"},
        { &KMFOID_ECC_sect131r2, "sect131r2"},
        { &KMFOID_ECC_sect163k1, "sect163k1"},
        { &KMFOID_ECC_sect163r1, "sect163r1"},
        { &KMFOID_ECC_sect163r2, "sect163r2"},
        { &KMFOID_ECC_sect193r1, "sect193r1"},
        { &KMFOID_ECC_sect193r2, "sect193r2"},
        { &KMFOID_ECC_sect233k1, "sect233k1"},
        { &KMFOID_ECC_sect233r1, "sect233r1"},
        { &KMFOID_ECC_sect239k1, "sect239k1"},
        { &KMFOID_ECC_sect283k1, "sect283k1"},
        { &KMFOID_ECC_sect283r1, "sect283r1"},
        { &KMFOID_ECC_sect409k1, "sect409k1"},
        { &KMFOID_ECC_sect409r1, "sect409r1"},
        { &KMFOID_ECC_sect571k1, "sect571k1"},
        { &KMFOID_ECC_sect571r1, "sect571r1"},
        { &KMFOID_ECC_c2pnb163v1, "c2pnb163v1"},
        { &KMFOID_ECC_c2pnb163v2, "c2pnb163v2"},
        { &KMFOID_ECC_c2pnb163v3, "c2pnb163v3"},
        { &KMFOID_ECC_c2pnb176v1, "c2pnb176v1"},
        { &KMFOID_ECC_c2tnb191v1, "c2tnb191v1"},
        { &KMFOID_ECC_c2tnb191v2, "c2tnb191v2"},
        { &KMFOID_ECC_c2tnb191v3, "c2tnb191v3"},
        { &KMFOID_ECC_c2pnb208w1, "c2pnb208w1"},
        { &KMFOID_ECC_c2tnb239v1, "c2tnb239v1"},
        { &KMFOID_ECC_c2tnb239v2, "c2tnb239v2"},
        { &KMFOID_ECC_c2tnb239v3, "c2tnb239v3"},
        { &KMFOID_ECC_c2pnb272w1, "c2pnb272w1"},
        { &KMFOID_ECC_c2pnb304w1, "c2pnb304w1"},
        { &KMFOID_ECC_c2tnb359v1, "c2tnb359v1"},
        { &KMFOID_ECC_c2pnb368w1, "c2pnb368w1"},
        { &KMFOID_ECC_c2tnb431r1, "c2tnb431r1"},
        { &KMFOID_ECC_prime192v2, "prime192v2"},
        { &KMFOID_ECC_prime192v3, "prime192v3"},
        { &KMFOID_MD5, "md5"},
        { &KMFOID_SHA1, "sha1"},
        { &KMFOID_SHA256, "sha256"},
        { &KMFOID_SHA384, "sha384"},
        { &KMFOID_SHA512, "sha512"}
};
int number_of_oids = sizeof (oid_table) / sizeof (struct oid_table_entry);
#define number_of_curves (number_of_oids - 5)

/*
 * Perform PKCS#11 setup here.  Currently only C_Initialize is required,
 * along with setting/resetting state variables.
 */
static CK_RV
init_pkcs11(void)
{
        CK_RV           rv = CKR_OK;

        /* If C_Initialize() already called, nothing to do here. */
        if (initialized == B_TRUE)
                return (CKR_OK);

        /* Reset state variables because C_Initialize() not yet done. */
        session_opened = B_FALSE;
        logged_in = B_FALSE;

        /* Initialize PKCS#11 library. */
        if ((rv = C_Initialize(NULL_PTR)) != CKR_OK &&
            rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) {
                return (rv);
        }

        initialized = B_TRUE;
        return (CKR_OK);
}

/*
 * Finalize PKCS#11 library and reset state variables.  Open sessions,
 * if any, are closed, and thereby any logins are logged out also.
 */
void
final_pk11(CK_SESSION_HANDLE sess)
{

        /* If the library wasn't initialized, nothing to do here. */
        if (!initialized)
                return;

        /* Make sure the sesion is closed first. */
        close_sess(sess);

        (void) C_Finalize(NULL);
        initialized = B_FALSE;
}

/*
 * Close PKCS#11 session and reset state variables.  Any logins are
 * logged out.
 */
static void
close_sess(CK_SESSION_HANDLE sess)
{

        if (sess == 0) {
                return;
        }

        /* If session is already closed, nothing to do here. */
        if (!session_opened)
                return;

        /* Make sure user is logged out of token. */
        logout_token(sess);

        (void) C_CloseSession(sess);
        session_opened = B_FALSE;
}

/*
 * Log user out of token and reset status variable.
 */
static void
logout_token(CK_SESSION_HANDLE sess)
{

        if (sess == 0) {
                return;
        }

        /* If already logged out, nothing to do here. */
        if (!logged_in)
                return;

        (void) C_Logout(sess);
        logged_in = B_FALSE;
}

/*
 * Gets PIN from user.  Caller needs to free the returned PIN when done.
 * If two prompts are given, the PIN is confirmed with second prompt.
 * Note that getphassphrase() may return data in static memory area.
 */
CK_RV
get_pin(char *prompt1, char *prompt2, CK_UTF8CHAR_PTR *pin, CK_ULONG *pinlen)
{
        char *save_phrase, *phrase1, *phrase2;

        /* Prompt user for a PIN. */
        if (prompt1 == NULL) {
                return (CKR_ARGUMENTS_BAD);
        }
        if ((phrase1 = getpassphrase(prompt1)) == NULL) {
                return (CKR_FUNCTION_FAILED);
        }

        /* Duplicate 1st PIN in separate chunk of memory. */
        if ((save_phrase = strdup(phrase1)) == NULL)
                return (CKR_HOST_MEMORY);

        /* If second prompt given, PIN confirmation is requested. */
        if (prompt2 != NULL) {
                if ((phrase2 = getpassphrase(prompt2)) == NULL) {
                        free(save_phrase);
                        return (CKR_FUNCTION_FAILED);
                }
                if (strcmp(save_phrase, phrase2) != 0) {
                        free(save_phrase);
                        return (CKR_PIN_INCORRECT);
                }
        }

        *pin = (CK_UTF8CHAR_PTR)save_phrase;
        *pinlen = strlen(save_phrase);
        return (CKR_OK);
}

int
yn_to_int(char *ynstr)
{
        char *y = gettext("yes");
        char *n = gettext("no");
        if (ynstr == NULL)
                return (-1);

        if (strncasecmp(ynstr, y, 1) == 0)
                return (1);
        else if (strncasecmp(ynstr, n, 1) == 0)
                return (0);
        else
                return (-1);
}

/*
 * Gets yes/no response from user.  If either no prompt is supplied, a
 * default prompt is used.  If not message for invalid input is supplied,
 * a default will not be provided.  If the user provides no response,
 * the input default B_TRUE == yes, B_FALSE == no is returned.
 * Otherwise, B_TRUE is returned for yes, and B_FALSE for no.
 */
boolean_t
yesno(char *prompt, char *invalid, boolean_t dflt)
{
        char    *response, buf[1024];
        int     ans;

        if (prompt == NULL)
                prompt = gettext("Enter (y)es or (n)o? ");

        for (;;) {
                /* Prompt user. */
                (void) printf("%s", prompt);
                (void) fflush(stdout);

                /* Get the response. */
                if ((response = fgets(buf, sizeof (buf), stdin)) == NULL)
                        break;          /* go to default response */

                /* Skip any leading white space. */
                while (isspace(*response))
                        response++;
                if (*response == '\0')
                        break;          /* go to default response */

                ans = yn_to_int(response);
                if (ans == 1)
                        return (B_TRUE);
                else if (ans == 0)
                        return (B_FALSE);

                /* Indicate invalid input, and try again. */
                if (invalid != NULL)
                        (void) printf("%s", invalid);
        }
        return (dflt);
}

/*
 * Gets the list of slots which have tokens in them.  Keeps adjusting
 * the size of the slot list buffer until the call is successful or an
 * irrecoverable error occurs.
 */
CK_RV
get_token_slots(CK_SLOT_ID_PTR *slot_list, CK_ULONG *slot_count)
{
        CK_ULONG        tmp_count = 0;
        CK_SLOT_ID_PTR  tmp_list = NULL_PTR, tmp2_list = NULL_PTR;
        int             rv = CKR_OK;

        if (!initialized)
                if ((rv = init_pkcs11()) != CKR_OK)
                        return (rv);

        /*
         * Get the slot count first because we don't know how many
         * slots there are and how many of those slots even have tokens.
         * Don't specify an arbitrary buffer size for the slot list;
         * it may be too small (see section 11.5 of PKCS#11 spec).
         * Also select only those slots that have tokens in them,
         * because this tool has no need to know about empty slots.
         */
        if ((rv = C_GetSlotList(1, NULL_PTR, &tmp_count)) != CKR_OK)
                return (rv);

        if (tmp_count == 0) {
                *slot_list = NULL_PTR;
                *slot_count = 0;
                return (CKR_OK);
        }

        /* Allocate initial space for the slot list. */
        if ((tmp_list = (CK_SLOT_ID_PTR) malloc(tmp_count *
            sizeof (CK_SLOT_ID))) == NULL)
                return (CKR_HOST_MEMORY);

        /* Then get the slot list itself. */
        for (;;) {
                if ((rv = C_GetSlotList(1, tmp_list, &tmp_count)) == CKR_OK) {
                        *slot_list = tmp_list;
                        *slot_count = tmp_count;
                        break;
                }

                if (rv != CKR_BUFFER_TOO_SMALL) {
                        free(tmp_list);
                        break;
                }

                /* If the number of slots grew, try again. */
                if ((tmp2_list = (CK_SLOT_ID_PTR) realloc(tmp_list,
                    tmp_count * sizeof (CK_SLOT_ID))) == NULL) {
                        free(tmp_list);
                        rv = CKR_HOST_MEMORY;
                        break;
                }
                tmp_list = tmp2_list;
        }

        return (rv);
}

/*
 * Breaks out the getopt-style option string into a structure that can be
 * traversed later for calls to getopt_av().  Option string is NOT altered,
 * but the struct fields point to locations within option string.
 */
static int
populate_opts(char *optstring)
{
        int             i;
        av_opts         *temp;
        char            *marker;

        if (optstring == NULL || *optstring == '\0')
                return (0);

        /*
         * This tries to imitate getopt(3c) Each option must conform to:
         * <short name char> [ ':' ] [ '(' <long name string> ')' ]
         * If long name is missing, the short name is used for long name.
         */
        for (i = 0; *optstring != '\0'; i++) {
                if ((temp = (av_opts *)((i == 0) ? malloc(sizeof (av_opts)) :
                    realloc(opts_av, (i+1) * sizeof (av_opts)))) == NULL) {
                        if (opts_av != NULL)
                                free(opts_av);
                        opts_av = NULL;
                        return (0);
                } else {
                        opts_av = (av_opts *)temp;
                }

                (void) memset(&opts_av[i], 0, sizeof (av_opts));
                marker = optstring;             /* may need optstring later */

                opts_av[i].shortnm = *marker++; /* set short name */

                if (*marker == ':') {           /* check for opt arg */
                        marker++;
                        opts_av[i].has_arg = B_TRUE;
                }

                if (*marker == '(') {           /* check and set long name */
                        marker++;
                        opts_av[i].longnm = marker;
                        opts_av[i].longnm_len = strcspn(marker, ")");
                        optstring = marker + opts_av[i].longnm_len + 1;
                } else {
                        /* use short name option character */
                        opts_av[i].longnm = optstring;
                        opts_av[i].longnm_len = 1;
                        optstring = marker;
                }
        }

        return (i);
}

/*
 * getopt_av() is very similar to getopt(3c) in that the takes an option
 * string, compares command line arguments for matches, and returns a single
 * letter option when a match is found.  However, getopt_av() differs from
 * getopt(3c) by requiring that only longname options and values be found
 * on the command line and all leading dashes are omitted.  In other words,
 * it tries to enforce only longname "option=value" arguments on the command
 * line.  Boolean options are not allowed either.
 */
int
getopt_av(int argc, char * const *argv, const char *optstring)
{
        int     i;
        int     len;
        char   *cur_option;

        if (optind_av >= argc)
                return (EOF);

        /* First time or when optstring changes from previous one */
        if (_save_optstr != optstring) {
                if (opts_av != NULL)
                        free(opts_av);
                opts_av = NULL;
                _save_optstr = optstring;
                _save_numopts = populate_opts((char *)optstring);
        }

        for (i = 0; i < _save_numopts; i++) {
                cur_option = argv[optind_av];

                if (strcmp(cur_option, "--") == 0) {
                        optind_av++;
                        break;
                }

                if (cur_option[0] == '-' && strlen(cur_option) == 2) {
                        len = 1;
                        cur_option++; /* remove "-" */
                } else {
                        len = strcspn(cur_option, "=");
                }

                if (len == opts_av[i].longnm_len && strncmp(cur_option,
                    opts_av[i].longnm, opts_av[i].longnm_len) == 0) {
                        /* matched */
                        if (!opts_av[i].has_arg) {
                                optind_av++;
                                return (opts_av[i].shortnm);
                        }

                        /* needs optarg */
                        if (cur_option[len] == '=') {
                                optarg_av = &(cur_option[len+1]);
                                optind_av++;
                                return (opts_av[i].shortnm);
                        }

                        optarg_av = NULL;
                        optind_av++;
                        return ((int)'?');
                }
        }

        return (EOF);
}

KMF_KEYSTORE_TYPE
KS2Int(char *keystore_str)
{
        if (keystore_str == NULL)
                return (0);
        if (strcasecmp(keystore_str, "pkcs11") == 0)
                return (KMF_KEYSTORE_PK11TOKEN);
        else if (strcasecmp(keystore_str, "nss") == 0)
                return (KMF_KEYSTORE_NSS);
        else if (strcasecmp(keystore_str, "file") == 0)
                return (KMF_KEYSTORE_OPENSSL);
        else
                return (0);
}

/*
 * compare_oids
 * return 1 if equal
 */
boolean_t
compare_oids(KMF_OID *oid1, const KMF_OID *oid2)
{
        return ((oid1->Length == oid2->Length) &&
            !memcmp(oid1->Data, oid2->Data, oid1->Length));
}

int
Str2KeyType(char *algm, KMF_OID *hashoid, KMF_KEY_ALG *ktype,
    KMF_ALGORITHM_INDEX *sigAlg)
{
        if (algm == NULL) {
                /* Default to SHA1+RSA */
                *sigAlg = KMF_ALGID_SHA1WithRSA;
                *ktype = KMF_RSA;
        } else if (strcasecmp(algm, "DSA") == 0) {
                if (hashoid == NULL ||
                    compare_oids(hashoid, &KMFOID_SHA1))
                        *sigAlg = KMF_ALGID_SHA1WithDSA;
                else if (compare_oids(hashoid, &KMFOID_SHA256))
                        *sigAlg = KMF_ALGID_SHA256WithDSA;
                else
                        return (-1); /* unsupported hash/key combo */
                *ktype = KMF_DSA;
        } else if (strcasecmp(algm, "RSA") == 0) {
                if (hashoid == NULL ||
                    compare_oids(hashoid, &KMFOID_SHA1))
                        *sigAlg = KMF_ALGID_SHA1WithRSA;
                else if (compare_oids(hashoid, &KMFOID_SHA256))
                        *sigAlg = KMF_ALGID_SHA256WithRSA;
                else if (compare_oids(hashoid, &KMFOID_SHA384))
                        *sigAlg = KMF_ALGID_SHA384WithRSA;
                else if (compare_oids(hashoid, &KMFOID_SHA512))
                        *sigAlg = KMF_ALGID_SHA512WithRSA;
                else if (compare_oids(hashoid, &KMFOID_MD5))
                        *sigAlg = KMF_ALGID_MD5WithRSA;
                else
                        return (-1); /* unsupported hash/key combo */
                *ktype = KMF_RSA;
        } else if (strcasecmp(algm, "EC") == 0) {
                /* EC keys may be used with some SHA2 hashes */
                if (hashoid == NULL ||
                    compare_oids(hashoid, &KMFOID_SHA1))
                        *sigAlg = KMF_ALGID_SHA1WithECDSA;
                else if (compare_oids(hashoid, &KMFOID_SHA256))
                        *sigAlg = KMF_ALGID_SHA256WithECDSA;
                else if (compare_oids(hashoid, &KMFOID_SHA384))
                        *sigAlg = KMF_ALGID_SHA384WithECDSA;
                else if (compare_oids(hashoid, &KMFOID_SHA512))
                        *sigAlg = KMF_ALGID_SHA512WithECDSA;
                else
                        return (-1); /* unsupported hash/key combo */

                *ktype = KMF_ECDSA;
        } else {
                return (-1);
        }
        return (0);
}

int
Str2SymKeyType(char *algm, KMF_KEY_ALG *ktype)
{
        if (algm == NULL)
                *ktype = KMF_AES;
        else if (strcasecmp(algm, "aes") == 0)
                *ktype = KMF_AES;
        else if (strcasecmp(algm, "arcfour") == 0)
                *ktype = KMF_RC4;
        else if (strcasecmp(algm, "des") == 0)
                *ktype = KMF_DES;
        else if (strcasecmp(algm, "3des") == 0)
                *ktype = KMF_DES3;
        else if (strcasecmp(algm, "generic") == 0)
                *ktype = KMF_GENERIC_SECRET;
        else
                return (-1);

        return (0);
}

int
Str2Lifetime(char *ltimestr, uint32_t *ltime)
{
        int num;
        char timetok[6];

        if (ltimestr == NULL || strlen(ltimestr) == 0) {
                /* default to 1 year lifetime */
                *ltime = SECSPERDAY * DAYSPERNYEAR;
                return (0);
        }

        (void) memset(timetok, 0, sizeof (timetok));
        if (sscanf(ltimestr, "%d-%06s", &num, timetok) != 2)
                return (-1);

        if (strcasecmp(timetok, "day") == 0||
            strcasecmp(timetok, "days") == 0) {
                *ltime = num * SECSPERDAY;
        } else if (strcasecmp(timetok, "hour") == 0||
            strcasecmp(timetok, "hours") == 0) {
                *ltime = num * SECSPERHOUR;
        } else if (strcasecmp(timetok, "year") == 0 ||
            strcasecmp(timetok, "years") == 0) {
                *ltime = num * SECSPERDAY * DAYSPERNYEAR;
        } else {
                *ltime = 0;
                return (-1);
        }

        return (0);
}

int
OT2Int(char *objclass)
{
        char *c = NULL;
        int retval = 0;

        if (objclass == NULL)
                return (-1);

        c = strchr(objclass, ':');
        if (c != NULL) {
                if (strcasecmp(c, ":private") == 0)
                        retval = PK_PRIVATE_OBJ;
                else if (strcasecmp(c, ":public") == 0)
                        retval = PK_PUBLIC_OBJ;
                else if (strcasecmp(c, ":both") == 0)
                        retval = PK_PRIVATE_OBJ | PK_PUBLIC_OBJ;
                else /* unrecognized option */
                        return (-1);

                *c = '\0';
        }

        if (strcasecmp(objclass, "public") == 0) {
                if (retval)
                        return (-1);
                return (retval | PK_PUBLIC_OBJ | PK_CERT_OBJ | PK_PUBKEY_OBJ);
        } else if (strcasecmp(objclass, "private") == 0) {
                if (retval)
                        return (-1);
                return (retval | PK_PRIKEY_OBJ | PK_PRIVATE_OBJ);
        } else if (strcasecmp(objclass, "both") == 0) {
                if (retval)
                        return (-1);
                return (PK_KEY_OBJ | PK_PUBLIC_OBJ | PK_PRIVATE_OBJ);
        } else if (strcasecmp(objclass, "cert") == 0) {
                return (retval | PK_CERT_OBJ);
        } else if (strcasecmp(objclass, "key") == 0) {
                if (retval == 0) /* return all keys */
                        return (retval | PK_KEY_OBJ);
                else if (retval == (PK_PRIVATE_OBJ | PK_PUBLIC_OBJ))
                        /* return all keys */
                        return (retval | PK_KEY_OBJ);
                else if (retval & PK_PUBLIC_OBJ)
                        /* Only return public keys */
                        return (retval | PK_PUBKEY_OBJ);
                else if (retval & PK_PRIVATE_OBJ)
                        /* Only return private keys */
                        return (retval | PK_PRIKEY_OBJ);
        } else if (strcasecmp(objclass, "crl") == 0) {
                if (retval)
                        return (-1);
                return (retval | PK_CRL_OBJ);
        }

        if (retval == 0) /* No matches found */
                retval = -1;
        return (retval);
}

KMF_ENCODE_FORMAT
Str2Format(char *formstr)
{
        if (formstr == NULL || strcasecmp(formstr, "der") == 0)
                return (KMF_FORMAT_ASN1);
        if (strcasecmp(formstr, "pem") == 0)
                return (KMF_FORMAT_PEM);
        if (strcasecmp(formstr, "pkcs12") == 0)
                return (KMF_FORMAT_PKCS12);
        if (strcasecmp(formstr, "raw") == 0)
                return (KMF_FORMAT_RAWKEY);

        return (KMF_FORMAT_UNDEF);
}

KMF_RETURN
select_token(void *kmfhandle, char *token, int readonly)
{
        KMF_ATTRIBUTE attlist[10];
        int i = 0;
        KMF_KEYSTORE_TYPE kstype = KMF_KEYSTORE_PK11TOKEN;
        KMF_RETURN rv = KMF_OK;

        if (token == NULL)
                return (KMF_ERR_BAD_PARAMETER);

        kmf_set_attr_at_index(attlist, i,
            KMF_KEYSTORE_TYPE_ATTR, &kstype,
            sizeof (kstype));
        i++;

        if (token) {
                kmf_set_attr_at_index(attlist, i,
                    KMF_TOKEN_LABEL_ATTR, token,
                    strlen(token));
                i++;
        }

        kmf_set_attr_at_index(attlist, i,
            KMF_READONLY_ATTR, &readonly,
            sizeof (readonly));
        i++;

        rv = kmf_configure_keystore(kmfhandle, i, attlist);
        if (rv == KMF_ERR_TOKEN_SELECTED)
                rv = KMF_OK;
        return (rv);
}

KMF_RETURN
configure_nss(void *kmfhandle, char *dir, char *prefix)
{
        KMF_ATTRIBUTE attlist[10];
        int i = 0;
        KMF_KEYSTORE_TYPE kstype = KMF_KEYSTORE_NSS;
        KMF_RETURN rv = KMF_OK;

        kmf_set_attr_at_index(attlist, i,
            KMF_KEYSTORE_TYPE_ATTR, &kstype,
            sizeof (kstype));
        i++;

        if (dir) {
                kmf_set_attr_at_index(attlist, i,
                    KMF_DIRPATH_ATTR, dir,
                    strlen(dir));
                i++;
        }

        if (prefix) {
                kmf_set_attr_at_index(attlist, i,
                    KMF_CERTPREFIX_ATTR, prefix,
                    strlen(prefix));
                i++;

                kmf_set_attr_at_index(attlist, i,
                    KMF_KEYPREFIX_ATTR, prefix,
                    strlen(prefix));
                i++;
        }

        rv = kmf_configure_keystore(kmfhandle, i, attlist);
        if (rv == KMF_KEYSTORE_ALREADY_INITIALIZED)
                rv = KMF_OK;

        return (rv);
}

KMF_RETURN
get_pk12_password(KMF_CREDENTIAL *cred)
{
        KMF_RETURN rv = KMF_OK;
        char prompt[1024];

        /*
         * Get the password to use for the PK12 encryption.
         */
        (void) strlcpy(prompt,
            gettext("Enter password to use for "
            "accessing the PKCS12 file: "), sizeof (prompt));

        if (get_pin(prompt, NULL, (uchar_t **)&cred->cred,
            (ulong_t *)&cred->credlen) != CKR_OK) {
                cred->cred = NULL;
                cred->credlen = 0;
        }

        return (rv);
}

#define FILENAME_PROMPT gettext("Filename:")
#define FILENAME_MINLEN 1
#define FILENAME_MAXLEN MAXPATHLEN

#define COUNTRY_PROMPT  gettext("Country Name (2 letter code) [US]:")
#define STATE_PROMPT    gettext("State or Province Name (full name) " \
        "[Some-State]:")
#define LOCALITY_PROMPT gettext("Locality Name (eg, city) []:")
#define ORG_PROMPT      gettext("Organization Name (eg, company) []:")
#define UNIT_PROMPT     gettext("Organizational Unit Name (eg, section) []:")
#define NAME_PROMPT     gettext("Common Name (eg, YOUR name) []:")
#define EMAIL_PROMPT    gettext("Email Address []:")

#define SERNO_PROMPT    gettext("Serial Number (hex value, example: " \
        "0x01020304):")
#define SERNO_MINLEN    3
#define SERNO_MAXLEN    42

#define LABEL_PROMPT    gettext("Enter a label for the certificate:")
#define LABEL_MINLEN    1
#define LABEL_MAXLEN    1024

#define COUNTRY_DEFAULT "US"
#define STATE_DEFAULT   NULL
#define INVALID_INPUT   gettext("Invalid input; please re-enter ...")

#define SUBNAMESIZ      1024
#define RDN_MIN         1
#define RDN_MAX         64
#define COUNTRYNAME_MIN 2
#define COUNTRYNAME_MAX 2

static char *
get_input_string(char *prompt, char *default_str, int min_len, int max_len)
{
        char buf[1024];
        char *response = NULL;
        char *ret = NULL;
        int len;

        for (;;) {
                (void) printf("\t%s", prompt);
                (void) fflush(stdout);

                response = fgets(buf, sizeof (buf), stdin);
                if (response == NULL) {
                        if (default_str != NULL) {
                                ret = strdup(default_str);
                        }
                        break;
                }

                /* Skip any leading white space. */
                while (isspace(*response))
                        response++;
                if (*response == '\0') {
                        if (default_str != NULL) {
                                ret = strdup(default_str);
                        }
                        break;
                }

                len = strlen(response);
                response[len-1] = '\0'; /* get rid of "LF" */
                len--;
                if (len >= min_len && len <= max_len) {
                        ret = strdup(response);
                        break;
                }

                (void) printf("%s\n", INVALID_INPUT);

        }

        return (ret);
}

int
get_filename(char *txt, char **result)
{
        char prompt[1024];
        char *fname = NULL;

        (void) snprintf(prompt, sizeof (prompt),
            gettext("Enter filename for the %s: "),
            txt);
        fname = get_input_string(prompt, NULL,
            FILENAME_MINLEN, FILENAME_MAXLEN);
        *result = fname;
        return (0);
}

int
get_certlabel(char **result)
{
        char *label = NULL;

        label = get_input_string(LABEL_PROMPT, NULL,
            LABEL_MINLEN, LABEL_MAXLEN);
        *result = label;
        return (0);
}

int
get_serial(char **result)
{
        char *serial = NULL;

        serial = get_input_string(SERNO_PROMPT, NULL, SERNO_MINLEN,
            SERNO_MAXLEN);

        *result = serial;
        return (0);
}

int
get_subname(char **result)
{
        char *country = NULL;
        char *state = NULL;
        char *locality = NULL;
        char *org = NULL;
        char *unit = NULL;
        char *name = NULL;
        char *email = NULL;
        char *subname = NULL;

        (void) printf("Entering following fields for subject (a DN) ...\n");
        country = get_input_string(COUNTRY_PROMPT, COUNTRY_DEFAULT,
            COUNTRYNAME_MIN, COUNTRYNAME_MAX);
        if (country == NULL)
                return (-1);

        state = get_input_string(STATE_PROMPT, STATE_DEFAULT,
            RDN_MIN, RDN_MAX);

        locality = get_input_string(LOCALITY_PROMPT, NULL, RDN_MIN, RDN_MAX);
        org = get_input_string(ORG_PROMPT, NULL, RDN_MIN, RDN_MAX);
        unit = get_input_string(UNIT_PROMPT, NULL, RDN_MIN, RDN_MAX);
        name = get_input_string(NAME_PROMPT, NULL, RDN_MIN, RDN_MAX);
        email = get_input_string(EMAIL_PROMPT, NULL, RDN_MIN, RDN_MAX);

        /* Now create a subject name from the input strings */
        if ((subname = malloc(SUBNAMESIZ)) == NULL)
                goto out;

        (void) memset(subname, 0, SUBNAMESIZ);
        (void) strlcpy(subname, "C=", SUBNAMESIZ);
        (void) strlcat(subname, country, SUBNAMESIZ);
        if (state != NULL) {
                (void) strlcat(subname, ", ST=", SUBNAMESIZ);
                (void) strlcat(subname, state, SUBNAMESIZ);
        }

        if (locality != NULL) {
                (void) strlcat(subname, ", L=", SUBNAMESIZ);
                (void) strlcat(subname, locality, SUBNAMESIZ);
        }

        if (org != NULL) {
                (void) strlcat(subname, ", O=", SUBNAMESIZ);
                (void) strlcat(subname, org, SUBNAMESIZ);
        }

        if (unit != NULL) {
                (void) strlcat(subname, ", OU=", SUBNAMESIZ);
                (void) strlcat(subname, unit, SUBNAMESIZ);
        }

        if (name != NULL) {
                (void) strlcat(subname, ", CN=", SUBNAMESIZ);
                (void) strlcat(subname, name, SUBNAMESIZ);
        }

        if (email != NULL) {
                (void) strlcat(subname, ", E=", SUBNAMESIZ);
                (void) strlcat(subname, email, SUBNAMESIZ);
        }

out:
        if (country)
                free(country);
        if (state)
                free(state);
        if (locality)
                free(locality);
        if (org)
                free(org);
        if (unit)
                free(unit);
        if (name)
                free(name);
        if (email)
                free(email);

        if (subname == NULL)
                return (-1);
        else {
                *result = subname;
                return (0);
        }
}

/*
 * Parse a string of KeyUsage values and convert
 * them to the correct KU Bits.
 * The field may be marked "critical" by prepending
 * "critical:" to the list.
 * EX:  critical:digitialSignature,keyEncipherment
 */
KMF_RETURN
verify_keyusage(char *kustr, uint16_t *kubits, int *critical)
{
        KMF_RETURN ret = KMF_OK;
        uint16_t kuval;
        char *k;

        *kubits = 0;
        if (kustr == NULL || strlen(kustr) == 0)
                return (KMF_ERR_BAD_PARAMETER);

        /* Check to see if this is critical */
        if (strncasecmp(kustr, "critical:", strlen("critical:")) == 0) {
                *critical = TRUE;
                kustr += strlen("critical:");
        } else {
                *critical = FALSE;
        }

        k = strtok(kustr, ",");
        while (k != NULL) {
                kuval = kmf_string_to_ku(k);
                if (kuval == 0) {
                        *kubits = 0;
                        return (KMF_ERR_BAD_PARAMETER);
                }
                *kubits |= kuval;
                k = strtok(NULL, ",");
        }

        return (ret);
}

/*
 * Verify the alternate subject label is real or invalid.
 *
 * The field may be marked "critical" by prepending
 * "critical:" to the list.
 * EX:  "critical:IP=1.2.3.4"
 */
KMF_RETURN
verify_altname(char *arg, KMF_GENERALNAMECHOICES *type, int *critical)
{
        char *p;
        KMF_RETURN rv = KMF_OK;

        /* Check to see if this is critical */
        if (strncasecmp(arg, "critical:", strlen("critical:")) == 0) {
                *critical = TRUE;
                arg += strlen("critical:");
        } else {
                *critical = FALSE;
        }

        /* Make sure there is an "=" sign */
        p = strchr(arg, '=');
        if (p == NULL)
                return (KMF_ERR_BAD_PARAMETER);

        p[0] = '\0';

        if (strcmp(arg, "IP") == 0)
                *type = GENNAME_IPADDRESS;
        else if (strcmp(arg, "DNS") == 0)
                *type = GENNAME_DNSNAME;
        else if (strcmp(arg, "EMAIL") == 0)
                *type = GENNAME_RFC822NAME;
        else if (strcmp(arg, "URI") == 0)
                *type = GENNAME_URI;
        else if (strcmp(arg, "DN") == 0)
                *type = GENNAME_DIRECTORYNAME;
        else if (strcmp(arg, "RID") == 0)
                *type = GENNAME_REGISTEREDID;
        else if (strcmp(arg, "KRB") == 0)
                *type = GENNAME_KRB5PRINC;
        else if (strcmp(arg, "UPN") == 0)
                *type = GENNAME_SCLOGON_UPN;
        else
                rv = KMF_ERR_BAD_PARAMETER;

        p[0] = '=';

        return (rv);
}

int
get_token_password(KMF_KEYSTORE_TYPE kstype,
        char *token_spec, KMF_CREDENTIAL *cred)
{
        char    prompt[1024];
        char    temptoken[32];
        char    *p = NULL;
        char    *t = NULL;
        int     len;

        (void) memset(temptoken, 0, sizeof (temptoken));
        if (kstype == KMF_KEYSTORE_PK11TOKEN) {
                p = strchr(token_spec, ':');
                if (p != NULL)
                        *p = 0;
        }
        len = strlen(token_spec);
        if (len > sizeof (temptoken))
                len = sizeof (temptoken);

        (void) strncpy(temptoken, token_spec, len);

        /*
         * Strip trailing whitespace
         */
        t = temptoken + (len - 1);
        while (isspace(*t) && t >= temptoken) {
                *t = 0x00;
                t--;
        }

        /*
         * Login to the token first.
         */
        (void) snprintf(prompt, sizeof (prompt),
            gettext(DEFAULT_TOKEN_PROMPT), temptoken);

        if (get_pin(prompt, NULL, (uchar_t **)&cred->cred,
            (ulong_t *)&cred->credlen) != CKR_OK) {
                cred->cred = NULL;
                cred->credlen = 0;
        }

        if (kstype == KMF_KEYSTORE_PK11TOKEN && p != NULL)
                *p = ':';
        return (KMF_OK);
}

KMF_RETURN
verify_file(char *filename)
{
        KMF_RETURN ret = KMF_OK;
        int fd;

        /*
         * Attempt to open with  the EXCL flag so that if
         * it already exists, the open will fail.  It will
         * also fail if the file cannot be created due to
         * permissions on the parent directory, or if the
         * parent directory itself does not exist.
         */
        fd = open(filename, O_CREAT | O_EXCL, 0600);
        if (fd == -1) {
                if (errno == EEXIST)
                        return (KMF_ERR_OPEN_FILE);
                else
                        return (KMF_ERR_WRITE_FILE);
        }

        /* If we were able to create it, delete it. */
        (void) close(fd);
        (void) unlink(filename);

        return (ret);
}

void
display_error(void *handle, KMF_RETURN errcode, char *prefix)
{
        KMF_RETURN rv1, rv2;
        char *plugin_errmsg = NULL;
        char *kmf_errmsg = NULL;

        rv1 = kmf_get_plugin_error_str(handle, &plugin_errmsg);
        rv2 = kmf_get_kmf_error_str(errcode, &kmf_errmsg);

        cryptoerror(LOG_STDERR, "%s:", prefix);
        if (rv1 == KMF_OK && plugin_errmsg) {
                cryptoerror(LOG_STDERR, gettext("keystore error: %s"),
                    plugin_errmsg);
                kmf_free_str(plugin_errmsg);
        }

        if (rv2 == KMF_OK && kmf_errmsg) {
                cryptoerror(LOG_STDERR, gettext("libkmf error: %s"),
                    kmf_errmsg);
                kmf_free_str(kmf_errmsg);
        }

        if (rv1 != KMF_OK && rv2 != KMF_OK)
                cryptoerror(LOG_STDERR, gettext("<unknown error>\n"));

}

static KMF_RETURN
addToEKUList(EKU_LIST *ekus, int critical, KMF_OID *newoid)
{
        if (newoid != NULL && ekus != NULL) {
                ekus->eku_count++;

                ekus->critlist = realloc(ekus->critlist,
                    ekus->eku_count * sizeof (int));
                if (ekus->critlist != NULL)
                        ekus->critlist[ekus->eku_count-1] = critical;
                else
                        return (KMF_ERR_MEMORY);

                ekus->ekulist = realloc(
                    ekus->ekulist, ekus->eku_count * sizeof (KMF_OID));
                if (ekus->ekulist != NULL)
                        ekus->ekulist[ekus->eku_count-1] = *newoid;
                else
                        return (KMF_ERR_MEMORY);
        }
        return (KMF_OK);
}

void
free_eku_list(EKU_LIST *ekus)
{
        if (ekus != NULL && ekus->eku_count > 0) {
                int i;
                for (i = 0; i < ekus->eku_count; i++) {
                        kmf_free_data(&ekus->ekulist[i]);
                }
                free(ekus->ekulist);
                free(ekus->critlist);
                free(ekus);
        }
}

static KMF_RETURN
parse_ekus(char *ekustr, EKU_LIST *ekus)
{
        KMF_RETURN rv = KMF_OK;
        KMF_OID *newoid;
        int critical;

        if (strncasecmp(ekustr, "critical:",
            strlen("critical:")) == 0) {
                critical = TRUE;
                ekustr += strlen("critical:");
        } else {
                critical = FALSE;
        }
        newoid = kmf_ekuname_to_oid(ekustr);
        if (newoid != NULL) {
                rv = addToEKUList(ekus, critical, newoid);
                free(newoid);
        } else {
                rv = PK_ERR_USAGE;
        }

        return (rv);
}

KMF_RETURN
verify_ekunames(char *ekuliststr, EKU_LIST **ekulist)
{
        KMF_RETURN rv = KMF_OK;
        char *p;
        EKU_LIST *ekus = NULL;

        if (ekuliststr == NULL || strlen(ekuliststr) == 0)
                return (0);

        ekus = calloc(sizeof (EKU_LIST), 1);
        if (ekus == NULL)
                return (KMF_ERR_MEMORY);

        /*
         * The list should be comma separated list of EKU Names.
         */
        p = strtok(ekuliststr, ",");

        /* If no tokens found, then maybe it's just a single EKU value */
        if (p == NULL) {
                rv = parse_ekus(ekuliststr, ekus);
        }

        while (p != NULL) {
                rv = parse_ekus(p, ekus);

                if (rv != KMF_OK)
                        break;
                p = strtok(NULL, ",");
        }

        if (rv != KMF_OK)
                free_eku_list(ekus);
        else
                *ekulist = ekus;

        return (rv);
}

KMF_RETURN
token_auth_needed(KMF_HANDLE_T handle, char *tokenlabel, int *auth)
{
        CK_TOKEN_INFO info;
        CK_SLOT_ID slot;
        CK_RV ckrv;
        KMF_RETURN rv;

        *auth = 0;
        rv = kmf_pk11_token_lookup(handle, tokenlabel, &slot);
        if (rv != KMF_OK)
                return (rv);

        ckrv = C_GetTokenInfo(slot, &info);
        if (ckrv != KMF_OK)
                return (KMF_ERR_INTERNAL);

        *auth = (info.flags & CKF_LOGIN_REQUIRED);

        return (KMF_OK);
}

void
show_ecc_curves()
{
        int i;

        (void) printf(gettext("Supported ECC curve names:\n"));
        for (i = 0; i < number_of_curves; i++) {
                (void) printf("%s", oid_table[i].name);
                if (i > 0 && ((i+1) % 5) == 0)
                        (void) printf("\n");
                else if (i+1 < number_of_curves)
                        (void) printf(", ");
        }
        (void) printf("\n");
}

KMF_OID *
ecc_name_to_oid(char *name)
{
        int i;
        for (i = 0; i < number_of_oids; i++) {
                if (strcasecmp(name, oid_table[i].name) == 0)
                        return ((KMF_OID *)oid_table[i].oid);
        }
        return (NULL);
}