/*

  rgf.c

  Author: Mika Kojo <mkojo@ssh.fi>

  Copyright (c) 1999 SSH Communications Security, Finland
  All rights reserved.

  Created Thu Dec  9 21:25:47 1999.

  */

/* A library for redundancy generation functions for specific algorithms.
 */

#include "sshincludes.h"
#include "sshcrypt.h"
#include "sshcrypti.h"
#include "sshmp.h"
#include "genmp.h"
#include "md5.h"
#include "sha.h"

/* Some useful generic hash definitions. */

SSH_RGF_HASH_ALLOCATE_FUNC(ssh_rgf_std_hash_allocate)
{
  SshRGFHash created;

  if (def == NULL)
    ssh_fatal("ssh_rgf_std_hash_allocate: no hash definition.");
  
  created = ssh_xcalloc(1, sizeof(*created));
  created->def = def;
  
  if (context != NULL)
    {
      created->context = ssh_hash_allocate_internal(context);
      if (created->context == NULL)
        {
          ssh_xfree(created);
          return NULL;
        }
      ssh_hash_reset(created->context);
    }
  return created;
}

SSH_RGF_HASH_FREE_FUNC(ssh_rgf_std_hash_free)
{
  ssh_hash_free(hash->context);
  ssh_xfree(hash);
}

SSH_RGF_HASH_UPDATE_FUNC(ssh_rgf_std_hash_update)
{
  if (hash->context == NULL)
    ssh_fatal("ssh_rgf_std_hash_update: no hash state.");

  ssh_hash_update(hash->context, data, data_len);
}

/* With no hashing. */

typedef struct
{
  const unsigned char *data;
  size_t         data_len;
} SshRGFData;

SSH_RGF_HASH_ALLOCATE_FUNC(ssh_rgf_none_hash_allocate)
{
  SshRGFHash created;

  if (def == NULL)
    ssh_fatal("ssh_rgf_none_hash_allocate: no hash definition.");
  
  created = ssh_xcalloc(1, sizeof(*created));
  created->def     = def;
  created->context = ssh_xcalloc(1, sizeof(SshRGFData));
  return created;
}

SSH_RGF_HASH_FREE_FUNC(ssh_rgf_none_hash_free)
{
  ssh_xfree(hash->context);
  ssh_xfree(hash);
}

SSH_RGF_HASH_UPDATE_FUNC(ssh_rgf_none_hash_update)
{
  SshRGFData *rgf_data;
  if (hash->context == NULL)
    ssh_fatal("ssh_rgf_none_hash_update: no hash state.");
  rgf_data = hash->context;
  if (rgf_data->data != NULL)
    ssh_fatal("ssh_rgf_none_hash_update: can only handle one message.");
  rgf_data->data     = data;
  rgf_data->data_len = data_len;
}

/* Basic hash definitions. */
const SshRGFHashDef ssh_rgf_std_hash_def =
{
  ssh_rgf_std_hash_free,
  ssh_rgf_std_hash_update,
  NULL
};

const SshRGFHashDef ssh_rgf_none_hash_def =
{
  ssh_rgf_none_hash_free,
  ssh_rgf_none_hash_update,
  NULL
};

/* Basic redundancy function implementations. */

/* A generic routines that can be used with many cryptosystems with
   little redundancy management. These include e.g. the DSA algorithm.

   Common idea with all the methods is that they basically do not
   do any redundancy related operations. For example, they just hash
   the message for signature using one of the standard hash functions.
   They do not pad the digest before signing, usually because these
   methods include the digest into the cryptosystem in more complicated
   manner than RSA does, for example. 
   */

SSH_RGF_ENCRYPT_FUNC(ssh_rgf_std_encrypt)
{
  if (msg_len > output_msg_len)
    return SSH_RGF_OP_FAILED;

  /* Zero the output msg. */
  memset(output_msg, 0, output_msg_len);
  memcpy(output_msg + (output_msg_len - msg_len), msg, msg_len);

  return SSH_RGF_OK;
}

SSH_RGF_DECRYPT_FUNC(ssh_rgf_std_decrypt)
{
  if (decrypted_msg_len > max_output_msg_len)
    return SSH_RGF_OP_FAILED;
  *output_msg = ssh_xmemdup(decrypted_msg, decrypted_msg_len);
  *output_msg_len = decrypted_msg_len;
  return SSH_RGF_OK;
}

SSH_RGF_SIGN_FUNC(ssh_rgf_std_sign)
{
  unsigned char *digest;
  size_t         digest_len;

  if (hash->context == NULL)
    return SSH_RGF_OP_FAILED;

  /* Create the digest. */
  digest_len = ssh_hash_digest_length(hash->context);
  digest     = ssh_xmalloc(digest_len);
  ssh_hash_final(hash->context, digest);
  ssh_hash_free(hash->context);

  /* Free the hash context. */
  ssh_xfree(hash);

  /* Now check whether we can output the digest or not. */
  if (digest_len > output_msg_len)
    {
      ssh_xfree(digest);
      return SSH_RGF_OP_FAILED;
    }
  memset(output_msg, 0, output_msg_len);
  memcpy(output_msg + (output_msg_len - digest_len), digest, digest_len);
  ssh_xfree(digest);
  return SSH_RGF_OK;
}

