// ---------------------------------------------------------------------------
// - X509.cpp                                                                -
// - afnix:itu module - X509 certificate 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-2013 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "X509.hpp"
#include "Vector.hpp"
#include "AsnOid.hpp"
#include "AsnSet.hpp"
#include "AsnUtc.hpp"
#include "AsnGtm.hpp"
#include "AsnBits.hpp"
#include "Integer.hpp"
#include "AsnBuffer.hpp"
#include "AsnOctets.hpp"
#include "InputFile.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "AsnBoolean.hpp"
#include "AsnInteger.hpp"
#include "AsnSequence.hpp"

namespace afnix {

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

  // get the certificate node from the asn buffer
  static AsnSequence* x509_tocert (const AsnBuffer& abuf) {
    // map the buffer to a node
    AsnNode* node = abuf.mapnode ();
    // check for a sequence
    AsnSequence* cert = dynamic_cast <AsnSequence*> (node);
    if (cert == nilp) {
      delete node;
      throw Exception ("X509-error", "cannot map certificate sequence node");
    }
    // check the sequence length
    if (cert->getnlen () != 3) {
      delete node;
      throw Exception ("X509-error", "invalid certificate sequence length");
    }
    return cert;
  }

  // get the tbs certificate from the certificate node
  static AsnSequence* x509_toxtbs (const AsnSequence* cert) {
    // check for nil
    if (cert == nilp) {
      throw Exception ("X509-error", "invalid nil certificate node");
    }
    // get the first node
    AsnNode* node = cert->getnode (0);
    // check for a sequence
    AsnSequence* xtbs = dynamic_cast <AsnSequence*> (node);
    if (xtbs == nilp) {
      throw Exception ("X509-error", "cannot map certificate tbs node");
    }
    // check the sequence length
    if (xtbs->getnlen () < 7) {
      throw Exception ("X509-error", "invalid certificate tbs length");
    }
    return xtbs;
  }

  // get the certificate version from the tbs certificate
  static long x509_tovers (const AsnSequence* xtbs) {
    // check for nil
    if (xtbs == nilp) {
      throw Exception ("X509-error", "invalid nil tbs certificate node");
    }
    // get the first node (version) - in x509, the tag is explicit [0]
    // and the node must be constructed with the underlying node as an integer
    AsnNode*   node = xtbs->getnode (0);
    AsnBuffer* nbuf = dynamic_cast <AsnBuffer*> (node);
    if ((node != nilp) && (node->iscst () == false)) {
      throw Exception ("X509-error", "invalid implicit version node");
    }
    if (nbuf == nilp) {
      throw Exception ("X509-error", "cannot map version node buffer");
    }
    // get the content buffer
    Buffer* vbuf = nbuf->getcbuf ();
    if (vbuf == nilp) {
      throw Exception ("X509-error", "cannot get version content buffer");
    }
    // map the version node
    AsnBuffer abuf (*vbuf);
    AsnNode*  vnod = nilp;
    try {
      vnod = abuf.mapnode ();
    } catch (...) {
      delete vbuf;
      throw;
    }
    AsnInteger* inod = dynamic_cast <AsnInteger*> (vnod);
    if (inod == nilp) {
      delete vbuf;
      delete vnod;
      throw Exception ("X509-error", "cannot map integer version node");
    }
    Relatif rval = inod->torelatif ();
    delete vbuf;
    delete vnod;
    return rval.tolong ();
  }
  
  // get the certificate serial number from the tbs certificate
  static Relatif x509_tosnum (const AsnSequence* xtbs) {
    // check for nil
    if (xtbs == nilp) {
      throw Exception ("X509-error", "invalid nil tbs certificate node");
    }
    // get the serial node [1]
    AsnNode* node = xtbs->getnode (1);
    AsnInteger* inod = dynamic_cast <AsnInteger*> (node);
    if (inod == nilp) {
      throw Exception ("X509-error", "cannot map integer serial number node");
    }
    return inod->torelatif ();
  }

