/*
 * OpenSSL helper functions, e.g. for implementing MD5 support
 * et al
 *
 * Copyright (C) 2002 Olaf Kirch <okir@suse.de>
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#ifdef ENABLE_OPENSSL		/* empty file without openssl */
#include <string.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/opensslv.h>
#include <openssl/x509.h>
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
#include <openssl/conf.h>
#include <openssl/opensslconf.h> /* for OPENSSL_NO_* */
#include "libopensc/sc-ossl-compat.h"
#ifndef OPENSSL_NO_EC
#include <openssl/ec.h>
#endif /* OPENSSL_NO_EC */
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif /* OPENSSL_NO_ENGINE */
#include <openssl/asn1.h>
#include <openssl/crypto.h>
#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L */

#include "sc-pkcs11.h"

static CK_RV	sc_pkcs11_openssl_md_init(sc_pkcs11_operation_t *);
static CK_RV	sc_pkcs11_openssl_md_update(sc_pkcs11_operation_t *,
					CK_BYTE_PTR, CK_ULONG);
static CK_RV	sc_pkcs11_openssl_md_final(sc_pkcs11_operation_t *,
					CK_BYTE_PTR, CK_ULONG_PTR);
static void	sc_pkcs11_openssl_md_release(sc_pkcs11_operation_t *);

static sc_pkcs11_mechanism_type_t openssl_sha1_mech = {
	CKM_SHA_1,
	{ 0, 0, CKF_DIGEST },
	0,
	sizeof(struct sc_pkcs11_operation),
	sc_pkcs11_openssl_md_release,
	sc_pkcs11_openssl_md_init,
	sc_pkcs11_openssl_md_update,
	sc_pkcs11_openssl_md_final,
	NULL, NULL, NULL, NULL,	/* sign_* */
	NULL, NULL, NULL,	/* verif_* */
	NULL, NULL,		/* decrypt_* */
	NULL,			/* derive */
	NULL,			/* wrap */
	NULL,			/* unwrap */
	NULL,			/* mech_data */
	NULL,			/* free_mech_data */
};

static sc_pkcs11_mechanism_type_t openssl_sha224_mech = {
	CKM_SHA224,
	{ 0, 0, CKF_DIGEST },
	0,
	sizeof(struct sc_pkcs11_operation),
	sc_pkcs11_openssl_md_release,
	sc_pkcs11_openssl_md_init,
	sc_pkcs11_openssl_md_update,
	sc_pkcs11_openssl_md_final,
	NULL, NULL, NULL, NULL,	/* sign_* */
	NULL, NULL, NULL,	/* verif_* */
	NULL, NULL,		/* decrypt_* */
	NULL,			/* derive */
	NULL,			/* wrap */
	NULL,			/* unwrap */
	NULL,			/* mech_data */
	NULL,			/* free_mech_data */
};

static sc_pkcs11_mechanism_type_t openssl_sha256_mech = {
	CKM_SHA256,
	{ 0, 0, CKF_DIGEST },
	0,
	sizeof(struct sc_pkcs11_operation),
	sc_pkcs11_openssl_md_release,
	sc_pkcs11_openssl_md_init,
	sc_pkcs11_openssl_md_update,
	sc_pkcs11_openssl_md_final,
	NULL, NULL, NULL, NULL,	/* sign_* */
	NULL, NULL, NULL,	/* verif_* */
	NULL, NULL,		/* decrypt_* */
	NULL,			/* derive */
	NULL,			/* wrap */
	NULL,			/* unwrap */
	NULL,			/* mech_data */
	NULL,			/* free_mech_data */
};

static sc_pkcs11_mechanism_type_t openssl_sha384_mech = {
	CKM_SHA384,
	{ 0, 0, CKF_DIGEST },
	0,
	sizeof(struct sc_pkcs11_operation),
	sc_pkcs11_openssl_md_release,
	sc_pkcs11_openssl_md_init,
	sc_pkcs11_openssl_md_update,
	sc_pkcs11_openssl_md_final,
	NULL, NULL, NULL, NULL,	/* sign_* */
	NULL, NULL, NULL,	/* verif_* */
	NULL, NULL,		/* decrypt_* */
	NULL,			/* derive */
	NULL,			/* wrap */
	NULL,			/* unwrap */
	NULL,			/* mech_data */
	NULL,			/* free_mech_data */
};

