/*
 * Copyright (C) 2003 Mathias Brossard <mathias.brossard@idealx.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *  
 * This program 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
 * General Public License for more details.
 *  
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301,
 * USA.
*
 * In addition, as two special exceptions:
 *
 * 1) IDEALX S.A.S gives permission to:
 *  * link the code of portions of his program with the OpenSSL library under
 *    certain conditions described in each source file
 *  * distribute linked combinations including the two, with respect to the
 *    OpenSSL license and with the GPL
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception,
 * you may extend this exception to your version of the file(s), but you are
 * not obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version, in all files (this very one along with all
 * source files).

 * 2) IDEALX S.A.S acknowledges that portions of his sourcecode uses (by the
 * way of headers inclusion) some work published by 'RSA Security Inc.'. Those
 * portions are "derived from the RSA Security Inc. PKCS #11Cryptographic
 * Token Interface (Cryptoki)" as described in each individual source file.
 */
 
#include <string.h>
#include <stdio.h>

#include "pkcs11_pkey.h"

#include <openssl/rsa.h>


int pkcs11_priv_enc (int flen, const unsigned char *from,
		     unsigned char *to, RSA *rsa, int padding);

int pkcs11_priv_dec(int flen, const unsigned char *from,
			   unsigned char *to, RSA *rsa, int padding);

int pkcs11_finish (RSA *rsa);

static RSA_METHOD pkcs11_rsa_method = {
  "PKCS#11",
  NULL,
  NULL,
  &pkcs11_priv_enc,
  &pkcs11_priv_dec,
  NULL,
  NULL,
  NULL,
  &pkcs11_finish,
  RSA_METHOD_FLAG_NO_CHECK | RSA_FLAG_EXT_PKEY, // see BUGS section in RSA_set_method (3) manpage
  NULL,
  NULL,
  NULL
};

#define USE_INDEX
#ifdef USE_INDEX
static int pkcs11_data_index = -1;
#endif

EVP_PKEY *pkcs11_get_pkey(CK_FUNCTION_LIST_PTR funcs,
			  CK_SESSION_HANDLE    h_session,
			  CK_OBJECT_HANDLE     h_key,
			  X509                 *x509)
{
  EVP_PKEY *ret = X509_get_pubkey(x509);
  const RSA_METHOD *def = RSA_PKCS1_SSLeay();
  pkcs11_key_data *data = (pkcs11_key_data *)
    malloc(sizeof(pkcs11_key_data));

  data->funcs     = funcs    ;
  data->h_session = h_session;
  data->h_key     = h_key    ;
  data->x509      = x509     ;

  pkcs11_rsa_method.rsa_pub_enc = def->rsa_pub_enc;
  pkcs11_rsa_method.rsa_pub_dec = def->rsa_pub_dec;
  pkcs11_rsa_method.init = def->init;

  ret->pkey.rsa->meth = &pkcs11_rsa_method;

#ifdef USE_INDEX
  if (pkcs11_data_index == -1) {
	pkcs11_data_index = RSA_get_ex_new_index(0, NULL, NULL, NULL, NULL);
  }
  RSA_set_ex_data(ret->pkey.rsa, pkcs11_data_index, (char*)data);
#else
  ret->pkey.rsa->bignum_data = (char*)data;
#endif

  return ret;
}

int pkcs11_priv_enc (int flen, const unsigned char *from,
		     unsigned char *to, RSA *rsa, int padding)
{
    CK_ULONG len = 0;
    CK_RV rv;
    CK_MECHANISM rsa_mech = { 0, NULL, 0 };
#ifdef USE_INDEX
    pkcs11_key_data *data = (pkcs11_key_data *)
	  RSA_get_ex_data(rsa, pkcs11_data_index);
#else
	pkcs11_key_data *data = (pkcs11_key_data *)
	  rsa->bignum_data;
#endif

    switch(padding) {
    case RSA_PKCS1_PADDING:
      rsa_mech.mechanism = CKM_RSA_PKCS;
      break;

    case RSA_PKCS1_OAEP_PADDING:
      rsa_mech.mechanism = CKM_RSA_PKCS_OAEP;
      break;
 
    case RSA_SSLV23_PADDING:
    case RSA_NO_PADDING:
      /* CKM_RSA_X_509 ? */
    default:
      return -1;
    }

    if (data->h_key != CK_INVALID_HANDLE)
    {
	rv = data->funcs->C_SignInit(data->h_session, &rsa_mech, data->h_key);
	if (rv != CKR_OK) {
	  return -1;
	}
	
	rv = data->funcs->C_Sign(data->h_session, (unsigned char *)from,
				   flen, NULL_PTR, &len);
	if (rv != CKR_OK) {
	    return -1;
	}
	
	rv = data->funcs->C_Sign(data->h_session, (unsigned char *)from,
				 flen, to, &len);
	if (rv != CKR_OK) {
	    return -1;
	}
    }
    
    return len;
}

int pkcs11_priv_dec(int flen, const unsigned char *from,
			   unsigned char *to, RSA *rsa, int padding)
{
    CK_ULONG len = 0;
    CK_RV rv;
    CK_MECHANISM rsa_mech = { 0, NULL, 0 };
#ifdef USE_INDEX
    pkcs11_key_data *data = (pkcs11_key_data *)
	  RSA_get_ex_data(rsa, pkcs11_data_index);
#else
	pkcs11_key_data *data = (pkcs11_key_data *)
	  rsa->bignum_data;
#endif
    switch(padding) {
    case RSA_PKCS1_PADDING:
      rsa_mech.mechanism = CKM_RSA_PKCS;
      break;

    case RSA_PKCS1_OAEP_PADDING:
      rsa_mech.mechanism = CKM_RSA_PKCS_OAEP;
      break;
 
    case RSA_SSLV23_PADDING:
    case RSA_NO_PADDING:
      /* CKM_RSA_X_509 ? */
    default:
      return -1;
    }

    
    if (data->h_key != CK_INVALID_HANDLE)
    {
	rv = data->funcs->C_DecryptInit(data->h_session, &rsa_mech, data->h_key);
	if (rv != CKR_OK) {
	    return -1;
	}
	
	rv = data->funcs->C_Decrypt(data->h_session, (unsigned char *)from,
				    flen,  NULL_PTR, &len);

	if (rv != CKR_OK) {
	    return -1;
	}
	
	rv = data->funcs->C_Decrypt(data->h_session, (unsigned char *)from,
				 flen, to, &len);
	if (rv != CKR_OK) {
	    return -1;
	}
    }

    return len;
}

int pkcs11_finish (RSA *rsa)
{
#ifdef USE_INDEX
  pkcs11_key_data *data = (pkcs11_key_data *)
	RSA_get_ex_data(rsa, pkcs11_data_index);
#else
  pkcs11_key_data *data = (pkcs11_key_data *)
	rsa->bignum_data;
#endif
  free(data);
  return 0;
}

