/*
 * GnuTLS PKCS#11 support
 * Copyright (C) 2010-2012 Free Software Foundation, Inc.
 * 
 * Authors: Nikos Mavrogiannopoulos, Stef Walter
 *
 * The GnuTLS is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>
 */

#ifndef GNUTLS_LIB_PKCS11_INT_H
#define GNUTLS_LIB_PKCS11_INT_H

#ifdef ENABLE_PKCS11

#define CRYPTOKI_GNU
#include <p11-kit/pkcs11.h>
#include <gnutls/pkcs11.h>
#include "x509/x509_int.h"

/* Part of PKCS#11 3.0 interface, which was added in p11-kit 0.23.14 */
#if defined(CKM_EDDSA) && defined(CKM_EC_EDWARDS_KEY_PAIR_GEN)
#define HAVE_PKCS11_EDDSA
#endif

#define PKCS11_ID_SIZE 128
#define PKCS11_LABEL_SIZE 128

#include <p11-kit/p11-kit.h>
#include <p11-kit/pin.h>
#include <p11-kit/uri.h>
typedef unsigned char ck_bool_t;

struct pkcs11_session_info {
	struct ck_function_list *module;
	struct ck_token_info tinfo;
	struct ck_slot_info slot_info;
	ck_session_handle_t pks;
	ck_slot_id_t sid;
	unsigned int init;
	unsigned int trusted; /* whether module is marked as trusted */
};

struct gnutls_pkcs11_obj_st {
	gnutls_datum_t raw;
	gnutls_pkcs11_obj_type_t type;
	ck_object_class_t class;

	unsigned int flags;
	struct p11_kit_uri *info;

	/* only when pubkey */
	gnutls_datum_t pubkey[MAX_PUBLIC_PARAMS_SIZE];
	unsigned pubkey_size;
	gnutls_pk_algorithm_t pk_algorithm;
	unsigned int key_usage;

	struct pin_info_st pin;
};

struct gnutls_pkcs11_privkey_st {
	gnutls_pk_algorithm_t pk_algorithm;
	unsigned int rsa_pss_ok; /* if it is an RSA key, it can do RSA-PSS */
	unsigned int bits;

	unsigned int flags;
	struct p11_kit_uri *uinfo;
	char *url;

	struct pkcs11_session_info sinfo;
	ck_object_handle_t ref; /* the key in the session */
	unsigned reauth; /* whether we need to login on each operation */

	void *mutex; /* lock for operations requiring co-ordination */

	struct pin_info_st pin;
};

/* This must be called on every function that uses a PKCS #11 function
 * directly. It can be provided a callback function to run when a reinitialization
 * occurs. */
typedef int (*pkcs11_reinit_function)(void *priv);

typedef enum init_level_t {
	PROV_UNINITIALIZED = 0,
	PROV_INIT_MANUAL,
	PROV_INIT_MANUAL_TRUSTED,
	PROV_INIT_TRUSTED,
	PROV_INIT_ALL
} init_level_t;

/* See _gnutls_pkcs11_check_init() for possible Transitions.
 */

int _gnutls_pkcs11_check_init(init_level_t req_level, void *priv,
			      pkcs11_reinit_function cb);

#define FIX_KEY_USAGE(pk, usage)                              \
	if (usage == 0) {                                     \
		if (pk == GNUTLS_PK_RSA)                      \
			usage = GNUTLS_KEY_DECIPHER_ONLY |    \
				GNUTLS_KEY_DIGITAL_SIGNATURE; \
		else                                          \
			usage = GNUTLS_KEY_DIGITAL_SIGNATURE; \
	}

#define PKCS11_CHECK_INIT                                           \
	ret = _gnutls_pkcs11_check_init(PROV_INIT_ALL, NULL, NULL); \
	if (ret < 0)                                                \
	return gnutls_assert_val(ret)

#define PKCS11_CHECK_INIT_RET(x)                                    \
	ret = _gnutls_pkcs11_check_init(PROV_INIT_ALL, NULL, NULL); \
	if (ret < 0)                                                \
	return gnutls_assert_val(x)

#define PKCS11_CHECK_INIT_FLAGS(f)                                       \
	ret = _gnutls_pkcs11_check_init(                                 \
		(f & GNUTLS_PKCS11_OBJ_FLAG_PRESENT_IN_TRUSTED_MODULE) ? \
			PROV_INIT_TRUSTED :                              \
			PROV_INIT_ALL,                                   \
		NULL, NULL);                                             \
	if (ret < 0)                                                     \
	return gnutls_assert_val(ret)

