/* $Id: keyregression.C,v 1.26 2005/07/26 19:10:37 fubob Exp $ */

/*
 *
 * Copyright (C) 2005 Kevin Fu (fubob@mit.edu)
 * Copyright (C) 2004 Anjali Prakash (anjali@cs.jhu.edu)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include "keyregression.h"

template<class T> static bool
file2xdr (T &t, str file) 
{
  assert (file);

  str tstr = file2str (file);
  if (!tstr) {
    warn << "Unable to open " << file << "\n";
    warn << (errno ? strerror (errno) : "") << "\n";
    return false;
  }

  /*
  tstr = dearmor64 (tstr.cstr (), tstr.len () - 1);
  if (!tstr) {
    warn << "Unable to dearmor64\n";
    return false;
  }
  */

  if (!str2xdr (t, tstr)) {
    warn << "Unable to convert tstr to T.  Perhaps T type does not match tstr type?\n"; 
    return false;
  }

  return true;
}

template<class T> static bool
xdr2file (str file, T &t) 
{
  assert (file);

  str tstr = xdr2str (t);
  if (!tstr) {
    warn << "xdr2file: xdr2str failed\n";
    return false;
  }

  /*
  tstr = armor64 (tstr);
  if (!tstr) {
    warn << "xdr2file: armor64 failed\n";
    return false;
  }
  */

  if (!str2file (file, tstr)) {
    warn << "Could not store T in " << file << "\n";
    warn << (errno ? strerror (errno) : "") << "\n";
    return false;
  }

  return true;
}

void
keyregression::update (chefs_stm &stm)
{
  assert (stm.vers >= curr_idx); 
  hvec.setsize (stm.vers + 1);
  hvec[stm.vers] = stm.keystate;
  kdvec.setsize (stm.vers + 1);
  keyder (stm.vers);
  //  kdvec[stm.vers] = NULL;

  for (uint32 i = stm.vers; i > curr_idx; i--) {
    sha1_hash (hvec[i-1].base (), hvec[i].base (), sha1::hashsize);
    keyder (i-1);
    //  kdvec[i-1] = NULL;
  }
  curr_idx = stm.vers;
}

ptr<rpc_bytes<> >
keyregression::keyder (uint32 idx)
{
  if (idx > kdvec.size ())
    return NULL;

  if (kdvec[idx])
    if (kdvec[idx]->size () > 0)
      return kdvec[idx];

  if (idx > hvec.size ()) {
    warn << "hvec too small";
    return NULL;
  }

  char zerobyte = 0;
  char out[sha1::hashsize];
  iovec iov[2];
  iov[0].iov_len = 1;
  iov[0].iov_base = &zerobyte;
  iov[1].iov_len = sha1::hashsize;
  iov[1].iov_base = hvec[idx].base ();
  sha1_hashv (out, iov, 2);

  kdvec[idx] = New refcounted<rpc_bytes <> > ();
  kdvec[idx]->setsize (16);
  memcpy (kdvec[idx]->base (), out, 16);
  return kdvec[idx];
}


keyregression_owner::keyregression_owner (str infile)
{
  if (!file2xdr (hvec, infile))
    fatal << "keyregression_owner, file2xdr";
  kdvec.setsize (hvec.size ());
}

void
keyregression_owner::toFile (str outfile)
{
  if (!xdr2file (outfile, hvec))
    fatal << "keyregression_owner::toFile, xdr2file";
}

void
keyregression_owner::noKRtoFile (str outfile)
{
  int fd;
  int n;
  if ((fd = open (outfile.cstr (), O_CREAT|O_WRONLY|O_EXCL, 0600)) < 0) {
    fatal ("%s: %m\n", outfile.cstr ());
  } else
    warn << "noKRtofile: " << outfile << "\n";
  
  ptr<rpc_bytes <> > kder;
  for (uint32 i=0; i< kdvec.size (); i++) {
    kder = keyder (i);
    assert (kder->size () == 16);
    n = write (fd, kder->base (), kder->size ());
    if (n < (int (kder->size ()))) 
      warn << outfile << ": write to outfile failed\n";
  }

  if (fsync (fd) < 0)
    fatal << "noKRtoFile fsync";
  if (close (fd) < 0) 
    fatal << "noKRtoFile close";
}

