/*
 * Copyright (c) 2007 iptelorg GmbH
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*!
 * \file
 * \brief Kamailio auth-identity :: Crypt
 * \ingroup auth-identity
 * Module: \ref auth-identity
 */


#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/crypto.h>
#include <openssl/x509_vfy.h>

#include "../../core/mem/mem.h"
#include "../../core/parser/parse_uri.h"

#include "auth_identity.h"


int retrieve_x509(X509 **pcert, str *scert, int bacceptpem)
{
	BIO *bcer = NULL;
	char serr[160];
	int iRet = 0;


	if(!(bcer = BIO_new(BIO_s_mem()))) {
		LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: Unable to create BIO\n");

		return -1;
	}

	do {
		if(BIO_write(bcer, scert->s, scert->len) != scert->len) {
			LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: Unable to write BIO\n");
			iRet = -2;
			break;
		}

		/* RFC 4474 only accepts certs in the DER form but it can not harm
		 * to be a little bit more flexible and accept PEM as well. */
		if(bacceptpem && scert->len > BEGIN_PEM_CERT_LEN
				&& memmem(scert->s, scert->len, BEGIN_PEM_CERT,
						BEGIN_PEM_CERT_LEN)) {
			if(!(*pcert = PEM_read_bio_X509(bcer, NULL, NULL, NULL))) {
				ERR_error_string_n(ERR_get_error(), serr, sizeof(serr));
				LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: PEM Certificate %s\n",
						serr);
				iRet = -4;
			}
		} else {
			if(!(*pcert = d2i_X509_bio(bcer, NULL))) {
				ERR_error_string_n(ERR_get_error(), serr, sizeof(serr));
				LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: DER Certificate %s\n",
						serr);
				iRet = -3;
			}
		}
	} while(0);

	BIO_free(bcer);

	return iRet;
}

int check_x509_subj(X509 *pcert, str *sdom)
{
	STACK_OF(GENERAL_NAME) * altnames;
	int ialts, i1, ilen, altlen;
	const GENERAL_NAME *actname;
	char scname[AUTH_DOMAIN_LENGTH];
	char *altptr;
	struct sip_uri suri;
	int ret = 0;


	/* we're looking for subjectAltName for the first time */
	altnames = X509_get_ext_d2i(pcert, NID_subject_alt_name, NULL, NULL);

	if(altnames) {
		ialts = sk_GENERAL_NAME_num(altnames);

		for(i1 = 0; i1 < ialts; i1++) {
			actname = sk_GENERAL_NAME_value(altnames, i1);

			if(actname->type == GEN_DNS || actname->type == GEN_URI) {
				/* we've found one */
#if OPENSSL_VERSION_NUMBER >= 0x010100000L
				altptr = (char *)ASN1_STRING_get0_data(actname->d.ia5);
#else
				altptr = (char *)ASN1_STRING_data(actname->d.ia5);
#endif
				if(actname->type == GEN_URI) {
					if(parse_uri(altptr, strlen(altptr), &suri) != 0) {
						continue;
					}
					if(!(suri.type == SIP_URI_T || suri.type == SIPS_URI_T)) {
						continue;
					}
					if(suri.user.len != 0 || suri.passwd.len != 0) {
						continue;
					}
					altptr = suri.host.s;
					altlen = suri.host.len;
				} else {
					altlen = strlen(altptr);
				}
				if(sdom->len != altlen
						|| strncasecmp(altptr, sdom->s, sdom->len)) {
					LOG(L_INFO, "AUTH_IDENTITY VERIFIER: subAltName of "
								"certificate doesn't match host name\n");
					ret = -1;
				} else {
					ret = 1;
					break;
				}
			}
		}
		GENERAL_NAMES_free(altnames);
	}

	if(ret != 0) {
		return ret == 1 ? 0 : ret;
	}

	/* certificate supplier host and certificate subject match check */
	ilen = X509_NAME_get_text_by_NID(X509_get_subject_name(pcert),
			NID_commonName, scname, sizeof(scname));
	if(sdom->len != ilen || strncasecmp(scname, sdom->s, sdom->len)) {
		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: common name of certificate "
					"doesn't match host name\n");
		return -2;
	}

	return 0;
}