  // get an algorithm from a sequence
  static Xalgo x509_toalgo (AsnNode* node) {
    // check for nil
    if (node == nilp) {
      throw Exception ("X509-error", "invalid nil sequence as algorithm");
    }
    // map the algorithm node
    AsnSequence* snod = dynamic_cast <AsnSequence*> (node);
    if (snod == nilp) {
      throw Exception ("X509-error", "cannot map algorithm sequence node");
    }
    long slen = snod->getnlen ();
    if ((slen != 1) && (slen != 2)) {
      throw Exception ("X509-error", "invalid algorithm sequence length");
    }
    // get the signature oid
    AsnOid* soid = dynamic_cast <AsnOid*> (snod->getnode (0));
    if (soid == nilp) {
      throw Exception ("X509-error", "invalid nil oid in algorithm sequence");
    }
    // get the oid representation
    Oid    oid = soid->getoid ();
    String aid = oid.format   ();
    // get the signature parameters
    AsnNode* aop = (slen == 2) ? snod->getnode (1) : nilp;
    // create the algorithm identifier
    return Xalgo (aid, aop);
  }
 
  // get a rdn name as a property list
  static Plist x509_toname (const AsnSequence* xtbs, const long xidx) {
    // check for nil
    if (xtbs == nilp) {
      throw Exception ("X509-error", "invalid nil tbs certificate node");
    }
    // get the sequence node
    AsnSequence* node = dynamic_cast <AsnSequence*> (xtbs->getnode (xidx));
    if (node == nilp) {
      throw Exception ("X509-error", "cannot map name sequence node");
    }
    // get the number of rdn
    long slen = node->getnlen ();
    if (slen == 0) {
      throw Exception ("X509-error", "invalid nil name sequence length");
    }
    // loop and set the plist result
    Plist result;
    for (long k = 0; k < slen; k++) {
      // get the set node
      AsnSet* snod = dynamic_cast <AsnSet*> (node->getnode (k));
      if (snod == nilp) {
	throw Exception ("X509-error", "cannot map name set node");
      }
      if (snod->getnlen () != 1) {
	throw Exception ("X509-error", "invalid name sequence set length");
      }
      // get the attribute node
      AsnSequence* anod = dynamic_cast <AsnSequence*> (snod->getnode (0));
      if (anod == nilp) {
	throw Exception ("X509-error", "cannot map name attribute node");
      }
      if (anod->getnlen () != 2) {
	throw Exception ("X509-error", "invalid name attrbute set length");
      }
      // get the oid attribute
      AsnOid* aid = dynamic_cast <AsnOid*> (anod->getnode (0));
      if (aid == nilp) {
	throw Exception ("X509-error", "cannot map name attribute oid");
      }
      Oid oid = aid->getoid ();
      // get the attribute value
      AsnNode* vnod = anod->getnode (1);
      // check for octets string
      AsnOctets* onod = dynamic_cast <AsnOctets*> (vnod);
      if (onod != nilp) {
	result.add (oid.format (), onod->tostring ());
	continue;
      }
      // check for boolean
      AsnBoolean* bnod = dynamic_cast <AsnBoolean*> (vnod);
      if (bnod != nilp) {
	result.add (oid.format (), bnod->tobool ());
	continue;
      }
      // check for integer
      AsnInteger* inod = dynamic_cast <AsnInteger*> (vnod);
      if (inod != nilp) {
	Relatif rval = inod->torelatif ();
	result.add (oid.format (), rval.tolong ());
	continue;
      }
      // invalid object
      throw Exception ("X509-error", "cannot map name attribute valud",
		       Object::repr (vnod));
    }
    return result;
  }

  // this procedure map a node to a time value
  static t_long x509_totime (AsnNode* node) {
    // check for nil first
    if (node == nilp) {
      throw Exception ("X509-error", "invalid nil time node");
    }
    // check for utc node
    AsnUtc* utcn = dynamic_cast <AsnUtc*> (node);
    if (utcn != nilp) return utcn->totime ();
    // check for generailized time node
    AsnGtm* gtmn = dynamic_cast <AsnGtm*> (node);
    if (gtmn != nilp) return gtmn->totime ();
    // invalid node
    throw Exception ("X509-error", "invalid asn object as time node",
		     Object::repr (node));
  }

