#include <stdlib.h>
#include <memory.h>
#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/sha.h>
#include <openssl/err.h>

#define OK			1
#define KO			0
#define LG_DATE_HEURE	12
#define LG_SIGN	64
#define LG_BUFFER	512
#define SIGNATURE_MODE_TEST	0
#define SIGNATURE_MODE_REEL	1

#define CLE_PUBLIQUE_DGFIP_PEDI_REEL "-----BEGIN PUBLIC KEY-----\n\
MFowDQYJKoZIhvcNAQEBBQADSQAwRgJBANwHsEkUqKPVxBKRpxBj9gO9TWpq4U5x\n\
bVFO751+JO7uAXsSIi9BtF1mR2ok1XmJEFSqYr6wkj4eqXN8O4SxGqcCAQM=\n\
-----END PUBLIC KEY-----"
#define CLE_PUBLIQUE_DGFIP_PEDI_TEST "-----BEGIN PUBLIC KEY-----\n\
MFowDQYJKoZIhvcNAQEBBQADSQAwRgJBALeWQD1MW6qwI4Qiq790AN1V0jU+zQKT\n\
Ded9mARJT2QgVi+W9IpVq6LdZBsSBZOhHCluklibOZjv1xts/cgqUtkCAQM=\n\
-----END PUBLIC KEY-----"

/*
 * La seule vocation de ce programme est de vérifier la signature d'un fichier transmis en retour par la DGFiP (message CONTRL, INFENT CR...)
 * 
 * Un paramètre en entrée du programme :
 *
 *    - le fichier signé à contrôler
 *
 * En sortie, le programme fournit le résultat de la vérification
 *
 * Pour information, l'accréditation transmise dans le segment USR n'est ici pas exploitable par les fonctions de la librairie crypto ;
 * d'où l'utilisation de la clé publique DGFiP (pedi) au format PEM, stockée pour l'exemple dans le programme
 */

