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

#include "Certificate.hh"
#include "error.hh"
#include "Utils.hh"

#include <openssl/evp.h>
#include <openssl/md5.h> 


extern "C" {
    void OPENSSL_add_all_algorithms_noconf(void);
    void OPENSSL_add_all_algorithms_conf(void);
};

namespace Cryptonit {
    /**
     *
     */
    Certificate::Certificate():
	certificate(NULL)
    {
    }
  
    Certificate::Certificate(const Certificate &c):
	certificate(NULL)
    {
	setX509Certificate(c.certificate);
    }
    

    /**
     *
     */
    Certificate::Certificate(const char* _fileName, FileFormat format):
	certificate(NULL)
    {
	int res = load(_fileName, format);

	if(res == CERTIFICATE_FILE_NOT_FOUND 
	   || res ==  CERTIFICATE_LOAD_ERROR)
	    {
#ifdef DEBUG
 		std::cout << "Error with file " << _fileName << std::endl;
 		std::cout << getErrorMsg(res) << std::endl;
#endif 
	    }
    }

    Certificate::Certificate( void* buffer, int len ):
	certificate(NULL)
    {
	BIO* buf;
	buf = BIO_new_mem_buf( buffer, len );
	certificate = d2i_X509_bio(buf, NULL);
	BIO_free( buf );
    }


    int Certificate::load( void* buffer, int len )
    {
	free();
	
	BIO* buf;
	buf = BIO_new_mem_buf( buffer, len );
	certificate = d2i_X509_bio(buf, NULL);
	BIO_free( buf );
	if (certificate != NULL)
	    return SUCCESS;
	return CERTIFICATE_LOAD_ERROR;
    }

    int Certificate::load(const char* _fileName)
    {
	if(load(_fileName,pem_format) != SUCCESS)
	    if(load(_fileName, der_format) != SUCCESS)
		if(load(_fileName,pkcs12_format) != SUCCESS)
		    return CERTIFICATE_LOAD_ERROR;

	return SUCCESS;
    }


    /**
     *
     */
    int Certificate::load(const char* _fileName, FileFormat format)
    {
	FILE* f;
	free();

	if((f=fopen(_fileName,"rb"))==NULL) {
	    return CERTIFICATE_FILE_NOT_FOUND;
	}
    
	if(_fileName) {
	    // free();
	    if(format == pem_format){
		certificate = PEM_read_X509(f, NULL, NULL, NULL);
		fclose(f);
		if(!certificate)
		    return CERTIFICATE_LOAD_ERROR;
	    }

	    else if (format == der_format){
		certificate = d2i_X509_fp(f, NULL);
		fclose(f);
		if(!certificate)
		    return CERTIFICATE_LOAD_ERROR;
	    }

	    else if (format == pkcs12_format)
		{
		    PKCS12 *p12 = d2i_PKCS12_fp(f, NULL);
		    PKCS12_parse(p12, NULL, NULL, &certificate, NULL);
		    PKCS12_free(p12);
		    fclose(f);
		    if(!certificate)
			return CERTIFICATE_LOAD_ERROR;

		}
	}

	//DEBUG_MSG("Certif Loading : OK\n");

	return SUCCESS;
    };

    int Certificate::save(const char* filename, FileFormat format)
    {
	if(certificate){
	    BIO *out;
	    int ret = SUCCESS;
	    if (!(out = BIO_new_file(filename, "wb")))
		return -2;

	    if(format == der_format){
		if(i2d_X509_bio(out, certificate)==0)
		    ret = -3;
	    }
	    else {
		if(PEM_write_bio_X509(out, certificate)==0)
		    ret = -4;
	    }
      
	    BIO_free_all(out);	  
	    return ret;
	}
    
	return -1;
    }


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

  
    void Certificate::free()
    {
	if (certificate != NULL)
	    {
		//	X509_free(certificate);
		certificate = NULL;
	    }
    };


    /**
     *
     *
     int Certificate::verify(RevokedList _revokedList)
     {
     //Not yet implemented
     };
    */


