/*
 * GTK VNC Widget, Diffie Hellman
 *
 * Copyright (C) 2008  Red Hat, Inc.
 *
 * Derived from gnutls_dh.c, also under:
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include <config.h>

#include "dhpriv.h"
#include "vncutil.h"

/*
 * General plan, as per gnutls_dh.c
 *
 *
 *  VNC server: X = g ^ x mod p;
 *  VNC client: Y = g ^ y mod p;
 *
 *  Server key = Y ^ x mod p;
 *  Client key = X ^ y mod p;
 *
 * Where
 *   g == gen
 *   p == mod
 *
 *   y == priv
 *   Y == pub
 *  Client key == key
 *
 *
 */

struct vnc_dh {
    mpz_t gen;  /* g */
    mpz_t mod;  /* p */

    mpz_t priv; /* y */
    mpz_t pub;  /* Y = g ^ y mod p */

    mpz_t key;  /*     X ^ y mod p */
};

#define VNC_DH_MAX_BITS 31


struct vnc_dh *vnc_dh_new(GBytes *gen, GBytes *mod)
{
    struct vnc_dh *ret = g_new0(struct vnc_dh, 1);

    vnc_bytes_to_mpi(gen, ret->gen);
    vnc_bytes_to_mpi(mod, ret->mod);

    return ret;
}


GBytes *vnc_dh_gen_secret(struct vnc_dh *dh, size_t len)
{
    gmp_randstate_t rng;

    mpz_init2(dh->priv, VNC_DH_MAX_BITS);

    gmp_randinit_default(rng);

    do {
        mpz_urandomb(dh->priv, rng, (VNC_DH_MAX_BITS / 8) * 8);
    } while (mpz_cmp_ui (dh->priv, 0) == 0);

    gmp_randclear(rng);

    mpz_init2(dh->pub, VNC_DH_MAX_BITS);

    mpz_powm(dh->pub, dh->gen, dh->priv, dh->mod);

    return vnc_mpi_to_bytes(dh->pub, len);
}

GBytes *vnc_dh_gen_key(struct vnc_dh *dh, GBytes *inter, size_t len)
{
    mpz_t ginter;

    vnc_bytes_to_mpi(inter, ginter);

    mpz_init2(dh->key, VNC_DH_MAX_BITS);

    mpz_powm(dh->key, ginter, dh->priv, dh->mod);
    mpz_clear(ginter);

    return vnc_mpi_to_bytes(dh->key, len);
}

void vnc_dh_free(struct vnc_dh *dh)
{
    mpz_clear(dh->key);
    mpz_clear(dh->pub);
    mpz_clear(dh->priv);
    mpz_clear(dh->mod);
    mpz_clear(dh->gen);
    g_free(dh);
}

GBytes *vnc_mpi_to_bytes(const mpz_t value, size_t size)
{
    size_t len = 0;
    int i;
    guchar *data = NULL;
    guchar *result;

    data = mpz_export(NULL, &len, size, 1, 1, 0, value);
    result = g_new0(guchar, size);

    /* right adjust the result
       68 183 219 160 0 0 0 0 becomes
       0 0 0 0 68 183 219 160 */
    for(i=size-1;i>(int)size-1-(int)len;--i) {
        result[i] = data[i-size+len];
    }
    for(;i>=0;--i) {
        result[i] = 0;
    }

    g_free(data);
    return g_bytes_new_take(result, size);
}

void vnc_bytes_to_mpi(GBytes *value, mpz_t ret)
{
    mpz_init2(ret, VNC_DH_MAX_BITS);
    mpz_import(ret, g_bytes_get_size(value),
               1, 1, 1, 0,
               g_bytes_get_data(value, NULL));
}
