/*
 * The Cryptonit security software suite is developped by IDEALX
 * Cryptonit Team (http://IDEALX.org/ and http://cryptonit.org).
 *
 * Copyright 2003-2006 IDEALX
 *
 * 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
 * 02110-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 "pkcs12.hh"
#include "Utils.hh"
#include <openssl/err.h>

namespace Cryptonit{


    pkcs12::pkcs12(){
	pkey = NULL;
	cert = NULL;
	CA = NULL;
	mP12 = NULL;
    }
    
    
    
    
    
    pkcs12::pkcs12(const pkcs12 &p){
	pkey = NULL;
	cert = NULL;
	CA = NULL;
	mP12 = NULL;
	ERR_load_crypto_strings();

 	if( p.pkey != NULL)
	    pkey = new pkcs8( *(p.pkey) );
	
	if(p.cert != NULL)
	    cert = new Certificate(  *(p.cert) );
	
	if( p.CA != NULL )
	    CA = new std::vector<Certificate>( *(p.CA) );
	
	/* PKCS12 dup */
	if( p.mP12 != NULL ){
	    BIO *bout = BIO_new(BIO_s_mem());
	    i2d_PKCS12_bio( bout , p.mP12 );
	    if( (mP12 = d2i_PKCS12_bio(bout, NULL)) == NULL )
		ERR_print_errors_fp( stdout );
	}

	ERR_free_strings();
    }
    
    pkcs12::~pkcs12(){ 
	free();    
    }
    
    
    void pkcs12::free(){
	if(cert)
	    delete cert;
	if(pkey)
	    delete pkey;
	if(CA != NULL ){
	    CA->clear(); //free the CA vector
	    delete CA;
	}
	if( mP12 != NULL ){
	    PKCS12_free( mP12 );
	    mP12 = NULL;
	}
    }
    
    
  int pkcs12::load(const char *filename, const char *password){
      FILE *fin;
      PKCS12 *p12=NULL;
      X509 *cer=NULL;
      EVP_PKEY *key=NULL;
      STACK_OF(X509) *ca=NULL;
      
    if( (fin=fopen(filename,"r"))==NULL)
	return PKCS12_FILE_NOT_FOUND;
    
    if(p12) free(); //penser à faire du menage
    if ( (p12 = d2i_PKCS12_fp(fin, NULL))==NULL){
	fclose( fin );
	return PKCS12_LOAD_ERROR;
    }
    
    if(! PKCS12_parse_fix(p12, password, &key, &cer ,&ca)){
	fclose( fin );
	return PKCS12_PARSE_ERROR;
    }
    
    fclose( fin );
    if(key){
      delete pkey;
      pkey = new pkcs8(key);
    }
    
    fillCertAndCA( key , cer , ca );
/*
    if(cer){
	cert = new Certificate();
	cert->setX509Certificate(cer);
    }
    
    CA = new std::vector<Certificate>;
    for(int i = 0; i < sk_X509_num(ca); i++){
	Certificate cacert ;
	cacert.setX509Certificate(sk_X509_value(ca,i));
	CA->push_back(cacert);
      
    }*/

    sk_X509_pop_free(ca, X509_free);


    PKCS12_free(p12);
    return SUCCESS;
  }

    int pkcs12::load(const char *filename){
	FILE *fin;

	if( (fin=fopen(filename,"r"))==NULL)
	    return PKCS12_FILE_NOT_FOUND;
	
	if(mP12) free(); //penser à faire du menage
	if ( (mP12 = d2i_PKCS12_fp(fin, NULL))==NULL) {
	    fclose( fin );
	    return PKCS12_LOAD_ERROR;

	}
	fclose( fin );
	return SUCCESS;
    }




  int pkcs12::load(BIO *bio, const char *password){
    PKCS12 *p12=NULL;
    X509 *cer=NULL;
    EVP_PKEY *key=NULL;
    STACK_OF(X509) *ca=NULL;
    Certificate *cacert=NULL;

    if(bio)
	  {
	    free();
	    p12 = d2i_PKCS12_bio(bio, NULL);
	    if(!p12)
	      return PKCS12_BIO_READ_ERROR;
	    if(!PKCS12_parse_fix(p12, password, &key, &cer, &ca))
	      return PKCS12_PARSE_ERROR
		;
	    if(key!=NULL)
	      pkey->setKey(key);
	  /*  if(cer!=NULL)
	      cert->setX509Certificate(cer);
	    if(ca!=NULL)
	      {
		for(int i = 0; i < sk_X509_num(ca); i++){
		  cacert = new Certificate();
		  cacert->setX509Certificate(sk_X509_value(ca,i));
	  CA->push_back(*cacert);
		}
	      }*/
	    
	    fillCertAndCA( key , cer , ca );

	    if( ca )
		sk_X509_pop_free(ca, X509_free);

	    PKCS12_free(p12);
	    return SUCCESS;
	  }
    else
      return PKCS12_LOAD_BIO_ERROR;
  }
  


    


    Certificate  const  &pkcs12::getCertificate()  { return *cert; }

    pkcs8  &pkcs12::getKey() const { return *pkey; }
    

    bool pkcs12::parse( const std::string password ){
	return parse( password.c_str() );
    }

    bool pkcs12::parse(const char* password)  {
	X509 *cer=NULL;
	EVP_PKEY *key=NULL;
	STACK_OF(X509) *ca=NULL;
	bool mismatch=false; //test if key matches the first certificat in the PKCS12
	
	
	if(!PKCS12_parse_fix(mP12, password, &key, &cer ,&ca)){
	    return false;
	}
	
	if(key){
	    delete pkey;
	    pkey = new pkcs8(key);
	}
	
	fillCertAndCA( key , cer , ca );
/*
	mismatch = (X509_check_private_key( cer, key ) == 0);
	CA = new std::vector<Certificate>;
	
	if(cer && !mismatch ){
	    cert = new Certificate();
	    cert->setX509Certificate(cer);

	} else if( cer && mismatch ){
	    Certificate cacert;
	    cacert.setX509Certificate( cer );
	    CA->push_back( cacert );
	}
	
        if(ca){
	    CA = new std::vector<Certificate>;
	    for(int i = 0; i < sk_X509_num(ca); i++){
		Certificate cacert;
		X509 *c = sk_X509_value(ca,i);
		if( c ){
		    if(mismatch && X509_check_private_key( c, key )){
			cert = new Certificate();
			cert->setX509Certificate(c);
		    } else {
			
			cacert.setX509Certificate( c );
			CA->push_back(cacert);
		    }
		}
		
	    }
	    sk_X509_pop_free(ca, X509_free);
	}
	
*/
	if( mP12 != NULL ) {
	    PKCS12_free( mP12 );
	    mP12 = NULL;
	}
	if( cer != NULL ) X509_free( cer );
	if( ca != NULL ) sk_X509_pop_free(ca, X509_free);

	//if( key != NULL ) EVP_PKEY_free( key ); //don't free it because we call pkcs8( EVP_PKEY*p)
	


	return true;
	
  
    }
  

    void pkcs12::fillCertAndCA(EVP_PKEY *key , X509 *cer , STACK_OF(X509) *ca){
	if(cer ){
	    cert = new Certificate();
	    cert->setX509Certificate(cer);
	} 
	
        if(ca){
	    if(CA) delete CA;
	    CA = new std::vector<Certificate>;
	    for(int i = 0; i < sk_X509_num(ca); i++){
		Certificate cacert;
		X509 *c = sk_X509_value(ca,i);
		if( c ){
		    cacert.setX509Certificate( c );
		    CA->push_back(cacert);
		}
	    }
	    
	}
    }


  std::vector<Certificate>& pkcs12::getCA() const { return *CA; }



    int pkcs12::create( pkcs8 privateKey, Certificate certificate, 
			char* password, char* name, int nid_key, 
			int nid_cert, int iter, int mac_iter, int keytype) 
    {

	STACK_OF(X509) *ca = NULL;

	// TODO: add local CA certificates if the issuer match.

	if( cert != NULL )
	    delete cert;
	if( pkey != NULL )
	    delete pkey;

	mP12 = PKCS12_create( password, name, privateKey.getKey(), 
			      certificate.getX509Certificate(), ca,
			      nid_key, nid_cert, iter, mac_iter, keytype );

	if( mP12 != NULL ) {
	    cert = new Certificate( certificate );
	    pkey = new pkcs8( privateKey );
	    return 0;
	}
	else
	    return -1;

    }




    int pkcs12::save( const char* filename )
    {
	if( mP12 != NULL && filename != NULL ) {
	    FILE* fp;

	    if( (fp = fopen( filename, "wb" )) == NULL ) {
#ifdef DEBUG
 		std::cerr << "Cannot create PKCS12 file." << std::endl;
#endif 
		return -2;
	    }
	    else {
		int ret = save( fp );
		fclose( fp );
		return ret;
	    }
	}
	else
	    return -1;
    }


    int pkcs12::save( FILE* fp )
    {
	if( mP12 != NULL && fp != NULL ) {
	    return i2d_PKCS12_fp( fp, mP12 );
	}
	else
	    return -1;
    }

    
    int pkcs12::save( BIO* bp )
    {
	
	if( mP12 != NULL && bp != NULL ){
	    return i2d_PKCS12_bio( bp , mP12 );
	}
	else
	    return -1;
    }


    bool pkcs12::parsed()
    {
	return (cert != NULL && pkey != NULL );
    }
    
    

    std::string pkcs12::getHash(){
	if( cert != NULL )
	    return cert->getHash();
	else return "";
    }



    pkcs12& pkcs12::operator=( const pkcs12 &src ){
	pkey = NULL;
	cert = NULL;
	CA = NULL;
	mP12 = NULL;
	ERR_load_crypto_strings();
	
 	if( src.pkey != NULL)
	    pkey = new pkcs8( *(src.pkey) );
	
	if(src.cert != NULL)
	    cert = new Certificate(  *(src.cert) );
	
	if( src.CA != NULL )
	    CA = new std::vector<Certificate>( *(src.CA) );
	
	/* PKCS12 dup */
	if( src.mP12 != NULL ){
	    BIO *bout = BIO_new(BIO_s_mem());
	    i2d_PKCS12_bio( bout , src.mP12 );
	    if( (mP12 = d2i_PKCS12_bio(bout, NULL)) == NULL )
		ERR_print_errors_fp( stdout );
	}

	ERR_free_strings();	
	return *this;
    }
    

  
}