void
keyregression_owner::init ()
{
  rnd.getbytes (hvec[hvec.size () - 1].base (), hvec[hvec.size () - 1].size ());
  warn << "hvec.size () - 1 = " << hvec.size () - 1 << "\n";
  update ();
}

void 
keyregression_owner::update ()
{
  for (uint32 i = hvec.size () - 1; i > 0; i--) {
    sha1_hash (hvec[i-1].base (), hvec[i].base (), sha1::hashsize);
  }
}


#if 0
keyregression::keyregression (str infile)
{
  assert (infile);

  ku = New refcounted<sfsro_keyupdate> ();

  str kufile = strbuf () << infile << ".ku";
  str wfile = strbuf () << infile << ".w";

  if (!file2xdr (ku, kufile)) {
    // XXX not a clean way to kill self
    fatal << "Unable to load " << kufile << "\n";
    return;
  }

  if (!file2xdr (w, wfile))
    w = NULL;
}

// Works on SHA1 only

ptr<rpc_bytes<> > 
keyregression::gk (uint32 i) {
#ifdef SFSRO_PROXY
  assert (proxy_lox);
  ptr<rpc_bytes<> > appkey = New refcounted<rpc_bytes<> >;
  appkey->setsize(proxy_lox->size ());
  memcpy(appkey->base (), proxy_lox->base (), proxy_lox->size ());
  return appkey;
#else
  if (i > ku->sub.vers) {
    warnx << "Do not have access to application key in future version " 
	 << i << "\n";
    return NULL;
  }

  if (w && i < w->vers) {
    warnx << "Do not have access to application key in past version " 
	  << i << "\n";
    return NULL;
  }
  
  ptr<rpc_bytes<> > appkey = 
    New refcounted<rpc_bytes<> >;
  ptr<rpc_bytes<> > subkey = 
    New refcounted<rpc_bytes<> >;
  appkey->setsize(ku->keysize);
  subkey->setsize(sha1::hashsize);

  hashchain (subkey->base (), ku->sub.key.base (), ku->sub.vers - i); 

  // truncate the appkey to keysize 
  memcpy(appkey->base (), subkey->base (), ku->keysize);

  return appkey;
#endif
}

uint32
keyregression::curr_vers () {
  return ku->sub.vers;
}

uint32
keyregression::get_id () {
  return ku->id;
}

keyregression_owner::keyregression_owner (str directory, uint32 id,
					  uint32 keysize,
					  uint32 chainlen,
					  bool create,
					  bool verbose)
{ 
  if (!directory) {
    delete this;
    return;
  }

  osfile = strbuf () << directory << "/" << id;

  /** Create os based on type **/
  os = New refcounted<sfsro_ownerstate> (type);
  if (create) {
    if (type == SFSRO_KRSHA1) {
      if (keysize > sha1::hashsize) {
	delete this;
	return;
      } else
	os->osh->keysize = keysize;
      os->osh->id = id;
      os->osh->current_vers = 0;
      os->osh->final_vers = chainlen;
      os->osh->current_key.setsize (sha1::hashsize);
      os->osh->final_subkey.setsize (sha1::hashsize);

      rnd.getbytes (os->osh->final_subkey.base (), 
		    os->osh->final_subkey.size ());
      hashchain (os->osh->current_key.base (), os->osh->final_subkey.base (), 
		 os->osh->final_vers);

      if (verbose) {
	warnx << "OWNER STATE\n";
	warnx << "ID              :" << os->osh->id << "\n";
	warnx << "Current version :" << os->osh->current_vers << "\n";
	warnx << "Final version   :" << os->osh->final_vers << "\n";
	warnx << "Current key     :" << hexdump(os->osh->current_key.base (), 
						os->osh->current_key.size ()) 
	      << "\n"; 
	warnx << "Final key       :" << hexdump(os->osh->final_subkey.base (), 
						os->osh->final_subkey.size ()) 
	      << "\n"; 
      }
      
      
    } else if (type == SFSRO_KRRSA) {
      
      // fill rabin structures. eventually with better abstraction
      // get rid of this if else ladder.
    }
    if (!xdr2file (osfile, os)) {
      warn << "Unable to write xdr to " << osfile << "\n";
      delete this;
      return;
    }
  } else 
    if (!file2xdr (os, osfile)) {
      warn << "Unable to read xdr from " << osfile << "\n";
      delete this;
      return;
    }
}