    /*
      crl[i] is the crl for CA[i]
    */
    int Certificate::isValid(std::vector<Crl> *crl, std::vector<Certificate> *CA)
    {
	std::vector<Certificate>::iterator CAIter;
	std::vector<Crl>::iterator CrlIter;
	
	bool crl_found=false;

	if(!timeValidity())
	  return VERIF_CERTIFICATE_EXPIRED;
      
	if( !crl->empty() ){
	    for(CrlIter = crl->begin() , CAIter = CA->begin() ; CrlIter != crl->end() , CAIter != CA->end(); CrlIter++ , CAIter++ ){
		if(  CrlIter->getCRL()){
		    if(CrlIter->isIssued(*CAIter) && isIssued( *CAIter )== X509_V_OK ){
			crl_found=true;
			X509_REVOKED revoked;	
			revoked.serialNumber = X509_get_serialNumber(certificate);
			if(CrlIter->isUpToDate() == X509_V_OK){
			    if(sk_X509_REVOKED_find(CrlIter->getCRL()->crl->revoked, &revoked) != -1){
				return VERIF_CERTIFICATE_IS_REVOKED ; //false;
			    }
			} else {
			    if(sk_X509_REVOKED_find(CrlIter->getCRL()->crl->revoked, &revoked) != -1){
				return VERIF_CRL_IS_OUT_DATED | VERIF_CERTIFICATE_IS_REVOKED ; //false;
			    } else {
				return VERIF_CRL_IS_OUT_DATED | VERIF_CERTIFICATE_IS_VALID;
			    }
			}
		    }
		}
	    }
	}
	if( !crl_found ) return VERIF_CRL_NOT_FOUND;
    
	return VERIF_CERTIFICATE_IS_VALID;
    }
    
    
    
    /**
     */
    Certificate Certificate::*getCaList()
    {
	//Not yet implemented
	return NULL;
    };

    /**
     *
     */
    X509* Certificate::getX509Certificate()
    {
	return X509_dup(certificate);
    };

    int Certificate::getVersion() const
    {

	return (ASN1_INTEGER_get(certificate->cert_info->version));
	//    return x509_get_version(certificate);

	//return 0;
    }

    std::string Certificate::getVersionString() const {
	int v = getVersion();
	v++;
	ASN1_INTEGER *asn= (ASN1_INTEGER*)malloc(sizeof(ASN1_INTEGER));
	ASN1_INTEGER_set(asn,(long)v);
	BIGNUM *bn = ASN1_INTEGER_to_BN(asn,NULL);
	std::string ret(BN_bn2dec(bn));
	return ret;
    }


    std::string Certificate::getSerialNumber() const
    {
	ASN1_INTEGER *n=X509_get_serialNumber(certificate);
	BIGNUM *bn=ASN1_INTEGER_to_BN(n,NULL);
	std::string ret(BN_bn2hex(bn));
	return ret;
    }


    dn Certificate::getSubjectName() const
    {
	return dn(X509_get_subject_name(certificate));
    }

    dn Certificate::getIssuerName() const
    {
	return dn(X509_get_issuer_name(certificate));
    }


    unsigned char * Certificate::getStartDate()
    {
	ASN1_TIME *t = X509_get_notBefore(certificate);
	if(!t) return NULL;
	return t->data;
    }

    unsigned char * Certificate::getEndDate()
    {
	ASN1_TIME *t = X509_get_notAfter(certificate);
	if(!t) return NULL;
	return t->data;
    }

     
    bool Certificate::timeValidity() const {
	ASN1_TIME *nbefore=X509_get_notBefore(certificate);
        ASN1_TIME *nafter=X509_get_notAfter(certificate);
        return ((X509_cmp_current_time(nbefore)<=0)&&(X509_cmp_current_time(nafter)>=0));
    }

    std::string Certificate::printV3ext()
    {
	return printV3ext(certificate);
    }

    std::string Certificate::printV3ext(X509 *cert)
    {
	ASN1_OBJECT *obj;
	BIO *bio = BIO_new(BIO_s_mem());
	int i, len, n = X509_get_ext_count(cert);
	char buffer[200];
	X509_EXTENSION *ex;
	std::string text="";
	for (i=0; i<n; i++) {
	    ex = X509_get_ext(cert,i);
	    obj = X509_EXTENSION_get_object(ex);
	    len = i2t_ASN1_OBJECT(buffer, 200, obj);
	    buffer[len] = '\0';
	    text+=buffer;
	    text+=": \n";
	    if (X509_EXTENSION_get_critical(ex))
		text += " critical :";
	    if(!X509V3_EXT_print(bio, ex, 0, 0))
		M_ASN1_OCTET_STRING_print(bio,ex->value);
	    len = BIO_read(bio, buffer, 200);
	    buffer[len] = '\0';
	    text+=buffer;
	    text+="\n";
	}
	BIO_free(bio);
	return text;
    }


