/**********************************************************************
 
	Copyright (C) 2003-2005
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomohito Nakajima <nakajima@zeta.co.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/

#include "ossl.h"
#include "utils.h"

static void get_key_usage_text(char *buff, int flag){
	int len;
	*buff = '\0';
	if(flag & X509v3_KU_DIGITAL_SIGNATURE){
		strcat(buff, "digitalSignature,");
	}
	if(flag & X509v3_KU_NON_REPUDIATION){
		strcat(buff, "nonRepudiation,");
	}
	if(flag & X509v3_KU_KEY_ENCIPHERMENT){
		strcat(buff, "keyEncipherment,");
	}
	if(flag & X509v3_KU_DATA_ENCIPHERMENT){
		strcat(buff, "dataEncipherment,");
	}
	if(flag & X509v3_KU_KEY_AGREEMENT){
		strcat(buff, "keyAgreement,");
	}
	if(flag & X509v3_KU_KEY_CERT_SIGN){
		strcat(buff, "keyCertSign,");
	}
	if(flag & X509v3_KU_CRL_SIGN){
		strcat(buff, "cRLSign,");
	}
	if(flag & X509v3_KU_ENCIPHER_ONLY){
		strcat(buff, "encipherOnly,");
	}
	if(flag & X509v3_KU_DECIPHER_ONLY){
		strcat(buff, "decipherOnly,");
	}
	len = strlen(buff);
	if(len){
		buff[len-1] = '\0';
	}
}

static OSSL_BOOL set_ext(X509 *cert, X509V3_CTX *ctx, const char *key, const char *value)
{
	X509_EXTENSION *ext;
	char *err_buff;
	char *key_;
	char *value_;

	key_ = copy_str((char*)key);
	value_ = copy_str((char*)value);

	ext = X509V3_EXT_conf(NULL, ctx, key_, value_);
	d_f_ree(key_);
	d_f_ree(value_);

	if(!ext){
		oSSL_print_all_error();
		err_buff = d_alloc(strlen(key)+strlen(value)+256);
		sprintf(err_buff, "error create conf %s=%s", key, value);
		ossl_error(err_buff);
		d_f_ree(err_buff);
		return FALSE;
	}
	if(!X509_add_ext(cert, ext, -1)){
		err_buff = d_alloc(strlen(key)+strlen(value)+256);
		sprintf(err_buff, "error set conf %s=%s", key, value);
		ossl_error(err_buff);
		d_f_ree(err_buff);
		return FALSE;
	}
	
	X509_EXTENSION_free(ext);
	return TRUE;
}

OSSL_BOOL copy_ext_from_csr_to_cert(X509_REQ* csr, X509 *cert){
	STACK_OF(X509_EXTENSION) *req_exts;
	X509_EXTENSION *ext;
	
	int num;
	int i;

	req_exts = X509_REQ_get_extensions(csr);
	if(!req_exts){
		return TRUE;
	}
	
	num = sk_X509_EXTENSION_num(req_exts);
	for(i=0; i<num; ++i){
		ext = sk_X509_EXTENSION_value(req_exts,i);
		if(!X509_add_ext(cert, ext, -1)){
			ossl_error("error adding extensiton to certifocate");
			return FALSE;
		}
	}

	/*
	subject_alt_name_pos = X509v3_get_ext_by_NID(req_exts, OBJ_sn2nid("subjectAltName"), -1);
	if(subject_alt_name_pos != -1){
		subject_alt_name = X509v3_get_ext(req_exts, subject_alt_name_pos);
		if(!X509_add_ext(cert, subject_alt_name, -1)){
			ossl_error("error adding subjectAltName to certifocate");
			return FALSE;
		}
	}
	*/
	
	return TRUE;
}

