/* Certificate and Private Key Format Conversions for FreeS/WAN
 *
 * Copyright (C) 2000 Andreas Gruenbacher, <a.gruenbacher@computer.org>
 *
 * This program 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * 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.
 */

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>

#define _GNU_SOURCE
#include <getopt.h>

#include <termios.h>

#include <openssl/crypto.h>
#include <openssl/buffer.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/pem.h>

struct option long_options[] = {
	{"certificate",   0, 0, 'c'},
	{"key",           0, 0, 'k'},
	{"left",          0, 0, 'l'},
	{"raw",           0, 0, 'R'},
	{"right",         0, 0, 'r'},
	{"type",          1, 0,  1 },
	{"format",        1, 0,  2 },
	{"quiet",         0, 0, 'q'},
	{"directory",     1, 0, 'C'},
	{"version",       0, 0, 'v'},
	{"help",          0, 0, 'h'},
	{NULL,            0, 0,  0 }
};

int prompt_for_passwords = 0;
const char *peer_prefix = NULL;
int raw_file = 0;
int opt_quiet = 0;
const char *input_file = NULL;

#define DUMP_CERTIFICATE	1
#define DUMP_KEY		2
int opt_what = 0;

BIO *bio_err = NULL;

enum input_file_type { IN_PKCS12 = 1, IN_X509 = 2, IN_RSA = 3 };
enum input_format { FORMAT_ASN1 = 1, FORMAT_PEM = 3 };

void print_errors(int ssl, const char *fmt, ...)
{
	if (fmt) {
		va_list args;
	
		va_start(args, fmt);
		if (input_file)
			fprintf(stderr, "%s: ", input_file);
		fprintf(stderr, "Error ");
		vfprintf(stderr, fmt, args);
		fprintf(stderr, "\n");
		va_end(args);
	}
	if (ssl && !opt_quiet)
		ERR_print_errors(bio_err);
}

const char *read_password(void)
{
	static char password[1024];
	struct termios saved, no_echo;

	printf("Password: ");

	tcgetattr(0, &saved);
	no_echo = saved;
	no_echo.c_lflag &= ~ECHO;
	tcsetattr(0, TCSADRAIN, &no_echo);
	fgets(password, sizeof(password), stdin);
	tcsetattr(0, TCSADRAIN, &saved);
	puts("");

	if (strchr(password, '\n'))
		*strchr(password, '\n') = '\0';
	return password;
}

void BN_print_0(BIGNUM *num)
{
	int bits = BN_num_bits(num);

	if ((bits-1) % 8 <= 3)
		putchar('0');
	BN_print_fp(stdout, num);
}

#define print(what, num) do { \
	printf("\t%s 0x", what); \
	BN_print_0(num); \
	putchar('\n'); \
	} while(0)

int dump_rsa_private_key(RSA *rsa)
{
	if (rsa->d == NULL) {
		print_errors(0, "no private exponent");
		return 1;
	}
	if (opt_what & DUMP_KEY) {
		printf(": RSA {\n");
		print("Modulus:        ", rsa->n);
		print("PublicExponent: ", rsa->e);
		print("PrivateExponent:", rsa->d);
		print("Prime1:         ", rsa->p);
		print("Prime2:         ", rsa->q);
		print("Exponent1:      ", rsa->dmp1);
		print("Exponent2:      ", rsa->dmq1);
		print("Coefficient:    ", rsa->iqmp);
                printf("  }\n");
	}
	return 0;
}

#undef print

int dump_x509_certificate(X509 *x509)
{
	BUF_MEM *subject;
	EVP_PKEY *pkey;
	RSA *rsa;
	int i, error = 0;
	
	if (!x509->cert_info || !x509->cert_info->subject ||
            !x509->cert_info->subject->bytes) {
		fprintf(stderr, "Certificate contains no subject\n");
		return 1;
	}
	subject = &x509->cert_info->subject->bytes[0];

	if (opt_what & DUMP_CERTIFICATE) {
	  if(!raw_file) {
		printf("\t%sid=@~", peer_prefix);
		for (i = 0; i < subject->length; i++)
			printf("%02X", (unsigned char)subject->data[i]);
		printf("\n");
	  }
	}

	pkey = X509_get_pubkey(x509);
	if (pkey == NULL) {
		print_errors(1, "getting public key");
		return 1;
	}
	if (pkey->type != EVP_PKEY_RSA) {
		print_errors(0, "not an RSA public key");
		error = 1;
		goto cleanup;
	}
	rsa = pkey->pkey.rsa;
	
	if (opt_what & DUMP_CERTIFICATE) {
		int bytes = BN_num_bytes(rsa->e);
		if(!raw_file) {
		  printf("\t%srsasigkey=0x", peer_prefix);
		}
		printf("%02X", bytes);
		BN_print_0(rsa->e);
		BN_print_0(rsa->n);
		putchar('\n');
	}

cleanup:
	EVP_PKEY_free(pkey);
	return 0;
}

