/*
 * nasd_security.c
 *
 * Put the Security in NASD!
 *
 * Author: Marc Unangst, Howard Gobioff
 */
/*
 * 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_drive_types.h>
#include <nasd/nasd_security.h>
#include <nasd/nasd_marshall.h>
#include <cryptolib/libcrypt.h>

nasd_uint16 nasd_security_levels[] = {
  NASD_NO_PROTECTION,
#if NASD_SECURE_RPCS_ENABLE > 0
  NASD_INTEGRITY_ARGS,
  NASD_INTEGRITY_DATA,
  (NASD_INTEGRITY_ARGS | NASD_INTEGRITY_DATA),
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
};

char *nasd_security_strings[] = {
  "NASD_NO_PROTECTION",
#if NASD_SECURE_RPCS_ENABLE > 0
  "NASD_INTEGRITY_ARGS",
  "NASD_INTEGRITY_DATA",
  "(NASD_INTEGRITY_ARGS | NASD_INTEGRITY_DATA)",
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
};

/* This should be about the size the internal compression function of
   our hash function takes ... hgobioff */
#define NASD_HASH_BASIC_SIZE SHA_CBLOCK

#define NASD_INTEGRITY_CONSTANT 0xA

/* Generate the integrity key from the capability key. */
nasd_status_t
nasd_sec_integrity_key(nasd_key_t cred_key, nasd_key_t integ_key)
{
  unsigned char buf[NASD_HASH_BASIC_SIZE];
  SHA1_CTX context;
  nasd_digest_t tdigest;

  memset(buf, NASD_INTEGRITY_CONSTANT, NASD_HASH_BASIC_SIZE);

  SHA1_Init(0, &context);

  if (cred_key != NULL) { /* XXX -- yes, this is fucking ugly. */
    bcopy(cred_key, buf, sizeof(nasd_key_t));
  }

  SHA1_Update(0, &context, buf, NASD_HASH_BASIC_SIZE);
  SHA1_Final(0, tdigest, &context);
  bcopy(tdigest, integ_key, NASD_MIN(sizeof(tdigest), sizeof(nasd_key_t)));

  return NASD_SUCCESS;
}


/* Fill in the nonce (digest and timestamp). */
void
nasd_sec_fill_nonce(nasd_security_param_t *sec_param,
		    nasd_key_t op_key,
		    nasd_key_t integrity_key,
		    nasd_iv_t icv,
		    int is_reply,
		    void *args_otw,
		    int args_otw_len,
		    nasd_timespec_t *timestamp,
		    nasd_digest_nonce_otw_t digest_otw,
		    nasd_security_context_t *context)
{
  nasd_digest_nonce_t *otw_digest;
  HMAC_SHA1_CTX *ctxp, local_ctx;

  if(sec_param->actual_protection == NASD_NO_PROTECTION) {
    bzero(digest_otw, sizeof(nasd_digest_nonce_otw_t));
    return;
  }

  otw_digest = (nasd_digest_nonce_t *) digest_otw;

  nasd_timespec_t_marshall(timestamp, (nasd_otw_base_t *)&otw_digest->nonce);


  /* generate the message digest */
  if(sec_param->actual_protection &
     (NASD_INTEGRITY_ARGS | NASD_INTEGRITY_DATA)) {
    if(context) {
      ctxp = &context->HMAC_context;
    } else {
      ctxp = &local_ctx;
    }

    if(!context || !is_reply) {
      /* set up the HMAC context */
      HMAC_SHA1_Init(ctxp, (char *) integrity_key, sizeof(nasd_key_t));
    }

    if(sec_param->actual_protection & NASD_INTEGRITY_ARGS) {
      HMAC_SHA1_Update(ctxp, args_otw, args_otw_len);
      HMAC_SHA1_Update(ctxp, (nasd_byte_t *) &otw_digest->nonce,
		       sizeof(nasd_timespec_t));
    }

    if(context) {
      bcopy(ctxp, &local_ctx, sizeof(HMAC_SHA1_CTX));
    }

    HMAC_SHA1_Final(otw_digest->digest, &local_ctx);
  }
}

/* for debugging purposes */
void
nasd_sec_print_capability(nasd_key_t in_key, nasd_capability_t *cap)
{
  nasd_printf("Cookie {[");
  nasd_sec_print_key(in_key);
  nasd_printf("],ni=%" NASD_ID_FMT
	      ",expire=%d,rights=%x,min_prot=%d,AuditID=%d,"
	      "type=%d,region_start=%" NASD_64s_FMT ",region_end=%"
	      NASD_64s_FMT ",partnum=%u\n",
	      cap->ni,
	      cap->expiration_seconds,
	      cap->rights,

	      cap->min_protection,
	      cap->AuditID,
	      cap->type,

	      cap->region_start,
	      cap->region_end,
	      cap->partnum);
}

