/*
 * 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 "Crl.hh"
#include "User.hh"
#include "Utils.hh"
/**
 * Crl 
 */

namespace Cryptonit {

    Crl::Crl()
    {
	crl = NULL;
    }; 

    Crl::Crl(X509_CRL *_crl){
	crl = X509_CRL_dup(_crl);
    }

    Crl::Crl(const Crl &c){
	crl = X509_CRL_dup(c.crl);
    }
    
    Crl::~Crl()
    {
	free();
    }; 

    void Crl::free()
    {
	if(crl){
	    X509_CRL_free(crl);
	    crl = NULL;
	}
    }

    int Crl::load(const char *filename , FileFormat format)
    {
	FILE *in;
	PKCS7 *p7=NULL;
	PKCS7_SIGNED *p7s=NULL;
	int ret = SUCCESS;
    
	if((in=fopen(filename,"r"))==NULL)
	    return CRL_FILE_NOT_FOUND;
    

	if(format == pem_format) {
	    if( (crl = PEM_read_X509_CRL(in,NULL,NULL,NULL)) == NULL)
		ret = CRL_PEM_LOAD_ERROR;
	} else if(format == der_format) {
	    if( (crl = d2i_X509_CRL_fp(in, NULL)) == NULL)
		ret = CRL_DER_LOAD_ERROR;
	} else if((format == pkcs7_pem_format) || (format == pkcs7_der_format )) {
	    if(format == pkcs7_pem_format) p7 =  PEM_read_PKCS7(in, NULL, NULL, NULL);
	    else p7 = d2i_PKCS7_fp(in,NULL);

	    if((p7 == NULL) || (OBJ_obj2nid(p7->type) != NID_pkcs7_signed) ) {
		ret = CRL_PKCS7_LOAD_ERROR;
	    } else {
		p7s = p7->d.sign;
		if(OBJ_obj2nid(p7s->contents->type)==NID_pkcs7_data) {
		    crl =  sk_X509_CRL_pop(p7s->crl);
		}
	    }
	} else ret = CRL_LOAD_BAD_FORMAT;
    
	fclose(in);
	if(p7 != NULL)PKCS7_free(p7);
	return ret;
    }


    int Crl::load(const char *filename)
    {
	if(load(filename, pem_format) != SUCCESS)
	    if(load(filename, der_format) != SUCCESS)
		if(load(filename, pkcs7_pem_format) != SUCCESS)
		    if(load(filename, pkcs7_der_format) != SUCCESS)
			return CRL_LOAD_BAD_FORMAT;
	return SUCCESS;
    }
  
  

    int Crl::load(BIO *bio, FileFormat format)
    {
	PKCS7 *p7 = NULL;
	PKCS7_SIGNED *p7s = NULL;

	if(!bio) return CRL_LOAD_BIO_NULL;
    
	if(format == pem_format){
	    if( (crl = PEM_read_bio_X509_CRL(bio,NULL,NULL,NULL)) != NULL)
		return SUCCESS;
	    return CRL_PEM_LOAD_ERROR;
	}
	else if(format == der_format){
	    if( (crl = d2i_X509_CRL_bio(bio,NULL)) != NULL)
		return SUCCESS;
	    return CRL_DER_LOAD_ERROR;
	}
	else if( (format == pkcs7_pem_format) || (format == pkcs7_der_format )){
	    if(format == pkcs7_pem_format) p7 = PEM_read_bio_PKCS7(bio, NULL, NULL, NULL);
	    else  p7=d2i_PKCS7_bio(bio,NULL);

	    if((p7 == NULL) || (OBJ_obj2nid(p7->type) != NID_pkcs7_signed) ){
		return CRL_PKCS7_LOAD_ERROR;
	    }else{
		p7s = p7->d.sign;
		if(OBJ_obj2nid(p7s->contents->type)==NID_pkcs7_data){
		    if((crl =  sk_X509_CRL_pop(p7s->crl))!=NULL)
			return SUCCESS;
		    else return CRL_PKCS7_LOAD_ERROR;
		}
	    }
	}
	return CRL_LOAD_BAD_FORMAT;
    }
  

    int Crl::save(const char* filename, FileFormat format){
	if(crl){
	    BIO *out;
	    int ret = SUCCESS;
	    if (!(out = BIO_new_file(filename, "wb")))
		return -2;
	    
	    if(format == der_format){
		if(i2d_X509_CRL_bio(out, crl)==0)
		    ret = -3;
	    }
	    else {
		if(PEM_write_bio_X509_CRL(out, crl)==0)
		    ret = -4;
	    }
	    
	    BIO_free_all(out);	  
	    return ret;
	}
	
	return -1;
	
    }
    
    
    X509_CRL *Crl::getCRL()
    {
	if( crl ){
	    return X509_CRL_dup(crl);
	    
	} else {
	    return NULL;
	}
    }
  


    int Crl::getVersion()
    {
	return X509_CRL_get_version(crl);
    }
  


    dn Crl::getIssuerName()
    {
	return dn(X509_CRL_get_issuer(crl));
    }
  


    std::string Crl::getAttributes()
    {
	std::string attr;
    
	attr += "Issuer Name  : ";
	attr += getIssuerName().getValues();
	attr +="\nLast Update :";
	attr +=getLastUpdate();
	attr +="\nNext Update :";
	attr +=getNextUpdate();
	return attr;
    }

    std::string Crl::getLastUpdate()
    {
	if( crl ){
	BIO *bio= BIO_new(BIO_s_mem());
	char *pointer;
	ASN1_TIME *t= X509_CRL_get_lastUpdate(crl);
      
	ASN1_TIME_print(bio,t);
	BIO_get_mem_data(bio, &pointer);
	std::string ret(pointer);
	BIO_free(bio);
	return ret.substr(0,24);
	}
	return "crl null";
    }
    
