// Copyright 2011-2012 Renato Tegon Forti

#define BOOST_ALL_DYN_LINK
#define BOOST_THREAD_USE_DLL  //thread header not compliant with 'BOOST_ALL_DYN_LINK'
#define BOOST_LIB_DIAGNOSTIC

#include <boost/test/minimal.hpp>
#include <dsafe/xmlsig.hpp>

#include <vector>

#define XMLSEC_CRYPTO_OPENSSL

// ---------------------

#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <libxml/tree.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

#ifndef XMLSEC_NO_XSLT
#include <libxslt/xslt.h>
#include <libxslt/security.h>
#endif /* XMLSEC_NO_XSLT */

#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/xmldsig.h>
#include <xmlsec/crypto.h>


const std::string XML_FILE = "/Projects/project.dokfile.vses/hades/trunk/products/doksafe/engine/libs/xmldsig/test/"
                             "mt-embedded-id-dtd-attr.xml";
							 // "mt.xml";
							 
void printNode(xmlNodePtr nd)
{
   xmlNodePtr nodeLevel1;
   xmlNodePtr nodeLevel2;

   for(	nodeLevel1 = nd->children;
		nodeLevel1 != NULL;
		nodeLevel1 = nodeLevel1->next)
	{
		printf("%s\n",nodeLevel1->name);
		for(	nodeLevel2 = nodeLevel1->children;
			nodeLevel2 != NULL;
			nodeLevel2 = nodeLevel2->next)
		{
        		printf("\t%s\n",nodeLevel2->name);
    		}
	}
}

// return -1 Failed to verify signature
//         0 Signature is INVALID
//         1 Signature is VALID
int verify_f(xmlSecKeysMngrPtr mngr, const char* xmlFile, 
             const std::string& nodeName = "NFe", 
			 const std::string& ns = "http://www.portalfiscal.inf.br/nfe", 
			 bool printDebugInfo = true)
{

   if(printDebugInfo)
   {
      fprintf(stderr, "========================================================================\n");
      fprintf(stderr, "verify_f\n");
      fprintf(stderr, "========================================================================\n");
   }
   
   int rc = -1;
	  
   xmlDocPtr doc = NULL;
   xmlSecDSigCtxPtr dsigCtx = NULL;
   
   xmlNodePtr interestNone = NULL; 
   xmlNodePtr keyInfo = NULL; xmlSecKeyPtr keyInfoKey = NULL;
   xmlNodePtr signature = NULL;
   
   xmlSecKeyDataPtr certData = NULL, certKey = NULL;
   
   doc = xmlParseFile(xmlFile);
   
   if ((doc == NULL) || (xmlDocGetRootElement(doc) == NULL))
   {
      fprintf(stderr, "---> ERROR : Error: unable to parse file \"%s\"\n", xmlFile);
      goto cleanup;  
   }
   
   dsigCtx = xmlSecDSigCtxCreate(mngr);
   
   if(dsigCtx == NULL) 
   {
      fprintf(stderr,"---> ERROR : Error: failed to create signature context\n");
      goto cleanup;
   }
   
   interestNone = xmlSecFindNode(xmlDocGetRootElement(doc), 
                                ( xmlChar* ) nodeName.c_str(), 
								( xmlChar* ) ns.c_str());
  
   if (interestNone == NULL)
   {
      fprintf(stderr, "---> ERROR : interestNone Error\n");
      goto cleanup;  
   }

   signature = xmlSecFindNode(interestNone, xmlSecNodeSignature, xmlSecDSigNs);
  
   if (signature == NULL)
   {
      fprintf(stderr, "---> ERROR : signature Error");
      goto cleanup;  
   }
   
   keyInfo = xmlSecFindNode(signature, xmlSecNodeKeyInfo, xmlSecDSigNs);
   
   if (keyInfo == NULL)
   {
      fprintf(stderr, "---> ERROR : keyInfo Error");
      goto cleanup;  
   }
   
   keyInfoKey = xmlSecKeyCreate();
   
   if (xmlSecKeyInfoNodeRead(keyInfo, keyInfoKey, &dsigCtx->keyInfoReadCtx) < 0)
   {
      fprintf(stderr, "---> ERROR : xmlSecKeyInfoNodeRead Error");
      goto cleanup;  
   }
   
   certData = xmlSecKeyGetData(keyInfoKey, xmlSecOpenSSLKeyDataX509GetKlass());
   
   if(certData == NULL)
   {
      fprintf(stderr, "---> ERROR : certData Error");
      goto cleanup;  
   }
   
   certKey = xmlSecOpenSSLX509CertGetKey(xmlSecOpenSSLKeyDataX509GetCert(certData, 0));
   
   if(certKey == NULL)
   {
      fprintf(stderr, "---> ERROR : certKey Error");
      goto cleanup;  
   }
   
   rc = xmlSecKeySetValue(keyInfoKey, certKey);
   
   if(rc != 0)
   {
      fprintf(stderr, "---> ERROR : xmlSecKeySetValue Error");
      goto cleanup;  
   }
   
   if(xmlSecDSigCtxVerify(dsigCtx, signature) < 0) 
   {
      fprintf(stderr,"Error: signature verify\n");
      goto cleanup;
   }
        
   // print verification result to stdout 
   if(dsigCtx->status == xmlSecDSigStatusSucceeded) 
   {
      fprintf(stdout, "Signature is OK\n");
   } 
   else 
   {
      fprintf(stdout, "Signature is ***INVALID***\n");
      goto cleanup;
   }  

   rc = 0; // if we here sig is valid...
   
   cleanup:    
   
   if(printDebugInfo)
   {
      xmlSecDSigCtxDebugXmlDump(dsigCtx, 0);
      xmlSecDSigCtxDebugDump(dsigCtx, 0);
   }
     
   if(keyInfoKey != NULL)
   {  
      xmlSecKeyDestroy(keyInfoKey);
   }
   
   if(dsigCtx != NULL) 
   {
      xmlSecDSigCtxDestroy(dsigCtx);
   }
   
   if(doc != NULL) 
   {
      xmlFreeDoc(doc); 
   }
   
   return rc;
}
			 