int verify_x509(X509 *pcert, X509_STORE *pcacerts)
{
	X509_STORE_CTX *ca_ctx = NULL;
	char *strerr;

	ca_ctx = X509_STORE_CTX_new();
	if(ca_ctx == NULL) {
		LM_ERR("cannot get a x509 context\n");
		return -1;
	}

	if(X509_STORE_CTX_init(ca_ctx, pcacerts, pcert, NULL) != 1) {
		LOG(L_ERR,
				"AUTH_IDENTITY:verify_x509: Unable to init X509 store ctx\n");
		X509_STORE_CTX_free(ca_ctx);
		return -1;
	}

	if(X509_verify_cert(ca_ctx) != 1) {
		strerr = (char *)X509_verify_cert_error_string(
				X509_STORE_CTX_get_error(ca_ctx));
		LOG(L_ERR,
				"AUTH_IDENTITY VERIFIER: Certificate verification error: %s\n",
				strerr);
		X509_STORE_CTX_cleanup(ca_ctx);
		X509_STORE_CTX_free(ca_ctx);
		return -2;
	}
	X509_STORE_CTX_cleanup(ca_ctx);
	X509_STORE_CTX_free(ca_ctx);

	LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Certificate is valid\n");

	return 0;
}

int rsa_sha1_enc(
		dynstr *sdigeststr, dynstr *senc, dynstr *sencb64, RSA *hmyprivkey)
{
	unsigned char sstrcrypted[SHA_DIGEST_LENGTH];
	int ires;
	char serr[160];


	SHA1((unsigned char *)getstr_dynstr(sdigeststr).s,
			getstr_dynstr(sdigeststr).len, sstrcrypted);

#ifdef NEW_RSA_PROC
	ires = senc->size;
	if(RSA_sign(NID_sha1, sstrcrypted, sizeof sstrcrypted,
			   (unsigned char *)getstr_dynstr(senc).s, (unsigned int *)&ires,
			   hmyprivkey)
			!= 1) {
		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
		LOG(L_ERR, "AUTH_IDENTITY:rsa_sha1_enc: '%s'\n", serr);
		return -2;
	}
#else
	ires = RSA_private_encrypt(sizeof sstrcrypted, sstrcrypted,
			(unsigned char *)getstr_dynstr(senc).s, hmyprivkey,
			RSA_PKCS1_PADDING);
	if(ires < 0) {
		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
		LOG(L_ERR, "AUTH_IDENTITY:rsa_sha1_enc: '%s'\n", serr);
		return -1;
	}
#endif

	base64encode(getstr_dynstr(senc).s, senc->size, getstr_dynstr(sencb64).s,
			&getstr_dynstr(sencb64).len);

	return 0;
}

int rsa_sha1_dec(char *sencedsha, int iencedshalen, char *ssha, int sshasize,
		int *ishalen, X509 *pcertx509)
{
	EVP_PKEY *pkey;
	RSA *hpubkey;
	unsigned long lerr;
	char serr[160];


	pkey = X509_get_pubkey(pcertx509);
	if(pkey == NULL) {
		lerr = ERR_get_error();
		ERR_error_string_n(lerr, serr, sizeof(serr));
		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: Pubkey %s\n", serr);
		return -1;
	}

	X509_free(pcertx509);

	hpubkey = EVP_PKEY_get1_RSA(pkey);
	EVP_PKEY_free(pkey);
	if(hpubkey == NULL) {
		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: Error getting RSA key\n");
		return -2;
	}

#ifdef NEW_RSA_PROC
	if(RSA_verify(NID_sha1, (unsigned char *)ssha, sshasize,
			   (unsigned char *)sencedsha, iencedshalen, hpubkey)
			!= 1) {
		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: RSA verify returned: '%s'\n",
				ERR_error_string(ERR_get_error(), NULL));
		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: RSA verify failed -> Invalid "
					"Identity Header\n");
		RSA_free(hpubkey);
		return -5;
	}
#else
	/* it is bigger than the output buffer */
	if(RSA_size(hpubkey) > sshasize) {
		LOG(L_ERR,
				"AUTH_IDENTITY:decrypt_identity: Unexpected Identity hash "
				"length (%d > %d)\n",
				RSA_size(hpubkey), sshasize);
		RSA_free(hpubkey);
		return -3;
	}
	*ishalen = RSA_public_decrypt(iencedshalen, (unsigned char *)sencedsha,
			(unsigned char *)ssha, hpubkey, RSA_PKCS1_PADDING);
	if(*ishalen <= 0) {
		lerr = ERR_get_error();
		ERR_error_string_n(lerr, serr, sizeof(serr));
		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: RSA operation error %s\n",
				serr);
		RSA_free(hpubkey);
		return -4;
	}
#endif

	RSA_free(hpubkey);

	return 0;
}