int read_pkcs12_safebag(PKCS12_SAFEBAG *bag, const char *password)
{
	PKCS8_PRIV_KEY_INFO *p8;
	EVP_PKEY *pkey;
	X509 *x509;
	RSA *rsa;
	int error = 0;
	
	switch (M_PKCS12_bag_type(bag)) {
		case NID_keyBag:
			p8 = bag->value.keybag;
			pkey = EVP_PKCS82PKEY(p8);
			if (!pkey) {
				print_errors(1, "grabbing private key");
				return 1;
			}
			rsa = EVP_PKEY_get1_RSA(pkey);
			EVP_PKEY_free(pkey);
			if (!rsa) {
				print_errors(1, "getting RSA key");
				return 1;
			}
			error = dump_rsa_private_key(rsa);
			RSA_free(rsa);
			break;

		case NID_pkcs8ShroudedKeyBag:
			p8 = M_PKCS12_decrypt_skey(bag, password, -1);
			if (!p8) {
				print_errors(1, "decrypting private key");
				return 1;
			}
			pkey = EVP_PKCS82PKEY(p8);
			PKCS8_PRIV_KEY_INFO_free(p8);
			if (!pkey) {
				print_errors(1, "grabbing private key");
				return 1;
			}
			rsa = EVP_PKEY_get1_RSA(pkey);
			EVP_PKEY_free(pkey);
			if (!rsa) {
				print_errors(1, "getting RSA private key");
				return 1;
			}
			error = dump_rsa_private_key(rsa);
			RSA_free(rsa);
			break;

		case NID_certBag:
			if (M_PKCS12_cert_bag_type(bag) != NID_x509Certificate){
				print_errors(0, "not an X.509 certificate");
				return 1;
			}
			/* only dump the key's certificate */
			if (!PKCS12_get_attr(bag, NID_localKeyID))
				return 0;
			x509 = M_PKCS12_certbag2x509(bag);
			if (!x509) {
				print_errors(1, "grabbing certificate");
				return 1;
			}
			if (dump_x509_certificate(x509))
				return 1;
			X509_free(x509);
			break;

		/* default: */
			/* other bag entry */
	}
	return error;
}

int read_pkcs12_file(BIO *bio_in)
{
	const char *password = "\0";
	PKCS12 *p12;
	STACK /* _OF(PKCS7) */ *asafes = NULL;
	PKCS7 *p7;
	int i;

	p12 = d2i_PKCS12_bio (bio_in, NULL);
	if (!p12) {
		print_errors(1, "reading PKCS12 file");
		return 1;
	}
	if (!PKCS12_verify_mac(p12, password, 0)) {
		if (prompt_for_passwords)
			password = read_password();
		if (!prompt_for_passwords ||
		    !PKCS12_verify_mac(p12, password, -1)) {
			print_errors(1, "verifying MAC: wrong password?");
			return 1;
		}
	}

	/* Unpack PKCS12 file */
	asafes = M_PKCS12_unpack_authsafes (p12);
	if (!asafes) {
		print_errors(1, "unpacking PKCS12 file");
		return 1;
	}
	for (i = 0; i < sk_num(asafes); i++) {
		int bagnid, j;
		STACK /* _OK(PKCS12) */ *bags;

		p7 = (PKCS7 *) sk_value(asafes, i);
		bagnid = OBJ_obj2nid(p7->type);
		if (bagnid == NID_pkcs7_data)
			bags = M_PKCS12_unpack_p7data(p7);
		else if (bagnid == NID_pkcs7_encrypted)
			bags = M_PKCS12_unpack_p7encdata(p7, password, -1);
		else
			continue;
		if (!bags) {
			print_errors(1, "unpacking PKCS12 file");
			return 1;
		}
		for (j = 0; j < sk_num(bags); j++) {
			PKCS12_SAFEBAG *bag =
				(PKCS12_SAFEBAG *)sk_value(bags, j);
			if (read_pkcs12_safebag(bag, password))
				return 1;
		}
		sk_pop_free(bags, (void*)PKCS12_SAFEBAG_free);
	}
	sk_pop_free(asafes, (void*)PKCS7_free);
	return 0;
}

int read_x509_file(BIO *bio_in, enum input_format input_format)
{
	X509 *x509 = NULL;
	int error;

	if (input_format == FORMAT_PEM)
		x509 = PEM_read_bio_X509(bio_in, NULL, NULL, NULL);
	else if (input_format == FORMAT_ASN1)
		x509 = d2i_X509_bio(bio_in, NULL);
	if (!x509) {
		print_errors(1, "reading X509 certificate");
		return 1;
	}
	error = dump_x509_certificate(x509);
	X509_free(x509);

	return error;
}

