/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include <libgen.h>
#include <errno.h>
#include <cryptoutil.h>
#include <security/cryptoki.h>
#include "common.h"
#include <kmfapi.h>


static KMF_RETURN
genkey_nss(KMF_HANDLE_T kmfhandle, char *token, char *dir, char *prefix,
    char *keylabel, KMF_KEY_ALG keyAlg, int keylen, KMF_CREDENTIAL *tokencred)
{
	KMF_RETURN kmfrv = KMF_OK;
	KMF_CREATESYMKEY_PARAMS csk_params;
	KMF_KEY_HANDLE key;

	if (keylabel == NULL) {
		cryptoerror(LOG_STDERR,
		    gettext("A key label must be specified \n"));
		return (KMF_ERR_BAD_PARAMETER);
	}

	kmfrv = configure_nss(kmfhandle, dir, prefix);
	if (kmfrv != KMF_OK)
		return (kmfrv);

	(void) memset(&key, 0, sizeof (KMF_KEY_HANDLE));
	csk_params.kstype = KMF_KEYSTORE_NSS;
	csk_params.nssparms.slotlabel = token;
	csk_params.keytype = keyAlg;
	csk_params.keylength = keylen;
	csk_params.keylabel = keylabel;
	csk_params.cred.cred = tokencred->cred;
	csk_params.cred.credlen = tokencred->credlen;
	kmfrv = KMF_CreateSymKey(kmfhandle, &csk_params, &key);

	return (kmfrv);
}

static KMF_RETURN
genkey_pkcs11(KMF_HANDLE_T kmfhandle, char *token,
	char *keylabel, KMF_KEY_ALG keyAlg, int keylen,
	char *senstr, char *extstr, boolean_t print_hex,
	KMF_CREDENTIAL *tokencred)
{
	KMF_RETURN kmfrv = KMF_OK;
	KMF_CREATESYMKEY_PARAMS params;
	KMF_KEY_HANDLE key;
	KMF_RAW_SYM_KEY  *rkey = NULL;
	boolean_t 	sensitive = B_FALSE;
	boolean_t	not_extractable = B_FALSE;
	char *hexstr = NULL;
	int  hexstrlen;

	if (keylabel == NULL) {
		cryptoerror(LOG_STDERR,
		    gettext("A key label must be specified \n"));
		return (KMF_ERR_BAD_PARAMETER);
	}

	/* Check the sensitive option value if specified. */
	if (senstr != NULL) {
		if (tolower(senstr[0]) == 'y')
			sensitive = B_TRUE;
		else if (tolower(senstr[0]) == 'n')
			sensitive = B_FALSE;
		else {
			cryptoerror(LOG_STDERR,
			    gettext("Incorrect sensitive option value.\n"));
			return (KMF_ERR_BAD_PARAMETER);
		}
	}

	/* Check the extractable option value if specified. */
	if (extstr != NULL) {
		if (tolower(extstr[0]) == 'y')
			not_extractable = B_FALSE;
		else if (tolower(extstr[0]) == 'n')
			not_extractable = B_TRUE;
		else {
			cryptoerror(LOG_STDERR,
			    gettext("Incorrect extractable option value.\n"));
			return (KMF_ERR_BAD_PARAMETER);
		}
	}

	/* Select a PKCS11 token first */
	kmfrv = select_token(kmfhandle, token, FALSE);
	if (kmfrv != KMF_OK) {
		return (kmfrv);
	}

	(void) memset(&key, 0, sizeof (KMF_KEY_HANDLE));
	params.kstype = KMF_KEYSTORE_PK11TOKEN;
	params.keytype = keyAlg;
	params.keylength = keylen; /* bits */
	params.keylabel = keylabel;
	params.pkcs11parms.sensitive = sensitive;
	params.pkcs11parms.not_extractable = not_extractable;
	params.cred.cred = tokencred->cred;
	params.cred.credlen = tokencred->credlen;
	kmfrv = KMF_CreateSymKey(kmfhandle, &params, &key);
	if (kmfrv != KMF_OK) {
		goto out;
	}

	if (print_hex) {
		if (sensitive == B_TRUE || not_extractable == B_TRUE) {
			cryptoerror(LOG_STDERR,
			    gettext("Warning: can not reveal the key value "
			    "for a sensitive or non-extractable key.\n"));
			goto out;
		} else {
			rkey = malloc(sizeof (KMF_RAW_SYM_KEY));
			if (rkey == NULL) {
				kmfrv = KMF_ERR_MEMORY;
				goto out;
			}
			(void) memset(rkey, 0, sizeof (KMF_RAW_SYM_KEY));
			kmfrv = KMF_GetSymKeyValue(kmfhandle, &key, rkey);
			if (kmfrv != KMF_OK) {
				goto out;
			}
			hexstrlen = 2 * rkey->keydata.len + 1;
			hexstr = malloc(hexstrlen);
			if (hexstr == NULL) {
				kmfrv = KMF_ERR_MEMORY;
				goto out;
			}

			tohexstr(rkey->keydata.val, rkey->keydata.len, hexstr,
			    hexstrlen);
			(void) printf(gettext("\tKey Value =\"%s\"\n"), hexstr);
		}
	}

out:
	KMF_FreeRawSymKey(rkey);

	if (hexstr != NULL)
		free(hexstr);

	return (kmfrv);
}