bool
keyregression_owner::add (str outfile, uint32 window_startvers, bool verbose)
{
  ptr<sfsro_keyupdate> ku = NULL;
  ptr<sfsro_window> w = NULL;

  if (!outfile) {
    warn << "No outfile\n";
    return false;
  }
  

  ku = New refcounted<sfsro_keyupdate> ();
  ku->type = os->type;
  
  if (os->type == SFSRO_KRSHA1) {
    ku->id = os->osh->id;
    ku->keysize = os->osh->keysize;

    if(window_startvers > os->osh->current_vers) {
      warn << "Invalid window start version\n";
      return false;
    }
    
    if (os->osh->window_start.size () > 0) {
      w = New refcounted<sfsro_window> ();
      w->vers = window_startvers;
      w->edge.setsize (20);    
      hashchain(w->edge.base (), os->osh->window_start.base (), 
		window_startvers);
    } 
    
    ku->sub.vers = os->osh->current_vers;
    ku->sub.key.setsize (sha1::hashsize);
    memcpy (ku->sub.key.base (), 
	    os->osh->current_key.base (), os->osh->current_key.size ());


    if (verbose) {
      warnx << "KEY UPDATE STRUCT\n";
      warnx << "Subkey version  :" << ku->sub.vers << "\n";
      warnx << "Subkey          :" << hexdump(ku->sub.key.base (),
					      ku->sub.key.size ()) << "\n";
      warnx << "OWNER STATE\n";
      warnx << "Current version :" << os->osh->current_vers << "\n";
      warnx << "Current key     :" << hexdump(os->osh->current_key.base (), 
					      os->osh->current_key.size ()) 
	    << "\n\n"; 
      
    }

  } else if (os->type == SFSRO_KRRSA) {
    // add rabin key
  }
  else {
    warn << "Invalid protocol type\n";
    return false;
  }
  
  str kufile = strbuf () << outfile << ".ku";
  str wfile = strbuf () << outfile << ".w";

  if (!xdr2file (kufile, ku)) {
    warn << "Unable to write ku to " << kufile << "\n";
    return false;
  }

  if (w && !xdr2file (wfile, w)) {
    warn << "Unable to write w to " << wfile << "\n";
    return false;
  }
  return true;
}

bool
keyregression_owner::wind (str outfile, bool verbose) 
{
  ptr<sfsro_keyupdate> ku;
  if (!outfile) {
    warn << "No outfile\n";
    return false;
  }

  ku = New refcounted<sfsro_keyupdate> ();
  ku->type = os->type;
  if (os->type == SFSRO_KRSHA1) {
    ku->id = os->osh->id;
    ku->keysize = os->osh->keysize;
	
    if(os->osh->current_vers == os->osh->final_vers) {
      warn << "Versions exhausted: Cannot wind group key: " << 
	os->osh->id << "\n";
      return false;
    } else 
      os->osh->current_vers++;

    ku->sub.vers = os->osh->current_vers;
    ku->sub.key.setsize (20);

    hashchain (os->osh->current_key.base (), os->osh->final_subkey.base (), 
	       (os->osh->final_vers - os->osh->current_vers));
    
    memcpy (ku->sub.key.base (), 
	    os->osh->current_key.base (), os->osh->current_key.size ());
    
    if (verbose) {
      warnx << "KEY UPDATE STRUCT\n";
      warnx << "Subkey version  :" << ku->sub.vers << "\n";
      warnx << "Subkey          :" << hexdump(ku->sub.key.base (),
					      ku->sub.key.size ()) << "\n";
      warnx << "OWNER STATE\n";
      warnx << "Current version :" << os->osh->current_vers << "\n";
      warnx << "Current key     :" << hexdump(os->osh->current_key.base (), 
					      os->osh->current_key.size ()) 
	    << "\n\n"; 
    }
  } else if (os->type == SFSRO_KRRSA) {
    // wind rabin key
  }
  else {
    warn << "Invalid protocol type\n";
    return false;
  }

  if (!xdr2file (osfile, os)) {
    warn << "Unable to write os to " << osfile << "\n";
    return false;
  }

  str kufile = strbuf () << outfile << ".ku";

  if (!xdr2file (kufile, ku)) {
    warn << "Unable to write ku to " << kufile << "\n";
    return false;
  }
  return true;
}

#endif