int main ( int argc, char * * argv)
{
	RSA * clePublique ;
	FILE * ptrFichier ;
	BIO * ptrClePublique ;
	
	char fichierGF [ LG_BUFFER ] ;
	
	SHA_CTX contexteSHA1 ;
	unsigned char hash [ SHA_DIGEST_LENGTH ] ;
	unsigned char signDecrypt [ LG_SIGN + 1 ] ;
	
	unsigned char buffer_lecture [ LG_BUFFER ] ;
	unsigned char signature_bin [ LG_SIGN + 1 ] ;
	unsigned char signature_hexa [ LG_SIGN * 2 + 1 ] ;
	unsigned char date_heure [ LG_DATE_HEURE + 1 ] ;

	int compteur ;
	int nb_car_lus ;
	int caractere ;
	int	mode_signature ;
	
	if ( argc != 2 )
	{
		fprintf ( stderr, "Usage : %s <fichier_a_controler>\n", argv[0] ) ;
		
		exit ( 1 ) ;
	}
	
	/* Récupération des informations de signature                                                */
	/* ------------------------------------------                                                */
	/* A partir du fichier à contrôler :                                                         */ 
	/* - création d'un fichier temporaire contenant uniquement le groupe fonctionnel principal ; */
	/* - récupération de la date et de l'heure de signature ;                                    */
	/* - récupération de la signature en hexadécimal ;                                           */
	/* - récupération du mode de signature (test/réel)                                           */
	/* ========================================================================================= */
	sprintf ( fichierGF, "/tmp/%s_%d", basename(argv[0]), getpid()) ;
	memset ( date_heure, 0, sizeof ( date_heure ) ) ;
	memset ( signature_hexa, 0, sizeof ( signature_hexa ) ) ;

	if ( recuperer_elements_signature ( argv[1], fichierGF, date_heure, signature_hexa, & mode_signature )  != OK )
	{
		fprintf ( stderr, "Erreur d'extraction des éléments de signature pour le fichier %s\n", argv[1] ) ;
		exit ( 1 ) ;
	}

	/* Initialisation */
	/* ============== */
	OpenSSL_add_all_ciphers() ;
	
	/* Lecture de la clé publique */
	/* ========================== */
	ptrClePublique = BIO_new_mem_buf ( mode_signature == SIGNATURE_MODE_TEST ? CLE_PUBLIQUE_DGFIP_PEDI_TEST : CLE_PUBLIQUE_DGFIP_PEDI_REEL, -1 ) ;
	clePublique = (RSA *) PEM_read_bio_RSA_PUBKEY ( ptrClePublique, NULL, 0, NULL ) ;
	BIO_set_close ( ptrClePublique, BIO_CLOSE ) ;
	BIO_free ( ptrClePublique ) ;

	if ( clePublique == NULL )
	{
		fprintf ( stderr, "Erreur de lecture de la cle publique\n" ) ;
		ERR_print_errors_fp ( stderr ) ;

		exit ( 1 ) ;
	}
	
	/* Création de l'empreinte horodatée */
	/* =================================
	
	/* Chargement des données à "hasher" dans le contexte SHA */
	/* ------------------------------------------------------ */
	
	/* Initialisation */
	if ( SHA1_Init ( & contexteSHA1 ) != OK )
	{
		fprintf ( stderr, "Erreur d'initialisation du contexte pour le calcul de l'empreinte SHA1\n" ) ;
		ERR_print_errors_fp ( stderr ) ;

		exit ( 1 ) ;
	}
	ptrFichier = fopen ( fichierGF, "r" ) ;

	if ( ptrFichier == NULL )
	{
		fprintf ( stderr, "Erreur d'ouvertue du fichier %s contenant le groupe fonctionnel extrait du fichier à contrôler\n", fichierGF ) ;
		unlink ( fichierGF ) ;
		exit ( 1 ) ;
	}
	/* Chargement des données */
	while ( ( nb_car_lus = fread ( buffer_lecture, 1, LG_BUFFER, ptrFichier ) ) )
	{
		if ( SHA1_Update ( & contexteSHA1, buffer_lecture, nb_car_lus ) != OK )
		{
			fclose ( ptrFichier ) ;
			fprintf ( stderr, "Erreur de mise à jour du contexte pour le calcul de l'empreinte SHA1\n" ) ;
			ERR_print_errors_fp ( stderr ) ;
			unlink ( fichierGF ) ;

			exit ( 1 ) ;
		}
	}
	
	if ( ferror ( ptrFichier ) )
	{
		fprintf ( stderr, "Erreur de lecture du fichier %s contenant le groupe fonctionnel extrait du fichier à contrôler\n", fichierGF ) ;
		unlink ( fichierGF ) ;

		exit ( 1 ) ;
	}
	fclose ( ptrFichier ) ;
	
	/* Suppression du fichier temporaire contenant le groupe fonctionnel extrait */
	unlink ( fichierGF ) ;
	
	/* Calcul de l'empreinte */
	if ( SHA1_Final ( hash, & contexteSHA1 ) != OK )
	{
		fprintf ( stderr, "Erreur de calcul de l'empreinte SHA1\n" ) ;
		ERR_print_errors_fp ( stderr ) ;

		exit ( 1 ) ;
	}
	
	/* Ajout de la date (AAAAMMJJ) et de l'heure (HHMM)  */
	if ( strlen ( date_heure ) != LG_DATE_HEURE )
	{
		fprintf ( stderr, "La date et l'heure lues ne sont pas au format AAAAMMJJhhmm\n" ) ;
		
		exit ( 1 ) ;
	}
	memcpy ( hash + SHA_DIGEST_LENGTH, date_heure, LG_DATE_HEURE ) ;

	/* Déchiffrement de la signature */
	/* ============================= */
	/* (on commence par la convertir d'hexa en binaire) */
	memset ( signature_bin, 0, sizeof ( signature_bin ) ) ;
	for ( compteur = 0 ; compteur < (LG_SIGN * 2) ; compteur += 2 )
    {
      if ( sscanf ( signature_hexa + compteur, "%2x", & caractere ) == 0 )
      {
          fprintf ( stderr, "Erreur de conversion de la signature d'hexadécimal en binaire\n" ) ;
		  
		  exit ( 1 ) ;
      }
	  signature_bin [compteur / 2] =  (unsigned char) caractere ;
    }
	
	memset ( signDecrypt, 0, sizeof ( signDecrypt ) ) ;

	if ( RSA_public_decrypt ( LG_SIGN, signature_bin, signDecrypt, clePublique, RSA_NO_PADDING ) == -1 )
	{
		fprintf ( stderr, "Erreur de déchiffrement de la signature\n" ) ;
		ERR_print_errors_fp ( stderr ) ;

		exit ( 1 ) ;
	}
   
	RSA_free ( clePublique ) ;

	/* Comparaison entre l'empreinte horodatée calculée et la signature déchiffrée */
	/* =========================================================================== */
	/* Remarque : seuls les 32 derniers octets de la signature déchiffrée sont significatifs dans notre environnement */
	if ( memcmp ( hash, signDecrypt + 32, 32 ) )
	{
		fprintf ( stdout, "Signature KO !\n" ) ;
	}
	else
	{
		fprintf ( stdout, "Signature OK !\n" ) ;
	}
	
	return ( 0 ) ;
}