static KMF_RETURN
genkey_file(KMF_HANDLE_T kmfhandle, KMF_KEY_ALG keyAlg, int keylen, char *dir,
    char *outkey, boolean_t print_hex)
{
	KMF_RETURN kmfrv = KMF_OK;
	KMF_CREATESYMKEY_PARAMS csk_params;
	KMF_KEY_HANDLE key;
	KMF_RAW_SYM_KEY *rkey = NULL;
	char *hexstr = NULL;
	int hexstrlen;

	if (EMPTYSTRING(outkey)) {
		cryptoerror(LOG_STDERR,
		    gettext("No output key file was specified for the key\n"));
		return (KMF_ERR_BAD_PARAMETER);
	}

	if (verify_file(outkey)) {
		cryptoerror(LOG_STDERR,
			gettext("Cannot write the indicated output "
				"key file (%s).\n"), outkey);
		return (KMF_ERR_BAD_PARAMETER);
	}

	(void) memset(&key, 0, sizeof (KMF_KEY_HANDLE));
	csk_params.kstype = KMF_KEYSTORE_OPENSSL;
	csk_params.keytype = keyAlg;
	csk_params.keylength = keylen;
	csk_params.cred.cred = NULL;
	csk_params.cred.credlen = 0;
	csk_params.sslparms.dirpath = (dir == NULL) ? "." : dir;
	csk_params.sslparms.keyfile = outkey;

	kmfrv = KMF_CreateSymKey(kmfhandle, &csk_params, &key);
	if (kmfrv != KMF_OK) {
		goto out;
	}

	if (print_hex) {
		rkey = malloc(sizeof (KMF_RAW_SYM_KEY));
		if (rkey == NULL) {
			kmfrv = KMF_ERR_MEMORY;
			goto out;
		}
		(void) memset(rkey, 0, sizeof (KMF_RAW_SYM_KEY));
		kmfrv = KMF_GetSymKeyValue(kmfhandle, &key, rkey);
		if (kmfrv != KMF_OK) {
			goto out;
		}

		hexstrlen = 2 * rkey->keydata.len + 1;
		hexstr = malloc(hexstrlen);
		if (hexstr == NULL) {
			kmfrv = KMF_ERR_MEMORY;
			goto out;
		}
		tohexstr(rkey->keydata.val, rkey->keydata.len, hexstr,
		    hexstrlen);
		(void) printf(gettext("\tKey Value =\"%s\"\n"), hexstr);
	}

out:
	KMF_FreeRawSymKey(rkey);

	if (hexstr != NULL)
		free(hexstr);

	return (kmfrv);
}