#define PKCS11_CHECK_INIT_FLAGS_RET(f, x)                                \
	ret = _gnutls_pkcs11_check_init(                                 \
		(f & GNUTLS_PKCS11_OBJ_FLAG_PRESENT_IN_TRUSTED_MODULE) ? \
			PROV_INIT_TRUSTED :                              \
			PROV_INIT_ALL,                                   \
		NULL, NULL);                                             \
	if (ret < 0)                                                     \
	return gnutls_assert_val(x)

/* thus function is called for every token in the traverse_tokens
 * function. Once everything is traversed it is called with NULL tinfo.
 * It should return 0 if found what it was looking for.
 */
typedef int (*find_func_t)(struct ck_function_list *,
			   struct pkcs11_session_info *,
			   struct ck_token_info *tinfo, struct ck_info *,
			   void *input);

int pkcs11_rv_to_err(ck_rv_t rv);
int pkcs11_url_to_info(const char *url, struct p11_kit_uri **info,
		       unsigned flags);
int pkcs11_find_slot(struct ck_function_list **module, ck_slot_id_t *slot,
		     struct p11_kit_uri *info, struct ck_token_info *_tinfo,
		     struct ck_slot_info *_slot_info, unsigned int *trusted);

int pkcs11_read_pubkey(struct ck_function_list *module, ck_session_handle_t pks,
		       ck_object_handle_t obj, ck_key_type_t key_type,
		       gnutls_pkcs11_obj_t pobj);

int pkcs11_override_cert_exts(struct pkcs11_session_info *sinfo,
			      gnutls_datum_t *spki, gnutls_datum_t *der);

int pkcs11_get_info(struct p11_kit_uri *info, gnutls_pkcs11_obj_info_t itype,
		    void *output, size_t *output_size);
int pkcs11_login(struct pkcs11_session_info *sinfo,
		 struct pin_info_st *pin_info, struct p11_kit_uri *info,
		 unsigned flags);

int pkcs11_call_token_func(struct p11_kit_uri *info, const unsigned retry);

extern gnutls_pkcs11_token_callback_t _gnutls_token_func;
extern void *_gnutls_token_data;

void pkcs11_rescan_slots(void);
int pkcs11_info_to_url(struct p11_kit_uri *info,
		       gnutls_pkcs11_url_type_t detailed, char **url);

int _gnutls_x509_crt_import_pkcs11_url(gnutls_x509_crt_t crt, const char *url,
				       unsigned int flags);

#define SESSION_WRITE (1 << 0)
#define SESSION_LOGIN (1 << 1)
#define SESSION_SO (1 << 2) /* security officer session */
#define SESSION_TRUSTED \
	(1 << 3) /* session on a marked as trusted (p11-kit) module */
#define SESSION_FORCE_LOGIN \
	(1 << 4) /* force login even when CFK_LOGIN_REQUIRED is not set */
#define SESSION_CONTEXT_SPECIFIC (1 << 5)
#define SESSION_NO_CLOSE (1 << 6)
/* don't close session on success */

int pkcs11_open_session(struct pkcs11_session_info *sinfo,
			struct pin_info_st *pin_info, struct p11_kit_uri *info,
			unsigned int flags);
int _pkcs11_traverse_tokens(find_func_t find_func, void *input,
			    struct p11_kit_uri *info,
			    struct pin_info_st *pin_info, unsigned int flags);
ck_object_class_t pkcs11_strtype_to_class(const char *type);

/* Additional internal flags for gnutls_pkcs11_obj_flags */
/* @GNUTLS_PKCS11_OBJ_FLAG_EXPECT_CERT: When importing an object, provide a hint on the type, to allow incomplete URLs
 * @GNUTLS_PKCS11_OBJ_FLAG_EXPECT_PRIVKEY: Hint for private key */
#define GNUTLS_PKCS11_OBJ_FLAG_FIRST_CLOSE_MATCH ((unsigned int)1 << 28)
#define GNUTLS_PKCS11_OBJ_FLAG_EXPECT_CERT (1 << 29)
#define GNUTLS_PKCS11_OBJ_FLAG_EXPECT_PRIVKEY (1 << 30)
#define GNUTLS_PKCS11_OBJ_FLAG_EXPECT_PUBKEY ((unsigned int)1 << 31)

int pkcs11_token_matches_info(struct p11_kit_uri *info,
			      struct ck_token_info *tinfo,
			      struct ck_info *lib_info);

unsigned int pkcs11_obj_flags_to_int(unsigned int flags);

int _gnutls_pkcs11_privkey_sign(gnutls_pkcs11_privkey_t key,
				const gnutls_sign_entry_st *se,
				const gnutls_datum_t *hash,
				gnutls_datum_t *signature,
				gnutls_x509_spki_st *spki_params);