static sc_pkcs11_mechanism_type_t openssl_sha512_mech = {
	CKM_SHA512,
	{ 0, 0, CKF_DIGEST },
	0,
	sizeof(struct sc_pkcs11_operation),
	sc_pkcs11_openssl_md_release,
	sc_pkcs11_openssl_md_init,
	sc_pkcs11_openssl_md_update,
	sc_pkcs11_openssl_md_final,
	NULL, NULL, NULL, NULL,	/* sign_* */
	NULL, NULL, NULL,	/* verif_* */
	NULL, NULL,		/* decrypt_* */
	NULL,			/* derive */
	NULL,			/* wrap */
	NULL,			/* unwrap */
	NULL,			/* mech_data */
	NULL,			/* free_mech_data */
};

#if OPENSSL_VERSION_NUMBER >= 0x10000000L
static sc_pkcs11_mechanism_type_t openssl_gostr3411_mech = {
	CKM_GOSTR3411,
	{ 0, 0, CKF_DIGEST },
	0,
	sizeof(struct sc_pkcs11_operation),
	sc_pkcs11_openssl_md_release,
	sc_pkcs11_openssl_md_init,
	sc_pkcs11_openssl_md_update,
	sc_pkcs11_openssl_md_final,
	NULL, NULL, NULL, NULL,	/* sign_* */
	NULL, NULL, NULL,	/* verif_* */
	NULL, NULL,		/* decrypt_* */
	NULL,			/* derive */
	NULL,			/* wrap */
	NULL,			/* unwrap */
	NULL,			/* mech_data */
	NULL,			/* free_mech_data */
};
#endif

static sc_pkcs11_mechanism_type_t openssl_md5_mech = {
	CKM_MD5,
	{ 0, 0, CKF_DIGEST },
	0,
	sizeof(struct sc_pkcs11_operation),
	sc_pkcs11_openssl_md_release,
	sc_pkcs11_openssl_md_init,
	sc_pkcs11_openssl_md_update,
	sc_pkcs11_openssl_md_final,
	NULL, NULL, NULL, NULL,	/* sign_* */
	NULL, NULL, NULL,	/* verif_* */
	NULL, NULL,		/* decrypt_* */
	NULL,			/* derive */
	NULL,			/* wrap */
	NULL,			/* unwrap */
	NULL,			/* mech_data */
	NULL,			/* free_mech_data */
};

static sc_pkcs11_mechanism_type_t openssl_ripemd160_mech = {
	CKM_RIPEMD160,
	{ 0, 0, CKF_DIGEST },
	0,
	sizeof(struct sc_pkcs11_operation),
	sc_pkcs11_openssl_md_release,
	sc_pkcs11_openssl_md_init,
	sc_pkcs11_openssl_md_update,
	sc_pkcs11_openssl_md_final,
	NULL, NULL, NULL, NULL,	/* sign_* */
	NULL, NULL, NULL,	/* verif_* */
	NULL, NULL,		/* decrypt_* */
	NULL,			/* derive */
	NULL,			/* wrap */
	NULL,			/* unwrap */
	NULL,			/* mech_data */
	NULL,			/* free_mech_data */
};

static void * dup_mem(void *in, size_t in_len)
{
	void *out = malloc(in_len);
	if (out)
		memcpy(out, in, in_len);
	return out;
}

void
sc_pkcs11_register_openssl_mechanisms(struct sc_pkcs11_card *p11card)
{
#if OPENSSL_VERSION_NUMBER >= 0x10000000L && !defined(OPENSSL_NO_ENGINE)
	ENGINE *e;
/* crypto locking removed in 1.1 */
#if OPENSSL_VERSION_NUMBER  < 0x10100000L
	void (*locking_cb)(int, int, const char *, int);

	locking_cb = CRYPTO_get_locking_callback();
	if (locking_cb)
		CRYPTO_set_locking_callback(NULL);
#endif

	e = ENGINE_by_id("gost");
	if (!e)
	{
#if !defined(OPENSSL_NO_STATIC_ENGINE) && !defined(OPENSSL_NO_GOST) && !defined(LIBRESSL_VERSION_NUMBER)

/* ENGINE_load_gost removed in 1.1 */
#if OPENSSL_VERSION_NUMBER  < 0x10100000L
		ENGINE_load_gost();
#endif
		e = ENGINE_by_id("gost");
#else
		/* try to load dynamic gost engine */
		e = ENGINE_by_id("dynamic");
		if (!e) {
			ENGINE_load_dynamic();
			e = ENGINE_by_id("dynamic");
		}
		if (e && (!ENGINE_ctrl_cmd_string(e, "SO_PATH", "gost", 0) ||
					!ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))) {
			ENGINE_free(e);
			e = NULL;
		}
#endif /* !OPENSSL_NO_STATIC_ENGINE && !OPENSSL_NO_GOST */
	}
	if (e) {
		ENGINE_set_default(e, ENGINE_METHOD_ALL);
		ENGINE_free(e);
	}