void
nasd_sec_print_key(nasd_key_t in_key)
{
  int i;
  char buf[80];
  char *p;

  p = buf;
  for(i = 0; i < sizeof(nasd_key_t); i++) {
    sprintf(p, "0x%02x ", ((nasd_byte_t *)in_key)[i]);
    p += 5;
  }
  *p = '\0';
  nasd_printf("%s", buf);
}

/* This function does nothing except initialize the cryptographic
   keys to fixed constants -- you do *NOT* want to do this in a
   real system. This is purely for ease of debugging  - hgobioff */  
nasd_status_t
nasd_sec_password_to_keys(char *password,
			  nasd_partnum_t partnum,
			  nasd_sec_keyring_t *keys)
{
#if NASD_SEC_USE_CONSTANT_KEYS > 0
  int i;

  for(i=0;i<sizeof(nasd_key_t);i++) {
    keys->master_key[i]=1;
    keys->drive_key[i]=2;
    keys->part_key[i]=partnum;
    keys->red_key[i]=partnum+1;
    keys->black_key[i]=partnum+2;
  }
#else /* NASD_SEC_USE_CONSTANT_KEYS > 0 */
  nasd_sec_generate_key(password, 0x01, keys->master_key);
  nasd_sec_generate_key(password, 0x02, keys->drive_key);
  nasd_sec_generate_key(password, partnum, keys->part_key);
  nasd_sec_generate_key(password, partnum+1, keys->red_key);
  nasd_sec_generate_key(password, partnum+2, keys->black_key);
#endif /* NASD_SEC_USE_CONSTANT_KEYS > 0 */
  return NASD_SUCCESS;
}

/* Generate a key from some password string and a salt. */
void
nasd_sec_generate_key(char *password, nasd_byte constant,
		      nasd_key_t key)
{
  SHA1_CTX md_context;
  unsigned char buf[64];
  nasd_digest_t digest;

  memset(buf, constant, 64);
  memcpy(buf, password, strlen(password));
  SHA1_Init(0, &md_context);
  SHA1_Update(0, &md_context, buf, 64);
  SHA1_Final(0, digest, &md_context);
  bcopy(digest, key, NASD_MIN(sizeof(nasd_digest_t), sizeof(nasd_key_t)));
}

/* Generate a capability with the specified parameters under the specified
   basis key.  Caller is responsible for ensuring that the specified type
   and the basis key correspond.  Places generated capability in
   out_cookie->capability and the corresponding private key in
   out_cookie->key. */
void
nasd_sec_build_capability(nasd_partnum_t partnum,
			  nasd_identifier_t ni,
			  nasd_access_rights_t rights,
			  nasd_audit_t AuditID,
			  nasd_int32 expiration,
			  nasd_uint16 min_protection,
			  nasd_uint16 type,
			  nasd_offset_t region_start,
			  nasd_offset_t region_end,
			  nasd_av_t av,
			  nasd_key_t basis_key,
			  nasd_cookie_t *out_cookie)
{
  nasd_capability_otw_t capbuf;
  nasd_av_otw_t avbuf;
  nasd_byte key_buff[SHA_DIGEST_LENGTH];
  HMAC_SHA1_CTX HMAC_context;

  bzero(out_cookie, sizeof(nasd_cookie_t));

  out_cookie->capability.ni = ni;
  out_cookie->capability.expiration_seconds = expiration;
  out_cookie->capability.rights = rights;
  out_cookie->capability.min_protection = min_protection;
  out_cookie->capability.AuditID = AuditID;
  out_cookie->capability.type = type;
  out_cookie->capability.region_start = region_start;
  out_cookie->capability.region_end = region_end;
  out_cookie->capability.partnum = partnum;

  if (min_protection != NASD_NO_PROTECTION) {
    nasd_capability_t_marshall(&out_cookie->capability, capbuf);
    nasd_av_t_marshall(&av, avbuf);
    
    HMAC_SHA1_Init(&HMAC_context, (char *) basis_key, sizeof(nasd_key_t));
    HMAC_SHA1_Update(&HMAC_context, (unsigned char *)capbuf,
      sizeof(nasd_capability_t));
    HMAC_SHA1_Update(&HMAC_context, (unsigned char *)avbuf,
      sizeof(nasd_av_t));
    HMAC_SHA1_Final(key_buff, &HMAC_context);

    bcopy(key_buff, out_cookie->key,
	  NASD_MIN(SHA_DIGEST_LENGTH,sizeof(nasd_key_t)));
  }
}

nasd_status_t
nasd_sec_seclevel_to_protection(int sec_level, nasd_uint16 *protection)
{
  if(sec_level < 0 || sec_level > NASD_MAX_SECURITY_LEVEL)
    return NASD_FAIL;

  *protection = nasd_security_levels[sec_level];
  return NASD_SUCCESS;
}

char *
nasd_sec_level_string(int sec_level)
{
  if(sec_level < 0 || sec_level > NASD_MAX_SECURITY_LEVEL)
    return NULL;

  return nasd_security_strings[sec_level];
}