int
pk_genkey(int argc, char *argv[])
{
	int rv;
	int opt;
	extern int	optind_av;
	extern char	*optarg_av;
	KMF_KEYSTORE_TYPE kstype = KMF_KEYSTORE_PK11TOKEN;
	char *tokenname = NULL;
	char *dir = NULL;
	char *prefix = NULL;
	char *keytype = "AES";
	char *keylenstr = NULL;
	int keylen = 0;
	char *keylabel = NULL;
	char *outkey = NULL;
	char *senstr = NULL;
	char *extstr = NULL;
	char *printstr = NULL;
	KMF_HANDLE_T kmfhandle = NULL;
	KMF_KEY_ALG keyAlg = KMF_AES;
	boolean_t print_hex = B_FALSE;
	KMF_CREDENTIAL tokencred = {NULL, 0};

	while ((opt = getopt_av(argc, argv,
		"k:(keystore)l:(label)T:(token)d:(dir)p:(prefix)"
		"t:(keytype)y:(keylen)K:(outkey)P:(print)"
		"s:(sensitive)e:(extractable)")) != EOF) {
		if (EMPTYSTRING(optarg_av))
			return (PK_ERR_USAGE);
		switch (opt) {
			case 'k':
				kstype = KS2Int(optarg_av);
				if (kstype == 0)
					return (PK_ERR_USAGE);
				break;
			case 'l':
				if (keylabel)
					return (PK_ERR_USAGE);
				keylabel = optarg_av;
				break;
			case 'T':
				if (tokenname)
					return (PK_ERR_USAGE);
				tokenname = optarg_av;
				break;
			case 'd':
				if (dir)
					return (PK_ERR_USAGE);
				dir = optarg_av;
				break;
			case 'p':
				if (prefix)
					return (PK_ERR_USAGE);
				prefix = optarg_av;
				break;
			case 't':
				keytype = optarg_av;
				break;
			case 'y':
				if (keylenstr)
					return (PK_ERR_USAGE);
				keylenstr = optarg_av;
				break;
			case 'K':
				if (outkey)
					return (PK_ERR_USAGE);
				outkey = optarg_av;
				break;
			case 'P':
				if (printstr)
					return (PK_ERR_USAGE);
				printstr = optarg_av;
				break;
			case 's':
				if (senstr)
					return (PK_ERR_USAGE);
				senstr = optarg_av;
				break;
			case 'e':
				if (extstr)
					return (PK_ERR_USAGE);
				extstr = optarg_av;
				break;
			default:
				return (PK_ERR_USAGE);
		}
	}

	/* No additional args allowed. */
	argc -= optind_av;
	argv += optind_av;
	if (argc) {
		return (PK_ERR_USAGE);
	}

	/* Check keytype. If not specified, default to AES */
	if (keytype != NULL && Str2SymKeyType(keytype, &keyAlg) != 0) {
		cryptoerror(LOG_STDERR, gettext("Unrecognized keytype(%s).\n"),
			keytype);
		return (PK_ERR_USAGE);
	}

	/*
	 * Check and set the key length.
	 * - For DES and 3DES, the key size are fixed. Ingore the keylen
	 *   option, even if it is specified.
	 * - For AES and ARCFOUR, if keylen is not specified, default to
	 *   128 bits.
	 */
	if (keyAlg == KMF_DES)
		keylen = 64;  /* fixed size; ignore input */
	else if (keyAlg == KMF_DES3)
		keylen = 192; /* fixed size; ignore input */
	else /* AES, ARCFOUR, or GENERIC SECRET */ {
		if (keylenstr == NULL) {
			cryptoerror(LOG_STDERR,
				gettext("Key length must be specified for "
				"AES, ARCFOUR or GENERIC symmetric keys.\n"));
			return (PK_ERR_USAGE);
		}
		if (sscanf(keylenstr, "%d", &keylen) != 1) {
			cryptoerror(LOG_STDERR,
				gettext("Unrecognized key length (%s).\n"),
				keytype);
			return (PK_ERR_USAGE);
		}
		if (keylen == 0 || (keylen % 8) != 0) {
			cryptoerror(LOG_STDERR,
				gettext("Key length bitlength must be a "
					"multiple of 8.\n"));
			return (PK_ERR_USAGE);
		}
	}

	/* check the print option */
	if (printstr != NULL) {
		if (kstype == KMF_KEYSTORE_NSS) {
			cryptoerror(LOG_STDERR,
			    gettext("The print option does not apply "
			    "to the NSS keystore.\n"));
			return (PK_ERR_USAGE);
		}

		if (tolower(printstr[0]) == 'y')
			print_hex = B_TRUE;
		else if (tolower(printstr[0]) == 'n')
			print_hex = B_FALSE;
		else {
			cryptoerror(LOG_STDERR,
			    gettext("Incorrect print option value.\n"));
			return (PK_ERR_USAGE);
		}
	}

	/* check the sensitive and extractable options */
	if ((senstr != NULL || extstr != NULL) &&
	    (kstype == KMF_KEYSTORE_NSS || kstype == KMF_KEYSTORE_OPENSSL)) {
		cryptoerror(LOG_STDERR,
		    gettext("The sensitive or extractable option applies "
		    "to the PKCS11 keystore only.\n"));
		return (PK_ERR_USAGE);
	}

	if (kstype == KMF_KEYSTORE_PK11TOKEN && tokenname == NULL) {
		tokenname = PK_DEFAULT_PK11TOKEN;
	} else if (kstype == KMF_KEYSTORE_NSS && tokenname == NULL) {
		tokenname = DEFAULT_NSS_TOKEN;
	}

	if (kstype == KMF_KEYSTORE_PK11TOKEN || kstype == KMF_KEYSTORE_NSS)
		(void) get_token_password(kstype, tokenname, &tokencred);

	if ((rv = KMF_Initialize(&kmfhandle, NULL, NULL)) != KMF_OK) {
		cryptoerror(LOG_STDERR, gettext("Error initializing KMF\n"));
		goto end;
	}

	if (kstype == KMF_KEYSTORE_NSS) {
		rv = genkey_nss(kmfhandle, tokenname, dir, prefix,
		    keylabel, keyAlg, keylen, &tokencred);
	} else if (kstype == KMF_KEYSTORE_OPENSSL) {
		rv = genkey_file(kmfhandle, keyAlg, keylen, dir, outkey,
		    print_hex);
	} else {
		rv = genkey_pkcs11(kmfhandle, tokenname, keylabel, keyAlg,
		    keylen, senstr, extstr, print_hex, &tokencred);
	}

end:
	if (rv != KMF_OK)
		display_error(kmfhandle, rv,
			gettext("Error generating key"));

	if (tokencred.cred != NULL)
		free(tokencred.cred);

	(void) KMF_Finalize(kmfhandle);
	if (rv != KMF_OK)
		return (PK_ERR_USAGE);

	return (0);
}