/* copypasted from ser/modules/rr/avp_cookie.c + this adds '=' sign! ) */
void base64encode(char *src_buf, int src_len, char *tgt_buf, int *tgt_len)
{
	static char code64[64 + 1] =
			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	int pos;
	for(pos = 0, *tgt_len = 0; pos < src_len; pos += 3, *tgt_len += 4) {
		tgt_buf[*tgt_len + 0] = code64[(unsigned char)src_buf[pos + 0] >> 2];
		tgt_buf[*tgt_len + 1] =
				code64[(((unsigned char)src_buf[pos + 0] & 0x03) << 4)
						| ((pos + 1 < src_len)
										? ((unsigned char)src_buf[pos + 1] >> 4)
										: 0)];
		if(pos + 1 < src_len)
			tgt_buf[*tgt_len + 2] =
					code64[(((unsigned char)src_buf[pos + 1] & 0x0F) << 2)
							| ((pos + 2 < src_len) ? (
									   (unsigned char)src_buf[pos + 2] >> 6)
												   : 0)];
		else
			tgt_buf[*tgt_len + 2] = '=';
		if(pos + 2 < src_len)
			tgt_buf[*tgt_len + 3] =
					code64[(unsigned char)src_buf[pos + 2] & 0x3F];
		else
			tgt_buf[*tgt_len + 3] = '=';
	}
}


/* copypasted from ser/modules/rr/avp_cookie.c */
void base64decode(char *src_buf, int src_len, char *tgt_buf, int *tgt_len)
{
	int pos, i, n;
	unsigned char c[4];
	for(pos = 0, i = 0, *tgt_len = 0; pos < src_len; pos++) {
		if(src_buf[pos] >= 'A' && src_buf[pos] <= 'Z')
			c[i] = src_buf[pos] - 65; /* <65..90>  --> <0..25> */
		else if(src_buf[pos] >= 'a' && src_buf[pos] <= 'z')
			c[i] = src_buf[pos] - 71; /* <97..122>  --> <26..51> */
		else if(src_buf[pos] >= '0' && src_buf[pos] <= '9')
			c[i] = src_buf[pos] + 4; /* <48..56>  --> <52..61> */
		else if(src_buf[pos] == '+')
			c[i] = 62;
		else if(src_buf[pos] == '/')
			c[i] = 63;
		else /* '=' */
			c[i] = 64;
		i++;
		if(pos == src_len - 1) {
			while(i < 4) {
				c[i] = 64;
				i++;
			}
		}
		if(i == 4) {
			if(c[0] == 64)
				n = 0;
			else if(c[2] == 64)
				n = 1;
			else if(c[3] == 64)
				n = 2;
			else
				n = 3;
			switch(n) {
				case 3:
					tgt_buf[*tgt_len + 2] = (char)(((c[2] & 0x03) << 6) | c[3]);
					/* no break */
				case 2:
					tgt_buf[*tgt_len + 1] =
							(char)(((c[1] & 0x0F) << 4) | (c[2] >> 2));
					/* no break */
				case 1:
					tgt_buf[*tgt_len + 0] = (char)((c[0] << 2) | (c[1] >> 4));
					break;
			}
			i = 0;
			*tgt_len += n;
		}
	}
}

int x509_get_validitytime(time_t *tout, ASN1_UTCTIME *tin)
{
	char *sasn1;
	int i1;
	struct tm tmptm;


	memset(&tmptm, 0, sizeof(tmptm));
	i1 = tin->length;
	sasn1 = (char *)tin->data;

	if(i1 < 10)
		return -1;
	/*	if (sasn1[i1-1]!='Z')
		return -1;*/
	for(i1 = 0; i1 < 10; i1++)
		if((sasn1[i1] > '9') || (sasn1[i1] < '0'))
			return -2;

	tmptm.tm_year = (sasn1[0] - '0') * 10 + (sasn1[1] - '0');
	if(tmptm.tm_year < 50)
		tmptm.tm_year += 100;

	tmptm.tm_mon = (sasn1[2] - '0') * 10 + (sasn1[3] - '0') - 1;
	if((tmptm.tm_mon > 11) || (tmptm.tm_mon < 0))
		return -3;

	tmptm.tm_mday = (sasn1[4] - '0') * 10 + (sasn1[5] - '0');
	tmptm.tm_hour = (sasn1[6] - '0') * 10 + (sasn1[7] - '0');
	tmptm.tm_min = (sasn1[8] - '0') * 10 + (sasn1[9] - '0');

	if((sasn1[10] >= '0') && (sasn1[10] <= '9') && (sasn1[11] >= '0')
			&& (sasn1[11] <= '9'))
		tmptm.tm_sec = (sasn1[10] - '0') * 10 + (sasn1[11] - '0');

#ifdef HAVE_TIMEGM
	*tout = timegm(&tmptm);
#else
	*tout = _timegm(&tmptm);
#endif

	return 0;
}

int x509_get_notbefore(time_t *tout, X509 *pcert)
{
	return (x509_get_validitytime(tout, X509_get_notBefore(pcert)));
}

int x509_get_notafter(time_t *tout, X509 *pcert)
{
	return (x509_get_validitytime(tout, X509_get_notAfter(pcert)));
}
