#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <errno.h>

#include <openssl/evp.h> // digest
#include <opensc/pkcs15.h> // sign



static int sign(const char *key_id_str, const u8 *in, u8 *const out, int len) {
	// It's assumed that len(in) == len(out), which is the case for RSA

	sc_context_param_t ctx_param;
	memset(&ctx_param, 0, sizeof(ctx_param));
	ctx_param.ver = 0;
	ctx_param.app_name = "pkcs15-check";

	sc_context_t *ctx = NULL;
	sc_card_t *card = NULL;
	struct sc_pkcs15_card *p15card = NULL;

	if (sc_context_create(&ctx, &ctx_param)) goto end;

	int i;
	sc_reader_t *reader; int slot_id = 0; // only this slot is checked
	for (i = 0; i < sc_ctx_get_reader_count(ctx); i++) {
		reader = sc_ctx_get_reader(ctx, i);
		if (sc_detect_card_presence(reader, 0) & SC_SLOT_CARD_PRESENT) {
			reader = sc_ctx_get_reader(ctx, i);
			if (sc_detect_card_presence(reader, slot_id) > 0
				 && !sc_connect_card(reader, slot_id, &card)) break;
			else card = NULL; } }
	if (card == NULL) goto end;
	else sc_lock(card);

	if (sc_pkcs15_bind(card, &p15card)) goto end;

	struct sc_pkcs15_object *key;
	sc_pkcs15_id_t key_id;
	if (sc_pkcs15_hex_string_to_id(key_id_str, &key_id)) goto end;
	if (sc_pkcs15_find_prkey_by_id_usage( p15card, &key_id,
		SC_PKCS15_PRKEY_USAGE_SIGN |
		SC_PKCS15_PRKEY_USAGE_SIGNRECOVER |
		SC_PKCS15_PRKEY_USAGE_NONREPUDIATION, &key ) < 0) goto end;

	if ((len = sc_pkcs15_compute_signature(
		p15card, key, 0, // 0 is opt_crypt_flags
		in, len, out, len )) < 0) goto end;

	return(0);

end:
	/* fprintf(stderr, "OpenSC error: %s\n", sc_strerror(len)); */
	if (p15card) sc_pkcs15_unbind(p15card);
	if (card) {
		sc_unlock(card);
		sc_disconnect_card(card, 0); }
	if (ctx) sc_release_context(ctx);
	errno = EFAULT;
	return(-1); }




int digest(const char *path, u8 *const out, int *const out_len) {
	EVP_MD_CTX hash[5];
	unsigned int hash_count = sizeof(hash) / sizeof(hash[0]);

	int i;
	for (i=0; i < hash_count; i++) EVP_MD_CTX_init(&hash[i]);

	if (!( EVP_DigestInit(&hash[0], EVP_md5())
			&& EVP_DigestInit(&hash[1], EVP_ripemd160())
			&& EVP_DigestInit(&hash[2], EVP_mdc2())
			&& EVP_DigestInit(&hash[3], EVP_sha1())
			&& EVP_DigestInit(&hash[4], EVP_sha512()) )) {
		errno = EFAULT;
		goto end; }

	FILE *src;
	if (!strcmp(path, "-")) src = stdin;
	else if ((src = fopen(path, "r")) == NULL) {
		errno = EIO;
		goto end; }

	u8 chunk[8192];
	size_t bs; unsigned int len;

	while ((bs = fread(chunk, 1, sizeof(chunk), src)) != 0)
		for (i=0; i < hash_count; i++)
			if (!EVP_DigestUpdate(&hash[i], chunk, bs)) {
				errno = EFAULT;
				goto end; }
	fclose(src);

	*out_len = 0;
	for (i=0; i < hash_count; i++) {
		if (!EVP_DigestFinal_ex(&hash[i], chunk, &len)) {
			errno = EFAULT;
			goto end; }
		memcpy(out + *out_len, chunk, len);
		*out_len += len; }

	for (i=0; i < hash_count; i++) EVP_MD_CTX_cleanup(&hash[i]);

	return(0);

end:
	return(-1); }



int key_size = 256;

int main(const int argc, char *const *argv) {
	int usage() {
		fprintf( stderr, ( "Usage: %s ( -h | --help | [ -d ] [ -k<KEY_ID> ] [ FILE ] )\n"
			"Calculate %d-byte padded binary digest (or it's signature,"
			" with KEY_ID specified) of a FILE (dash for stdin, empty for"
			" %s) and write it to stdout (binary data or hexdigest w/ '-d' key).\n" ),
			argv[0], key_size, argv[0] );
		return(1); }


	// CLI
	if (argc > 4 || (argc > 1 && (
			!strcmp(argv[1], "-h")
			|| !strcmp(argv[1], "--help") ))) {
		usage();
		return(0); }

	char *key_id = NULL;
	int i = 1, out_ascii = 0;
	if ((argc > i) && !strcmp(argv[i], "-d")) {
		out_ascii = 1;
		i++; }
	if ((argc > i) && !strncmp(argv[i], "-k", 2)) key_id = argv[i++] + 2;
	if (argc > (i+1)) return(usage());
	char *src = (argc > i) ? argv[i] : argv[0];

	// Find self-binary if no FILE specified
	struct stat stat_void;
	char *path = NULL;
	if (src == argv[0] && stat(argv[0], &stat_void)) {
		char *env_path = getenv("PATH");
		char *dir = strtok(env_path, ":");
		path = malloc(256);
		src = path;
		while(1) {
			strcpy(path, dir);
			strcat(path, "/");
			strcat(path, argv[0]);
			if (!stat(path, &stat_void)) break;
			if ((dir = strtok(NULL, ":")) == NULL) {
				fprintf(stderr, "Unable to auto-determine self-binary location\n");
				return(1); } } }


	// Digest
	u8 *hash = malloc(key_size);
	int len;
	memset(hash, 0, key_size);
	if (digest(src, hash, &len)) {
		fprintf(stderr, "Digest error: %s\n", strerror(errno));
		return(1); }
	if (path) free(path);

	// Sign
	if (key_id) {
		u8 *crypt = malloc(key_size);
		if (sign(key_id, hash, crypt, key_size)) {
			fprintf(stderr, "Sign error: %s\n", strerror(errno));
			return(1); }
		free(hash);
		hash = crypt; }


	// Dump
	if (!out_ascii) {
		if (!fwrite(hash, 1, key_size, stdout)) {
			fprintf(stderr, "Error on digest output: %s\n", strerror(errno));
			return(1); } }
	else {
		for (i=0; i < key_size; i++)
			if (!fprintf(stdout, "%02x", hash[i])) {
				fprintf(stderr, "Error on digest output: %s\n", strerror(errno));
				return(1); } }

	return(0); }



