/*
 * RabbitSign - Tools for signing TI graphing calculator software
 * Copyright (C) 2009 Benjamin Moody
 *
 * 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 3 of the
 * License, or (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif

#ifdef HAVE_STRING_H
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
#  include <strings.h>
# endif
#endif

#include "rabbitsign.h"
#include "internal.h"
#include "autokeys.h"

/*
 * Get key ID for the given program.
 */
unsigned long rs_program_get_key_id(const RSProgram* prgm)
{
  const unsigned char* hdr;
  unsigned long hdrstart, hdrsize;

  if (prgm->header_length > 0) {
    hdr = prgm->header;
    hdrsize = prgm->header_length;
  }
  else if (prgm->length > 0) {
    hdr = prgm->data;
    hdrsize = prgm->length;
    if (hdrsize > 128)
      hdrsize = 128;
  }
  else
    return 0;

  rs_get_field_size(hdr, &hdrstart, NULL);
  hdrsize -= hdrstart;

  if (hdr[0] == 0x81)
    return rs_get_numeric_field(0x8110, hdr + hdrstart, hdrsize);
  else
    return rs_get_numeric_field(0x8010, hdr + hdrstart, hdrsize);
}

/*
 * Try to load key from a file.
 */
static int try_key_file(RSKey* key, /* key structure to store result */
			const char* a, /* first path element */
			const char* b, /* second path element */
			const char* c) /* third path element */
{
  char* s;
  FILE* f;
  int e;

  s = rs_malloc(strlen(a) + strlen(b) + strlen(c) + 1);
  if (!s)
    return RS_ERR_OUT_OF_MEMORY;
  strcpy(s, a);
  strcat(s, b);
  strcat(s, c);

  f = fopen(s, "rt");
  if (!f) {
    rs_free(s);
    return RS_ERR_KEY_NOT_FOUND;
  }

  if ((e = rs_read_key_file(key, f, s, 1))) {
    fclose(f);
    rs_free(s);
    return e;
  }

  fclose(f);
  rs_free(s);
  return RS_SUCCESS;
}

/*
 * Try to locate a given key file.
 */
static int find_key_file(RSKey* key,           /* key structure to
						  store result */
			 const char* filename) /* file name to search
						  for */
{
  const char* p;
  int e;

  e = try_key_file(key, "", "", filename);
  if (e != RS_ERR_KEY_NOT_FOUND)
    return e;

  if ((p = getenv("RABBITSIGN_KEY_DIR"))) {
#if defined(__MSDOS__) || defined(__WIN32__)
    e = try_key_file(key, p, "\\", filename);
#else
    e = try_key_file(key, p, "/", filename);
#endif
    if (e != RS_ERR_KEY_NOT_FOUND)
      return e;
  }

#if defined(__MSDOS__) || defined(__WIN32__)
  if ((p = getenv("TI83PLUSDIR"))) {
    e = try_key_file(key, p, "\\Utils\\", filename);
    if (e != RS_ERR_KEY_NOT_FOUND)
      return e;
  }
#endif

#ifdef SHARE_DIR
  e = try_key_file(key, SHARE_DIR, "", filename);
  if (e != RS_ERR_KEY_NOT_FOUND)
    return e;
#endif

  return RS_ERR_KEY_NOT_FOUND;
}

/*
 * Find key file for the given ID.
 */
int rs_key_find_for_id(RSKey* key,	    /* key structure to store
					       result */
		       unsigned long keyid, /* key ID to search for */
		       int publiconly)	    /* 1 = search for public
					       key only */
{
  static const char* fmts[] = { "%02lx.%s", "%02lX.%s",
				"%04lx.%s", "%04lX.%s", NULL };
  char buf[16];
  int i, e;

  mpz_set_ui(key->p, 0);
  mpz_set_ui(key->q, 0);
  mpz_set_ui(key->qinv, 0);
  mpz_set_ui(key->d, 0);

  if (keyid > 0xFF)
    sprintf(buf, "%04lX", keyid);
  else
    sprintf(buf, "%02lX", keyid);

  for (i = 0; known_priv_keys[i].n; i++) {
    if (keyid == known_priv_keys[i].id) {
      if ((e = rs_parse_key_value(key->n, known_priv_keys[i].n)))
	return e;

      if (known_priv_keys[i].p
	  && (e = rs_parse_key_value(key->p, known_priv_keys[i].p)))
	return e;
      if (known_priv_keys[i].q
	  && (e = rs_parse_key_value(key->q, known_priv_keys[i].q)))
	return e;
      if (known_priv_keys[i].d
	  && (e = rs_parse_key_value(key->d, known_priv_keys[i].d)))
	return e;

      rs_message(2, key, NULL, "Loaded builtin private key %s:", buf);
      rs_message(2, key, NULL, " n = %ZX", key->n);
      if (mpz_sgn(key->p))
	rs_message(2, key, NULL, " p = %ZX", key->p);
      if (mpz_sgn(key->q))
	rs_message(2, key, NULL, " q = %ZX", key->q);
      if (mpz_sgn(key->d))
	rs_message(2, key, NULL, " d = %ZX", key->d);

      key->id = keyid;
      return 0;
    }
  }

  if (publiconly) {
    for (i = 0; known_pub_keys[i].n; i++) {
      if (keyid == known_pub_keys[i].id) {
	if ((e = rs_parse_key_value(key->n, known_pub_keys[i].n)))
	  return e;

	rs_message(2, key, NULL, "Loaded builtin public key %s:", buf);
	rs_message(2, key, NULL, " n = %ZX", key->n);

	key->id = keyid;
	return 0;
      }
    }
  }

  for (i = 0; fmts[i]; i++) {
    sprintf(buf, fmts[i], keyid, "key");
    e = find_key_file(key, buf);
    if (e != RS_ERR_KEY_NOT_FOUND) {
      if (e == 0 && !key->id)
	key->id = keyid;
      return e;
    }
  }

  if (publiconly) {
    for (i = 0; fmts[i]; i++) {
      sprintf(buf, fmts[i], keyid, "pub");
      e = find_key_file(key, buf);
      if (e != RS_ERR_KEY_NOT_FOUND) {
	if (e == 0 && !key->id)
	  key->id = keyid;
	return e;
      }
    }
  }

  rs_error(NULL, NULL, "cannot find key file %s", buf);
  return RS_ERR_KEY_NOT_FOUND;
}