/*
 * The Cryptonit security software suite is developped by IDEALX
 * Cryptonit Team (http://IDEALX.org/ and http://cryptonit.org).
 *
 * Copyright 2003-2006 IDEALX
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 * 
 * 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * In addition, as two special exceptions:
 *
 * 1) IDEALX S.A.S gives permission to:
 *  * link the code of portions of his program with the OpenSSL library under
 *    certain conditions described in each source file
 *  * distribute linked combinations including the two, with respect to the
 *    OpenSSL license and with the GPL
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception,
 * you may extend this exception to your version of the file(s), but you are
 * not obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version, in all files (this very one along with all
 * source files).

 * 2) IDEALX S.A.S acknowledges that portions of his sourcecode uses (by the
 * way of headers inclusion) some work published by 'RSA Security Inc.'. Those
 * portions are "derived from the RSA Security Inc. PKCS #11Cryptographic
 * Token Interface (Cryptoki)" as described in each individual source file.
 */

#include "Utils.hh"

#include <string> //strlen
#include <algorithm> // replace
#include <assert.h> // assert
#include <stdlib.h> //getenv

#include <openssl/evp.h> // for password encrypting
#include <openssl/md5.h>
#include <openssl/rand.h>

#include <sys/stat.h>  // mkdir
#include <sys/types.h> // mkdir
#include <fcntl.h>     // mkdir

#include <errno.h>
extern int errno;

#include "config.h"

#ifndef HAVE_MKSTEMP
// mkstemp wrapper
#include <sys/stat.h>
#endif


#define CRYPTONIT_HOME_WIN32 "\\Cryptonit\\"
#define CRYPTONIT_HOME_UNIX  "/.cryptonit/"

#ifndef WIN32
extern "C" {
    int mkdir(const char *pathname, mode_t mode);
}
int MKDIR(const char *x)
{
  return mkdir(x, 0700);
}
#else
int MKDIR(const char *x)
{
  return mkdir(x);
}
#endif

char *generateSalt()
{
    char *salt_p = NULL;
    int i;
			
    salt_p = (char*) OPENSSL_malloc(9);
    if (salt_p == NULL) {
	return NULL;
    }
    for( i = 0; i < 9; i++ ) salt_p[i] = 0;

    if (RAND_pseudo_bytes((unsigned char *)salt_p, 8) < 0) {
	return 0;
    }

    for (i = 0; i < 8; i++) {
      (salt_p)[i] = cov_2char[(salt_p)[i] & 0x3f]; /* 6 bits */
    }
    (salt_p)[8] = 0;
    
    return salt_p;
}