int _gnutls_pkcs11_privkey_decrypt_data(gnutls_pkcs11_privkey_t key,
					unsigned int flags,
					const gnutls_datum_t *ciphertext,
					gnutls_datum_t *plaintext);

int _gnutls_pkcs11_privkey_decrypt_data2(gnutls_pkcs11_privkey_t key,
					 unsigned int flags,
					 const gnutls_datum_t *ciphertext,
					 unsigned char *plaintext,
					 size_t plaintext_size);

int _pkcs11_privkey_get_pubkey(gnutls_pkcs11_privkey_t pkey,
			       gnutls_pubkey_t *pub, unsigned flags);

int _gnutls_pubkey_parse_ecc_eddsa_params(const gnutls_datum_t *parameters,
					  gnutls_ecc_curve_t *outcurve);

static inline int pk_to_mech(gnutls_pk_algorithm_t pk)
{
	if (pk == GNUTLS_PK_DSA)
		return CKM_DSA;
	else if (pk == GNUTLS_PK_EC)
		return CKM_ECDSA;
	else if (pk == GNUTLS_PK_RSA)
		return CKM_RSA_PKCS;
	else if (pk == GNUTLS_PK_RSA_PSS)
		return CKM_RSA_PKCS_PSS;
#ifdef HAVE_PKCS11_EDDSA
	else if (pk == GNUTLS_PK_EDDSA_ED25519 || pk == GNUTLS_PK_EDDSA_ED448)
		return CKM_EDDSA;
#endif
	else
		return -1;
}

static inline int pk_to_key_type(gnutls_pk_algorithm_t pk)
{
	if (pk == GNUTLS_PK_DSA)
		return CKK_DSA;
	else if (pk == GNUTLS_PK_EC)
		return CKK_ECDSA;
	else if (pk == GNUTLS_PK_RSA_PSS || pk == GNUTLS_PK_RSA)
		return CKK_RSA;
#ifdef HAVE_PKCS11_EDDSA
	else if (pk == GNUTLS_PK_EDDSA_ED25519 || pk == GNUTLS_PK_EDDSA_ED448)
		return CKK_EC_EDWARDS;
#endif
	else
		return -1;
}

static inline int pk_to_genmech(gnutls_pk_algorithm_t pk, ck_key_type_t *type)
{
	if (pk == GNUTLS_PK_DSA) {
		*type = CKK_DSA;
		return CKM_DSA_KEY_PAIR_GEN;
	} else if (pk == GNUTLS_PK_EC) {
		*type = CKK_ECDSA;
		return CKM_ECDSA_KEY_PAIR_GEN;
	} else if (pk == GNUTLS_PK_RSA_PSS || pk == GNUTLS_PK_RSA) {
		*type = CKK_RSA;
		return CKM_RSA_PKCS_KEY_PAIR_GEN;
#ifdef HAVE_PKCS11_EDDSA
	} else if (pk == GNUTLS_PK_EDDSA_ED25519 ||
		   pk == GNUTLS_PK_EDDSA_ED448) {
		*type = CKK_EC_EDWARDS;
		return CKM_EC_EDWARDS_KEY_PAIR_GEN;
#endif
	} else {
		*type = -1;
		return -1;
	}
}

int pkcs11_retrieve_pin(struct pin_info_st *pin_info, struct p11_kit_uri *info,
			struct ck_token_info *token_info, int attempts,
			ck_user_type_t user_type, struct p11_kit_pin **pin);

ck_object_class_t pkcs11_type_to_class(gnutls_pkcs11_obj_type_t type);

ck_rv_t pkcs11_generate_key(struct ck_function_list *module,
			    ck_session_handle_t sess,
			    struct ck_mechanism *mechanism,
			    struct ck_attribute *templ, unsigned long count,
			    ck_object_handle_t *key);

ck_rv_t pkcs11_generate_key_pair(
	struct ck_function_list *module, ck_session_handle_t sess,
	struct ck_mechanism *mechanism, struct ck_attribute *pub_templ,
	unsigned long pub_templ_count, struct ck_attribute *priv_templ,
	unsigned long priv_templ_count, ck_object_handle_t *pub,
	ck_object_handle_t *priv);

ck_rv_t pkcs11_get_slot_list(struct ck_function_list *module,
			     unsigned char token_present,
			     ck_slot_id_t *slot_list, unsigned long *count);

ck_rv_t pkcs11_get_module_info(struct ck_function_list *module,
			       struct ck_info *info);

ck_rv_t pkcs11_get_slot_info(struct ck_function_list *module,
			     ck_slot_id_t slot_id, struct ck_slot_info *info);