  // get the certificate signature
  static Xalgo x509_tosign (const AsnSequence* xtbs) {
    // check for nil
    if (xtbs == nilp) {
      throw Exception ("X509-error", "invalid nil tbs certificate node");
    }
    // get the signature node [2]
    AsnNode* node = xtbs->getnode (2);
    // get the signature algorithm
    return x509_toalgo (node);
  }

  // map the validity time sequence
  static void x509_tovldt (t_long& tvlb,t_long& tvla,const AsnSequence* xtbs) {
    // check for nil
    if (xtbs == nilp) {
      throw Exception ("X509-error", "invalid nil tbs certificate node");
    }
    // get the validity node [4]
    AsnSequence* node = dynamic_cast <AsnSequence*> (xtbs->getnode (4));
    if (node == nilp) {
      throw Exception ("X509-error", "cannot map validity sequence node");
    }
    // get the number of nodes
    long slen = node->getnlen ();
    if (slen != 2) {
      throw Exception ("X509-error", "invalid validity sequence length");
    }
    // get the validity before time
    tvlb = x509_totime (node->getnode (0));
    // get the validity after time
    tvla = x509_totime (node->getnode (1));
  }

  // get the subject public key sequence
  static AsnSequence* x509_tospks (const AsnSequence* xtbs) {
    // check for nil
    if (xtbs == nilp) {
      throw Exception ("X509-error", "invalid nil tbs certificate node");
    }
    // get the subject public key node [6]
    AsnSequence* node = dynamic_cast <AsnSequence*> (xtbs->getnode (6));
    if (node == nilp) {
      throw Exception ("X509-error", 
		       "cannot map subject public key info sequence node");
    }
    // get the number of nodes
    long slen = node->getnlen ();
    if (slen != 2) {
      throw Exception ("X509-error", 
		       "invalid validity subject public key info length");
    }
    return node;
  }

  // get the subject public key algorithm
  static Xalgo x509_tospka (const AsnSequence* snod) {
    AsnNode* node = (snod == nilp) ? nilp : snod->getnode (0);
    return x509_toalgo (node);
  }