// taken from $OPENSSL_SRC/apps/password.c
char* encryptPassword(const char *passwd, const char *magic, const char *salt)
{
    static char out_buf[6 + 9 + 24 + 2]; /* "@apr1@..salt..@.......md5hash..........\0" */
    unsigned char buf[MD5_DIGEST_LENGTH];
    char *salt_out;
    int n;
    EVP_MD_CTX md,md2;
    size_t passwd_len, salt_len, i;
    
    passwd_len = strlen(passwd);
    out_buf[0] = '@';
    out_buf[1] = 0;
    assert(strlen(magic) <= 4); /* "1" or "apr1" */
    strncat(out_buf, magic, 4);
    strncat(out_buf, "@", 1);
    strncat(out_buf, salt, 8);
    assert(strlen(out_buf) <= 6 + 8); /* "@apr1@..salt.." */
    salt_out = out_buf + 2 + strlen(magic);
    salt_len = strlen(salt_out);
    assert(salt_len <= 8);
	
    EVP_MD_CTX_init(&md);
    EVP_DigestInit_ex(&md,EVP_md5(), NULL);
    EVP_DigestUpdate(&md, passwd, passwd_len);
    EVP_DigestUpdate(&md, "@", 1);
    EVP_DigestUpdate(&md, magic, strlen(magic));
    EVP_DigestUpdate(&md, "@", 1);
    EVP_DigestUpdate(&md, salt_out, salt_len);
    
    EVP_MD_CTX_init(&md2);
    EVP_DigestInit_ex(&md2,EVP_md5(), NULL);
    EVP_DigestUpdate(&md2, passwd, passwd_len);
    EVP_DigestUpdate(&md2, salt_out, salt_len);
    EVP_DigestUpdate(&md2, passwd, passwd_len);
    EVP_DigestFinal_ex(&md2, buf, NULL);
    
    for (i = passwd_len; i > sizeof buf; i -= sizeof buf) {
	EVP_DigestUpdate(&md, buf, sizeof buf);
    }
    EVP_DigestUpdate(&md, buf, i);
	
    n = passwd_len;
    while (n) {
	EVP_DigestUpdate(&md, (n & 1) ? "\0" : passwd, 1);
	n >>= 1;
    }
    EVP_DigestFinal_ex(&md, buf, NULL);

    for (i = 0; i < 1000; i++) {
	EVP_DigestInit_ex(&md2,EVP_md5(), NULL);
	EVP_DigestUpdate(&md2, (i & 1) ? (unsigned char *) passwd : buf,
			 (i & 1) ? passwd_len : sizeof buf);

	if (i % 3) {
	    EVP_DigestUpdate(&md2, salt_out, salt_len);
	}
	if (i % 7) {
	    EVP_DigestUpdate(&md2, passwd, passwd_len);
	}

	EVP_DigestUpdate(&md2, (i & 1) ? buf : (unsigned char *) passwd,
			 (i & 1) ? sizeof buf : passwd_len);

	EVP_DigestFinal_ex(&md2, buf, NULL);
    }
    EVP_MD_CTX_cleanup(&md2);
	
    {
	/* transform buf into output string */
	
	unsigned char buf_perm[sizeof buf];
	int dest, source;
	char *output;

	/* silly output permutation */
	for (dest = 0, source = 0; dest < 14; dest++, source = (source + 6) % 17) {
	    buf_perm[dest] = buf[source];
	}
	buf_perm[14] = buf[5];
	buf_perm[15] = buf[11];

#ifndef PEDANTIC /* Unfortunately, this generates a "no effect" warning */
	assert(16 == sizeof buf_perm);
#endif
	
	output = salt_out + salt_len;
	assert(output == out_buf + strlen(out_buf));
		
	*output++ = '@';

	for (i = 0; i < 15; i += 3) {
	    *output++ = cov_2char[buf_perm[i+2] & 0x3f];
	    *output++ = cov_2char[((buf_perm[i+1] & 0xf) << 2) |
				  (buf_perm[i+2] >> 6)];
	    *output++ = cov_2char[((buf_perm[i] & 3) << 4) |
				  (buf_perm[i+1] >> 4)];
	    *output++ = cov_2char[buf_perm[i] >> 2];
	}

	assert(i == 15);
	*output++ = cov_2char[buf_perm[i] & 0x3f];
	*output++ = cov_2char[buf_perm[i] >> 6];
	*output = 0;
	assert(strlen(out_buf) < sizeof(out_buf));
    }
    EVP_MD_CTX_cleanup(&md);

    return out_buf;
}


/** get the salt of the encrypted password
 *  for encrypted password @1@9xTVu72p@PgynDMILpo3UdmUASiT4O0
 *  the salt is 9xTVu72p
 **/
std::string getSalt(const char *password)
{
    char salt[9];
    salt[8]='\0';
    memcpy(salt, password+3, 8*sizeof(char));
    return std::string(salt);
}

#define BUFSIZE 1024*8

std::string simpleHash( void* buffer, int len )
{
    BIO* bio;
    bio = BIO_new_mem_buf( buffer, len );
    std::string hash = simpleHash( bio );
    BIO_free( bio );
    return hash;
}

std::string simpleHash( const char* filename )
{
    BIO* bio;
    bio = BIO_new_file( filename, "rb" );
    std::string hash = simpleHash( bio );
    BIO_free( bio );
    return hash;
}

std::string simpleHash(BIO* b)
{
    char buf[1024];
    int len = 0;
    const EVP_MD *md=NULL;
    BIO* inp=NULL;
    BIO* bmd=NULL;
    BIO* in=NULL;
          
    unsigned char *buffer=NULL;
    std::string hash="";
    
    if ((buffer=(unsigned char *)OPENSSL_malloc(BUFSIZE)) == NULL) {
	return "";
    }
    OpenSSL_add_all_digests();
    md=EVP_get_digestbyname("sha1");
    
    in = BIO_new(BIO_s_mem());
    bmd = BIO_new(BIO_f_md());
    BIO_set_md(bmd , md);
    inp = BIO_push(bmd , in);
    
    int i = 0;
    while( 1 ) {
	i = BIO_read(b , buf , 1024);
	if(i <=0) {
	    break;
	}
	BIO_write(in , buf , i);
    }
    
    if( BIO_read(inp,(char *)buffer,BUFSIZE) == 0) {
	return "";
    }

    len = BIO_gets(inp,(char *)buffer , BUFSIZE);
    
    char h[3];
    
    for(unsigned int i = 0 ; i < 16 ; i++) {
	sprintf(h , "%02x", buffer[i]);
	hash += h;
    }
    
    BIO_free_all(in);
    std::free(buffer);
    return hash;
}

    

