/*
 * Copyright (C) 2003 Mathias Brossard <mathias.brossard@idealx.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02111-1301,
 * USA
 *
 * In addition, as one special exception:
 *
 * 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).
 */

#include "types.h"

/* BUGFIX: it seems mingw32 (at least in Debian packaging) forgot to
   define off64_t */
typedef long long off64_t; 
#include <stdio.h>
#include <string.h>
#ifndef WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <openssl/rand.h>

#define AES_encrypt aesencrypt
#define AES_decrypt aesdecrypt

#include "encryptSelf.h"
#include "types.h"

#include "aes.h"
#include "aes.c"

#define CIPHER_BLOCK_SIZE AES_BLOCK_SIZE
#define BLOCK_SIZE 1024

#include "sha2.h"
#include "sha2.c"

#include "pkcs5.c"

#include "stub_win32.h"
#include "stub_posix.h"

/* return a random 64 bits salt */
/* 64 bits is sufficient for the key derivation algorithm (HMAC) */
uint64 get_salt()
{
    uint64 salt;
    /* Seeding the PRNG */
#ifdef WIN32
    /* Use user's screen for seeding the PRNG */
    fprintf(stderr, "Seeding PRNG using user's screen...\n");
    RAND_screen();
#else 
    /* Try to use the /dev/urandom of Unix systems */
    /* Check if /dev/urandom exist */
    struct stat s;
    if( stat( "/dev/urandom", &s ) == 0 ) {
	if( S_ISCHR(s.st_mode) ) {
	    fprintf(stderr, "Seeding PRNG from /dev/urandom...\n");
	    while( RAND_status == 0 )
		RAND_load_file( "/dev/urandmon", 4 * 64 );
	}
    } else {
	fprintf(stderr, "Cannot stat /dev/urandom !\n");

	/* Fallback to /dev/random */
	if( stat( "/dev/random", &s ) == 0 ) {
	    if( S_ISCHR(s.st_mode) ) {
		fprintf(stderr, "Seeding PRNG from /dev/random...\n");
		while( RAND_status == 0 )
		    RAND_load_file( "/dev/randmon", 4 * 64 );
	    }
	} else {
	    fprintf(stderr, "Cannot stat /dev/random !\n");
	    fprintf(stderr, "PRNG is NOT seeded !\n");

	    return -1;
	}
    }
    
    
#endif	

    fprintf(stderr, "PRNG seeded.\n");
    
    if( RAND_bytes((unsigned char*)&salt, 8) != 1 ) {
	fprintf(stderr, "An error occured while generating salt !\n");
	return -1;
    }

    return salt;
}



static int get_size(FILE *f)
{
  int size;
  fseek(f, 0, SEEK_END);
  size = ftell(f);
  rewind(f);
  return size;
}



#ifdef DEBUG
void dump(char *name, unsigned char *val, int size)
{
  int i;
  fprintf(stderr, "%s = ", name);
  for(i=0;i<size;i++) {
    if((i%32 == 0) && (i)) {
      fprintf(stderr, "\n");
    }
    fprintf(stderr, "%02x", val[i]);
  }
  fprintf(stderr, "\n");
}
#define DUMP(x,l) \
  dump(#x, x, l); 
#else
#define DUMP(x,l)
#endif



int encrypt_self(const char *source, const char *destination,
		 const char *password, STUB platform)
{
  unsigned int size, index, next;
  int rv = 0;
  hmac_ctx hmac;
  aes_ctx aes;
  uint64 salt;

  uint8 buffer[BLOCK_SIZE];
  uint8 key[SHA256_DIGEST_LENGTH];
  uint8 hash[SHA256_DIGEST_LENGTH];
  FILE *fin = NULL, *fout = NULL;

  /* Do we have the right amount of arguments ? */
  /* FIXME Check */

  /* Open file for reading */
  if((fin = fopen(source, "r+b")) == NULL) {
    rv = -2;
    goto end;
  }

  /* Opening output file for writing */
  if((fout = fopen(destination, "w+b")) == NULL) {
    rv = -3;
    goto end;
  }

  /* Write stub loader */
  if (platform == STUB_WIN32) {
#ifdef DEBUG
    printf("Writing WIN32 stub from 0 to %d\n", sizeof(stub_win32));
#endif
    if(fwrite(stub_win32, sizeof(stub_win32), 1, fout) != 1) {
      rv = -4;
      goto end;
    }     
  }
  else if(platform == STUB_POSIX) {
#ifdef DEBUG
      printf("Writing POSIX stub from 0 to %d\n", sizeof(stub_posix));
#endif
    if(fwrite(stub_posix, sizeof(stub_posix), 1, fout) != 1) {
      rv = -4;
      goto end;
    } 
  }
  else {
      rv = -4;
      goto end;
  }

  /* Get file size */
  size = get_size(fin);

  /* Get salt for password derivating algorithm (HMAC) */
  salt = get_salt();


  /* Derive password into key */
  derive_key(password, strlen(password),
	     (char *)&salt, 8, key);

  DUMP(key, SHA256_DIGEST_LENGTH);

  aes_set_key(&aes, key, 256, 1);

  /* Initializing HMAC using the drived key */
  hmac_init( key, SHA256_DIGEST_LENGTH, &hmac );

  for(index = 0; index < size; index += next) {
    int extra = 0;
    next =  size - index;
    if(next > BLOCK_SIZE) {
      next = BLOCK_SIZE;
    } else {
      /* Do we need padding ? */
      if(next % CIPHER_BLOCK_SIZE) {
	extra = CIPHER_BLOCK_SIZE -
	  (next % CIPHER_BLOCK_SIZE);
      }
    }

    /* Read unencrypted block */
    if(fread(buffer, next, 1, fin) != 1) {
      rv = -4;
      goto end;
    }
    
    /* Add padding ? */
    if(extra) {
      memcpy(buffer + next, hash, extra);
    }

    /* Encrypt block */
    aes_cbc_encrypt(&aes, key, buffer, (next + extra));

    /* Update the hmac with the encrypted block, 
     * encrypting-then-authentificating paradigm.
     */
    hmac_update(buffer, (next + extra), &hmac);

    /* Write block */
    if(fwrite(buffer, (next + extra), 1, fout) != 1) {
      rv = -5;
      goto end;
    }
  }

  /* Finish HMAC */
  hmac_final(buffer, &hmac);


#ifdef DEBUG
  dump("HMAC", buffer, SHA256_DIGEST_LENGTH);
  printf("Writing hmac from %d to ", ftell(fout));
#endif

  /* And write it */
  if(fwrite(buffer, SHA256_DIGEST_LENGTH, 1, fout) != 1) {
    rv = -6;
    goto end;
  }

#ifdef DEBUG
  printf("%d.\n", ftell(fout));

  printf("File size = %d\n", size);
  printf("Writing file size from %d to ", ftell(fout));
#endif

  /* Write file size */
  if(fwrite(&size, sizeof(unsigned int), 1, fout) != 1) {
    rv = -7;
    goto end;
  }

#ifdef DEBUG
  printf("%d.\n", ftell(fout));

  printf("salt = %u\n", salt);
  printf("Writing salt from %d to ", ftell(fout));
#endif

  /* Write salt */
  if(fwrite(&salt, sizeof(uint64), 1, fout ) != 1) {
   rv = -8;
   goto end;
  }

#ifdef DEBUG
  printf("%d.\n", ftell(fout));
#endif

 end:
  if(fin)
    fclose(fin);
  if(fout)
    fclose(fout);
  return rv;
}