    std::string Crl::getNextUpdate()
    {
	if( crl ) {
	    BIO *bio= BIO_new(BIO_s_mem());
	    char *pointer;
	    ASN1_TIME *t= X509_CRL_get_nextUpdate(crl);
	    
	    ASN1_TIME_print(bio,t);
	    BIO_get_mem_data(bio, &pointer);
	    std::string ret(pointer);
	    BIO_free(bio);
	    return ret.substr(0,24);
	}
	return "crl null";
    }
    
    std::vector<std::string> Crl::getExtensions()
    {
	std::vector<std::string> extension;
	if( crl ){
	    
	    ASN1_OBJECT *obj;
	    BIO *bio = BIO_new(BIO_s_mem());
	    int i, len, n = X509_CRL_get_ext_count(crl);
	    char buffer[200];
	    X509_EXTENSION *ex;
	    
	    for (i=0; i<n; i++) {
		std::string text="";
		ex = X509_CRL_get_ext(crl,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 :";
		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::vector<std::string> Crl::getRevokedSerials()
    {
	std::vector<std::string> revoked;
	STACK_OF(X509_REVOKED) *stackRevoked=NULL;
	X509_REVOKED *rev=NULL;
	unsigned char *sn;
	int length;
	std::string serial="";
	char hex[4]="";
	hex[3]='\0';
  
	stackRevoked = sk_X509_REVOKED_dup(X509_CRL_get_REVOKED(crl));
    
	while( (rev=sk_X509_REVOKED_pop(stackRevoked))!=NULL){
	    sn=ASN1_STRING_data(rev->serialNumber);
	    length = ASN1_STRING_length(rev->serialNumber);
      
	    for(int i=0; i<length ; i++) {
		sprintf(hex,"%x",sn[i]);
		serial += hex; //hexadecimal conversion
	    }
	    revoked.push_back(serial);
	}
    
	X509_REVOKED_free(rev);
	sk_X509_REVOKED_free(stackRevoked);
	return revoked;
    }

    bool Crl::isIssued(Certificate &cert)
    {
	EVP_PKEY *pkey = NULL;
	X509 *x;
	int ok =  X509_V_OK;
    
	if(crl) {
	    x = cert.getX509Certificate();
	    if(x) {
		pkey = X509_get_pubkey(x);
		
		if(!pkey) {
		    ok = X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY;
		} else {
		    /* Verify CRL signature */
		    if(X509_CRL_verify(crl, pkey) <= 0) {
			ok = X509_V_ERR_CRL_SIGNATURE_FAILURE;
		    }
		    EVP_PKEY_free(pkey);
		}
		X509_free(x);
	    } else {
		ok = X509_V_ERR_CERT_REJECTED;
	    }
	    
	} else {
	    ok = X509_V_ERR_UNABLE_TO_GET_CRL;
	}


	return (ok ==  X509_V_OK);
    
    }


    int Crl::isUpToDate()
    {
	if( crl ) {
	    int i, ok = X509_V_OK;
	    
	    i = X509_cmp_time(X509_CRL_get_lastUpdate(crl), NULL);
	    if(i == 0) {
		ok = X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD;
	    } else if (i > 0) {
		ok = X509_V_ERR_CRL_NOT_YET_VALID;
	    } else {
		
		if(X509_CRL_get_nextUpdate(crl)) {
		    i = X509_cmp_time(X509_CRL_get_nextUpdate(crl), NULL);
		    
		    if(i < 0) {
			ok = X509_V_ERR_CRL_HAS_EXPIRED;
		    }
		}
	    }
	    return ok;
	}
	return -1;
    }
    


    
    std::string Crl::getHash(){
	BIO *b= BIO_new(BIO_s_mem());
	if(i2d_X509_CRL_bio(b, crl)==0) {
	    BIO_free(b);
	    return "";
	}
	std::string hash = simpleHash(b);
	BIO_free(b);
	return hash;
    }
 
    
    
    bool Crl::outDated(std::string uri , User *user , Certificate *c){
	
	
	if( ! user->authorities->getAuthority(c->getIssuerName().getValues (DN_DISPLAY_LONG | DN_DISPLAY_VALUE, ',')) ){
	    return true;
	}
	
	
	std::vector<std::string> crlValues = user->authorities->getAuthority
	    (c->getIssuerName().getValues
	     (DN_DISPLAY_LONG | DN_DISPLAY_VALUE, ',') )->getAttributeValues( "CRL" );	
	
	
	std::vector<std::string> uriValues = user->authorities->getAuthority
	    (c->getIssuerName().getValues
	     (DN_DISPLAY_LONG | DN_DISPLAY_VALUE, ',') )->getAttributeValues( "URI" );	
	
	std::vector<std::string>::iterator valuesIt = uriValues.begin();
	std::vector<std::string>::iterator crlIt = crlValues.begin();
	int i=0;
	while(valuesIt != uriValues.end() && crlIt != crlValues.end() && uri != *valuesIt ){
	    i++;
	    valuesIt++;
	    crlIt++;
	}
	
	if( valuesIt != uriValues.end()  && uri == *valuesIt){ //find same uri , check if this crl is outdated
	    Crl crl;
	    std::string crlFile = user->getCRLDir();
	    crlFile = appendDir( crlFile , *crlIt );
	    if(crl.load( crlFile.c_str() ) != SUCCESS)
		return true; //problem while loading crl, assume it's outdated ...
	    
	    return (crl.isUpToDate() != X509_V_OK);
	}
	return true;
    }
}