xmlSecKeysMngrPtr 
load_trusted_certs(const std::vector<std::string> & certs) 
{
   if(certs.size() == 0)
      return NULL;
	  
   xmlSecKeysMngrPtr mngr;
   
   /* create and initialize keys manager, we use a simple list based
    * keys manager, implement your own xmlSecKeysStore klass if you need
    * something more sophisticated 
    */
	
   mngr = xmlSecKeysMngrCreate();
   if(mngr == NULL) {
      fprintf(stderr, "Error: failed to create keys manager.\n");
      return(NULL);
   }
   
   if(xmlSecCryptoAppDefaultKeysMngrInit(mngr) < 0) {
      fprintf(stderr, "Error: failed to initialize keys manager.\n");
      xmlSecKeysMngrDestroy(mngr);
      return(NULL);
   }  
   
   for(int i = 0; i < certs.size(); ++i) 
   {
      fprintf(stdout, certs[i].c_str());
	  fprintf(stdout, "\n");

      /* load trusted cert */
      if(xmlSecCryptoAppKeysMngrCertLoad(mngr, certs[i].c_str(), xmlSecKeyDataFormatPem, xmlSecKeyDataTypeTrusted) < 0)
	  {
         fprintf(stderr,"Error: failed to load pem certificate from \"%s\"\n", certs[i].c_str());
         xmlSecKeysMngrDestroy(mngr);
         return(NULL);
      }
   }

   return(mngr);   
}