  // get the subject public key info
  static Bitset x509_tospki (const AsnSequence* snod) {
    // map the bit string node
    AsnNode* node = (snod == nilp) ? nilp : snod->getnode (1);
    AsnBits* bnod = dynamic_cast <AsnBits*> (node);
    // check for nil
    if (bnod == nilp) {
      throw Exception ("x509-error", "invalid nil bit string as key info");
    }
    // map it as a bitset
    return bnod->tobits ();
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create an empty certificate

  X509::X509 (void) {
    reset ();
  }

  // create a certificate from an input file

  X509::X509 (const String& path) {
    // create the input file
    InputFile is (path);
    // parse the file
    parse (&is);
    // close the file
    is.close ();
  }

  // create a certificate from an input stream

  X509::X509 (InputStream* is) {
    parse (is);
  }

  // return the object class name

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

  // reset the certificate

  void X509::reset (void) {
    wrlock ();
    try {
      d_vers = 0;
      d_snum = 0;
      d_slgo.reset ();
      d_ilst.reset ();
      d_slst.reset ();
      d_tvlb = 0LL;
      d_tvla = 0LL;
      d_spka.reset ();
      d_spki.reset ();
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // parse an input stream

  void X509::parse (InputStream* is) {
    wrlock ();
    try {
      // check for valid stream
      if (is == nilp) {
	throw Exception ("X509-error", "invalid nil input stream to parse");
      }
      // reset the certificate
      reset ();
      // create an asn buffer
      AsnBuffer abuf (is);
      // map the buffer to a node
      AsnSequence* cert = x509_tocert (abuf);
      // get the tbs node
      AsnSequence* xtbs = x509_toxtbs (cert);
      // get the certificate version
      d_vers = x509_tovers (xtbs);
      // get the serial number
      d_snum = x509_tosnum (xtbs);
      // get the signature algorithm
      d_slgo = x509_tosign (xtbs);
      // get the issuer as a plist
      d_ilst = x509_toname (xtbs, 3);
      // get the subject as a plist
      d_slst = x509_toname (xtbs, 5);
      // get the validity time
      x509_tovldt (d_tvlb, d_tvla, xtbs);
      // get the subject key info
      AsnSequence* spks = x509_tospks (xtbs);
      d_spka = x509_tospka (spks);
      d_spki = x509_tospki (spks);
      // clean and return
      delete cert;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the certificate version

  long X509::getvers (void) const {
    rdlock ();
    try {
      long result = d_vers;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the certificate serial number

  Relatif X509::getsnum (void) const {
    rdlock ();
    try {
      Relatif result = d_snum;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the signature algorithm

  Xalgo X509::getslgo (void) const {
    rdlock ();
    try {
      Xalgo result = d_slgo;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the issuer plist

  Plist X509::getilst (void) const {
    rdlock ();
    try {
      Plist result = d_ilst;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the subject plist

  Plist X509::getslst (void) const {
    rdlock ();
    try {
      Plist result = d_slst;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the validity before time

  t_long X509::gettvlb (void) const {
    rdlock ();
    try {
      t_long result = d_tvlb;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the validity after time

  t_long X509::gettvla (void) const {
    rdlock ();
    try {
      t_long result = d_tvla;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the subject key algorithm

  Xalgo X509::getspka (void) const {
    rdlock ();
    try {
      Xalgo result = d_spka;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the subject key info

  Bitset X509::getspki (void) const {
    rdlock ();
    try {
      Bitset result = d_spki;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 9;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the object supported quarks
  static const long QUARK_GETVERS = zone.intern ("get-version");
  static const long QUARK_GETILST = zone.intern ("get-issuer-plist");
  static const long QUARK_GETSLST = zone.intern ("get-subject-plist");
  static const long QUARK_GETSNUM = zone.intern ("get-serial-number");
  static const long QUARK_GETSLGO = zone.intern ("get-signature-algorithm");
  static const long QUARK_GETTVLB = zone.intern ("get-validity-before");
  static const long QUARK_GETTVLA = zone.intern ("get-validity-after");
  static const long QUARK_GETSPKA = zone.intern ("get-subject-key-algorithm");
  static const long QUARK_GETSPKI = zone.intern ("get-subject-key-info");

  // create a new object in a generic way
 
  Object* X509::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();

    // create a default object
    if (argc == 0) return new X509;
    // check for 1 argument
    if (argc == 1) {
      Object* obj = argv->get (0);
      // check for a string
      String* path = dynamic_cast <String*> (obj);
      if (path != nilp) return new X509 (*path);
      // check for an input stream
      InputStream* is = dynamic_cast <InputStream*> (obj);
      if (is != nilp) return new X509 (is);
      // invalid object
      throw Exception ("type-error", "invalif object with X509",
		       Object::repr (obj));
    }
    // too many arguments
    throw Exception ("argument-error",
                     "too many argument with certificate constructor");
  }

  // return true if the given quark is defined

  bool X509::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true) {
      unlock ();
      return true;
    }
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // apply this object with a set of arguments and a quark

  Object* X509::apply (Runnable* robj, Nameset* nset, const long quark,
		       Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    
    // check for 0 argument
    if (argc == 0) {
      if (quark == QUARK_GETVERS) return new Integer (getvers ());
      if (quark == QUARK_GETSNUM) return new Relatif (getsnum ());
      if (quark == QUARK_GETSLGO) return new Xalgo   (getslgo ());
      if (quark == QUARK_GETILST) return new Plist   (getilst ());
      if (quark == QUARK_GETSLST) return new Plist   (getslst ());
      if (quark == QUARK_GETTVLB) return new Integer (gettvlb ());
      if (quark == QUARK_GETTVLA) return new Integer (gettvla ());
      if (quark == QUARK_GETSPKA) return new Xalgo   (getspka ());
      if (quark == QUARK_GETSPKI) return new Bitset  (getspki ());
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }

}
