// ---------------------------------------------------------------------------
// - TlsSigner.cpp                                                           -
// - afnix:tls service - tls signer 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 "Boolean.hpp"
#include "TlsSuite.hxx"
#include "TlsSigner.hpp"
#include "Evaluable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

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

  // this procedure maps a hash code to a byte
  static t_byte tls_hash_to_code (Crypto::t_hash shsh) {
    t_byte result = 0x00U;
    switch (shsh) {
    case Crypto::HASH_MD5:
      result = 1U;
      break;
    case Crypto::HASH_SHA1:
      result = 2U;
      break;
    case Crypto::HASH_SHA224:
      result = 3U;
      break;
    case Crypto::HASH_SHA256:
      result = 4U;
      break;
    case Crypto::HASH_SHA384:
      result = 5U;
      break;
    case Crypto::HASH_SHA512:
      result = 6U;
      break;
    default:
      break;
    }
    return result;
  }

  // this procedure maps a signature code to a byte
  static t_byte tls_sign_to_code (Crypto::t_sign ssgn) {
    t_byte result = 0x00U;
    switch (ssgn) {
    case Crypto::SIGN_DSA:
      result = 2U;
      break;
    case Crypto::SIGN_RSA:
      result = 1U;
      break;
    default:
      break;
    }
    return result;
  }
  
  // this procedure create a dhe rsa digitally signed block
  static TlsChunk tls_dhe_rsa_chunk (TlsState* ssta) {
    // check for null state (again)
    if (ssta == nullptr) {
      throw Exception ("tls-error", "null state in tls_dhe_rsa_chunk");
    }
    // check for version - tls.2 minimum for this scheme
    if ((ssta->getvmaj () < 3) || (ssta->getvmin () < 3)) {
      throw Exception ("tls-error", "unsupported tls version for signing");
    }
    // collect the dhe parameters
    TlsDhe* dhep = ssta->getdhep ();
    if (dhep == nullptr) {
      throw Exception ("tls-error", "null dhe in tls_dhe_rsa_chunk");
    }
    // collect the tls certificate private key
    Key* prvk =  ssta->getprvk ();
    if (dhep == nullptr) {
      throw Exception ("tls-error", "null private key tls_dhe_rsa_chunk");
    }
    if (prvk->gettype () != Key::CKEY_KRSA) {
      throw Exception ("tls-error", "invalid key tls_dhe_rsa_chunk");
    }
    // collect signature/hash algorithm
    Crypto::t_hash shsh = ssta->getshsh ();
    Crypto::t_sign ssgn = ssta->getssgn ();
    // prepare the chunk result
    TlsChunk result;
    result.add (tls_hash_to_code (shsh));
    result.add (tls_sign_to_code (ssgn));
    // prepare the parameters buffer for signature
    Buffer pbuf;
    pbuf.add (ssta->getcrnd ());
    pbuf.add (ssta->getsrnd ());
    pbuf.add (dhep->tochunk().tobuffer());
    // check for rsa signature scheme
    if (ssgn == Crypto::SIGN_RSA) {
      // collect the tls certificate private key
      Key* prvk =  ssta->getprvk ();
      if (dhep == nullptr) {
	throw Exception ("tls-error", "null private key tls_dhe_rsa_chunk");
      }
      if (prvk->gettype () != Key::CKEY_KRSA) {
	throw Exception ("tls-error", "invalid key tls_dhe_rsa_chunk");
      }
      // create a pkcs signer by key
      Pkcs pkcs (*prvk); pkcs.sethash (Crypto::tostring (shsh));
      // process the buffer
      Signature sign = pkcs.compute (pbuf);
      // collect rsa signature
      Relatif s = sign.getrcmp (Signature::SRSA_SCMP);
      result.add (s);
    }
    // check for dsa signature scheme
    if (ssgn == Crypto::SIGN_DSA) {
      throw Exception ("tls-error",
		       "unimplemented dsa signature with tls_dhe_rsa_chunk");
    }
    // all done
    return result;
  }
  
  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a tls signer by state

  TlsSigner::TlsSigner (TlsState* ssta) {
    Object::iref (p_ssta = ssta);
  }
  
  // destroy this tls signer

  TlsSigner::~TlsSigner (void) {
    Object::dref (p_ssta);
  }

  // return the class name
  
  String TlsSigner::repr (void) const {
    return "TlsSigner";
  }

  // reset this tls signer stream

  void TlsSigner::reset (void) {
    wrlock ();
    try {
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // create a digitally signed chunk

  TlsChunk TlsSigner::tochunk (void) const {
    rdlock ();
    try {
      // collect the cipher
      t_word cifr = (p_ssta == nullptr) ? 0x0000U : p_ssta->getcifr ();
      if (cifr == TLS_NULL_WITH_NULL_NULL) {
	throw Exception ("tls-error", "invalid state or cipher for signing");
      }
      TlsChunk result;
      // check for rsa dhe
      switch (cifr) {
      case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
      case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
      case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
      case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
      case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
      case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
	result = tls_dhe_rsa_chunk (p_ssta);
	break;
      default:
	break;
      }
      // check for valid chunk
      if (result.empty () == true) {
	throw Exception ("tls-error", "invalid signer chunk request");
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
}
