// ---------------------------------------------------------------------------
// - Pkcs.cpp                                                                 -
// - afnix:sec module - dss/pkcs signature class implementation               -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - 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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2023 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Pkcs.hpp"
#include "Vector.hpp"
#include "Crypto.hpp"
#include "Integer.hpp"
#include "Evaluable.hpp"
#include "QuarkZone.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // pkcs constants
  static const char*  PKCS_ALGO_NAME  = "PKCS";
  static const t_byte PKCS_HEAD_PAD[] = {
    0x00U, 0x01U
  };
  static const t_byte PKCS_HEAD_MD2[] = {
    0x30U, 0x20U, 0x30U, 0x0CU, 0x06U, 0x08U, 0x2AU, 0x86U,
    0x48U, 0x86U, 0xF7U, 0x0DU, 0x02U, 0x02U, 0x05U, 0x00U,
    0x04U
  };
  static const t_byte PKCS_HEAD_SHA1[] = {
    0x30U, 0x21U, 0x30U, 0x09U, 0x06U, 0x05U, 0x2BU, 0x0EU,
    0x03U, 0x02U, 0x1AU, 0x05U, 0x00U, 0x04U, 0x14U
  };
  static const t_byte PKCS_HEAD_SHA256[] = {
    0x30U, 0x31U, 0x30U, 0x0DU, 0x06U, 0x09U, 0x60U, 0x86U,
    0x48U, 0x01U, 0x65U, 0x03U, 0x04U, 0x02U, 0x01U, 0x05U,
    0x00U, 0x04U, 0x20U
  };
  static const t_byte PKCS_HEAD_SHA384[] = {
    0x30U, 0x41U, 0x30U, 0x0DU, 0x06U, 0x09U, 0x60U, 0x86U,
    0x48U, 0x01U, 0x65U, 0x03U, 0x04U, 0x02U, 0x02U, 0x05U,
    0x00U, 0x04U, 0x30U
  };
  static const t_byte PKCS_HEAD_SHA512[] = {
    0x30U, 0x51U, 0x30U, 0x0DU, 0x06U, 0x09U, 0x60U, 0x86U,
    0x48U, 0x01U, 0x65U, 0x03U, 0x04U, 0x02U, 0x03U, 0x05U,
    0x00U, 0x04U, 0x40U
  };
  
  // prepare a signing buffer by hasher  and target size
  static Buffer pkcs_to_sbuf (Hasher* hash, const long tsiz) {
    // finish hash processing
    if (hash != nullptr) hash->finish ();
    // get the hasher type
    Crypto::t_hash type = Crypto::tohasher (hash);
    if (type == Crypto::HASH_NIL) {
      throw Exception ("pkcs-error", "unsupported or nil hasher");
    }
    // prepare hash buffer
    Buffer hbuf;
    // check for MD2
    if (type == Crypto::HASH_MD2) {
      hbuf.add ((char*) PKCS_HEAD_MD2, sizeof (PKCS_HEAD_MD2));
    }
    // check for SHA1
    if (type == Crypto::HASH_SHA1) {
      hbuf.add ((char*) PKCS_HEAD_SHA1, sizeof (PKCS_HEAD_SHA1));
    }
    // check for SHA256
    if (type == Crypto::HASH_SHA256) {
      hbuf.add ((char*) PKCS_HEAD_SHA256, sizeof (PKCS_HEAD_SHA256));
    }
    // check for SHA384
    if (type == Crypto::HASH_SHA384) {
      hbuf.add ((char*) PKCS_HEAD_SHA384, sizeof (PKCS_HEAD_SHA384));
    }
    // check for SHA512
    if (type == Crypto::HASH_SHA512) {
      hbuf.add ((char*) PKCS_HEAD_SHA512, sizeof (PKCS_HEAD_SHA512));
    }
    // merge the hash content
    long hlen = hash->getrlen ();
    for (long k = 0L; k < hlen; k++) hbuf.add ((char)hash->getbyte(k));
    // extract pad length
    long plen = tsiz - 3 - hbuf.length ();
    if (plen < 8L) {
      throw Exception ("pkcs-error", "message padding is to short");
    }
    // build result buffer
    Buffer result; result.add ((char*) PKCS_HEAD_PAD, sizeof (PKCS_HEAD_PAD));
    for (long k = 0L; k < plen; k++) result.add ((char) 0xFF);
    result.add (nilc);
    // merge hash buffer
    result.add (hbuf);
    if (result.length () != tsiz) {
      throw Exception ("pkcs-error", "internal; padding error");
    }      
    return result;
  }
  
  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------
  
  // create a default pkcs signature

  Pkcs::Pkcs (void) : Signer (PKCS_ALGO_NAME) {
    p_hash = nullptr;
    // create a default key
    Key key (Key::CKEY_KRSA);
    // set the key
    if (setkey (key) == false) {
      throw Exception ("pkcs-error", "invalid key type for signature");
    }
    // set the default hasher
    if (sethash (Crypto::tostring (Crypto::HASH_SHA1)) == false) {
      throw Exception ("pkcs-error", "cannot set hasher");
    }
  }

  // create a pkcs signature by key

  Pkcs::Pkcs (const Key& key) : Signer (PKCS_ALGO_NAME) {
    // initialize
    p_hash = nullptr;
    // set the key
    if (setkey (key) ==  false) {
      throw Exception ("pkcs-error", "invalid key type for signature");
    }
    // set the default hasher
    if (sethash (Crypto::tostring (Crypto::HASH_SHA1)) == false) {
      throw Exception ("pkcs-error", "cannot set hasher");
    }
  }

  // create a pkcs signature by signature

  Pkcs::Pkcs (const Signature& sign) : Signer (PKCS_ALGO_NAME) {
    // initialize
    p_hash = nullptr;
    // set the key
    if (setkey (sign.getpubk ()) == false) {
      throw Exception ("pkcs-error", "invalid key type for signature");
    }
    // set the default hasher
    if (sethash (Crypto::tostring (Crypto::HASH_SHA1)) == false) {
      throw Exception ("pkcs-error", "cannot set hasher");
    }
  }
  
  // destroy this signature

  Pkcs::~Pkcs (void) {
    delete p_hash;
  }

  // return the class name

  String Pkcs::repr (void) const {
    return "Pkcs";
  }

  // reset this pkcs signature
  
  void Pkcs::reset (void) {
    wrlock ();
    try {
      // reset the hasher
      if (p_hash != nullptr) p_hash->reset ();
      // done
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    } 
  }

  // set the signature key
  
  bool Pkcs::setkey (const Key& key) {
    wrlock ();
    try {
      // check the key
      if (key.gettype () != Key::CKEY_KRSA) {
	unlock ();
	return false;
      }
      // set the signature key
      bool status = Signer::setkey (key);
      // reset everything
      reset ();
      // done
      unlock ();
      return status;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // process a message by data
  
  void Pkcs::process (const t_byte* data, const long size) {
    wrlock ();
    try {
      if (p_hash != nullptr) p_hash->process (data, size);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
 
  // process a message with a buffer
  
  void Pkcs::process (Buffer& buf) {
    try {
      if (p_hash != nullptr) p_hash->process (buf);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // process a message with an input stream
  
  void Pkcs::process (InputStream& is) {
    try {
      if (p_hash != nullptr) p_hash->process (is);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // finish the signature processing

  Signature Pkcs::finish (void) {
    rdlock ();
    try {
      // get the key components
      Relatif d = d_skey.getrkey (Key::KRSA_PMOD);
      Relatif e = d_skey.getrkey (Key::KRSA_SEXP);
      // compute target size
      long tsiz = d.getmsb () / 8L;
      // prepare signing buffer
      Buffer sbuf = pkcs_to_sbuf (p_hash, tsiz);
      if (sbuf.empty () == true) {
	throw Exception ("pkcs-error", "unsupported pkcs hasher",
			 Object::repr (p_hash));
      }
      // create a message
      Relatif m = Relatif (sbuf.tobyte (), sbuf.tosize (), false);
      // compute signature value
      Relatif r = m.pow (e, d);
      // generate the signature
      Vector ovec; ovec.add (new Relatif(r));
      Signature result (Signature::SIGN_SRSA, ovec);
      // bind the public key to the signature
      result.setpubk (d_skey.topublic ());
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // validate a signature

  bool Pkcs::validate (const Signature& sgn) {
    rdlock ();
    try {
      // colect key public components
      Relatif d = d_skey.getrkey (Key::KRSA_PMOD);
      Relatif e = d_skey.getrkey (Key::KRSA_PEXP);
      // compute target size
      long tsiz = d.getmsb () / 8L;
      // prepare message buffer
      Buffer sbuf = pkcs_to_sbuf (p_hash, tsiz);
      if (sbuf.empty () == true) {
	throw Exception ("pkcs-error", "unsupported pkcs hasher",
			 Object::repr (p_hash));
      }
      // create a message
      Relatif m = Relatif (sbuf.tobyte (), sbuf.tosize (), false);
      // collect signature message
      Relatif s = sgn.getrcmp (Signature::SRSA_SCMP);
      // recompute hash target
      Relatif r = s.pow (e, d);
      // compare hash targets
      bool result = (r == m) ? true : false;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }


  // bind a hasher to this signer

  bool Pkcs::sethash (const String& name) {
    wrlock ();
    try {
      // reset and bind hasher
      reset ();
      Object::dref (p_hash); Object::iref (p_hash = Crypto::mkhasher (name));
      bool status = (p_hash == nullptr) ? false : true;
      unlock ();
      return status;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // -------------------------------------------------------------------------
  // - object setion                                                         -
  // -------------------------------------------------------------------------

  // create a new object in a generic way
  
  Object* Pkcs::mknew (Vector* argv) {
    long argc = (argv == nullptr) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new Pkcs;
    // check for 1 argument
    if (argc == 1) {
      // check for a key
      Object* obj = argv->get (0);
      Key*    key = dynamic_cast <Key*> (obj);
      if (key != nullptr) return new Pkcs (*key);
      throw Exception ("argument-error", 
		       "invalid arguments with pkcs", Object::repr (obj));
    }
    throw Exception ("argument-error", "too many arguments with pkcs");
  }
}