SSH_RGF_SIGN_FUNC(ssh_rgf_std_sign_no_hash)
{
  SshRGFData *rgf_data = hash->context;
  const unsigned char *msg;
  size_t msg_len;

  /* Free the hash context. */
  ssh_xfree(hash);
  
  msg = rgf_data->data;
  msg_len = rgf_data->data_len;

  if (msg_len > output_msg_len)
    {
      msg += (msg_len - output_msg_len);
      msg_len = output_msg_len;
    }

  memset(output_msg, 0, output_msg_len);
  memcpy(output_msg + (output_msg_len - rgf_data->data_len),
         rgf_data->data, rgf_data->data_len);
  ssh_xfree(rgf_data);
  return SSH_RGF_OK;
}

SSH_RGF_VERIFY_FUNC(ssh_rgf_std_verify)
{
  unsigned char *digest;
  size_t         digest_len;

  *output_msg     = NULL;
  *output_msg_len = 0;
  
  if (hash->context == NULL)
    return SSH_RGF_OP_FAILED;

  /* Create the digest. */
  digest_len = ssh_hash_digest_length(hash->context);
  digest     = ssh_xmalloc(digest_len);
  ssh_hash_final(hash->context, digest);
  ssh_hash_free(hash->context);

  /* Free the hash context. */
  ssh_xfree(hash);

  if (digest_len != decrypted_signature_len ||
      memcmp(decrypted_signature, digest, digest_len) != 0)
    {
      ssh_xfree(digest);
      return SSH_RGF_OP_FAILED;
    }
  ssh_xfree(digest);
  return SSH_RGF_OK;
}

SSH_RGF_VERIFY_FUNC(ssh_rgf_std_verify_no_hash)
{
  SshRGFData *rgf_data = hash->context;
  SshRGFStatus rv = SSH_RGF_OK;

  *output_msg     = NULL;
  *output_msg_len = 0;
  
  /* First free the hash context. */
  ssh_xfree(hash);
  /* Check the validity. */
  if (rgf_data->data_len != decrypted_signature_len ||
      memcmp(rgf_data->data, decrypted_signature, rgf_data->data_len) != 0)
    rv = SSH_RGF_OP_FAILED;
  /* Free the data. */
  ssh_xfree(rgf_data);
  return rv;
}

const SshRGFDef ssh_rgf_std_sha1_def =
{
  "std-sha1",
  ssh_rgf_std_encrypt,
  ssh_rgf_std_decrypt,
  ssh_rgf_std_sign,
  ssh_rgf_std_verify,
  NULL,
  ssh_rgf_std_hash_allocate,
  &ssh_rgf_std_hash_def, 
  &ssh_hash_sha_def
};

const SshRGFDef ssh_rgf_std_md5_def =
{
  "std-md5",
  ssh_rgf_std_encrypt,
  ssh_rgf_std_decrypt,
  ssh_rgf_std_sign,
  ssh_rgf_std_verify,
  NULL,
  ssh_rgf_std_hash_allocate,
  &ssh_rgf_std_hash_def, 
  &ssh_hash_md5_def
};



const SshRGFDef ssh_rgf_dummy_def =
{
  "std-dummy",
  ssh_rgf_std_encrypt,
  ssh_rgf_std_decrypt,
  ssh_rgf_std_sign_no_hash,
  ssh_rgf_std_verify_no_hash,
  NULL,
  ssh_rgf_none_hash_allocate,
  &ssh_rgf_none_hash_def,
  NULL
};

/* TODO. More redundancy functions and similar constructs. */

/* The RGF manipulation functions. */

DLLEXPORT SshRGFHash DLLCALLCONV
ssh_rgf_hash_allocate_internal(const SshRGFDef *rgf_def)
{
  if (rgf_def->rgf_hash_allocate == NULL ||
      rgf_def->hash_def == NULL)
    return NULL;

  return (*rgf_def->rgf_hash_allocate)(rgf_def->hash_def,
                                       rgf_def->context);
}

DLLEXPORT void DLLCALLCONV
ssh_rgf_hash_update(SshRGFHash hash,
                    const unsigned char *data, size_t data_len)
{
  if (hash->def->rgf_hash_update == NULL)
    ssh_fatal("ssh_rgf_hash_update: no update function defined.");
  (*hash->def->rgf_hash_update)(hash,
                                data, data_len);
}
     
DLLEXPORT void DLLCALLCONV
ssh_rgf_hash_free(SshRGFHash hash)
{
  if (hash->def->rgf_hash_free == NULL)
    ssh_fatal("ssh_rgf_hash_free: no hash free function defined.");
  (*hash->def->rgf_hash_free)(hash);
}

/* rgf.c */
