/*
 * 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 <openssl/rand.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>


#include "pkcs8.hh"

/**
 * pkcs8 
 */


namespace Cryptonit {
    pkcs8::pkcs8()
    {
	// creation of the key
	key = EVP_PKEY_new();
    }
    
    /**
     *
     */
    pkcs8::pkcs8(EVP_PKEY *privateKey)
    {
	key = privateKey;
    }


    pkcs8::pkcs8(pkcs8 &src)
    {
	if (src.key){
	    // taken from xsc
	    key=NULL;
	    BIO *bout = BIO_new(BIO_s_mem());
	    src.save(bout);
	    this->load(bout);
	    BIO_free(bout);
	}
    }


    /**
     *
     */
    pkcs8::~pkcs8()
    {
	free();
    } 

    void pkcs8::free()
    {
	EVP_PKEY_free(key);
    }



    bool pkcs8::load(const char* filename, FileFormat format, const char* passwd)
    {
	OpenSSL_add_all_algorithms();
	FILE *file;
	if( !(file = fopen (filename, "r")) )
	    return false;
	return load(file, format, passwd);
    }



    bool pkcs8::load(FILE* file, FileFormat format, const char *passwd)
    {
	if(file) {
	    this->free();
	
	    if( format == pem_format ) {
		if(!(key = PEM_read_PrivateKey(file,NULL,NULL,(void*) passwd))){
#ifdef DEBUG
 		    std::cerr << "Error while loading private key !" << std::endl;
#endif 
		    return false;
		}
	    }
	    else if( format == der_format ) {
		if(!(key = d2i_PrivateKey_fp(file,NULL))) {
#ifdef DEBUG
 		    std::cerr << "Error while loading private key !" << std::endl;
#endif 
		    return false;
		}
	    }
	    else if( format == pkcs12_format ) {
		PKCS12* p12 = d2i_PKCS12_fp(file, NULL);
		PKCS12_parse(p12, NULL, &key, NULL, NULL);
		PKCS12_free(p12);
	    }
	    return (key != NULL);
	}
	else {
#ifdef DEBUG
 	    std::cerr << "File not found." << std::endl;
#endif 
	}
    
	return false;
    }
  

    int pkcs8::load(BIO *bin , FileFormat format)
    {
	if(bin) {
	    free();

	    if( format == pem_format )
		key = PEM_read_bio_PrivateKey(bin,NULL,NULL,NULL);

	    else if( format == der_format )
		key = d2i_PrivateKey_bio(bin,NULL);

	    else if( format == pkcs12_format ) {
		PKCS12 *p12 = d2i_PKCS12_bio(bin, NULL);
		PKCS12_parse(p12, NULL, &key, NULL, NULL);
		PKCS12_free(p12);
	    }
	    if( key != NULL )
		return SUCCESS;
	    else return -1;
	}
	else
	    return -1;
    }



    int pkcs8::save( BIO *bout, const FileFormat format) 
    {
	if(bout) {
	    int i = 0;
	
	    if( format == pem_format )
		i = PEM_write_bio_PrivateKey(bout,key,NULL,NULL,0,NULL,NULL);

	    else if (format == der_format)
		i = i2d_PrivateKey_bio(bout,key);
       
	    if(i != 0)  
		return SUCCESS;
	}
	return -1;
    }



    int pkcs8::save( const char* filename, const EVP_CIPHER* cipher, const char* password, int klen )
    {
	FILE* fp;

	if( filename == NULL ) 
	    return -1;

	if( ! (fp = fopen( filename, "w" )) ) 
	    return -2;

	return save( fp, cipher, password, klen );
    }


    int pkcs8::save( FILE* fp, const EVP_CIPHER* cipher, const char* password, int klen )
    {
	if( fp == NULL ) 
	    return -2;

	if( cipher == NULL || password == NULL ) {
	    // Write the private key on disk unencrypted
	    if( ! PEM_write_PrivateKey(fp, key, NULL, NULL, 0, 0, NULL) ) {
		return -3;
	    }
	    fclose(fp);
	    return 0;
	}

	// Use a non null terminated password
	if( password != NULL && klen != 0 ) {
	    // Write the private key on disk encrypted
	    if( ! PEM_write_PrivateKey(fp, key, cipher, (unsigned char*)password, klen, 0, NULL) ) {
		return -3;
	    }
	    fclose(fp);
	    return 0;
	}
    
	// Use a null terminated password
	if( password != NULL && klen == 0 ) {
	    // Write the private key on disk encrypted
	    if( ! PEM_write_PrivateKey(fp, key, cipher, NULL, 0, 0, (void*)password) ) {
		return -3;
	    }
	    fclose(fp);
	    return 0;
	}
    
	fclose(fp);
	return -4;
    }
  

    EVP_PKEY *pkcs8::getKey()
    {
	return key;
    }


    void pkcs8::setKey(EVP_PKEY* privateKey)
    {
	/* if key is used, it is freed */
	if(key) EVP_PKEY_free(key);
	
	/* assignation of the new key */
	key = privateKey;
    }


    int pkcs8::generateKeyPair( int bits, void (*callback)(int, int, void*), void* cb_arg )
    {
	/* Seeding the PRNG */
#ifdef WIN32
	/* Use user's screen for seeding the PRNG */
#ifdef DEBUG
 	std::cerr << "Seeding PRNG using user's screen..." << std::endl;
#endif 
	RAND_screen();
#else 
	/* Try to use the /dev/urandom of Unix systems */
	// Check if /dev/urandom exist
	struct stat s;
	if( stat( "/dev/urandom", &s ) == 0 ) {
	    if( S_ISCHR(s.st_mode) ) {
#ifdef DEBUG
 		std::cerr << "Seeding PRNG from /dev/urandom..." << std::endl;
#endif 
		while( RAND_status == 0 )
		    RAND_load_file( "/dev/urandom", 4 * bits );
	    }
	} else {
#ifdef DEBUG
 	    std::cerr << "Cannot stat /dev/urandom !" << std::endl;
#endif 
	    return -1;
	}
	
#endif	
#ifdef DEBUG
 	std::cout << "PRNG seeded !" << std::endl;
#endif 

	RSA* rsa = NULL;

	rsa = RSA_generate_key( bits, RSA_F4, callback, cb_arg );
	
	int ret = RSA_check_key( rsa );
	switch( ret ) {
	case 0: // Invalid key
	    ret = -2;
	    break;
	case -1: // An error occured while checking the key
	    ret = -3;
	    break;
	case 1: // The key is a valid RSA key
	    if( ! EVP_PKEY_assign_RSA( key, rsa ) )
		ret = -4;
	    else 
		ret = 0;
	    break;
	}

	if( ret != 0 && rsa != NULL ) {
	    RSA_free( rsa );
	    rsa = NULL;
	}

	return ret;
    }


    EVP_PKEY* pkcs8::getPublicKey()
    {
	return NULL;
    }


//     std::string pkcs8::getPublicKey()
//     {
// 	unsigned char* buffer = NULL;
// 	std::string res("");
	
// 	int ret = i2d_RSAPublicKey( EVP_PKEY_get1_RSA(key), &buffer );
	
// 	if( ret > 0 ) {
// 	    res = std::string( (char*)buffer, ret );
// 	    std::free( buffer );
// 	    return res;
// 	}
// 	else 
// 	    return res;
//     }

    
    EVP_PKEY* pkcs8::getPrivateKey()
    {
	return key;
    }


}