int read_rsa_file(BIO *bio_in, enum input_format input_format)
{
	RSA *rsa = NULL;
	int error;

	if (input_format == FORMAT_PEM)
		rsa = PEM_read_bio_RSAPrivateKey(bio_in, NULL, NULL, NULL);
	else if (input_format == FORMAT_ASN1)
		rsa = d2i_RSAPrivateKey_bio(bio_in, NULL);
	if (!rsa) {
                print_errors(1, "reading RSA private key");
                return 1;
        }
	error = dump_rsa_private_key(rsa);
	RSA_free(rsa);
	return error;
}

int main(int argc, char *argv[])
{
	enum input_file_type input_file_type = 0;
	enum input_format input_format = 0;
	int error = 0, opt;

	prompt_for_passwords = isatty(0) && isatty(1);

	while ((opt = getopt_long(argc, argv, "cklrqC:hvR",
	                          long_options, NULL)) != -1) {
		switch(opt) {
			case 1:  /* input file type */
				if (!strcmp(optarg, "pkcs12"))
					input_file_type = IN_PKCS12;
				else if (!strcmp(optarg, "x509"))
					input_file_type = IN_X509;
				else if (!strcmp(optarg, "rsa"))
					input_file_type = IN_RSA;
				else
					goto synopsis;
				break;

			case 2:  /* input format */
				if (!strcmp(optarg, "PEM"))
					input_format = FORMAT_PEM;
				else if (!strcmp(optarg, "DER"))
					input_format = FORMAT_ASN1;
				else
					goto synopsis;
				break;

			case 'c':  /* certificate */
				opt_what |= DUMP_CERTIFICATE;
				if (!input_file_type)
					input_file_type = IN_X509;
				break;

			case 'k':  /* key */
				opt_what |= DUMP_KEY;
				if (!input_file_type)
					input_file_type = IN_RSA;
				break;

			case 'l':  /* left */
				if (!opt_what)
					opt_what = DUMP_CERTIFICATE;
				peer_prefix = "left";
				break;

			case 'r':  /* right */
				if (!opt_what)
					opt_what = DUMP_CERTIFICATE;
				peer_prefix = "right";
				break;

		        case 'R':  /* DUMP it raw */
			        if(!opt_what) opt_what=DUMP_CERTIFICATE;
                                raw_file=1;
				break;

			case 'C':  /* change directory */
				if (chdir(optarg) && errno) {
					perror(optarg);
					error = 1;
				}
				break;

			case 'v':  /* print version and exit */
				printf("%s " VERSION "\n", argv[0]);
				return 0;

			case 'q':  /* quiet */
				opt_quiet = 1;
				break;

			default:
				goto synopsis;
		}
	}
	if (peer_prefix == NULL)
		peer_prefix = "";

	if (optind+1 != argc || !opt_what)
		goto synopsis;

	if (!input_file_type)
		input_file_type = IN_X509;
	if (!input_format)
		input_format = FORMAT_PEM;

	if (opt_quiet)
		dup2(1,2);  /* stderr is stdout (awk hack) */

	ERR_load_crypto_strings();
	SSLeay_add_all_algorithms();
	bio_err = BIO_new_fp (stderr, BIO_NOCLOSE);
	
	while (optind < argc) {
		BIO *bio_in;

		input_file = argv[optind];
		bio_in = BIO_new_file(input_file, "rb");
		if (!bio_in) {
			perror(input_file);
			error = 1;
		} else {
			if (input_file_type == IN_PKCS12)
				error |= read_pkcs12_file(bio_in);
			else if (input_file_type == IN_X509)
				error |= read_x509_file(bio_in, input_format);
			else if (input_file_type == IN_RSA)
				error |= read_rsa_file(bio_in, input_format);
			BIO_free(bio_in);
		}
		optind++;
	}

	BIO_free(bio_err);

	return error;

synopsis:
	fprintf(stderr, "SYNOPSIS: %s --cert|--key [--left|--right] "
	        "options file\n", argv[0]);
	if (!opt_quiet) {
		fprintf(stderr, "\t-c --cert\tprint certificate data\n"
		        "\t-k --key\tprint private RSA key data\n"
		        "\t-l --left\tleft-side parameters for ipsec.conf\n"
		        "\t-r --right\tright-side parameters for ipsec.conf\n"
		        "\t   --type=[x509|rsa|pkcs12]\tselect input file type\n"
		        "\t   --format=[PEM|DER]\tselect input file format\n"
		        "\t-C --directory=dir\tchange into dir first\n"
		        "\t-q --quiet\tsingle line error messages to stdout\n");
	}
	return error;
}