oSSL_object *oSSL_create_certificate(
	oSSL_object *csr, 
	oSSL_object *ca_private_key, 
	oSSL_object *ca_certificate, 
	int serial, 
	long expire_secs, 
	int ossl_key_usage_flag)
{
	EVP_PKEY *pkey;
	X509 *new_certificate;
	oSSL_object *ret;
	
	pkey = X509_REQ_get_pubkey(csr->obj.csr);
	if(!pkey){
		ossl_error("can not get public key from csr");
		return NULL;
	}
	if(X509_REQ_verify(csr->obj.csr, pkey) != 1){
		ossl_error("csr verify error");
		return NULL;
	}

	/*
	subject_name = X509_REQ_get_subject_name(csr->obj.csr);
	if(!subject_name){
		ossl_error("no subject name in csr");
		return NULL;
	}
	*/
	
	new_certificate = X509_new();
	X509_set_version(new_certificate, 2L);
	ASN1_INTEGER_set(X509_get_serialNumber(new_certificate), serial);
	
	X509_set_subject_name(new_certificate, X509_REQ_get_subject_name(csr->obj.csr));
	if(ca_certificate){
		X509_set_issuer_name(new_certificate, X509_get_subject_name(ca_certificate->obj.x509));
	}
	else{
		/* self sign */
		X509_set_issuer_name(new_certificate, X509_REQ_get_subject_name(csr->obj.csr));
	}

	X509_set_pubkey(new_certificate, pkey);
	
	X509_gmtime_adj(X509_get_notBefore(new_certificate), 0);
	X509_gmtime_adj(X509_get_notAfter(new_certificate), expire_secs);
	
	if(!copy_ext_from_csr_to_cert(csr->obj.csr, new_certificate)){
		return NULL;
	}

	{
		char key_usage_text[512];
		const char *basic_constraints_text;
		X509V3_CTX ctx;

		if(ca_certificate)
			X509V3_set_ctx(&ctx, ca_certificate->obj.x509, new_certificate, NULL, NULL, 0);
		else
			X509V3_set_ctx(&ctx, new_certificate, new_certificate, NULL, NULL, 0);
		
		if( (ossl_key_usage_flag & OSSL_KEY_USAGE_CA) == OSSL_KEY_USAGE_CA){
			basic_constraints_text = "CA:TRUE";
		}
		else{
			basic_constraints_text = "CA:FALSE";
		}
		
		if(!set_ext(new_certificate, &ctx, "basicConstraints", basic_constraints_text))
			return NULL;
		/*
		if(!set_ext(new_certificate, &ctx, "nsComment", "GBS with openssl Generated Certificates"))
			return NULL;
		*/
		if(!set_ext(new_certificate, &ctx, "subjectKeyIdentifier", "hash"))
			return NULL;
		if(!set_ext(new_certificate, &ctx, "authorityKeyIdentifier", "keyid,issuer:always"))
			return NULL;
		
		get_key_usage_text(key_usage_text, ossl_key_usage_flag);
		if(*key_usage_text){
			set_ext(new_certificate, &ctx, "keyUsage",  key_usage_text);
		}
	}
	
	if(!X509_sign(new_certificate, ca_private_key->obj.pkey.pkey, oSSL_get_MD_by_pkey(ca_private_key->obj.pkey.pkey))){
		ossl_error("error signing certificate");
		return NULL;
	}

	ret = oSSL_object_new(OSSL_CERTIFICATE);
	ret->obj.x509 = new_certificate;
	return ret;
}


OSSL_BOOL oSSL_cert_save_pem(oSSL_object *obj, const char* file, const char* password/* egnored */)
{
	FILE *fp;
	fp = fopen(file, "w");
	if(!fp){
		ossl_error("cannot open file");
		return FALSE;
	}
	if(PEM_write_X509(fp, obj->obj.x509)!=1){
		ossl_error("error while writing certificate");
		fclose(fp);
		return FALSE;
	}
	fclose(fp);
	return TRUE;
}

