/**********************************************************************
 
	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 "openssl/x509v3.h"
#include "string.h"

static void set_x509_name_entry(X509_NAME *subject_name, const char *entry_name, L_CHAR *value)
{
	int nid;
	X509_NAME_ENTRY *entry;

	nid = OBJ_txt2nid(entry_name);
	if(nid == NID_undef){
		ossl_error("set_x509_name_entry() entry_name is undefined");
		return;
	}
	
	entry = X509_NAME_ENTRY_create_by_NID(NULL, nid, MBSTRING_ASC, n_string(std_cm, value), -1);
	if(entry == NULL){
		ossl_error("set_x509_name_entry() entry == NULL");
		return;
	}
	
	if(!X509_NAME_add_entry(subject_name, entry, -1, 0)){
		ossl_error("set_x509_name_entry() add entry error");
	}
}

static OSSL_BOOL setup_extensions(
	X509_REQ *csr,
	oSSL_cert_extension *extensions,
	int extension_count){
	int i;
	X509_EXTENSION *ext;
	STACK_OF(X509_EXTENSION) *extlist;
	
	if(extension_count == 0)
		return TRUE;
	
	extlist = sk_X509_EXTENSION_new_null();
	
	for(i=0; i<extension_count; ++i){
		ext = X509V3_EXT_conf(NULL, NULL, n_string(std_cm, extensions[i].name), n_string(std_cm, extensions[i].value));
		if(ext == NULL){
			ossl_error("setup_extensions X509V3_EXT_conf error");
			return FALSE;
		}

		sk_X509_EXTENSION_push(extlist, ext);
	}
	
	if(!X509_REQ_add_extensions(csr, extlist)){
		ossl_error("setup_extensions() X509_REQ_add_extensions error");
		return FALSE;
	}
	
	sk_X509_EXTENSION_pop_free(extlist, X509_EXTENSION_free);
	return TRUE;
}

static OSSL_BOOL sign_csr(X509_REQ *csr, EVP_PKEY *pkey)
{
	if(!X509_REQ_sign(csr, pkey, oSSL_get_MD_by_pkey(pkey))){
		ossl_error("sign_csr() X509_REQ_sign ");
		return FALSE;
	}
	return TRUE;
}


oSSL_object *oSSL_create_csr(
	L_CHAR* country_name,
	L_CHAR* state_or_province_name,
	L_CHAR* locality_name,
	L_CHAR* organization_name,
	L_CHAR* organizational_unit_name,
	L_CHAR* common_name,
	L_CHAR* email_address,
	oSSL_object *private_key,
	oSSL_cert_extension *extensions,
	int extension_count)
{
	oSSL_object *ret;
	X509_NAME *subject_name;
	ret = NULL;

	if(private_key->type != OSSL_RSA_PRIVATEKEY){
		ossl_error("oSSL_create_csr() private_key type error");
		return NULL;
	}
	
	ret = oSSL_object_new(OSSL_CSR);
	
	ret->obj.csr = X509_REQ_new();
	if(ret->obj.csr == NULL){
		ossl_error("oSSL_create_csr() X509_REQ_new error.");
		goto err;
	}
	
	X509_REQ_set_pubkey(ret->obj.csr, private_key->obj.pkey.pkey);
	
	subject_name = X509_NAME_new();
	
	set_x509_name_entry(subject_name, "countryName", country_name);
	set_x509_name_entry(subject_name, "stateOrProvinceName", state_or_province_name);
	set_x509_name_entry(subject_name, "localityName", locality_name);
	set_x509_name_entry(subject_name, "organizationName", organization_name);
	set_x509_name_entry(subject_name, "organizationalUnitName", organizational_unit_name);
	set_x509_name_entry(subject_name, "commonName", common_name);
	set_x509_name_entry(subject_name, "emailAddress", email_address);
	
	X509_REQ_set_subject_name(ret->obj.csr, subject_name);
	
	if(!setup_extensions(ret->obj.csr, extensions, extension_count)){
		goto err;
	}
	
	if(!sign_csr(ret->obj.csr, private_key->obj.pkey.pkey)){
		goto err;
	}
	
	return ret;

err:
	if(ret){
		oSSL_object_free(ret);
	}
	return NULL;
}


OSSL_BOOL oSSL_csr_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_REQ(fp, obj->obj.csr)!=1){
		ossl_error("error while writing csr");
		fclose(fp);
		return FALSE;
	}
	fclose(fp);
	return TRUE;
}

OSSL_BOOL oSSL_csr_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, "open error file=%s", file);
	}
	else{
		obj->obj.csr = PEM_read_X509_REQ(fp, NULL, NULL, NULL);
		fclose(fp);
		if(!obj->obj.csr){
			err_msg = d_alloc(strlen(file)+200);
			sprintf(err_msg, "load error file=%s", file);
		}
	}
	
	if(err_msg){
		ossl_error(err_msg);
		d_f_ree(err_msg);
		return FALSE;
	}
	else{
		return TRUE;
	}
}

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

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

void oSSL_csr_free(oSSL_object *obj){
	X509_REQ_free(obj->obj.csr);
	d_f_ree(obj);
}

oSSL_method oSSL_csr_method = {
	OSSL_CSR,
	oSSL_csr_free,
	oSSL_csr_object2data,
	oSSL_csr_data2object,
	oSSL_csr_save_pem,
	oSSL_csr_load_pem
};


void oSSL_csr_init(){
	oSSL_types[OSSL_CSR] = &oSSL_csr_method;
}