#if OPENSSL_VERSION_NUMBER < 0x10100000L
	if (locking_cb)
		CRYPTO_set_locking_callback(locking_cb);
#endif
#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L && !defined(OPENSSL_NO_ENGINE) */

	openssl_sha1_mech.mech_data = EVP_sha1();
	sc_pkcs11_register_mechanism(p11card, dup_mem(&openssl_sha1_mech, sizeof openssl_sha1_mech));
	openssl_sha224_mech.mech_data = EVP_sha224();
	sc_pkcs11_register_mechanism(p11card, dup_mem(&openssl_sha224_mech, sizeof openssl_sha224_mech));
	openssl_sha256_mech.mech_data = EVP_sha256();
	sc_pkcs11_register_mechanism(p11card, dup_mem(&openssl_sha256_mech, sizeof openssl_sha256_mech));
	openssl_sha384_mech.mech_data = EVP_sha384();
	sc_pkcs11_register_mechanism(p11card, dup_mem(&openssl_sha384_mech, sizeof openssl_sha384_mech));
	openssl_sha512_mech.mech_data = EVP_sha512();
	sc_pkcs11_register_mechanism(p11card, dup_mem(&openssl_sha512_mech, sizeof openssl_sha512_mech));
	openssl_md5_mech.mech_data = EVP_md5();
	sc_pkcs11_register_mechanism(p11card, dup_mem(&openssl_md5_mech, sizeof openssl_md5_mech));
	openssl_ripemd160_mech.mech_data = EVP_ripemd160();
	sc_pkcs11_register_mechanism(p11card, dup_mem(&openssl_ripemd160_mech, sizeof openssl_ripemd160_mech));
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
	openssl_gostr3411_mech.mech_data = EVP_get_digestbynid(NID_id_GostR3411_94);
	sc_pkcs11_register_mechanism(p11card, dup_mem(&openssl_gostr3411_mech, sizeof openssl_gostr3411_mech));
#endif
}


/*
 * Handle OpenSSL digest functions
 */
#define DIGEST_CTX(op) \
	(op ? (EVP_MD_CTX *) (op)->priv_data : NULL)

static CK_RV sc_pkcs11_openssl_md_init(sc_pkcs11_operation_t *op)
{
	sc_pkcs11_mechanism_type_t *mt;
	EVP_MD_CTX	*md_ctx;
	EVP_MD		*md;

	if (!op || !(mt = op->type) || !(md = (EVP_MD *) mt->mech_data))
		return CKR_ARGUMENTS_BAD;

	if (!(md_ctx = EVP_MD_CTX_create()))
		return CKR_HOST_MEMORY;
	if (!EVP_DigestInit(md_ctx, md)) {
		EVP_MD_CTX_destroy(md_ctx);
		return CKR_GENERAL_ERROR;
	}
	op->priv_data = md_ctx;
	return CKR_OK;
}

static CK_RV sc_pkcs11_openssl_md_update(sc_pkcs11_operation_t *op,
				CK_BYTE_PTR pData, CK_ULONG pDataLen)
{
	EVP_MD_CTX *md_ctx = DIGEST_CTX(op);
	if (!md_ctx)
		return CKR_ARGUMENTS_BAD;
	if (!EVP_DigestUpdate(md_ctx, pData, pDataLen))
		return CKR_GENERAL_ERROR;
	return CKR_OK;
}