OSSL_BOOL oSSL_cert_load_pem(oSSL_object *obj, const char* file, const char* password/* egnored */)
{
	FILE *fp;
	char *err_msg;
	
	err_msg = NULL;
	fp = fopen(file, "rb");
	if(!fp){
		err_msg = d_alloc(strlen(file)+200);
		sprintf(err_msg, "oSSL_certificate_load_file open error file=%s\n", file);
	}
	else{
		obj->obj.x509 = PEM_read_X509(fp, NULL, NULL, NULL);
		fclose(fp);
		if(!obj->obj.x509){
			err_msg = d_alloc(strlen(file)+200);
			sprintf(err_msg, "oSSL_certificate_load_file load error file=%s\n", file);
		}
	}
	
	if(err_msg){
		ossl_error(err_msg);
		d_f_ree(err_msg);
		return FALSE;
	}
	else{
		return TRUE;
	}
}

/*
#define OSSL_IMPLEMENT_OBJECT2DATA(obj_type, i2d_func, internal_name) \
oSSL_data *oSSL_##obj_type##_object2data(oSSL_object * obj)\
{\
	unsigned char *d,*d2;\
	int len;\
	len = i2d_func(obj->obj.internal_name, NULL);\
	d2 = d = d_alloc(len);\
	i2d_func(obj->obj.x509, &d2);\
	return oSSL_data_new(obj->type, d, len, TRUE, len);\
}
OSSL_IMPLEMENT_OBJECT2DATA(cert, i2d_X509, x509)
*/

oSSL_data *oSSL_cert_object2data(oSSL_object * obj)
{
	unsigned char *d,*d2;
	int len;
	len = i2d_X509(obj->obj.x509, NULL);
	d2 = d = d_alloc(len);
	i2d_X509(obj->obj.x509, &d2);
	return oSSL_data_new(obj->type, d, len, TRUE, len);
}

oSSL_object *oSSL_cert_data2object(oSSL_data * data)
{
	oSSL_object *ret;
	ret = oSSL_object_new(data->type);
	ret->obj.x509 = d2i_X509(NULL, (unsigned char**)&data->data, (long)data->len);
	return ret;
}

void oSSL_cert_free(oSSL_object *obj){
	X509_free(obj->obj.x509);
	d_f_ree(obj);
}


oSSL_method oSSL_certificate_method = {
	OSSL_CERTIFICATE,
	oSSL_cert_free,
	oSSL_cert_object2data,
	oSSL_cert_data2object,
	oSSL_cert_save_pem,
	oSSL_cert_load_pem
};

void oSSL_cert_init(){
	oSSL_types[OSSL_CERTIFICATE] = &oSSL_certificate_method;
}


L_CHAR *oSSL_get_ext_cert_data(L_CHAR *name, oSSL_object *cert)
{
	L_CHAR *ret;
	int ext_count;
	int i;
	char *name_;
	BIO *out;
	BUF_MEM *buf_mem;

	ret = NULL;

	ext_count = X509_get_ext_count(cert->obj.x509);
	
	if(ext_count <= 0){
		return NULL;
	}
	
	name_ = n_string(std_cm, name);
	for(i=0; i<ext_count; ++i){
		const char * ext_str;
		X509_EXTENSION *ext;
		
		ext = X509_get_ext(cert->obj.x509, i);
		
		ext_str = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
		
		if(strcmp(ext_str, name_) == 0){
			/*
			ASN1_OBJECT *obj;
			*/
			out = BIO_new(BIO_s_mem());
			/*
			obj=X509_EXTENSION_get_object(ext);
			i2a_ASN1_OBJECT(out,obj);
			BIO_write(out,":",1);
			*/

			X509V3_EXT_print(out, ext, 0, 0);
			BIO_write(out,"",1);
			BIO_get_mem_ptr(out, &buf_mem);
			
			ret = l_string(std_cm, buf_mem->data);
			break;
		}
	}

	return ret;
}

