/*
 * 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_STRING_H
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
#  include <strings.h>
# endif
#endif

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

/*
 * Create a new key.
 */
RSKey* rs_key_new()
{
  RSKey* key = rs_malloc(sizeof(RSKey));

  if (!key)
    return NULL;

  key->filename = NULL;
  key->id = 0;
  mpz_init(key->n);
  mpz_init(key->p);
  mpz_init(key->q);
  mpz_init(key->qinv);
  mpz_init(key->d);

  return key;
}

/*
 * Free a key.
 */
void rs_key_free(RSKey* key)
{
  if (!key)
    return;

  rs_free(key->filename);
  mpz_clear(key->n);
  mpz_clear(key->p);
  mpz_clear(key->q);
  mpz_clear(key->qinv);
  mpz_clear(key->d);
  rs_free(key);
}

/*
 * Parse a number written in TI's hexadecimal key format.
 */
static int parse_value(mpz_t dest,	/* mpz to store result */
		       const char* str) /* string to parse */
{
  unsigned int count, b, i;
  int n;
  unsigned char buf[256];

  if (1 > sscanf(str, "%2X%n", &count, &n) || n != 2
      || (count * 2 + 2) > strlen(str))
    return 1;

  for (i = 0; i < count; i++) {
    if (1 > sscanf(str + 2 + 2 * i, "%2X%n", &b, &n) || n != 2)
      return 1;
    buf[i] = b;
  }

  mpz_import(dest, i, -1, 1, 0, 0, buf);
  return 0;
}

/*
 * Read key from a file.
 *
 * Two formats of key file are supported:
 *
 * "Rabin" style (the type used by the TI-83 Plus SDK) consists of
 * three lines: the public key (n) followed by its two factors (p and
 * q.)
 *
 * "RSA" style (the type used by the TI-89/92 Plus SDK) also consists
 * of three lines: the key ID, the public key (n), and the signing
 * exponent (d).
 *
 * In either case, if we are only interested in validating signatures,
 * the private key may be omitted.
 *
 * Note that "Rabin" style key files can be used to generate RSA
 * signatures, but not vice versa.
 */
int rs_read_key_file(RSKey* key,        /* key structure */
		     FILE* f,	        /* file to read */
		     const char* fname, /* file name */
		     int verify)	/* 1 = check key validity */
{
  char buf[1024];
  mpz_t tmp;

  rs_free(key->filename);
  key->filename = rs_strdup(fname);
  if (fname && !key->filename)
    return RS_ERR_OUT_OF_MEMORY;

  if (!fgets(buf, sizeof(buf), f)) {
    rs_error(key, NULL, "invalid key file syntax");
    return RS_ERR_KEY_SYNTAX;
  }

  if (strlen(buf) < 11) {
    if (1 > sscanf(buf, "%lX", &key->id)) {
      rs_error(key, NULL, "invalid key file syntax");
      return RS_ERR_KEY_SYNTAX;
    }

    if (!fgets(buf, sizeof(buf), f)
	|| parse_value(key->n, buf)) {
      rs_error(key, NULL, "invalid key file syntax");
      return RS_ERR_KEY_SYNTAX;
    }

    if (!fgets(buf, sizeof(buf), f)
	|| parse_value(key->d, buf))
      mpz_set_ui(key->d, 0);
    else if (verify) {
      /* We can't truly verify the key without factoring n (which is
	 possible, given d, but would take a bit of work.)  Instead,
	 test the key by performing a single RSA encryption and
	 decryption. */
      mpz_init(tmp);
      mpz_set_ui(tmp, 17);
      mpz_powm(tmp, tmp, tmp, key->n);
      mpz_powm(tmp, tmp, key->d, key->n);
      if (mpz_cmp_ui(tmp, 17)) {
	mpz_clear(tmp);
	rs_error(key, NULL, "private key incorrect (de != 1 mod phi(n))");
	return RS_ERR_INVALID_KEY;
      }
      mpz_clear(tmp);
    }

    mpz_set_ui(key->p, 0);
    mpz_set_ui(key->q, 0);
    mpz_set_ui(key->qinv, 0);
  }
  else {
    if (parse_value(key->n, buf)) {
      rs_error(key, NULL, "invalid key file");
      return RS_ERR_KEY_SYNTAX;
    }

    if (!fgets(buf, sizeof(buf), f)
	|| parse_value(key->p, buf)
	|| !fgets(buf, sizeof(buf), f)
	|| parse_value(key->q, buf)) {
      mpz_set_ui(key->p, 0);
      mpz_set_ui(key->q, 0);
    }
    else if (verify) {
      /* Verify that p * q = n (of course, that doesn't guarantee that
	 these are the only factors of n.) */
      mpz_init(tmp);
      mpz_mul(tmp, key->p, key->q);
      if (mpz_cmp(tmp, key->n)) {
	mpz_clear(tmp);
	rs_error(key, NULL, "private key incorrect (pq != n)");
	return RS_ERR_INVALID_KEY;
      }
      mpz_clear(tmp);
    }

    mpz_set_ui(key->qinv, 0);
    mpz_set_ui(key->d, 0);
    key->id = 0;
  }

  if (mpz_sgn(key->p) && mpz_sgn(key->q)) {
    rs_message(2, key, NULL, "Loaded Rabin/RSA private key:");
    rs_message(2, key, NULL, " n = %ZX", key->n);
    rs_message(2, key, NULL, " p = %ZX", key->p);
    rs_message(2, key, NULL, " q = %ZX", key->q);
  }
  else if (mpz_sgn(key->d)) {
    rs_message(2, key, NULL, "Loaded RSA private key:");
    rs_message(2, key, NULL, " n = %ZX", key->n);
    rs_message(2, key, NULL, " d = %ZX", key->d);
  }
  else {
    rs_message(2, key, NULL, "Loaded public key:");
    rs_message(2, key, NULL, " n = %ZX", key->n);
  }

  return RS_SUCCESS;
}

/*
 * Parse a number written in TI's hexadecimal key format.
 */
int rs_parse_key_value(mpz_t dest,	/* mpz to store result */
		       const char* str) /* string to parse */
{
  if (parse_value(dest, str)) {
    rs_error(NULL, NULL, "invalid key value syntax");
    return RS_ERR_KEY_SYNTAX;
  }
  else {
    return RS_SUCCESS;
  }
}
