/*
 * 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 <unistd.h> //for unlink
#include "SoftKeyStore.hh"
#include "DirectoryService.hh"
#include "ConfigDatabase.hh"
#include "Utils.hh"
#include "errno.h"

namespace Cryptonit {
    SoftKeyStore::SoftKeyStore(){
	store.clear();
    }
    

    int SoftKeyStore::getType(){
	return SOFT_KEYSTORE;
    }
    
    SoftKeyStore::SoftKeyStore( User *u , std::string configFile )
    {
	user = u;
	
	ConfigDatabase* ds = new ConfigDatabase( configFile );
	
	std::string params[] = {"", ""};
	if( ! ds->read( params ) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot load configuration file '" 
		      <<  user->getConfigFile() << "'." <<std::endl;

#endif 
	    return; 
	}

	OpenSSL_add_all_algorithms();

	Entry* entry;
	entry = ds->getEntry( user->getLogin() );
	
	if( entry ){
	    std::vector<std::string> p12Hash = entry->getAttributeValues("PKCS12");
	    std::string p12Dir = user->getP12Dir();
	    std::vector<std::string>::iterator strIt;
	    
	    std::vector<std::string> certHash = entry->getAttributeValues( "X509_Certificate" );
	    std::string certDir = user->getCertificatesDir(); 
	    std::vector<std::string>::iterator certIt = certHash.begin();
	    
#ifdef DEBUG
 	    std::cout << "OpenSSL KeyStore loaded from '" 
		      <<  user->getConfigFile() << "'." << std::endl;
 	    std::cout << p12Hash.size() << " private key(s) found." << std::endl;
#endif 
	    
	    
	    for( strIt = p12Hash.begin() ; strIt != p12Hash.end() ; strIt++ , certIt++){
		pkcs12 *p12 = new pkcs12();
		Certificate *cert = new Certificate();
		p12->load( appendDir( user->getP12Dir() , strIt->c_str() ).c_str() );
		cert->load( appendDir( user->getCertificatesDir() , certIt->c_str() ).c_str() );
		std::pair< Certificate , pkcs12 > newPair (*cert, *p12) ;
		delete cert;
		delete p12; 
		store.push_back( newPair );
	    }
	    delete entry;
	}
	delete ds; 
    }



    SoftKeyStore::SoftKeyStore( User *u )
    {

	user = u;

	ConfigDatabase* ds = new ConfigDatabase( user->getConfigFile() );
	
	std::string params[] = {"", ""};
	if( ! ds->read( params ) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot load configuration file '" 
		      <<  user->getConfigFile() << "'." <<std::endl;
#endif 
	    return; 
	}
	OpenSSL_add_all_algorithms();

	Entry* entry;
	entry = ds->getEntry( user->getLogin() );
	
	if( entry ){
	    std::vector<std::string> p12Hash = entry->getAttributeValues("PKCS12");
	    std::string p12Dir = user->getP12Dir();
	    std::vector<std::string>::iterator strIt;
	    
	    std::vector<std::string> certHash = entry->getAttributeValues( "X509_Certificate" );
	    std::string certDir = user->getCertificatesDir(); 
	    std::vector<std::string>::iterator certIt = certHash.begin();
	    
#ifdef DEBUG
 	    std::cout << "OpenSSL KeyStore loaded from '" 
		      <<  user->getConfigFile() << "'." << std::endl;
 	    std::cout << p12Hash.size() << " private key(s) found." << std::endl;
#endif 
	    
	    
	    for( strIt = p12Hash.begin() ; strIt != p12Hash.end() ; strIt++ , certIt++){
		pkcs12 *p12 = new pkcs12();
		Certificate *cert = new Certificate();
		if ( p12->load( appendDir( user->getP12Dir() , strIt->c_str() ).c_str() ) == SUCCESS && 
		    cert->load( appendDir( user->getCertificatesDir() , certIt->c_str() ).c_str() ) == SUCCESS ){
		std::pair< Certificate , pkcs12 > newPair (*cert, *p12) ;
		store.push_back( newPair );
		}
		delete cert;
		delete p12; 
	    }
	    delete entry;
	}
	delete ds; 
    }
    
    SoftKeyStore::SoftKeyStore( const SoftKeyStore &ks){
#ifdef DEBUG
 	std::cout << __FUNCTION__ << std::endl;
#endif 
	store = ks.store;
    }
    
    SoftKeyStore::~SoftKeyStore()
    {
	store.clear();
    }

    std::vector< std::pair< Certificate , pkcs12 > > SoftKeyStore::getStore()
    {
	return std::vector< std::pair< Certificate , pkcs12 > >( store ) ;
    }
    
    int SoftKeyStore::addKey( pkcs12 &p , const std::string password ) 
    {
	if( password == "" &&  !p.parsed() ) {
	    return KEYSTORE_PASSWORD_NEEDED;
	    
	} else if( !p.parsed() ) {
	    if( !p.parse( password )) {
		return KEYSTORE_PARSE_FAILURE;
	    } else {
		addKey( p );
	    }
	} else {
	    //add key in Preferences.conf
	    std::string p12filename;
	    std::string certfilename;
	    
	    
	    Certificate tmpc(p.getCertificate());
	    
	    p12filename = setExt( tmpc.getHash() , KEY_DEFAULT_EXT );
	    certfilename= setExt( tmpc.getHash() , CERT_DEFAULT_EXT);

	    //save certificate on disk
	    if(tmpc.save( certfilename.c_str() )){
		//save the p12 on disk
		if(p.save( p12filename.c_str() )) {
		    //ajouter les entrées dans preferences.conf
		    std::pair<Certificate , pkcs12> newPair;
		    newPair.first = tmpc;
		    newPair.second = p;
		    store.push_back( newPair );


		    ConfigDatabase* ds = new ConfigDatabase( user->getConfigFile() );
			
		    std::string params[] = {"", ""};
		    if( ! ds->read( params ) ) {
#ifdef DEBUG
 			std::cerr << "Cannot load configuration file '"
				  << user->getConfigFile() << "'." <<std::endl;
#endif 
			return -2; //KEYSTORE_CONFIG_FILE_ERROR;
		    };
		    Entry* entry;
		    entry = ds->getEntry( user->getLogin() );
		    
		    std::vector<std::string> certHashV = entry->getAttributeValues( "X509_Certificate");
		    certHashV.push_back( certfilename );
		    if( ! entry->append( "X509_Certificate" , new Attribute(certHashV ) ) ){
			return -3 ; //KEYSTORE_APPEND_ERROR
		    }

		    std::vector<std::string> p12HashV = entry->getAttributeValues( "PKCS12");
		    p12HashV.push_back( p12filename );
		    if( ! entry->append( "PKCS12" , new Attribute(p12HashV ) ) ){
			return -3 ; //KEYSTORE_APPEND_ERROR
		    }

		    return SUCCESS;
		}
		return -1; //KEYSTORE_SAVE_KEY_ERROR
	    }
	    
	    return -1; //KEYSTORE_SAVE_CERT_ERROR;
	}
	return -1;
    }


	
    int SoftKeyStore::removeKey( const size_t index , std::string &ret, const std::string password)
    {
#ifdef DEBUG
 	std::cout << __FUNCTION__ << std::endl;
#endif 
	if( index < store.size() ) {
	    std::vector< std::pair < Certificate , pkcs12 > >::iterator it = store.begin();
	    unsigned int i = 0;
	    
	    while( it != store.end() && i != index ){
		it++;
		i++;
	    }
	    
 	    pkcs12 *p12=NULL ;
	    Certificate *cert=NULL;
	    
	    if( i == index ){
		std::string p12filename;
		std::string certfilename;
		p12 =  new pkcs12( it->second );

		
		cert = new Certificate( it->first );

		if( !cert ||  !p12 ){
		    if( cert )
			delete cert;
		    if( p12 ) 
			delete p12;
		    return -4;
		}
		
		char *passwd=NULL;
		while( !p12->parsed()){
		    if(!pm->getPassword("" , cert->getHash(), &passwd, 0, cert)){
			return -7;
		    } else {
			if(!p12->parse( passwd )){
#ifdef DEBUG
 			    std::cout << "WRONG PASSWORD" << std::endl;
#endif 
			}
		    }
		}
		if(passwd)
		    free(passwd);

		// remove object 
		ret = "OK";
		certfilename = setExt(cert->getHash() , CERT_DEFAULT_EXT );
		certfilename = appendDir( user->getCertificatesDir() , certfilename );
		p12filename = setExt( p12->getHash() , KEY_DEFAULT_EXT );
		p12filename = appendDir( user->getP12Dir() , p12filename );
		
		std::vector<std::string> certInfos;
		std::vector<std::string> p12Infos;
		std::vector<std::string> identities;
		    
		certInfos = user->getInfos( "X509_Certificate" );
		p12Infos = user->getInfos( "PKCS12" );
		identities = user->getInfos( "Identities" );
		    
		p12Infos.erase( find( p12Infos.begin(), 
				      p12Infos.end(), 
				      std::string(cert->getHash() + ".p12")) );
		user->setInfos( "PKCS12", p12Infos );
		    
		identities.erase( find( identities.begin(), 
					identities.end(), 
					identities[index]) );
		user->setInfos( "Identities", identities );
		    
		    
		certInfos.erase(  find( certInfos.begin(), 
				       certInfos.end(), 
				       std::string(cert->getHash() + ".der")) );
		user->setInfos( "X509_Certificate", certInfos );
		    
		if( unlink( certfilename.c_str() ) == -1 ) {
		    ret = certfilename;
		    ret +="\n";
		    ret +=strerror(errno);
#ifdef DEBUG
 		    std::cout << ret << std::endl;
#endif 
		    return -1;//KEYSTORE_UNLINK_ERROR;
		}
		    
		if( unlink( p12filename.c_str() ) == -1 ) {
		    ret = p12filename;
		    ret +="\n";
		    ret +=strerror(errno);
#ifdef DEBUG
 		    std::cout << ret << std::endl;
#endif 
		    return -2;//KEYSTORE_UNLINK_ERROR;
		}
		
		
		delete cert;
		delete p12;
		store.erase( it );
		    
		return SUCCESS;
	    }
	}
	return -3;
    }

    std::vector<Certificate> SoftKeyStore::listCertificates(){
	std::vector<Certificate> ret;
	
	std::vector< std::pair< Certificate , pkcs12 > >::iterator storeIt;
	
	
	for(storeIt = store.begin() ; storeIt != store.end() ; storeIt++ ){
	    Certificate c( storeIt->first );
	    ret.push_back( c );
	}
	
	return ret;
    }
    
    int SoftKeyStore::getKey(Certificate c , Key **k){
	std::vector< std::pair< Certificate , pkcs12 > >::iterator storeIt;

	for( storeIt = store.begin() ; storeIt != store.end() ; storeIt++ ){

	    if( strcmp( storeIt->first.getHash().c_str() , c.getHash().c_str() ) == 0 ){
		//get it
		if( storeIt->second.parsed() ){
#ifdef DEBUG
 		    std::cout << "ALREADY PARSED" << std::endl;
#endif 
		    *k  = new pkcs8 (storeIt->second.getKey());
		    return GETKEY_SUCCESS;;
		} else {
		    // get password for this unparsed pkcs12
		    // PasswordManager::getPassword();
#ifdef DEBUG
 		    std::cout << "ASKING FOR PASSWORD" << std::endl;
#endif 
		    char *passwd=NULL;
		    if(pm->getPassword("", /*Please enter your Private Key password") ,*/ storeIt->first.getHash(), &passwd, 0, &c)){
			if( storeIt->second.parse( passwd ) ){
			    *k = new pkcs8(storeIt->second.getKey());
			    return GETKEY_SUCCESS;
			} else {
			    return GETKEY_BAD_PASSWD;
			    // return null key
			} 

		    }else {
			return GETKEY_CANCELED;
			 // return null key
		    }
		    if(passwd)
			free(passwd);
		}
	    }
	}
	return -1;
    }
    
    void SoftKeyStore::setPasswordManager(PasswordManager *m){
	pm = m;
    }
    
    
    void SoftKeyStore::addPair( Certificate& c , pkcs12& k ){
	std::pair<Certificate , pkcs12> newPair; 
	newPair.first = c ;
	newPair.second = k;
	store.push_back( newPair );	
	
    }


    void SoftKeyStore::display(){
	std::vector<std::pair<Certificate , pkcs12> >::iterator it;

#ifdef DEBUG
 	std::cout << "Nombre de clés dans le Soft KeyStore : " <<count() << std::endl;
	for( it = store.begin() ; it != store.end() ; it++ ){
 	    std::cout  << "PKCS12: " << it->first.getHash() << std::endl;
 	    std::cout  << " X 509: " << it->first.getSubjectName().getValues( DN_DISPLAY_LONG | DN_DISPLAY_VALUE ) << std::endl;
 	    std::cout << "******************" << std::endl;
	}
 	std::cout << "-------------------------------------" << std::endl;
#endif 
	
    }

}