int do_test_xmlsec()
{

#ifndef XMLSEC_NO_XSLT
    xsltSecurityPrefsPtr xsltSecPrefs = NULL;
#endif /* XMLSEC_NO_XSLT */
    xmlSecKeysMngrPtr mngr;
    
    /* Init libxml and libxslt libraries */
    xmlInitParser();
    LIBXML_TEST_VERSION
    xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
    xmlSubstituteEntitiesDefault(1);
#ifndef XMLSEC_NO_XSLT
    xmlIndentTreeOutput = 1; 
#endif /* XMLSEC_NO_XSLT */

    /* Init libxslt */
#ifndef XMLSEC_NO_XSLT
    /* disable everything */
    xsltSecPrefs = xsltNewSecurityPrefs(); 
    xsltSetSecurityPrefs(xsltSecPrefs,  XSLT_SECPREF_READ_FILE,        xsltSecurityForbid);
    xsltSetSecurityPrefs(xsltSecPrefs,  XSLT_SECPREF_WRITE_FILE,       xsltSecurityForbid);
    xsltSetSecurityPrefs(xsltSecPrefs,  XSLT_SECPREF_CREATE_DIRECTORY, xsltSecurityForbid);
    xsltSetSecurityPrefs(xsltSecPrefs,  XSLT_SECPREF_READ_NETWORK,     xsltSecurityForbid);
    xsltSetSecurityPrefs(xsltSecPrefs,  XSLT_SECPREF_WRITE_NETWORK,    xsltSecurityForbid);
    xsltSetDefaultSecurityPrefs(xsltSecPrefs); 
#endif /* XMLSEC_NO_XSLT */
                
    /* Init xmlsec library */
    if(xmlSecInit() < 0) {
        fprintf(stderr, "Error: xmlsec initialization failed.\n");
        return(-1);
    }

    /* Check loaded library version */
    if(xmlSecCheckVersion() != 1) {
        fprintf(stderr, "Error: loaded xmlsec library version is not compatible.\n");
        return(-1);
    }

    /* Load default crypto engine if we are supporting dynamic
     * loading for xmlsec-crypto libraries. Use the crypto library
     * name ("openssl", "nss", etc.) to load corresponding 
     * xmlsec-crypto library.
     */
#ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
    if(xmlSecCryptoDLLoadLibrary(BAD_CAST XMLSEC_CRYPTO) < 0) {
        fprintf(stderr, "Error: unable to load default xmlsec-crypto library. Make sure\n"
                        "that you have it installed and check shared libraries path\n"
                        "(LD_LIBRARY_PATH) envornment variable.\n");
        return(-1);     
    }
#endif /* XMLSEC_CRYPTO_DYNAMIC_LOADING */

    /* Init crypto library */
    if(xmlSecCryptoAppInit(NULL) < 0) {
        fprintf(stderr, "Error: crypto initialization failed.\n");
        return(-1);
    }

    /* Init xmlsec-crypto library */
    if(xmlSecCryptoInit() < 0) {
        fprintf(stderr, "Error: xmlsec-crypto initialization failed.\n");
        return(-1);
    }
	
   std::vector<std::string> certs;
   
   certs.push_back("/usr/lib/ssl/certs/Serasa_Certificadora_Digital_v2.pem");
   certs.push_back("/usr/lib/ssl/certs/Serasa_Autoridade_Certificadora_Principal_v2.pem");
   certs.push_back("/usr/lib/ssl/certs/Autoridade_Certificadora_Raiz_Brasileira_v2.pem");
	
   mngr = load_trusted_certs(certs); 
   // mngr = xmlSecKeysMngrCreate();
   if(mngr == NULL) {
        return(-1);
   }

    /* create keys manager and load trusted certificates */
    //mngr = load_trusted_certs(fileName, 1);
   // if(mngr == NULL) {
   //     return(-1);
    //}
    
    /* verify file */
   // if(verify_file(mngr, XML_FILE.c_str()) < 0) {
   if(verify_f(mngr, XML_FILE.c_str()) < 0) {
   //if(verify_file2(mngr, XML_FILE.c_str()) < 0) {
   //if(verify(mngr, XML_FILE.c_str()) < 0) {
        xmlSecKeysMngrDestroy(mngr);    
        return(-1);
    }    
    
    /* destroy keys manager */
    xmlSecKeysMngrDestroy(mngr);
    
    /* Shutdown xmlsec-crypto library */
    xmlSecCryptoShutdown();
    
    /* Shutdown crypto library */
    xmlSecCryptoAppShutdown();
    
    /* Shutdown xmlsec library */
    xmlSecShutdown();

    /* Shutdown libxslt/libxml */
#ifndef XMLSEC_NO_XSLT
    xsltFreeSecurityPrefs(xsltSecPrefs);
    xsltCleanupGlobals();            
#endif /* XMLSEC_NO_XSLT */
    xmlCleanupParser();
    
    return(0);
}

int test_main(int, char*[])
{

   do_test_xmlsec();

   return 0;
}