/*
	- Extraction du groupe fonctionnel principal (le premier après l'UNB) dans le fichier,
	  avec création d'un fichier temporaire dont le chemin est stocké dans la variable fournie en paramètre
	- Récupération de la date et de l'heure de signature (retournées dans la variable fournie en paramètre
	  au format AAAAMMJJHHMM)
	- Récupération de la signature en hexadécimal
	- Récupération du mode de signature (test ou réel)
	
	Renvoie OK ou KO
*/

int recuperer_elements_signature ( char * fichierAControler, char * fichierTmp, char * date_heure, char * signature, int * mode_signature )
{
  int   vl_iI, vl_iPos_Deb, vl_iPos_Fin, caractere ;
  FILE   * vl_psInter_Trav, * vl_psFich_Extrait ;

  /* Ouverture du fichier (interchange) */
  /* ================================== */
  vl_psInter_Trav = fopen ( fichierAControler, "r" ) ;
  if ( vl_psInter_Trav == NULL )
  {
	 return ( KO ) ;
  }

  /* Recherche de la séquence correspondant au */
  /* début du groupe fonctionnel des données   */
  /* ========================================= */
  if ( trouver_chaine_dans_fichier ( vl_psInter_Trav, "'UNG+", & vl_iPos_Deb ) == KO )
  {
	 fclose ( vl_psInter_Trav ) ;

	 return ( KO ) ;
  }

  /* On se repositionne dans le fichier... */
  /* ------------------------------------- */
  fseek ( vl_psInter_Trav, vl_iPos_Deb + 1, SEEK_SET ) ;

  /* Recherche de la séquence correspondant à */
  /* la fin du groupe fonctionnel des données */
  /* ======================================== */
  if ( trouver_chaine_dans_fichier ( vl_psInter_Trav, "'UNE+", & vl_iPos_Fin ) == KO )
  {
	 fclose ( vl_psInter_Trav ) ;

	 return ( KO ) ;
  }

  /* Recherche de la fin du groupe fonctionnel courant */
  /* ================================================= */
  if ( trouver_chaine_dans_fichier ( vl_psInter_Trav, "'", & vl_iPos_Fin ) == KO )
  {
	 fclose ( vl_psInter_Trav ) ;

	 return ( KO ) ;
  }

  /* Création du fichier issu de l'extraction des données signées */
  /* ============================================================ */

  /* Ouverture du fichier */
  /* -------------------- */
  vl_psFich_Extrait = fopen ( fichierTmp, "w" ) ;

  if ( vl_psFich_Extrait == NULL )
  {
	 fclose ( vl_psInter_Trav ) ;

	 return ( KO ) ;
  }

  /* Repositionnement du pointeur de fichier */
  /* --------------------------------------- */
  if ( fseek ( vl_psInter_Trav, vl_iPos_Deb + 1, SEEK_SET ) )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }

  /* Alimentation du fichier contenant l'extraction */
  /* ---------------------------------------------- */
  for ( vl_iI = vl_iPos_Deb ; vl_iI < vl_iPos_Fin ; vl_iI++ )
  {
	 /* On vérifie que le caractère est bien écrit */
	 /* ------------------------------------------ */
	 if ( fprintf ( vl_psFich_Extrait, "%c", fgetc ( vl_psInter_Trav ) ) != 1 )
	 {
		fclose ( vl_psInter_Trav ) ;
		fclose ( vl_psFich_Extrait ) ;
		unlink ( fichierTmp ) ;

		return ( KO ) ;
	 }
  }
  
  /* Lecture de la date et de l'heure de signature */
  /* --------------------------------------------- */
  
  /* On commence par stocker les deux premiers chiffres de l'année */
  strcpy ( date_heure, "20" ) ;
  
  /* On se déplace de 27 cacatères pour se positionner sur la date (au format AAMMJJ) */
  if ( fseek ( vl_psInter_Trav, 27, SEEK_CUR ) )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }
 
  /* On lit la date (AAMMJJ) */
  if ( fgets ( (char *)(date_heure + 2), 7 /* 6 caractères à lire plus le zéro de fin de chaîne */, vl_psInter_Trav ) == NULL )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }
  
  /* On laisse de côté le séparateur */
  if ( fseek ( vl_psInter_Trav, 1, SEEK_CUR ) )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }

  /* On lit l'heure (HHMM) */
  if ( fgets ( (char *)(date_heure + 8), 5 /* 4 caractères à lire plus le zéro de fin de chaîne */, vl_psInter_Trav ) == NULL )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }
  
  /* Pour déterminer le mode de signature, on recherche le deuxième caractère de l'accréditation */
  /* (1 => réel et 2 => test)
  /* =========================================================================================== */
  if ( trouver_chaine_dans_fichier ( vl_psInter_Trav, "'USR+", & vl_iPos_Deb ) == KO )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }
  
  /* Repositionnement du pointeur de fichier             */
  /* (sur le 2ème caractère de l'accréd. du segment USR) */
  /* --------------------------------------------------- */
  if ( fseek ( vl_psInter_Trav, vl_iPos_Deb + 8, SEEK_SET ) )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }
  
  /* On lit le caractère pour déterminer le mode de signature */
  /* -------------------------------------------------------- */
  caractere = fgetc ( vl_psInter_Trav )  ;
  
  if ( caractere == '1' )
	* mode_signature = SIGNATURE_MODE_REEL ;
  else if ( caractere == '2' )
	* mode_signature = SIGNATURE_MODE_TEST ;
  else
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }

  /* Recherche de la séquence correspondant au segment contenant la signature */
  /* ======================================================================== */
  if ( trouver_chaine_dans_fichier ( vl_psInter_Trav, "'USY+", & vl_iPos_Deb ) == KO )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }

  /* Repositionnement du pointeur de fichier        */
  /* (au début de la signature dans le segment USY) */
  /* ---------------------------------------------- */
  if ( fseek ( vl_psInter_Trav, vl_iPos_Deb + 9, SEEK_SET ) )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }
  
  /* On lit la signature */
  if ( fgets ( signature, (LG_SIGN*2 + 1), vl_psInter_Trav ) == NULL )
  {
	 fclose ( vl_psInter_Trav ) ;
	 fclose ( vl_psFich_Extrait ) ;
	 unlink ( fichierTmp ) ;

	 return ( KO ) ;
  }
  
  /* Fermeture des fichiers */
  /* ====================== */
  fclose ( vl_psInter_Trav ) ;
  fclose ( vl_psFich_Extrait ) ;

  return ( OK ) ;
}