static CK_RV sc_pkcs11_openssl_md_final(sc_pkcs11_operation_t *op,
				CK_BYTE_PTR pDigest, CK_ULONG_PTR pulDigestLen)
{
	EVP_MD_CTX *md_ctx = DIGEST_CTX(op);

	if (!md_ctx)
		return CKR_ARGUMENTS_BAD;
	if (*pulDigestLen < (unsigned) EVP_MD_CTX_size(md_ctx)) {
		sc_log(context, "Provided buffer too small: %lu < %d",
		       *pulDigestLen, EVP_MD_CTX_size(md_ctx));
		*pulDigestLen = EVP_MD_CTX_size(md_ctx);
		return CKR_BUFFER_TOO_SMALL;
	}
	if (!EVP_DigestFinal(md_ctx, pDigest, (unsigned *) pulDigestLen))
		return CKR_GENERAL_ERROR;

	return CKR_OK;
}

static void sc_pkcs11_openssl_md_release(sc_pkcs11_operation_t *op)
{
	if (op) {
		EVP_MD_CTX	*md_ctx = DIGEST_CTX(op);
		if (md_ctx)
			EVP_MD_CTX_destroy(md_ctx);
		op->priv_data = NULL;
	}
}

#if OPENSSL_VERSION_NUMBER >= 0x10000000L && !defined(OPENSSL_NO_EC)

static void reverse(unsigned char *buf, size_t len)
{
	unsigned char tmp;
	size_t i;

	for (i = 0; i < len / 2; ++i) {
		tmp = buf[i];
		buf[i] = buf[len - 1 - i];
		buf[len - 1 - i] = tmp;
	}
}

static CK_RV gostr3410_verify_data(const unsigned char *pubkey, unsigned int pubkey_len,
		const unsigned char *params, unsigned int params_len,
		unsigned char *data, unsigned int data_len,
		unsigned char *signat, unsigned int signat_len)
{
	EVP_PKEY *pkey;
	EVP_PKEY_CTX *pkey_ctx = NULL;
	EC_POINT *P;
	BIGNUM *X, *Y;
	ASN1_OCTET_STRING *octet = NULL;
	const EC_GROUP *group = NULL;
	char paramset[2] = "A";
	int r = -1, ret_vrf = 0;

	pkey = EVP_PKEY_new();
	if (!pkey)
		return CKR_HOST_MEMORY;
	r = EVP_PKEY_set_type(pkey, NID_id_GostR3410_2001);
	if (r == 1) {
		pkey_ctx = EVP_PKEY_CTX_new(pkey, NULL);
		if (!pkey_ctx) {
			EVP_PKEY_free(pkey);
			return CKR_HOST_MEMORY;
		}
		/* FIXME: fully check params[] */
		if (params_len > 0 && params[params_len - 1] >= 1 &&
				params[params_len - 1] <= 3) {
			paramset[0] += params[params_len - 1] - 1;
			r = EVP_PKEY_CTX_ctrl_str(pkey_ctx, "paramset", paramset);
		}
		else
			r = -1;
		if (r == 1)
			r = EVP_PKEY_paramgen_init(pkey_ctx);
		if (r == 1)
			r = EVP_PKEY_paramgen(pkey_ctx, &pkey);
		if (r == 1 && EVP_PKEY_get0(pkey) != NULL)
			group = EC_KEY_get0_group(EVP_PKEY_get0(pkey));
		r = -1;
		if (group)
			octet = d2i_ASN1_OCTET_STRING(NULL, &pubkey, (long)pubkey_len);
		if (group && octet) {
			reverse(octet->data, octet->length);
			Y = BN_bin2bn(octet->data, octet->length / 2, NULL);
			X = BN_bin2bn((const unsigned char*)octet->data +
					octet->length / 2, octet->length / 2, NULL);
			ASN1_OCTET_STRING_free(octet);
			P = EC_POINT_new(group);
			if (P && X && Y)
						r = EC_POINT_set_affine_coordinates_GFp(group,
						P, X, Y, NULL);
			BN_free(X);
			BN_free(Y);
			if (r == 1 && EVP_PKEY_get0(pkey) && P)
				r = EC_KEY_set_public_key(EVP_PKEY_get0(pkey), P);
			EC_POINT_free(P);
		}
		if (r == 1) {
			r = EVP_PKEY_verify_init(pkey_ctx);
			reverse(data, data_len);
			if (r == 1)
				ret_vrf = EVP_PKEY_verify(pkey_ctx, signat, signat_len,
						data, data_len);
		}
	}
	EVP_PKEY_CTX_free(pkey_ctx);
	EVP_PKEY_free(pkey);
	if (r != 1)
		return CKR_GENERAL_ERROR;
	return ret_vrf == 1 ? CKR_OK : CKR_SIGNATURE_INVALID;
}
#endif /* OPENSSL_VERSION_NUMBER >= 0x10000000L && !defined(OPENSSL_NO_EC) */