    std::vector<std::string> Certificate::getV3ext(){
	std::vector<std::string> extension;
	ASN1_OBJECT *obj;
	BIO *bio = BIO_new(BIO_s_mem());
	int i, len, n = X509_get_ext_count(certificate);
	char buffer[200];
	X509_EXTENSION *ex;

	for (i=0; i<n; i++) {
	    std::string text="";
	    ex = X509_get_ext(certificate,i);
	    obj = X509_EXTENSION_get_object(ex);
	    len = i2t_ASN1_OBJECT(buffer, 200, obj);
	    buffer[len] = '\0';
	    text+=buffer;
	    text+=": ";
	    if (X509_EXTENSION_get_critical(ex))
		text += " critical :";
	    text+="\n";
	    if(!X509V3_EXT_print(bio, ex, 0, 0))
		M_ASN1_OCTET_STRING_print(bio,ex->value);
	    len = BIO_read(bio, buffer, 200);
	    buffer[len] = '\0';
	    text+=buffer;
	    extension.push_back(text);
	}
	BIO_free(bio);
	return extension;

    }

    std::string Certificate::getPrettyStartDate(){
	ASN1_TIME *t;
	t=X509_get_notBefore(certificate);

	if(!t) return NULL;

	std::string ret((char*)(t->data));
	std::string pretty("20"+ret.substr(0,2)+"/"+ret.substr(2,2)+"/"+ret.substr(4,2));
	pretty+=" " + ret.substr(6,2)+ ":"+ret.substr(8,2);

	return pretty;
    }


    std::string Certificate::getPrettyEndDate(){
	ASN1_TIME *t;
	t=X509_get_notAfter(certificate);
	
	if(!t) return NULL;
	
	std::string ret((char*)(t->data));
	std::string pretty("20"+ret.substr(0,2)+"/"+ret.substr(2,2)+"/"+ret.substr(4,2));
	pretty+=" " + ret.substr(6,2)+ ":"+ret.substr(8,2);
	
	return pretty;
    }
    
    std::vector<Certificate> *Certificate::getChain(std::vector<Certificate> *certList)
    {
	std::vector<Certificate> *path = new std::vector<Certificate>;
	bool error=false;
	
	OpenSSL_add_all_algorithms(); 
	if(isIssued(*this) != X509_V_OK) //avoid recursion if this is a root cert
	    getPathToRootRec(path, certList, &error);	
	return path;
    }
    
    std::vector<Certificate> *Certificate::getPathToRoot(std::vector<Certificate> *certList)
    {
	std::vector<Certificate> *path = new std::vector<Certificate>;
	bool error=false;
	
	OpenSSL_add_all_algorithms(); 
	if(isIssued(*this) != X509_V_OK) //avoid recursion if this is a root cert
	    getPathToRootRec(path, certList, &error);	
	path->push_back(*this); 
	return path;
    }


    std::vector<Certificate> *Certificate::getPathToRootRec(std::vector<Certificate> *pathToRoot
							    , std::vector<Certificate> *certList
							    , bool *error)
    {
	int not_found = 1;
	std::vector<Certificate>::iterator it;
	dn subject, issuer;
       
	it = certList->begin();

	while(it != certList->end() && not_found){
	    if(isIssued(*it)==X509_V_OK){ //got it
		dn issuer(it->getIssuerName());
		//it is a root cert.
		if(!it->getSubjectName().compare(issuer)) {
		    pathToRoot = it->getPathToRootRec(pathToRoot, certList , error);
		}
		else {
		    if(! (it->isIssued(*it)==X509_V_OK ))
			*error = true;
		    else{
			not_found = 0;
		    }
		   
		}
	       
		pathToRoot->push_back(*it);
	    }
	    it++; //next cert
	}
	return pathToRoot;
    }

    int Certificate::isIssued(const Certificate &issuer){
	int i;
	EVP_PKEY *pkey = NULL;

	if(( i = X509_check_issued(issuer.certificate,certificate) != X509_V_OK))
	    return i;

	if((pkey = X509_get_pubkey(issuer.certificate))==NULL)
	    return X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY;

	i = X509_verify(certificate, pkey);
	EVP_PKEY_free(pkey);

	if(i<=0) //signature verification failed
	    return X509_V_ERR_CERT_SIGNATURE_FAILURE;

#ifdef STRICT_PKIX
	if (ca_check(issuer.certificate) != 1) {
	    return X509_V_ERR_INVALID_CA;
	}
#else
	if (ca_check(issuer.certificate) == 0) {
	    return X509_V_ERR_INVALID_CA;
	}
#endif
	return X509_V_OK;
    }

#define V1_ROOT (EXFLAG_V1|EXFLAG_SS)
#define ku_reject(x, usage) \
	(((x)->ex_flags & EXFLAG_KUSAGE) && !((x)->ex_kusage & (usage)))

    int Certificate::ca_check(const X509 *x)
    {
	/* keyUsage if present should allow cert signing */
	if(ku_reject(x, KU_KEY_CERT_SIGN)) return 0;
	if(x->ex_flags & EXFLAG_BCONS) {
	    if(x->ex_flags & EXFLAG_CA) return 1;
	    /* If basicConstraints says not a CA then say so */
	    else return 0;
	} else {
	    if((x->ex_flags & V1_ROOT) == V1_ROOT) return 3;
	    /* If key usage present it must have certSign so tolerate it */
	    else if (x->ex_flags & EXFLAG_KUSAGE) return 3;
	    else return 2;
	}
    }

