/* ssh2john utility, written in April of 2011 by Dhiru Kholia for GSoC.
 *
 * This software is Copyright (c) 2011, Dhiru Kholia <dhiru.kholia at gmail.com>,
 * and it is hereby released to the general public under the following terms:
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted.
 *
 * Usage:
 *
 * 1. Run ssh2john on private key file(s) as "ssh2john [key file(s)]". Output
 *    is written to standard output.
 * 2. Run JtR on the output generated by ssh2john as "john [output file]".
 *
 * Notes:
 *
 * Output Line Format: filename:$ssh2$hex-encoding-of-entire-file-contents*file-length
 *
 * TODO:
 *
 * 1. Support more formats of SSH private keys. */

#include <stdio.h>
#include <stdlib.h>
#if !AC_BUILT || HAVE_LIMITS_H
#include <limits.h>
#endif
#include <errno.h>
#include <string.h>

#include <openssl/ssl.h>
#include <openssl/dsa.h>
#include <openssl/rsa.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>

#include "jumbo.h"
#include "common.h"
#include "arch.h"
#include "params.h"
#include "memdbg.h"

static void process_file(const char *filename)
{
	FILE *keyfile;
	int i, count;
	unsigned char buffer[LINE_BUFFER_SIZE];
	BIO *bp;
	char *nm = NULL, *header = NULL;
	unsigned char *data = NULL;
	EVP_CIPHER_INFO cipher;
	EVP_PKEY pk = {0};
	long len;
	DSA *dsapkc = NULL;
	RSA *rsapkc = NULL;
	int type = 0;
	const char unsigned *dc;

	if (!(keyfile = fopen(filename, "rb"))) {
		fprintf(stderr, "! %s : %s\n", filename, strerror(errno));
		return;
	}
	/* verify input files using OpenSSL */
	bp = BIO_new(BIO_s_file());
	if(!bp) {
		fprintf(stderr, "OpenSSL BIO allocation failure\n");
		fclose(keyfile);
		return;
	}
	if(!BIO_read_filename(bp, filename)) {
		fprintf(stderr, "OpenSSL BIO_read_filename failure\n");
		ERR_print_errors_fp(stderr);
		fclose(keyfile);
		return;
	}

	// PEM_bytes_read_bio function is in crypto/pem/pem_lib.c
	// check_pem function is in crypto/pem/pem_lib.c */
	for (;;) {
		if (!PEM_read_bio(bp, &nm, &header, &data, &len)) {
			if (ERR_GET_REASON(ERR_peek_error()) ==
					PEM_R_NO_START_LINE) {
				/* ERR_print_errors_fp(stderr); */
				fprintf(stderr, "! %s : %s\n", filename, "input keyfile validation failed");
			}
			fclose(keyfile);
			return;
		}
		if(!nm) {
			fprintf(stderr, "! %s : %s\n", filename, "input keyfile validation failed");
			goto out;
		}
		/* PEM encoded DSA and RSA private keys are supported. */
		if (!strcmp(nm, PEM_STRING_DSA)) {
			pk.save_type = EVP_PKEY_DSA;
			break;
		}
		if (!strcmp(nm, PEM_STRING_RSA)) {
			pk.save_type = EVP_PKEY_RSA;
			break;
		}
		/* PKCS#8 private keys are also supported */
		if (!strcmp(nm, PEM_STRING_PKCS8)) {
			type = 1;
			break;
		}
		OPENSSL_free(nm);
		OPENSSL_free(header);
		OPENSSL_free(data);
	}
	OPENSSL_free(nm);

	if (!PEM_get_EVP_CIPHER_INFO(header, &cipher)) {
		ERR_print_errors_fp(stderr);
		goto out;
	}
	OPENSSL_free(header);

	/* check if key has no password */
	if(type == 1) {
		EVP_PKEY *key;
		key = PEM_read_bio_PrivateKey(bp, NULL, 0, "");
		if (key != NULL) {
			fprintf(stderr, "%s has no password!\n", filename);
			goto out;
		}
	}

	dc = data;
	if (PEM_do_header(&cipher, data, &len, NULL, (char *) "")) {
		if (pk.save_type == EVP_PKEY_DSA) {
			if ((dsapkc = d2i_DSAPrivateKey(NULL, &dc, len)) != NULL) {
				fprintf(stderr, "%s has no password!\n", filename);
				DSA_free(dsapkc);
				goto out;
			}
		}
		else if (pk.save_type == EVP_PKEY_RSA) {
			if ((rsapkc = d2i_RSAPrivateKey(NULL, &dc, len)) != NULL) {
				fprintf(stderr, "%s has no password!\n", filename);
				RSA_free(rsapkc);
				goto out;
			}
		}
	}

	/* key has been verified */
	count = fread(buffer, 1, LINE_BUFFER_SIZE, keyfile);
	printf("%s:$ssh2$", basename(filename));
	for (i = 0; i < count; i++) {
		printf("%c%c", itoa16[ARCH_INDEX(buffer[i] >> 4)],
				itoa16[ARCH_INDEX(buffer[i] & 0x0f)]);
	}

	// NOTE: old test vectors / hashes won't have the "type", but that is OK
	printf("*%d*%d\n", count, type);
out:
	OPENSSL_free(data);
	BIO_free(bp);
	fclose(keyfile);
}

int ssh2john(int argc, char **argv)
{
	int i;

	/* OpenSSL init, cleanup part is left to OS */
	SSL_load_error_strings();
	SSL_library_init();
	OpenSSL_add_all_algorithms();

	if (argc < 2) {
		puts("Usage: ssh2john [key file(s)]");
		return 0;
	}

	for (i = 1; i < argc; i++)
		process_file(argv[i]);

	return 0;
}