/* If no hash function was used, finish with RSA_public_decrypt().
 * If a hash function was used, we can make a big shortcut by
 *   finishing with EVP_VerifyFinal().
 */
CK_RV sc_pkcs11_verify_data(const unsigned char *pubkey, unsigned int pubkey_len,
			const unsigned char *pubkey_params, unsigned int pubkey_params_len,
			CK_MECHANISM_PTR mech, sc_pkcs11_operation_t *md,
			unsigned char *data, unsigned int data_len,
			unsigned char *signat, unsigned int signat_len)
{
	int res;
	CK_RV rv = CKR_GENERAL_ERROR;
	EVP_PKEY *pkey = NULL;
	const unsigned char *pubkey_tmp = NULL;

	if (mech->mechanism == CKM_GOSTR3410)
	{
#if OPENSSL_VERSION_NUMBER >= 0x10000000L && !defined(OPENSSL_NO_EC)
		return gostr3410_verify_data(pubkey, pubkey_len,
				pubkey_params, pubkey_params_len,
				data, data_len, signat, signat_len);
#else
		(void)pubkey_params, (void)pubkey_params_len; /* no warning */
		return CKR_FUNCTION_NOT_SUPPORTED;
#endif
	}

	/*
	 * PKCS#11 does not define CKA_VALUE for public keys, and different cards
	 * return either the raw or spki versions as defined in PKCS#15
	 * And we need to support more then just RSA.
	 * We can use d2i_PUBKEY which works for SPKI and any key type. 
	 */
	pubkey_tmp = pubkey; /* pass in so pubkey pointer is not modified */

	pkey = d2i_PUBKEY(NULL, &pubkey_tmp, pubkey_len);
	if (pkey == NULL)
		return CKR_GENERAL_ERROR;

	if (md != NULL && (mech->mechanism == CKM_SHA1_RSA_PKCS
		|| mech->mechanism == CKM_MD5_RSA_PKCS
		|| mech->mechanism == CKM_RIPEMD160_RSA_PKCS
		|| mech->mechanism == CKM_SHA224_RSA_PKCS
		|| mech->mechanism == CKM_SHA256_RSA_PKCS
		|| mech->mechanism == CKM_SHA384_RSA_PKCS
		|| mech->mechanism == CKM_SHA512_RSA_PKCS)) {
		EVP_MD_CTX *md_ctx = DIGEST_CTX(md);

		/* This does not really use the data argument, but the data
		 * are already collected in the md_ctx
		 */
		sc_log(context, "Trying to verify using EVP");
		if (md_ctx) {
			res = EVP_VerifyFinal(md_ctx, signat, signat_len, pkey);
		} else {
			res = -1;
		}
		EVP_PKEY_free(pkey);
		if (res == 1)
			return CKR_OK;
		else if (res == 0) {
			sc_log(context, "EVP_VerifyFinal(): Signature invalid");
			return CKR_SIGNATURE_INVALID;
		} else {
			sc_log(context, "EVP_VerifyFinal() returned %d\n", res);
			return CKR_GENERAL_ERROR;
		}
	} else {
		RSA *rsa;
		unsigned char *rsa_out = NULL, pad;
		int rsa_outlen = 0;

		sc_log(context, "Trying to verify using low-level API");
		switch (mech->mechanism) {
		case CKM_RSA_PKCS:
		case CKM_MD5_RSA_PKCS:
		case CKM_RIPEMD160_RSA_PKCS:
		 	pad = RSA_PKCS1_PADDING;
		 	break;
		case CKM_RSA_X_509:
			pad = RSA_NO_PADDING;
			break;
		case CKM_RSA_PKCS_PSS:
		case CKM_SHA1_RSA_PKCS_PSS:
		case CKM_SHA224_RSA_PKCS_PSS:
		case CKM_SHA256_RSA_PKCS_PSS:
		case CKM_SHA384_RSA_PKCS_PSS:
		case CKM_SHA512_RSA_PKCS_PSS:
			pad = RSA_NO_PADDING;
			break;
		default:
			EVP_PKEY_free(pkey);
			return CKR_ARGUMENTS_BAD;
		}

		rsa = EVP_PKEY_get1_RSA(pkey);
		EVP_PKEY_free(pkey);
		if (rsa == NULL)
			return CKR_DEVICE_MEMORY;

		rsa_out = calloc(1, RSA_size(rsa));
		if (rsa_out == NULL) {
			RSA_free(rsa);
			return CKR_DEVICE_MEMORY;
		}

		rsa_outlen = RSA_public_decrypt(signat_len, signat, rsa_out, rsa, pad);
		if (rsa_outlen <= 0) {
			RSA_free(rsa);
			free(rsa_out);
			sc_log(context, "RSA_public_decrypt() returned %d\n", rsa_outlen);
			return CKR_GENERAL_ERROR;
		}

		/* For PSS mechanisms we can not simply compare the "decrypted"
		 * data -- we need to verify the PSS padding is valid
		 */
		if (mech->mechanism == CKM_RSA_PKCS_PSS ||
		    mech->mechanism == CKM_SHA1_RSA_PKCS_PSS ||
		    mech->mechanism == CKM_SHA224_RSA_PKCS_PSS ||
		    mech->mechanism == CKM_SHA256_RSA_PKCS_PSS ||
		    mech->mechanism == CKM_SHA384_RSA_PKCS_PSS ||
		    mech->mechanism == CKM_SHA512_RSA_PKCS_PSS) {
			CK_RSA_PKCS_PSS_PARAMS* param = NULL;
			const EVP_MD *mgf_md, *pss_md;
			unsigned char digest[EVP_MAX_MD_SIZE];

			if (mech->pParameter == NULL) {
				RSA_free(rsa);
				free(rsa_out);
				sc_log(context, "PSS mechanism requires parameter");
				return CKR_MECHANISM_PARAM_INVALID;
			}

			param = (CK_RSA_PKCS_PSS_PARAMS*)mech->pParameter;
			switch (param->mgf) {
			case CKG_MGF1_SHA1:
				mgf_md = EVP_sha1();
				break;
			case CKG_MGF1_SHA224:
				mgf_md = EVP_sha224();
				break;
			case CKG_MGF1_SHA256:
				mgf_md = EVP_sha256();
				break;
			case CKG_MGF1_SHA384:
				mgf_md = EVP_sha384();
				break;
			case CKG_MGF1_SHA512:
				mgf_md = EVP_sha512();
				break;
			default:
				RSA_free(rsa);
				free(rsa_out);
				return CKR_MECHANISM_PARAM_INVALID;
			}

			switch (param->hashAlg) {
			case CKM_SHA_1:
				pss_md = EVP_sha1();
				break;
			case CKM_SHA224:
				pss_md = EVP_sha224();
				break;
			case CKM_SHA256:
				pss_md = EVP_sha256();
				break;
			case CKM_SHA384:
				pss_md = EVP_sha384();
				break;
			case CKM_SHA512:
				pss_md = EVP_sha512();
				break;
			default:
				RSA_free(rsa);
				free(rsa_out);
				return CKR_MECHANISM_PARAM_INVALID;
			}

			/* for the mechanisms with hash algorithm, the data
			 * is already added to the hash buffer, so we need
			 * to finish the hash operation here
			 */
			if (mech->mechanism != CKM_RSA_PKCS_PSS) {
				EVP_MD_CTX *md_ctx = DIGEST_CTX(md);
				unsigned char *tmp = digest;
				unsigned int tmp_len;

				if (!md_ctx || !EVP_DigestFinal(md_ctx, tmp, &tmp_len)) {
					RSA_free(rsa);
					free(rsa_out);
					return CKR_GENERAL_ERROR;
				}
				data = tmp;
				data_len = tmp_len;
			}
			rv = CKR_SIGNATURE_INVALID;
			if (data_len == (unsigned int) EVP_MD_size(pss_md)
					&& RSA_verify_PKCS1_PSS_mgf1(rsa, data, pss_md, mgf_md,
						rsa_out, EVP_MD_size(pss_md)/*sLen*/) == 1)
				rv = CKR_OK;
			RSA_free(rsa);
			free(rsa_out);
			sc_log(context, "Returning %lu", rv);
			return rv;
		}
		RSA_free(rsa);

		if ((unsigned int) rsa_outlen == data_len && memcmp(rsa_out, data, data_len) == 0)
			rv = CKR_OK;
		else
			rv = CKR_SIGNATURE_INVALID;

		free(rsa_out);
	}

	return rv;
}
#endif