ck_rv_t pkcs11_get_token_info(struct ck_function_list *module,
			      ck_slot_id_t slot_id, struct ck_token_info *info);

ck_rv_t pkcs11_find_objects_init(struct ck_function_list *module,
				 ck_session_handle_t sess,
				 struct ck_attribute *templ,
				 unsigned long count);

ck_rv_t pkcs11_find_objects(struct ck_function_list *module,
			    ck_session_handle_t sess,
			    ck_object_handle_t *objects,
			    unsigned long max_object_count,
			    unsigned long *object_count);

ck_rv_t pkcs11_find_objects_final(struct pkcs11_session_info *);

ck_rv_t pkcs11_close_session(struct pkcs11_session_info *);

ck_rv_t pkcs11_set_attribute_value(struct ck_function_list *module,
				   ck_session_handle_t sess,
				   ck_object_handle_t object,
				   struct ck_attribute *templ,
				   unsigned long count);

ck_rv_t pkcs11_get_attribute_value(struct ck_function_list *module,
				   ck_session_handle_t sess,
				   ck_object_handle_t object,
				   struct ck_attribute *templ,
				   unsigned long count);

ck_rv_t pkcs11_get_attribute_avalue(struct ck_function_list *module,
				    ck_session_handle_t sess,
				    ck_object_handle_t object,
				    ck_attribute_type_t type,
				    gnutls_datum_t *res);

ck_rv_t pkcs11_get_mechanism_list(struct ck_function_list *module,
				  ck_slot_id_t slot_id,
				  ck_mechanism_type_t *mechanism_list,
				  unsigned long *count);

ck_rv_t pkcs11_get_mechanism_info(struct ck_function_list *module,
				  ck_slot_id_t slot_id,
				  ck_mechanism_type_t mechanism,
				  struct ck_mechanism_info *ptr);

ck_rv_t pkcs11_sign_init(struct ck_function_list *module,
			 ck_session_handle_t sess,
			 struct ck_mechanism *mechanism,
			 ck_object_handle_t key);

ck_rv_t pkcs11_sign(struct ck_function_list *module, ck_session_handle_t sess,
		    unsigned char *data, unsigned long data_len,
		    unsigned char *signature, unsigned long *signature_len);

ck_rv_t pkcs11_decrypt_init(struct ck_function_list *module,
			    ck_session_handle_t sess,
			    struct ck_mechanism *mechanism,
			    ck_object_handle_t key);

ck_rv_t pkcs11_decrypt(struct ck_function_list *module,
		       ck_session_handle_t sess, unsigned char *encrypted_data,
		       unsigned long encrypted_data_len, unsigned char *data,
		       unsigned long *data_len);

ck_rv_t pkcs11_create_object(struct ck_function_list *module,
			     ck_session_handle_t sess,
			     struct ck_attribute *templ, unsigned long count,
			     ck_object_handle_t *object);

ck_rv_t pkcs11_destroy_object(struct ck_function_list *module,
			      ck_session_handle_t sess,
			      ck_object_handle_t object);

ck_rv_t pkcs11_init_token(struct ck_function_list *module, ck_slot_id_t slot_id,
			  unsigned char *pin, unsigned long pin_len,
			  unsigned char *label);

ck_rv_t pkcs11_init_pin(struct ck_function_list *module,
			ck_session_handle_t sess, unsigned char *pin,
			unsigned long pin_len);

ck_rv_t pkcs11_set_pin(struct ck_function_list *module,
		       ck_session_handle_t sess, const char *old_pin,
		       unsigned long old_len, const char *new_pin,
		       unsigned long new_len);

ck_rv_t _gnutls_pkcs11_get_random(struct ck_function_list *module,
				  ck_session_handle_t sess, void *data,
				  size_t len);

const char *pkcs11_strerror(ck_rv_t rv);

/* Returns 1 if the provided URL is an object, rather than
 * a token. */
inline static bool is_pkcs11_url_object(const char *url)
{
	if (strstr(url, "id=") != 0 || strstr(url, "object=") != 0)
		return 1;
	return 0;
}

unsigned _gnutls_pkcs11_crt_is_known(const char *url, gnutls_x509_crt_t cert,
				     unsigned int flags,
				     gnutls_x509_crt_t *trusted_cert);

time_t _gnutls_pkcs11_get_distrust_after(const char *url,
					 gnutls_x509_crt_t cert,
					 const char *purpose,
					 unsigned int flags);

#endif /* ENABLE_PKCS11 */

#endif /* GNUTLS_LIB_PKCS11_INT_H */