const EVP_CIPHER* getCipher( std::string cipher )
{
#ifndef OPENSSL_NO_DES
    if( cipher == "DES" )
	return EVP_des_cbc();
    if( cipher == "DES3" )
	return EVP_des_ede3_cbc();
    if( cipher == "DESX" )
	return EVP_desx_cbc();
#endif

#ifndef OPENSSL_NO_RC2
    if( cipher == "RC2-128" )
	return EVP_rc2_cbc();
    if( cipher == "RC2-64" )
	return EVP_rc2_64_cbc();
    if( cipher == "RC2-40" )
	return EVP_rc2_40_cbc();
#endif

#ifndef OPENSSL_NO_RC4
    if( cipher == "RC4" )
	return EVP_rc4();
#endif

#ifndef OPENSSL_NO_RC5
    if( cipher == "RC5" )
	return EVP_rc5_32_12_16_cbc();
#endif

#ifndef OPENSSL_NO_BF
    if( cipher == "BLOWFISH" )
	return EVP_bf_cbc();
#endif

#ifndef OPENSSL_NO_CAST
    if( cipher == "CAST" )
	return EVP_cast5_cbc();
#endif

#ifndef OPENSSL_NO_IDEA
    if( cipher == "IDEA" )
	return EVP_idea_cbc();
#endif

#ifndef OPENSSL_NO_AES
    if( cipher == "AES-128" )
	return EVP_aes_128_cbc();
    if( cipher == "AES-192" )
	return EVP_aes_192_cbc();
    if( cipher == "AES-256" )
	return EVP_aes_256_cbc();
#endif

    return NULL;
}

std::vector< std::pair< std::string, std::string> > getAllCiphers()
{
    std::vector< std::pair< std::string, std::string> > ret;
    std::pair<std::string, std::string> buffer;

#ifndef OPENSSL_NO_DES
    buffer.first = "DES"; buffer.second = "DES";
    ret.push_back( buffer );
    buffer.first = "DES 3"; buffer.second = "DES3";
    ret.push_back( buffer );
    buffer.first = "DES X"; buffer.second = "DESX";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_RC2
    buffer.first = "RC2 40 bits"; buffer.second = "RC2-40";
    ret.push_back( buffer );
    buffer.first = "RC2 64 bits"; buffer.second = "RC2-64";
    ret.push_back( buffer );
    buffer.first = "RC2 128 bits"; buffer.second = "RC2-128";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_RC4
    buffer.first = "RC4"; buffer.second = "RC4";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_RC5
    buffer.first = "RC5"; buffer.second = "RC5";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_BF
    buffer.first = "Blowfish"; buffer.second = "BLOWFISH";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_CAST
    buffer.first = "CAST"; buffer.second = "CAST";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_IDEA
    buffer.first = "IDEA"; buffer.second = "IDEA";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_AES
    buffer.first = "AES 128 bits"; buffer.second = "AES-128";
    ret.push_back( buffer );
    buffer.first = "AES 192 bits"; buffer.second = "AES-192";
    ret.push_back( buffer );
    buffer.first = "AES 256 bits"; buffer.second = "AES-256";
    ret.push_back( buffer );
#endif

    return ret;
}



std::vector< std::pair< std::string, std::string> > getAllDigests()
{
    std::vector< std::pair< std::string, std::string> > ret;
    std::pair<std::string, std::string> buffer;


#ifndef OPENSSL_NO_MD2
    buffer.first = "MD2"; buffer.second = "MD2";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_MD4
    buffer.first = "MD4"; buffer.second = "MD4";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_MD5
    buffer.first = "MD5"; buffer.second = "MD5";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_SHA
    buffer.first = "SHA1"; buffer.second = "SHA1";
    ret.push_back( buffer );
#endif
 
#if !defined(OPENSSL_NO_MDC2) && !defined(OPENSSL_NO_DES)
    buffer.first = "MDC2"; buffer.second = "MDC2";
    ret.push_back( buffer );
#endif

#ifndef OPENSSL_NO_RIPEMD
    buffer.first = "Ripemd"; buffer.second = "RIPEMD";
    ret.push_back( buffer );
    buffer.first = "rmd160"; buffer.second = "RIPEMD";
    ret.push_back( buffer );
#endif
    
    return ret;

}



std::string appendDir( const std::string path , const std::string dir )
{
    std::string separator;
    std::string newPath = path ;
    
#ifdef WIN32
    separator = "\\" ;
#else
    separator = "/" ;
#endif
    
    if( newPath.substr(newPath.size() - 1 , 1 ) != separator ) {
	newPath += separator ;
    }

    newPath += dir;
    return newPath;
}


