/*
  (C) 2007 FlexSecure GmbH
      2008-2010 Jack Lloyd
*
* Distributed under the terms of the Botan license
*/

#include <botan/cvc_self.h>
#include <botan/ecc_key.h>
#include <botan/point_gfp.h>
#include <botan/time.h>
#include <botan/oids.h>
#include <sstream>
#include <memory>

namespace Botan {

namespace {

/*
* cvc CHAT values
*/
enum CHAT_values{
      CVCA = 0xC0,
      DVCA_domestic = 0x80,
      DVCA_foreign =  0x40,
      IS   = 0x00,

      IRIS = 0x02,
      FINGERPRINT = 0x01
};

void encode_eac_bigint(DER_Encoder& der, const BigInt& x, ASN1_Tag tag)
   {
   der.encode(BigInt::encode_1363(x, x.bytes()), OCTET_STRING, tag);
   }

MemoryVector<byte> eac_1_1_encoding(const EC_PublicKey* key,
                                    const OID& sig_algo)
   {
   if(key->domain_format() == EC_DOMPAR_ENC_OID)
      throw Encoding_Error("CVC encoder: cannot encode parameters by OID");

   const EC_Group& domain = key->domain();

   // This is why we can't have nice things

   DER_Encoder enc;
   enc.start_cons(ASN1_Tag(73), APPLICATION)
      .encode(sig_algo);

   if(key->domain_format() == EC_DOMPAR_ENC_EXPLICIT)
      {
      encode_eac_bigint(enc, domain.get_curve().get_p(), ASN1_Tag(1));
      encode_eac_bigint(enc, domain.get_curve().get_a(), ASN1_Tag(2));
      encode_eac_bigint(enc, domain.get_curve().get_b(), ASN1_Tag(3));

      enc.encode(EC2OSP(domain.get_base_point(), PointGFp::UNCOMPRESSED),
                 OCTET_STRING, ASN1_Tag(4));

      encode_eac_bigint(enc, domain.get_order(), ASN1_Tag(4));
      }

   enc.encode(EC2OSP(key->public_point(), PointGFp::UNCOMPRESSED),
              OCTET_STRING, ASN1_Tag(6));

   if(key->domain_format() == EC_DOMPAR_ENC_EXPLICIT)
      encode_eac_bigint(enc, domain.get_cofactor(), ASN1_Tag(7));

   enc.end_cons();

   return enc.get_contents();
   }

std::string padding_and_hash_from_oid(OID const& oid)
   {
   std::string padding_and_hash = OIDS::lookup(oid); // use the hash

   if(padding_and_hash.substr(0,6) != "ECDSA/")
      throw Invalid_State("CVC: Can only use ECDSA, not " + padding_and_hash);

   padding_and_hash.erase(0, padding_and_hash.find("/") + 1);
   return padding_and_hash;
   }

}

namespace CVC_EAC {

EAC1_1_CVC create_self_signed_cert(Private_Key const& key,
                                   EAC1_1_CVC_Options const& opt,
                                   RandomNumberGenerator& rng)
   {
   // NOTE: we ignore the value of opt.chr

   const ECDSA_PrivateKey* priv_key = dynamic_cast<const ECDSA_PrivateKey*>(&key);

   if(priv_key == 0)
      throw Invalid_Argument("CVC_EAC::create_self_signed_cert(): unsupported key type");

   ASN1_Chr chr(opt.car.value());

   AlgorithmIdentifier sig_algo;
   std::string padding_and_hash("EMSA1_BSI(" + opt.hash_alg + ")");
   sig_algo.oid = OIDS::lookup(priv_key->algo_name() + "/" + padding_and_hash);
   sig_algo = AlgorithmIdentifier(sig_algo.oid, AlgorithmIdentifier::USE_NULL_PARAM);

   PK_Signer signer(*priv_key, padding_and_hash);

   MemoryVector<byte> enc_public_key = eac_1_1_encoding(priv_key, sig_algo.oid);

   return make_cvc_cert(signer,
                        enc_public_key,
                        opt.car, chr,
                        opt.holder_auth_templ,
                        opt.ced, opt.cex, rng);
   }

EAC1_1_Req create_cvc_req(Private_Key const& key,
                          ASN1_Chr const& chr,
                          std::string const& hash_alg,
                          RandomNumberGenerator& rng)
   {

   ECDSA_PrivateKey const* priv_key = dynamic_cast<ECDSA_PrivateKey const*>(&key);
   if (priv_key == 0)
      {
      throw Invalid_Argument("CVC_EAC::create_self_signed_cert(): unsupported key type");
      }
   AlgorithmIdentifier sig_algo;
   std::string padding_and_hash("EMSA1_BSI(" + hash_alg + ")");
   sig_algo.oid = OIDS::lookup(priv_key->algo_name() + "/" + padding_and_hash);
   sig_algo = AlgorithmIdentifier(sig_algo.oid, AlgorithmIdentifier::USE_NULL_PARAM);

   PK_Signer signer(*priv_key, padding_and_hash);

   MemoryVector<byte> enc_public_key = eac_1_1_encoding(priv_key, sig_algo.oid);

   MemoryVector<byte> enc_cpi;
   enc_cpi.push_back(0x00);
   MemoryVector<byte> tbs = DER_Encoder()
      .encode(enc_cpi, OCTET_STRING, ASN1_Tag(41), APPLICATION)
      .raw_bytes(enc_public_key)
      .encode(chr)
      .get_contents();

   MemoryVector<byte> signed_cert =
      EAC1_1_gen_CVC<EAC1_1_Req>::make_signed(signer,
                                              EAC1_1_gen_CVC<EAC1_1_Req>::build_cert_body(tbs),
                                              rng);

   DataSource_Memory source(signed_cert);
   return EAC1_1_Req(source);
   }

EAC1_1_ADO create_ado_req(Private_Key const& key,
                          EAC1_1_Req const& req,
                          ASN1_Car const& car,
                          RandomNumberGenerator& rng)
   {

   ECDSA_PrivateKey const* priv_key = dynamic_cast<ECDSA_PrivateKey const*>(&key);
   if (priv_key == 0)
      {
      throw Invalid_Argument("CVC_EAC::create_self_signed_cert(): unsupported key type");
      }
   std::string padding_and_hash = padding_and_hash_from_oid(req.signature_algorithm().oid);
   PK_Signer signer(*priv_key, padding_and_hash);
   SecureVector<byte> tbs_bits = req.BER_encode();
   tbs_bits += DER_Encoder().encode(car).get_contents();

   MemoryVector<byte> signed_cert =
      EAC1_1_ADO::make_signed(signer, tbs_bits, rng);

   DataSource_Memory source(signed_cert);
   return EAC1_1_ADO(source);
   }

} // namespace CVC_EAC
namespace DE_EAC
{

EAC1_1_CVC create_cvca(Private_Key const& key,
                       std::string const& hash,
                       ASN1_Car const& car, bool iris, bool fingerpr,
                       u32bit cvca_validity_months,
                       RandomNumberGenerator& rng)
   {
   ECDSA_PrivateKey const* priv_key = dynamic_cast<ECDSA_PrivateKey const*>(&key);
   if (priv_key == 0)
      {
      throw Invalid_Argument("CVC_EAC::create_self_signed_cert(): unsupported key type");
      }
   EAC1_1_CVC_Options opts;
   opts.car = car;
   const u64bit current_time = system_time();

   opts.ced = ASN1_Ced(current_time);
   opts.cex = ASN1_Cex(opts.ced);
   opts.cex.add_months(cvca_validity_months);
   opts.holder_auth_templ = (CVCA | (iris * IRIS) | (fingerpr * FINGERPRINT));
   opts.hash_alg = hash;
   return CVC_EAC::create_self_signed_cert(*priv_key, opts, rng);
   }



EAC1_1_CVC link_cvca(EAC1_1_CVC const& signer,
                     Private_Key const& key,
                     EAC1_1_CVC const& signee,
                     RandomNumberGenerator& rng)
   {
   ECDSA_PrivateKey const* priv_key = dynamic_cast<ECDSA_PrivateKey const*>(&key);
   if (priv_key == 0)
      {
      throw Invalid_Argument("CVC_EAC::create_self_signed_cert(): unsupported key type");
      }
   ASN1_Ced ced(system_time());
   ASN1_Cex cex(signee.get_cex());
   if (*static_cast<EAC_Time*>(&ced) > *static_cast<EAC_Time*>(&cex))
      {
      std::string detail("link_cvca(): validity periods of provided certificates don't overlap: currend time = ced = ");
      detail += ced.as_string();
      detail += ", signee.cex = ";
      detail += cex.as_string();
      throw Invalid_Argument(detail);
      }
   if (signer.signature_algorithm() != signee.signature_algorithm())
      {
      throw Invalid_Argument("link_cvca(): signature algorithms of signer and signee don't match");
      }
   AlgorithmIdentifier sig_algo = signer.signature_algorithm();
   std::string padding_and_hash = padding_and_hash_from_oid(sig_algo.oid);
   PK_Signer pk_signer(*priv_key, padding_and_hash);
   std::auto_ptr<Public_Key> pk(signee.subject_public_key());
   ECDSA_PublicKey* subj_pk = dynamic_cast<ECDSA_PublicKey*>(pk.get());
   subj_pk->set_parameter_encoding(EC_DOMPAR_ENC_EXPLICIT);

   MemoryVector<byte> enc_public_key = eac_1_1_encoding(priv_key, sig_algo.oid);

   return make_cvc_cert(pk_signer, enc_public_key,
                        signer.get_car(),
                        signee.get_chr(),
                        signer.get_chat_value(),
                        ced, cex,
                        rng);
   }

EAC1_1_CVC sign_request(EAC1_1_CVC const& signer_cert,
                        Private_Key const& key,
                        EAC1_1_Req const& signee,
                        u32bit seqnr,
                        u32bit seqnr_len,
                        bool domestic,
                        u32bit dvca_validity_months,
                        u32bit ca_is_validity_months,
                        RandomNumberGenerator& rng)
   {
   ECDSA_PrivateKey const* priv_key = dynamic_cast<ECDSA_PrivateKey const*>(&key);
   if (priv_key == 0)
      {
      throw Invalid_Argument("CVC_EAC::create_self_signed_cert(): unsupported key type");
      }
   std::string chr_str = signee.get_chr().value();
   chr_str += to_string(seqnr, seqnr_len);
   ASN1_Chr chr(chr_str);
   std::string padding_and_hash = padding_and_hash_from_oid(signee.signature_algorithm().oid);
   PK_Signer pk_signer(*priv_key, padding_and_hash);
   std::auto_ptr<Public_Key> pk(signee.subject_public_key());
   ECDSA_PublicKey*  subj_pk = dynamic_cast<ECDSA_PublicKey*>(pk.get());
   std::auto_ptr<Public_Key> signer_pk(signer_cert.subject_public_key());

   // for the case that the domain parameters are not set...
   // (we use those from the signer because they must fit)
   //subj_pk->set_domain_parameters(priv_key->domain_parameters());

   subj_pk->set_parameter_encoding(EC_DOMPAR_ENC_IMPLICITCA);

   AlgorithmIdentifier sig_algo(signer_cert.signature_algorithm());
   const u64bit current_time = system_time();
   ASN1_Ced ced(current_time);
   u32bit chat_val;
   u32bit chat_low = signer_cert.get_chat_value() & 0x3; // take the chat rights from signer
   ASN1_Cex cex(ced);
   if ((signer_cert.get_chat_value() & CVCA) == CVCA)
      {
      // we sign a dvca
      cex.add_months(dvca_validity_months);
      if (domestic)
         chat_val = DVCA_domestic | chat_low;
      else
         chat_val = DVCA_foreign | chat_low;
      }
   else if ((signer_cert.get_chat_value() & DVCA_domestic) == DVCA_domestic ||
            (signer_cert.get_chat_value() & DVCA_foreign) == DVCA_foreign)
      {
      cex.add_months(ca_is_validity_months);
      chat_val = IS | chat_low;
      }
   else
      {
      throw Invalid_Argument("sign_request(): encountered illegal value for CHAT");
      // (IS cannot sign certificates)
      }

   MemoryVector<byte> enc_public_key = eac_1_1_encoding(priv_key, sig_algo.oid);

   return make_cvc_cert(pk_signer, enc_public_key,
                        ASN1_Car(signer_cert.get_chr().iso_8859()),
                        chr,
                        chat_val,
                        ced,
                        cex,
                        rng);
   }

EAC1_1_Req create_cvc_req(Private_Key const& prkey,
                          ASN1_Chr const& chr,
                          std::string const& hash_alg,
                          RandomNumberGenerator& rng)
   {
   ECDSA_PrivateKey const* priv_key = dynamic_cast<ECDSA_PrivateKey const*>(&prkey);
   if (priv_key == 0)
      {
      throw Invalid_Argument("CVC_EAC::create_self_signed_cert(): unsupported key type");
      }
   ECDSA_PrivateKey key(*priv_key);
   key.set_parameter_encoding(EC_DOMPAR_ENC_IMPLICITCA);
   return CVC_EAC::create_cvc_req(key, chr, hash_alg, rng);
   }

} // namespace DE_EAC

}
