/*
 * 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 <stdio.h>
#ifdef HAVE_OPENSSL
#include <openssl/x509.h>
#endif

#include "pkcs11_util.h"

CK_RV pkcs11_get_value(CK_FUNCTION_LIST_PTR funcs,
					   CK_SESSION_HANDLE    h_s,
					   CK_OBJECT_HANDLE     h_o,
					   CK_ATTRIBUTE_PTR     a)
{
  CK_RV rc = funcs->C_GetAttributeValue( h_s, h_o, a, 1);

  if(rc == CKR_OK) {
    a->pValue = (CK_VOID_PTR) malloc(a->ulValueLen);
    if(a->pValue) {
      rc = funcs->C_GetAttributeValue( h_s, h_o, a, 1);
	} else {
      rc = CKR_HOST_MEMORY;
    }
  }
  return rc;
}

CK_RV pkcs11_find_by_class(CK_FUNCTION_LIST_PTR funcs,
						   CK_SESSION_HANDLE    h_session,
						   CK_OBJECT_CLASS      _class,
						   CK_OBJECT_HANDLE_PTR *objects,
						   CK_ULONG_PTR         count)
{
  CK_RV                rc = CKR_OK;
  CK_ULONG             i, j, k, index = 0, size = 4;
  CK_OBJECT_HANDLE_PTR buffer = (CK_OBJECT_HANDLE_PTR)
    malloc( sizeof(CK_OBJECT_HANDLE) * 4);
  CK_ATTRIBUTE         pTemplate = {
    CKA_CLASS, NULL, sizeof(CK_OBJECT_CLASS)
  };
  pTemplate.pValue = &_class;

  *count = 0;

  rc = funcs->C_FindObjectsInit( h_session, &pTemplate, 1 );
  if (rc != CKR_OK) {
    goto done;
  }
  
  do {
    i = size - index;
    if(i == 0) {
      CK_OBJECT_HANDLE_PTR tmp = buffer;
      size *= 2;
      buffer = (CK_OBJECT_HANDLE_PTR) malloc(sizeof(CK_OBJECT_HANDLE) * size);
      for(k = 0; k < index; k++) {
		buffer[k] = tmp[k];
      }
      free(tmp);
    }
    rc = funcs->C_FindObjects( h_session, buffer + index, i, &j );
    index += j;
    if (rc != CKR_OK) {
      goto done;
    }
  } while (i == j);

  rc = funcs->C_FindObjectsFinal( h_session );

  *count = index;
  *objects = buffer;

 done:
  return rc;
}

CK_RV pkcs11_find_matching(CK_FUNCTION_LIST_PTR funcs,
						   CK_SESSION_HANDLE    h_session,
						   CK_OBJECT_HANDLE     h_source,
						   CK_OBJECT_CLASS      _class,
						   CK_ATTRIBUTE_TYPE    type,
						   CK_OBJECT_HANDLE_PTR h_result,
						   CK_ULONG_PTR         found)
{
  CK_RV        rc        = CKR_OK;
  CK_ATTRIBUTE attribute = { 0, NULL, 0 };

  attribute.type = type;
  rc = pkcs11_get_value (funcs, h_session, h_source, &attribute);

  if ((rc == CKR_OK) && (attribute.ulValueLen > 0))  {
    CK_ATTRIBUTE pTemplate[] = {
      { CKA_CLASS, NULL, sizeof(CK_OBJECT_CLASS) },
      { 0,         NULL, 0                       }
    };
    pTemplate[0].pValue     = &_class;
    pTemplate[1].type       = type;
    pTemplate[1].pValue     = attribute.pValue;
    pTemplate[1].ulValueLen = attribute.ulValueLen;

    funcs->C_FindObjectsInit( h_session, pTemplate, 2 );
    funcs->C_FindObjects( h_session, h_result, 1, found );
    funcs->C_FindObjectsFinal( h_session );
  }
  free(attribute.pValue);
  return rc;
}

#ifdef HAVE_OPENSSL
CK_RV pkcs11_get_certificate(CK_FUNCTION_LIST_PTR funcs,
							 CK_SESSION_HANDLE    h_session,
							 CK_OBJECT_HANDLE     h_cert,
							 X509                 **x509)
{
  CK_RV        rc        = CKR_OK;
  CK_ATTRIBUTE attribute = { CKA_VALUE, NULL, 0 };

  rc = pkcs11_get_value (funcs, h_session, h_cert, &attribute);
  
  if(rc == CKR_OK) {
    BUF_MEM bm;
    BIO *b = BIO_new(BIO_s_mem());
    bm.length = attribute.ulValueLen;
    bm.data   = attribute.pValue;
    bm.max    = attribute.ulValueLen;
    BIO_set_mem_buf(b, &bm, 0);
    *x509 = d2i_X509_bio(b,NULL);
    BIO_free(b);
  }
  return rc;
}

CK_RV pkcs11_list_keys (CK_FUNCTION_LIST_PTR funcs,
						CK_SESSION_HANDLE    h_session,
						CK_OBJECT_HANDLE_PTR *h_keys,
						X509                 ***x509,
						CK_ULONG_PTR         count)
{
  CK_RV                rc = CKR_OK;
  CK_OBJECT_HANDLE_PTR h_certs;
  CK_ULONG             i, j;
  X509                 **certs;

  rc = pkcs11_find_by_class(funcs, h_session, CKO_PRIVATE_KEY, h_keys, count);

  if (rc == CKR_OK) {
    certs = (X509 **) malloc((*count) * sizeof(X509 *));
    h_certs = (CK_OBJECT_HANDLE_PTR) malloc((*count) * sizeof(CK_OBJECT_HANDLE));
    
    for(i = 0 ; i < *count; i++) {
      CK_ATTRIBUTE_TYPE types[3] = { CKA_ID, CKA_MODULUS, 0 };
      CK_ULONG          found    = 0;
      
      certs[i] = NULL;
      h_certs[i] = CK_INVALID_HANDLE;
      
      for(j = 0; (types[j]) && (!found); j++) {
		rc = pkcs11_find_matching(funcs, h_session, (*h_keys)[i], CKO_CERTIFICATE,
								  types[j], h_certs + i, &found);
	
		if ((rc == CKR_OK) && found) {
		  pkcs11_get_certificate(funcs, h_session, h_certs[i], certs + i);
		}
      }
    }
    *x509 = certs;
  }
  return rc;
}

CK_RV pkcs11_list_certificates(CK_FUNCTION_LIST_PTR funcs,
		CK_SESSION_HANDLE    h_session,
		CK_OBJECT_HANDLE_PTR *tab_hCerts,
		X509                 ***x509,
		CK_ULONG_PTR         count)
{
	CK_RV rv;
	CK_ULONG i;
	X509 **tabCerts = NULL_PTR;
	
	rv = pkcs11_find_by_class(funcs, h_session, CKO_CERTIFICATE, tab_hCerts, count);
	if(rv == CKR_OK)
	{
		if(*count > 0)
		{
			tabCerts = (X509 **)malloc((*count) * sizeof(X509 *));
			for(i = 0 ; i < *count ; i++)
				pkcs11_get_certificate(funcs, h_session, (*tab_hCerts)[i], &(tabCerts[i]));
		}
	}
	*x509 = tabCerts;
	return rv;
}

#endif