int makeTempFile(std::string &tmpl)
{
#ifdef HAVE_MKSTEMP
  return mkstemp( (char*)(const char*)tmpl.c_str() );
#else
    int len;
    std::string XXXXXX;
    int count, fd;
    static const char letters[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    static const int NLETTERS = sizeof (letters) - 1;
    long value;
    static int counter = 0;

//     GTimeVal tv;

    len = tmpl.length();
    if( len < 6 || (tmpl.substr(len - 6, len) != "XXXXXX") )
//     if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX"))
	return -1;

    /* This is where the Xs start.  */
//     XXXXXX = &tmpl[len - 6];
    XXXXXX = tmpl.substr(len - 6, len);


    /* Get some more or less random data.  */
    if( ! RAND_pseudo_bytes( (unsigned char*)&value, sizeof(long) ) )
	return -1;

    value += counter++;
//     g_get_current_time (&tv);
//     value = (tv.tv_usec ^ tv.tv_sec) + counter++;

    for (count = 0; count < 100; value += 7777, ++count) {
	long v = value;

	/* Fill in the random bits.  */
	XXXXXX[0] = letters[v % NLETTERS];
	v /= NLETTERS;
	XXXXXX[1] = letters[v % NLETTERS];
	v /= NLETTERS;
	XXXXXX[2] = letters[v % NLETTERS];
	v /= NLETTERS;
	XXXXXX[3] = letters[v % NLETTERS];
	v /= NLETTERS;
	XXXXXX[4] = letters[v % NLETTERS];
	v /= NLETTERS;
	XXXXXX[5] = letters[v % NLETTERS];
	
	/* reconstruct 'tmpl' with the new filename */
	int i, j = 0;
	for( i = len - 6; i < len; i++, j++ )
	    tmpl[i] = XXXXXX[j];


	fd = open (tmpl.c_str(), O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
	
	if (fd >= 0) {
	    return fd;
	}
	else if (errno != EEXIST)
	    /* Any other error will apply also to other names we might
	     *  try, and there are 2^32 or so of them, so give up now.
	     */
	    return -1;
    }

    /* We got out of the loop because we ran out of combinations to try.  */
    return -1;
#endif
}



int copyFile( const std::string src, const std::string dest )
{
    unsigned char buffer[BUFSIZ];
    FILE *fin=NULL, *fout=NULL;

    if( (fin = fopen(src.c_str(), "rb")) == NULL )
	return -1;

    if( (fout = fopen(dest.c_str(), "wb")) == NULL )
	return -2;

    size_t i = 0;
    while( 1 ) {
	i = fread( buffer, sizeof(unsigned char), BUFSIZ, fin );
	if( i <= 0 ) break;
	fwrite( buffer, sizeof(unsigned char), i, fout );
    }

    if( fclose(fin) != 0 )
	return -3;
    if( fclose(fout) != 0 )
	return -3;

    return 0;
}

std::string getCryptonitHome()
{
    std::string home = "";

#ifdef WIN32

    /* HOME */
    if( getenv("HOME") != NULL ) {
	home = std::string( getenv("HOME") );

	if( home != "" ) {
	    /* Sometimes there is / instead of \. */
	    replace( home.begin(), home.end(), '/', '\\' );
	}
    }

    /* USERPROFILE */
    if( home == "" ) {
	if( getenv("USERPROFILE") != NULL ) {
	    home = std::string( getenv("USERPROFILE") );
	}
    }

    /* HOMEDRIVE + HOMEPATH */
    if( home == "" ) {
	if( getenv("HOMEDRIVE") != NULL && getenv("HOMEPATH") != NULL) {
	    home = std::string( getenv("HOMEDRIVE") );
	    home += std::string( getenv("HOMEPATH") );
	}
    }

    /* Add Cryptonit Windows home */
    home += CRYPTONIT_HOME_WIN32;

#else
    
    /* UNIX */
    home = std::string( getenv("HOME") );

    /* Add Cryptonit Unix home */
    home += CRYPTONIT_HOME_UNIX;

#endif

    if( home != "" ) {
	/* Create home dir if it doesn't exist */
	if( MKDIR( home.c_str() ) != 0 && errno != EEXIST ) {
	    return home;
	}
    }

    return home;
}




std::string setExt( const std::string filename , const std::string ext ){

    std::string ret ="";
    ret += filename;
    ret += ".";
    ret += ext;
    return ret;
}

/*** OpenSSL fixup ... see Utils.hh for explainations ****/
int PKCS12_parse_fix(PKCS12 *p12 ,const char *password, EVP_PKEY **key , X509 **cer , STACK_OF(X509) **ca){
    STACK_OF(X509) *certs = NULL;
   
    if ((ca != NULL) && (*ca == NULL)) {
	if (!(*ca = sk_X509_new_null())) {
	    return 0;
	}
    }
    
    if (!(certs = sk_X509_new_null())) {
	return 0;
    }
    
    if(!dump_certs_keys_p12( NULL, p12 , password, strlen(password) ,  key , &certs ))
	return 0;
 
    for(int i=0 ; i < sk_X509_num(certs); i++){
	if( X509_check_private_key(sk_X509_value(certs,i), *key)){
	    *cer = sk_X509_value(certs,i);
	} else {
	    sk_X509_push(*ca , sk_X509_value(certs,i));
	}
    }
    return 1;
    sk_X509_free(certs); //free the stack not its content.
}


//from openssl : apps/pkcs12.c
int dump_certs_keys_p12 (BIO *out, PKCS12 *p12, const char *pass,
			 int passlen, EVP_PKEY **key, STACK_OF(X509) **ca)
{
    STACK_OF(PKCS7) *asafes;
    STACK_OF(PKCS12_SAFEBAG) *bags;
    int i, bagnid;
    PKCS7 *p7;
    
    if (!( asafes = PKCS12_unpack_authsafes(p12))) return 0;
    for (i = 0; i < sk_PKCS7_num (asafes); i++) {
	p7 = sk_PKCS7_value (asafes, i);
	bagnid = OBJ_obj2nid (p7->type);
		if (bagnid == NID_pkcs7_data) {
			bags = PKCS12_unpack_p7data(p7);
			//if (options & INFO) BIO_printf (bio_err, "PKCS7 Data\n");
		} else if (bagnid == NID_pkcs7_encrypted) {
			/*if (options & INFO) {
				BIO_printf(bio_err, "PKCS7 Encrypted data: ");
				alg_print(bio_err, 
					p7->d.encrypted->enc_data->algorithm);
			}*/
			bags = PKCS12_unpack_p7encdata(p7, pass, passlen);
		} else continue;
		if (!bags) return 0;
	    	if (!dump_certs_pkeys_bags (out, bags, pass, passlen, 
						 key, ca)) {
			sk_PKCS12_SAFEBAG_pop_free (bags, PKCS12_SAFEBAG_free);
			return 0;
		}
		sk_PKCS12_SAFEBAG_pop_free (bags, PKCS12_SAFEBAG_free);
	}
	sk_PKCS7_pop_free (asafes, PKCS7_free);
	return 1;
}

int dump_certs_pkeys_bags (BIO *out, STACK_OF(PKCS12_SAFEBAG) *bags,
			   const char *pass, int passlen, EVP_PKEY **key, STACK_OF(X509) **ca)
{
	int i;
	for (i = 0; i < sk_PKCS12_SAFEBAG_num (bags); i++) {
		if (!dump_certs_pkeys_bag (out,
					   sk_PKCS12_SAFEBAG_value (bags, i),
					   pass, passlen,
					   key, ca))
		    return 0;
	}
	return 1;
}



int dump_certs_pkeys_bag (BIO *out, PKCS12_SAFEBAG *bag, const char *pass,
	     int passlen, EVP_PKEY **key, STACK_OF(X509) **ca)
{
	EVP_PKEY *pkey;
	PKCS8_PRIV_KEY_INFO *p8;
	X509 *x509;
	
	switch (M_PKCS12_bag_type(bag))
	{
	case NID_keyBag:
		p8 = bag->value.keybag;
		if (!(*key = EVP_PKCS82PKEY (p8))) return 0;

	break;

	case NID_pkcs8ShroudedKeyBag:
	    if (!(p8 = PKCS12_decrypt_skey(bag, pass, passlen)))
		return 0;
	    if (!(*key = EVP_PKCS82PKEY (p8))) {
		PKCS8_PRIV_KEY_INFO_free(p8);
		return 0;
	    }
	    PKCS8_PRIV_KEY_INFO_free(p8);
	break;

	case NID_certBag:
	    if (M_PKCS12_cert_bag_type(bag) != NID_x509Certificate )
		return 1;
	    if (!(x509 = PKCS12_certbag2x509(bag))) return 0;
	    sk_X509_push( *ca , x509 );
	    // X509_free(x509);  TODO : check if we need to free this stuff 
	break;

	case NID_safeContentsBag:
	    return dump_certs_pkeys_bags (out, bag->value.safes, pass,
					  passlen, key , ca);
	    
	default:
	    return 1;
	    break;
	}
	return 1;
}

