/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Host functions for signature generation.
 */

/* TODO: change all 'return 0', 'return 1' into meaningful return codes */

#include <openssl/rsa.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "2sysincludes.h"

#include "2common.h"
#include "2rsa.h"
#include "2sha.h"
#include "host_common.h"
#include "host_signature2.h"
#include "vb2_common.h"

/* Invoke [external_signer] command with [pem_file] as an argument, contents of
 * [inbuf] passed redirected to stdin, and the stdout of the command is put
 * back into [outbuf].  Returns -1 on error, 0 on success.
 */
static int sign_external(uint32_t size, const uint8_t *inbuf, uint8_t *outbuf,
			 uint32_t outbufsize, const char *pem_file,
			 const char *external_signer)
{
	int rv = 0, n;
	int p_to_c[2], c_to_p[2];  /* pipe descriptors */
	pid_t pid;

	VB2_DEBUG("Will invoke \"%s %s\" to perform signing.\n"
		 "Input to the signer will be provided on standard in.\n"
		 "Output of the signer will be read from standard out.\n",
		  external_signer, pem_file);

	/* Need two pipes since we want to invoke the external_signer as
	 * a co-process writing to its stdin and reading from its stdout. */
	if (pipe(p_to_c) < 0 || pipe(c_to_p) < 0) {
		VB2_DEBUG("pipe() error\n");
		return -1;
	}
	if ((pid = fork()) < 0) {
		VB2_DEBUG("fork() error\n");
		return -1;
	} else if (pid > 0) {  /* Parent. */
		close(p_to_c[STDIN_FILENO]);
		close(c_to_p[STDOUT_FILENO]);

		/* We provide input to the child process (external signer). */
		if (write(p_to_c[STDOUT_FILENO], inbuf, size) != size) {
			VB2_DEBUG("write() error\n");
			rv = -1;
		} else {
			/* Send EOF to child (signer process). */
			close(p_to_c[STDOUT_FILENO]);

			do {
				n = read(c_to_p[STDIN_FILENO], outbuf,
					 outbufsize);
				outbuf += n;
				outbufsize -= n;
			} while (n > 0 && outbufsize);

			if (n < 0) {
				VB2_DEBUG("read() error\n");
				rv = -1;
			}
		}
		if (waitpid(pid, NULL, 0) < 0) {
			VB2_DEBUG("waitpid() error\n");
			rv = -1;
		}
	} else {  /* Child. */
		close (p_to_c[STDOUT_FILENO]);
		close (c_to_p[STDIN_FILENO]);
		/* Map the stdin to the first pipe (this pipe gets input
		 * from the parent) */
		if (STDIN_FILENO != p_to_c[STDIN_FILENO]) {
			if (dup2(p_to_c[STDIN_FILENO], STDIN_FILENO) !=
			    STDIN_FILENO) {
				VB2_DEBUG("stdin dup2() failed\n");
				close(p_to_c[0]);
				return -1;
			}
		}
		/* Map the stdout to the second pipe (this pipe sends back
		 * signer output to the parent) */
		if (STDOUT_FILENO != c_to_p[STDOUT_FILENO]) {
			if (dup2(c_to_p[STDOUT_FILENO], STDOUT_FILENO) !=
			    STDOUT_FILENO) {
				VB2_DEBUG("stdout dup2() failed\n");
				close(c_to_p[STDOUT_FILENO]);
				return -1;
			}
		}
		/* External signer is invoked here. */
		if (execl(external_signer, external_signer, pem_file,
			  (char *) 0) < 0) {
			VB2_DEBUG("execl() of external signer failed\n");
		}
	}
	return rv;
}

struct vb2_signature *vb2_external_signature(const uint8_t *data, uint32_t size,
					     const char *key_file,
					     uint32_t key_algorithm,
					     const char *external_signer)
{
	int vb2_alg = vb2_crypto_to_hash(key_algorithm);
	uint8_t digest[VB2_MAX_DIGEST_SIZE];
	int digest_size = vb2_digest_size(vb2_alg);

	uint32_t digest_info_size = 0;
	const uint8_t *digest_info = NULL;
	if (VB2_SUCCESS != vb2_digest_info(vb2_alg,
					   &digest_info, &digest_info_size))
		return NULL;


	uint8_t *signature_digest;
	uint64_t signature_digest_len = digest_size + digest_info_size;

	int rv;

	/* Calculate the digest */
	if (VB2_SUCCESS != vb2_digest_buffer(data, size, vb2_alg,
					     digest, sizeof(digest)))
		return NULL;

	/* Prepend the digest info to the digest */
	signature_digest = calloc(signature_digest_len, 1);
	if (!signature_digest)
		return NULL;

	memcpy(signature_digest, digest_info, digest_info_size);
	memcpy(signature_digest + digest_info_size, digest, digest_size);

	/* Allocate output signature */
	uint32_t sig_size =
		vb2_rsa_sig_size(vb2_crypto_to_signature(key_algorithm));
	struct vb2_signature *sig = vb2_alloc_signature(sig_size, size);
	if (!sig) {
		free(signature_digest);
		return NULL;
	}

	/* Sign the signature_digest into our output buffer */
	rv = sign_external(signature_digest_len,    /* Input length */
			   signature_digest,        /* Input data */
			   vb2_signature_data(sig), /* Output sig */
			   sig_size,                /* Max Output sig size */
			   key_file,                /* Key file to use */
			   external_signer);        /* External cmd to invoke */
	free(signature_digest);

	if (-1 == rv) {
		VB2_DEBUG("RSA_private_encrypt() failed.\n");
		free(sig);
		return NULL;
	}

	/* Return the signature */
	return sig;
}
