/*

  rgf.c

  Author: Mika Kojo <mkojo@ssh.fi>

  Copyright (c) 1999-2000 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;
  created->hash_def = hash_def;

  if (hash_def != NULL)
    {
      created->context = ssh_hash_allocate_internal(hash_def);
      if (created->context == NULL)
        {
          ssh_xfree(created);
          return NULL;
        }
      ssh_hash_reset(created->context);
    }
  else
    {
      ssh_xfree(created);
      return NULL;
    }
  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.");

  /* Handle the case when possibly setting the finalized digest before
     hand. */
  if (for_digest)
    {
      if (ssh_hash_digest_length(hash->context) == data_len)
        {
          /* This does not allocate new space for the
             data. */
          hash->precomp_digest        = data;
          hash->precomp_digest_length = data_len;
          return TRUE;
        }
      return FALSE;
    }

  if (hash->precomp_digest)
    return FALSE;

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

SSH_RGF_HASH_DIGEST_LENGTH_FUNC(ssh_rgf_std_hash_digest_length)
{
  if (hash->context == NULL)
    ssh_fatal("ssh_rgf_std_hash_digest_length: no hash state.");
  return ssh_hash_digest_length(hash->context);
}

SSH_RGF_HASH_FINALIZE_FUNC(ssh_rgf_std_hash_finalize)
{
  if (hash->context == NULL)
    ssh_fatal("ssh_rgf_std_hash_finalize: no hash state.");
  if (hash->precomp_digest)
    memcpy(digest, hash->precomp_digest, hash->precomp_digest_length);
  else
    ssh_hash_final(hash->context, digest);
}

SSH_RGF_HASH_ASN1_OID_FUNC(ssh_rgf_std_hash_asn1_oid)
{
  if (hash->context == NULL)
    ssh_fatal("ssh_rgf_std_hash_asn1_oid: no hash state.");
  return ssh_hash_asn1_oid(hash->context);
}

/* With no hashing. */

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->hash_def = NULL;
  return created;
}

SSH_RGF_HASH_FREE_FUNC(ssh_rgf_none_hash_free)
{
  ssh_xfree(hash);
}

SSH_RGF_HASH_UPDATE_FUNC(ssh_rgf_none_hash_update)
{
  hash->precomp_digest        = data;
  hash->precomp_digest_length = data_len;
  return TRUE;
}

SSH_RGF_HASH_DIGEST_LENGTH_FUNC(ssh_rgf_none_hash_digest_length)
{
  if (hash->precomp_digest == NULL)
    return -(size_t)1;
  return hash->precomp_digest_length;
}

SSH_RGF_HASH_FINALIZE_FUNC(ssh_rgf_none_hash_finalize)
{
  if (hash->precomp_digest)
    memcpy(digest, hash->precomp_digest, hash->precomp_digest_length);
}

SSH_RGF_HASH_ASN1_OID_FUNC(ssh_rgf_none_hash_asn1_oid)
{
  return NULL;
}

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

const SshRGFHashDef ssh_rgf_none_hash_def =
{
  ssh_rgf_none_hash_free,
  ssh_rgf_none_hash_update,
  ssh_rgf_none_hash_digest_length,
  ssh_rgf_none_hash_finalize,
  ssh_rgf_none_hash_asn1_oid,
  NULL,
  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 = (*hash->def->rgf_hash_digest_length)(hash);
  if (digest_len == -(size_t)1)
    return SSH_RGF_OP_FAILED;
  digest     = ssh_xmalloc(digest_len);
  (*hash->def->rgf_hash_finalize)(hash, digest);

  /* 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);

  ssh_rgf_hash_free(hash);

  return SSH_RGF_OK;
}

SSH_RGF_SIGN_FUNC(ssh_rgf_std_sign_no_hash)
{
  const unsigned char *msg;
  size_t msg_len;
  unsigned char *digest;
  size_t digest_len;

  /* Create the digest. */
  digest_len = (*hash->def->rgf_hash_digest_length)(hash);
  if (digest_len == -(size_t)1)
    return SSH_RGF_OP_FAILED;
  digest     = ssh_xmalloc(digest_len);
  (*hash->def->rgf_hash_finalize)(hash, digest);

  msg     = digest;
  msg_len = digest_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 - msg_len),
         msg, msg_len);

  ssh_xfree(digest);
  ssh_rgf_hash_free(hash);
  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 = (*hash->def->rgf_hash_digest_length)(hash);
  if (digest_len == -(size_t)1)
    return SSH_RGF_OP_FAILED;
  digest     = ssh_xmalloc(digest_len);
  (*hash->def->rgf_hash_finalize)(hash, digest);

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

SSH_RGF_VERIFY_FUNC(ssh_rgf_std_verify_no_hash)
{
  SshRGFStatus rv = SSH_RGF_OK;
  unsigned char *digest;
  size_t digest_len;

  *output_msg     = NULL;
  *output_msg_len = 0;

  /* Create the digest. */
  digest_len = (*hash->def->rgf_hash_digest_length)(hash);
  if (digest_len == -(size_t)1)
    return SSH_RGF_OP_FAILED;
  digest     = ssh_xmalloc(digest_len);
  (*hash->def->rgf_hash_finalize)(hash, digest);

  /* Check the validity. */
  if (digest_len != decrypted_signature_len ||
      memcmp(digest, decrypted_signature, digest_len) != 0)
    rv = SSH_RGF_OP_FAILED;

  ssh_xfree(digest);
  ssh_rgf_hash_free(hash);
  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,
  NULL
};

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,
  NULL
};



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,
  NULL
};

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

/* The RGF manipulation functions. */

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

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

void
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, FALSE,
                                data, data_len);
}

Boolean
ssh_rgf_hash_update_with_digest(SshRGFHash hash,
                                const unsigned char *data, size_t data_len)
{
  if (hash->def->rgf_hash_update == NULL)
    ssh_fatal("ssh_rgf_hash_update_with_digest: no update function defined.");
  return (*hash->def->rgf_hash_update)(hash, TRUE,
                                       data, data_len);
}

SshHash
ssh_rgf_hash_derive(SshRGFHash hash)
{
  /* Check whether the conversion is possible. */
  if (hash->hash_def == NULL)
    return NULL;
  return ssh_hash_allocate_internal(hash->hash_def);
}

void
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 */
