/*
 * 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 <vector>
#include <string>
#include <iostream>
#include <cassert>
#include <openssl/x509.h>
#include <openssl/pem.h>

#include "pkcs10.hh"


namespace Cryptonit
{

    pkcs10::pkcs10()
    {
	OpenSSL_add_all_algorithms();
	//seed_prng();

	request = NULL;
	privateKey = NULL;
	subject = NULL;
    }


    pkcs10::pkcs10( pkcs10& src )
    {
	OpenSSL_add_all_algorithms();
	//seed_prng();

	request = NULL;
	privateKey = NULL;
	subject = NULL;

 	if( src.request != NULL )
	    request = X509_REQ_dup(src.request);

	if( src.privateKey != NULL ) {
	    BIO *buffer = BIO_new( BIO_s_mem() );
	    i2d_PrivateKey_bio( buffer, src.privateKey );
	    privateKey = d2i_PrivateKey_bio( buffer, NULL );
	    BIO_free( buffer );
	}

	if( src.subject != NULL )
	    subject = X509_NAME_dup(src.subject);
    }


    pkcs10::pkcs10( const char* pkeyFilename, const char* password )
    {
	OpenSSL_add_all_algorithms();
	//seed_prng();

	request = NULL;
	privateKey = NULL;
	subject = NULL;

	if( setPrivateKey( pkeyFilename, password ) != 0 )
	    return;

	if( ! (request = X509_REQ_new()) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot create new X509 certificate request." << std::endl;
#endif 
	    return;
	}
	X509_REQ_set_pubkey( request, privateKey );

	if( ! (subject = X509_NAME_new()) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot create X509 name object." << std::endl;
#endif 
	    return;
	}


    }



    pkcs10::pkcs10( const char* pkeyFilename, const char* password,
					    const std::vector< std::pair< std::string, std::string > >& infos )
    {
	OpenSSL_add_all_algorithms();
	//seed_prng();

	request = NULL;
	privateKey = NULL;
	subject = NULL;

	if( setPrivateKey( pkeyFilename, password ) != 0 )
	    return;

	if( ! (request = X509_REQ_new()) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot create new X509 certificate request." << std::endl;
#endif 
	    return;
	}
	X509_REQ_set_pubkey( request, privateKey );

	if( ! (subject = X509_NAME_new()) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot create X509 name object." << std::endl;
#endif 
	    return;
	}

	if( setInformations( infos ) != 0 )
	    return;

    }



    pkcs10::~pkcs10()
    {
	if( privateKey != NULL )
	    EVP_PKEY_free( privateKey );
	if( request != NULL )
	    X509_REQ_free( request );
	if( subject != NULL )
	    X509_NAME_free( subject );
    }


    int pkcs10::setPrivateKey( const char* pkeyFilename, const char* password )
    {
	FILE* fp;

	if( pkeyFilename == NULL ) 
	    return -1;

	if( ! (fp = fopen( pkeyFilename, "r" )) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot open private key file: " << pkeyFilename << std::endl;
#endif 
	    return -2;
	}

	if( ! (privateKey = PEM_read_PrivateKey( fp, NULL, NULL, (void*)password )) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot read private key, maybe a wrong password." << std::endl;
#endif 
	    return -3;
	}
	fclose( fp );

	currentPrivateKeyFilename = pkeyFilename;
	return 0;
    }



    int pkcs10::setInformations( const std::vector< std::pair< std::string, std::string > >& infos )
    {
	if( infos.size() == 0 )
	    return -1;

	std::vector< std::pair< std::string, std::string > >::const_iterator it;
	for( it = infos.begin(); it != infos.end(); it++ ) {
	    int nid;
	    X509_NAME_ENTRY *ent;

	    if( (nid = OBJ_txt2nid(it->first.c_str())) == NID_undef ) {
#ifdef DEBUG
 		std::cerr << "Cannot find NID for the key '" << it->first << "'." << std::endl;
#endif 
		//return -2;
		continue;
	    }

	    if( ! (ent = X509_NAME_ENTRY_create_by_NID( NULL, nid, MBSTRING_ASC, 
							(unsigned char*)it->second.c_str(), -1 )) ) {
#ifdef DEBUG
 		std::cerr << "Cannot create entry '" << it->first << "'from NID." << std::endl;
#endif 
		return -3;
	    }

	    if( X509_NAME_add_entry( subject, ent, -1, 0 ) != 1 ) {
#ifdef DEBUG
 		std::cerr << "Cannot add entry (" << it->first << ", " << it->second << ")." << std::endl;
#endif 
		return -4;
	    }
	}
	
	if( X509_REQ_set_subject_name( request, subject ) != 1 ) {
#ifdef DEBUG
 	    std::cerr << "Cannot add subject to request." << std::endl;
#endif 
	    return -5;
	}

	return 0;
    }


    int pkcs10::write( const char* filename )
    {
	const EVP_MD* digest;
	FILE* fp;

	if( filename == NULL )
	    return -1;

	if( request == NULL )
	    return -2;
	
	if( privateKey == NULL ) 
	    return -3;

	if( subject == NULL )
	    return -4;

	if( EVP_PKEY_type( privateKey->type ) == EVP_PKEY_DSA )
	    digest = EVP_dss1();
	else if( EVP_PKEY_type( privateKey->type ) == EVP_PKEY_RSA )
	    digest = EVP_sha1();
	else {
#ifdef DEBUG
 	    std::cerr << "Cannot public key algorithm for generating digest" << std::endl;
#endif 
	    return -5;
	}

	if( ! X509_REQ_sign( request, privateKey, digest ) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot sign the certificate request." << std::endl;
#endif 
	    return -6;
	}

	if( ! (fp = fopen( filename, "w" )) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot open file '" << filename << "' for writing." << std::endl;
#endif 
	    return -7;
	}

	if( PEM_write_X509_REQ( fp, request ) != 1 ) {
#ifdef DEBUG
 	    std::cerr << "Cannot write certificate request into '" << filename << "'." << std::endl;
#endif 
	    fclose( fp );
	    return -8;
	}
	fclose( fp );

	return 0;
    }


    int pkcs10::write( FILE* fp )
    {
	const EVP_MD* digest;

	if( fp == NULL )
	    return -1;

	if( request == NULL )
	    return -2;
	
	if( privateKey == NULL ) 
	    return -3;

	if( subject == NULL )
	    return -4;

	if( EVP_PKEY_type( privateKey->type ) == EVP_PKEY_DSA )
	    digest = EVP_dss1();
	else if( EVP_PKEY_type( privateKey->type ) == EVP_PKEY_RSA )
	    digest = EVP_sha1();
	else {
#ifdef DEBUG
 	    std::cerr << "Cannot public key algorithm for generating digest" << std::endl;
#endif 
	    return -5;
	}

	if( ! X509_REQ_sign( request, privateKey, digest ) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot sign the certificate request." << std::endl;
#endif 
	    return -6;
	}

// 	if( ! (fp = fopen( filename, "w" )) ) {
#ifdef DEBUG
 // 	    std::cerr << "Cannot open file '" << filename << "' for writing." << std::endl;
#endif 
// 	    return -7;
// 	}

	if( PEM_write_X509_REQ( fp, request ) != 1 ) {
#ifdef DEBUG
 	    std::cerr << "Cannot write certificate request into the provided file pointer." << std::endl;
#endif 
	    return -8;
	}
	fclose( fp );

	return 0;
    }


    int pkcs10::load( const char* filename )
    {
	FILE* fp;

	if( filename == NULL )
	    return -1;

	if( ! (fp = fopen( filename, "rb" )) ) {
#ifdef DEBUG
 	    std::cerr << "Cannot open file '" << filename << "' for reading." << std::endl;
#endif 
	    return -2;
	}

	request = PEM_read_X509_REQ( fp, NULL, NULL, NULL );
	if( request == NULL ) {
#ifdef DEBUG
 	    std::cerr << "Cannot read certificate request from file " << filename << std::endl;
#endif 
	    fclose( fp );
	    return -3;
	}

	fclose( fp );
	return 0;
    }



    bool pkcs10::matchCertificate( Certificate& certificate )
    {
      
	unsigned char *certPublicKey = NULL, *reqPublicKey = NULL;

	// FIXME : workaround, the Certificate class (defined in
	// Certificate.hh) should probably make sure this assertion is
	// always true, as a Certificate instance with no valid
	// certificate member seems to be useless
	assert(certificate.getX509Certificate() != NULL);

	return( std::string( (char*)certPublicKey, 
			     i2d_PublicKey(X509_get_pubkey( certificate.getX509Certificate() ), 
					   &certPublicKey) )
		== std::string( (char*)reqPublicKey, 
				i2d_PublicKey(X509_REQ_get_pubkey( request ), 
					      &reqPublicKey ) ) );
    }


    bool pkcs10::matchPrivateKey( pkcs8& pkey )
    {
	X509* cert;

	cert = X509_REQ_to_X509( request, 365, pkey.getKey() );

	if( cert != NULL ) {
	    bool ret = false;

	    if( ! X509_check_private_key( cert, pkey.getKey() ) ) 
		ret = false;
	    else
		ret =  true;
	    
	    X509_free( cert );
	    return ret;
	}
	else
	    return false;
    }

}