int trouver_chaine_dans_fichier (FILE * vl_psFichID, char * vl_pcChaine, int * vl_piPos )
{
  char  * vl_pcPtr ;   /* Pointeur sur un caractère de la chaîne */
  int   vl_iCarPrec, vl_iChar ;   

  /* Initialisations */
  /*=================*/
  vl_pcPtr = vl_pcChaine ;
  vl_iCarPrec = -99999 ;

  if ( vl_psFichID == NULL )
	 return ( KO ) ;

  while ((vl_iChar = fgetc (vl_psFichID)) != EOF)
  {
	 /* Si on lit le premier caractère de la chaîne le       */
	 /* car. précédent ne doit pas être le car. d'échap. '?' */
	 /* ---------------------------------------------------- */
	 if ( vl_pcPtr == vl_pcChaine && vl_iCarPrec == '?' )
	 {
		vl_iCarPrec = vl_iChar ;
		continue ;
	 }

	 if (vl_iChar != * vl_pcPtr)
	 {
		if (vl_pcPtr != vl_pcChaine)
		{
		   fseek (vl_psFichID, * vl_piPos, SEEK_SET) ;
		   vl_pcPtr = vl_pcChaine ;
		}
		else /* Mémorisation du caractère précédent */
		   vl_iCarPrec = vl_iChar ;
		continue ;
	 }

	 if (vl_pcPtr == vl_pcChaine)
	 {
		* vl_piPos = ftell (vl_psFichID) ;
		vl_iCarPrec = vl_iChar ;
	 }

	 vl_pcPtr ++ ;

	 if (! * vl_pcPtr)
	 {
		/* Le début réel de la chaîne est position-1 */
		/* ----------------------------------------- */
		( * vl_piPos ) -- ;
		return ( OK ) ;
	 }
  }

  return ( KO ) ;
}