    bool Certificate::setX509Certificate(X509 *certif){
	free();
	//    X509_free(certificate);
	if(certif)
	    certificate = X509_dup(certif);
	return (certificate!=NULL);
    }

    // Taken from XSC, quick and dirty
    bool Certificate::isForSigning(){
	std::string ext = printV3ext();
	// if there are no extensions, return true
	if(ext.length() == 0) return true;
	return ((ext.find("Digital Signature") != std::string::npos) ||
		(ext.find("Non Repudiation")   != std::string::npos) ||
		(ext.find("E-mail Protection") != std::string::npos));
    }

    bool Certificate::isForEncrypting(){
	std::string ext = printV3ext();
	// if there are no extensions, return true
	if(ext.length() == 0) return true;
	return ((ext.find("Key Encipherment")  != std::string::npos) ||
		(ext.find("Data Encipherment") != std::string::npos) ||
		(ext.find("E-mail Protection") != std::string::npos));
    }

    bool Certificate::isForSigningAndEncrypting(){
	return (isForSigning() && isForEncrypting());
    }

    bool Certificate::isForSigningCRL(){
	std::string ext = printV3ext();
	return (ext.find("CRL Sign")!=std::string::npos);
    }

    bool Certificate::isCA(){
	std::string ext = printV3ext();
	return ( (ext.find("CA:TRUE")!=std::string::npos));
    }

    bool Certificate::isRevoked(Crl *crl){
	if(crl->getCRL()){
	    X509_REVOKED cert;
#ifdef X509_get_serialNumber
	    cert.serialNumber = X509_get_serialNumber(certificate());
#else
	    cert.serialNumber = certificate->cert_info->serialNumber;
#endif
	    if(sk_X509_REVOKED_find(crl->getCRL()->crl->revoked, &cert) != -1) {
		return true;
	    }
	}
	return false;
    }
  
    std::string Certificate::extractCrlUri(){
	ASN1_OBJECT *obj;
	BIO *bio = BIO_new(BIO_s_mem());
	int i, len, n = X509_get_ext_count(certificate);
	char buffer[200];
	X509_EXTENSION *ex;
	std::string uri;
    
	for (i=0; i<n; i++) {
	    ex = X509_get_ext(certificate,i);
	    obj = X509_EXTENSION_get_object(ex);
	    len = i2t_ASN1_OBJECT(buffer, 200, obj);
	    buffer[len] = '\0';
	    if(strcmp(buffer,"X509v3 CRL Distribution Points")==0){
		if(!X509V3_EXT_print(bio, ex, 0, 0))
		    M_ASN1_OCTET_STRING_print(bio,ex->value);
		len = BIO_read(bio, buffer, 200);
		buffer[len] = '\0';
		std::string uri(buffer);
		return uri.substr(4,len-5); //removing "URI and trailing \n
	    }
	}
	return "";
    }

    std::string Certificate::getHash(){
	if( certificate != NULL ){
	    BIO *b= BIO_new(BIO_s_mem());
	    if(i2d_X509_bio(b, certificate)==0) {
		BIO_free(b);
		return "";
	    }
	    std::string hash = simpleHash(b);
	    BIO_free(b);
	    return hash;
	}
	return "";
    }

    std::string Certificate::getEmailAddress(){
	ASN1_OBJECT *obj;
	BIO *bio = BIO_new(BIO_s_mem());
	int i, len, n = X509_get_ext_count(certificate);
	char buffer[200];
	X509_EXTENSION *ex;
    
	for (i=0; i<n; i++) {
      
	    ex = X509_get_ext(certificate,i);
	    obj = X509_EXTENSION_get_object(ex);
	    len = i2t_ASN1_OBJECT(buffer, 200, obj);
	    buffer[len] = '\0';
	    if(strcmp(buffer,"X509v3 Subject Alternative Name")==0){
		if(!X509V3_EXT_print(bio, ex, 0, 0))
		    M_ASN1_OCTET_STRING_print(bio,ex->value);
		len = BIO_read(bio, buffer, 200);
		buffer[len] = '\0';
		std::string email(buffer);
		if(strcmp(email.substr(0,6).c_str(), "email:")==0)
		    return email.substr(6,len); //removing "email:"
	    }
	}
	return "";
    }


    Certificate& Certificate::operator=( const Certificate &src){
	setX509Certificate(src.certificate);
	return *this;
    }


}
