/*
 * Copyright (c) 2002-2009 Moxie Marlinspike
 *
 * 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 3 of the
 * License, 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
 */

#ifndef __CERTIFICATE_H__
#define __CERTIFICATE_H__

#include <openssl/pem.h>
#include <openssl/conf.h>
#include <openssl/x509v3.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>

#include <boost/asio.hpp>

#include <iostream>
#include <string>

#include "../util/Util.hpp"
#include "../Logger.hpp"

class BadCertificateException : public std::exception {
public:
  virtual const char* what() const throw() {
    return "Could not parse certificate...";
  }
};

class Certificate {

private:
  X509 *cert;
  EVP_PKEY *key;
  std::string name;

  std::list<std::string> ocspHosts;
  std::list<boost::asio::ip::address> ips;
  std::list<boost::asio::ip::address> ocspIps;

  std::string parseNameFromOCSPUrl(std::string &url) {
    int forwardSlash = url.find('/', 7);

    if (forwardSlash) forwardSlash -= 7;
    else              forwardSlash  = (int) std::string::npos;

    std::string name = url.substr(7, forwardSlash);	
    
    return name;
  }

  void parseOCSPUrls(X509 *cert) {
    int crit = 0;
    int idx  = 0;

    STACK_OF(ACCESS_DESCRIPTION) *ads = 
      (STACK_OF(ACCESS_DESCRIPTION)*)X509_get_ext_d2i(cert, NID_info_access, &crit, &idx); 

    int adsnum = sk_ACCESS_DESCRIPTION_num(ads);

    for(int i=0; i<adsnum; i++) { 
      ACCESS_DESCRIPTION *ad = sk_ACCESS_DESCRIPTION_value(ads, i); 
      
      if (!ad) continue;
      
      if(OBJ_obj2nid(ad->method) == NID_ad_OCSP) { 
	unsigned char * data = ASN1_STRING_data(ad->location->d.ia5);

	std::string url(strdup((const char*)data));
	std::string name = parseNameFromOCSPUrl(url);

	this->ocspHosts.push_back(name);

	Logger::logInit("Added OCSP URL: " + name);
      } 
    }    
  }

  void parseCommonName(X509 *cert) {
    std::string distinguishedName(cert->name);
    std::string::size_type cnIndex = distinguishedName.find("CN=");

    if (cnIndex == std::string::npos) throw BadCertificateException();

    std::string commonName           = distinguishedName.substr(cnIndex+3);    
    std::string::size_type nullIndex = commonName.find("\\x00");
    
    if (nullIndex != std::string::npos) this->name = commonName.substr(0, nullIndex);
    else                                this->name = commonName;
  }

public:
  Certificate() {}

  Certificate(X509 *cert, EVP_PKEY* key, bool resolve) {
    this->cert = cert;
    this->key  = key;
    
    parseCommonName(cert);
    parseOCSPUrls(cert);
    
    if (resolve) {

      if (!isWildcard())
	Util::resolveName(name, ips);
      
      std::list<std::string>::iterator i   = ocspHosts.begin();
      std::list<std::string>::iterator end = ocspHosts.end();
      
      for ( ; i != end ; i++) {
	Util::resolveName(*i, ocspIps);
      }
    }

    Logger::logInit("Certificate Ready: " + this->name);
  }

  bool isOCSPAddress(boost::asio::ip::address &address) {
    std::list<boost::asio::ip::address>::iterator i   = ocspIps.begin();
    std::list<boost::asio::ip::address>::iterator end = ocspIps.end();

    for ( ; i != end ; i++) {
      if ((*i) == address) return true;
    }

    return false;    
  }

  bool isValidTarget(boost::asio::ip::address &address, bool wildcardOK) {
    if (wildcardOK && isWildcard()) {
      return true;
    }

    std::list<boost::asio::ip::address>::iterator i   = ips.begin();
    std::list<boost::asio::ip::address>::iterator end = ips.end();

    for ( ; i != end ; i++) {
      if ((*i) == address) return true;
    }

    return false;
  }

  X509* getCert() {
    return cert;
  }

  EVP_PKEY* getKey() {
    return key;
  }

  void setCert(X509 *cert) {
    this->cert = cert;
  }

  void setKey(EVP_PKEY *key) {
    this->key = key;
  }

  std::string& getName() {
    return name;
  }

  bool isWildcard() {
    return name == "*";
  }
};


#endif
