/*
 * nasd_security_cl.c
 *
 * Security routines for client library.
 *
 * Author: Marc Unangst
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */

 
#include <nasd/nasd_options.h>
#include <nasd/nasd_general.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_marshall.h>
#include <nasd/nasd_security.h>
#include <nasd/nasd_security_cl.h>
#include <nasd/nasd_nonce_mgmt.h>
#include <cryptolib/libcrypt.h>

void
nasd_pack_args(
	       nasd_drive_handle_t handle,
	       nasd_security_param_t *sec_param,
	       nasd_capability_t *capability,
	       void *args,
	       nasd_security_param_otw_t sec_param_otw,
	       nasd_capability_otw_t capability_otw,
	       nasd_digest_nonce_otw_t digest_otw,
	       void *args_otw,
	       int args_otw_len,
	       void (*marshall_fn)(),
	       nasd_iv_t icv,
	       nasd_key_t op_key,
	       nasd_security_context_t *context)
{
#if NASD_SECURE_RPCS_ENABLE > 0
  nasd_timespec_t now;
  nasd_key_t integrity_key;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */

  NASD_ASSERT(args != NULL);
  NASD_ASSERT(sec_param != NULL);

  /* marshall stuff */
  marshall_fn(args, args_otw);
  nasd_security_param_t_marshall(sec_param, sec_param_otw);
  if(capability)
    nasd_capability_t_marshall(capability, capability_otw);
  else
    bzero(capability_otw, sizeof(capability_otw));

  /* initialize security context */
  if(sec_param->actual_protection != NASD_NO_PROTECTION) {
#if NASD_SECURE_RPCS_ENABLE > 0
    if(context) {
      context->protection = sec_param->actual_protection;
      context->pending_hmac = 0;
    }

    /* fetch our idea of the drive's clock */
    nasd_drive_handle_get_time(handle, &now);

    nasd_sec_integrity_key(op_key, integrity_key);
    /* fill in the nonce (digest & timestamp); also initializes icv */
    nasd_sec_fill_nonce(sec_param, op_key, integrity_key, icv, 0, args_otw,
			args_otw_len, &now, digest_otw, context);

    if(sec_param->actual_protection &
       (NASD_PRIVACY_ARGS | NASD_PRIVACY_DATA)) {
      nasd_printf("WARNING: clientlib pack_args: unsupported security protection mask 0x%x\n",
		  sec_param->actual_protection);
    }


    /* reinitialize the SHA1 context if we will need it and we have one */
    if((sec_param->actual_protection & NASD_INTEGRITY_DATA) && context)
      SHA1_Init(0, &context->SHA1_ctx);
#else /* NASD_SECURE_RPCS_ENABLE > 0 */
    nasd_printf("WARNING: clientlib pack_args: unsupported security protection mask 0x%x\n",
		sec_param->actual_protection);
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
  } else {
    bzero(digest_otw, sizeof(digest_otw));
  }
}

nasd_status_t
nasd_unpack_res(
		nasd_drive_handle_t handle,
		nasd_security_param_t *sec_param,
		nasd_capability_t *capability,
		void *res_otw,
		int res_otw_len,
		nasd_digest_nonce_otw_t digest_otw,
		void *res,
		void (*unmarshall_fn)(),
		nasd_iv_t icv,
		nasd_key_t op_key,
		nasd_security_context_t *context)
{
#if NASD_SECURE_RPCS_ENABLE > 0
  nasd_status_t rc;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */

  if(sec_param->actual_protection != NASD_NO_PROTECTION) {
#if NASD_SECURE_RPCS_ENABLE > 0
    if(sec_param->actual_protection &
       (NASD_PRIVACY_ARGS | NASD_PRIVACY_DATA)) {
      nasd_printf("WARNING: clientlib unpack_res: unsupported security protection mask 0x%x\n",
		  sec_param->actual_protection);
      return NASD_UNSUPPORTED_PROTECTION;
    }

    /* verify nonce and message digest */
    if(sec_param->actual_protection &
       (NASD_INTEGRITY_DATA | NASD_INTEGRITY_ARGS)) {
      rc = nasd_sec_verify_res(handle, sec_param, capability, digest_otw,
			       res_otw, res_otw_len, op_key, context);
      if(rc)
	return rc;
    }
#else /* NASD_SECURE_RPCS_ENABLE > 0 */
    nasd_printf("WARNING: clientlib: unsupported security protection mask 0x%x\n",
		sec_param->actual_protection);
    return NASD_UNSUPPORTED_PROTECTION;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */    
  }

  /* finally, unmarshall and return */
  unmarshall_fn(res_otw, res);
  return NASD_SUCCESS;
}

nasd_status_t
nasd_sec_verify_res(
		    nasd_drive_handle_t handle,
		    nasd_security_param_t *sec_param,
		    nasd_capability_t *capability,
		    nasd_digest_nonce_otw_t digest_otw,
		    void *res_otw,
		    int res_otw_len,
		    nasd_key_t op_key,
		    nasd_security_context_t *context)
{
  HMAC_SHA1_CTX local_ctx;
  HMAC_SHA1_CTX *HMAC_contextp;
  nasd_byte md_out[SHA_DIGEST_LENGTH];
  nasd_digest_nonce_t *otw_digest;
  nasd_timespec_t nonce_ts;
  nasd_timespec_t now;
  nasd_key_t integrity_key;
  nasd_status_t rc;

  /* generate & check the MAC */
  otw_digest = (nasd_digest_nonce_t *) digest_otw;

  if(context) {
    HMAC_contextp = &context->HMAC_context;
    if(context->pending_hmac) {
      /* have a partial digest sitting around -- finish it */
      SHA1_Final(0, md_out, &context->SHA1_ctx);
      HMAC_SHA1_Update(HMAC_contextp, md_out, sizeof(nasd_digest_t));
      SHA1_Init(0, &context->SHA1_ctx);
    }
  } else {
    HMAC_contextp = &local_ctx;
    nasd_sec_integrity_key(op_key, integrity_key);
    HMAC_SHA1_Init(HMAC_contextp, (char *) integrity_key, sizeof(nasd_key_t));
  }

  if(sec_param->actual_protection & NASD_INTEGRITY_ARGS) {
    /* check the nonce for freshness */
    nasd_timespec_t_unmarshall((nasd_otw_base_t *)&otw_digest->nonce,
      &nonce_ts);
    nasd_drive_handle_get_time(handle, &now);
    rc = nasd_sec_check_nonce(handle->nonce_mgr, capability, op_key,
      &nonce_ts, &now);
    if(rc == NASD_BAD_NONCE) {
      /* need to rewrite this so client can distinguish between
         NASD_BAD_NONCE from the drive (i.e., request had bad nonce)
         and NASD_BAD_NONCE on the drive's reply. */
      return NASD_BAD_NONCE_REPLY;
    } else if(rc)
      return rc;

    /* add the results struct and the nonce to the digest */
    HMAC_SHA1_Update(HMAC_contextp, res_otw, res_otw_len);
    HMAC_SHA1_Update(HMAC_contextp, (nasd_byte *) &otw_digest->nonce,
		     sizeof(nasd_timespec_t));
  }

  HMAC_SHA1_Final(md_out, HMAC_contextp);
  if(bcmp(md_out, otw_digest->digest,
	  NASD_MIN(SHA_DIGEST_LENGTH, NASD_DIGEST_SIZE)) != 0) {
    return NASD_BAD_DIGEST_REPLY;
  }

  return NASD_SUCCESS;
}
