#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	256
#define LG_BUFFER	512
#define LG_CLE_PUBLIQUE 392

#define CLE_PUBLIQUE_DGFIP_PEDI "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5JNDEX4rKlgoOhHFYIvf77Eyw0XpC4VwSUFZKic/ggnBTZWUjtU0dw5fcIWiDc4WMML5NXzDfz0rPcpiPz+ly+CmLibgemUk/FfYGd0S/Wxj3t3Qfc2Tv1yC1vBmdXvNenv9u9jUQvuW3NUgWMz/oh/PnWC/fSaYABO1T7XLxEDIPbEni9brx/ZLH5EUpzm/rz56J0If6BfOAqYC39d6HFwg/d6SjwVtKsgOx2zyRVEo9dd8tjeYR7sW/ZbsOHvkb4yHzcfpn6H2hn1/SABjQNViZVKdG0fZsHzXh6SGoOuYCicRdojCd29PoBqV4B6ISrH+lZ1Ydgg62B5Y+xoBSQIDAQAB"

/*
 * 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
 *
 */

int main ( int argc, char * * argv)
{
	RSA * clePublique ;
	FILE * ptrFichier ;
	BIO * ptrClePublique ;
	
	char fichierGF [ LG_BUFFER ] ;
	
	SHA256_CTX contexteSHA256 ;
	unsigned char hash [ SHA256_DIGEST_LENGTH + LG_DATE_HEURE + 1 ] ;
	unsigned char signDecrypt [ LG_BUFFER ] ;
	
	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 ] ;
	unsigned char cle_publique_hexa [ LG_CLE_PUBLIQUE * 2 + 1 ] ;
	unsigned char cle_publique_bin [ LG_CLE_PUBLIQUE + 1 ] ;
	unsigned char cle_publique_pem [ LG_BUFFER ] ;

	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 de la clé publique DGFiP "partenaire EDI" en hexadécimal                   */
	/* ========================================================================================= */
	sprintf ( fichierGF, "/tmp/%s_%d", basename(argv[0]), getpid()) ;
	memset ( date_heure, 0, sizeof ( date_heure ) ) ;
	memset ( signature_hexa, 0, sizeof ( signature_hexa ) ) ;
	memset ( cle_publique_hexa, 0, sizeof ( cle_publique_hexa ) ) ;

	if ( recuperer_elements_signature ( argv[1], fichierGF, date_heure, signature_hexa, cle_publique_hexa )  != OK )
	{
		exit ( 1 ) ;
	}
	
	/* Conversion de la clé publique au format hexadécimal en une clé au format binaire (texte) */
	/* ======================================================================================== */
	memset ( cle_publique_bin, 0, sizeof ( cle_publique_bin ) ) ;
	for ( compteur = 0 ; compteur < (LG_CLE_PUBLIQUE * 2) ; compteur += 2 )
    	{
      		if ( sscanf ( cle_publique_hexa + compteur, "%2x", & caractere ) == 0 )
      		{
          		fprintf ( stderr, "Erreur de conversion de la signature d'hexadécimal en binaire\n" ) ;

			unlink ( fichierGF ) ;
		  
		  	exit ( 1 ) ;
      		}
	  	cle_publique_bin [compteur / 2] =  (unsigned char) caractere ;
    	}
	
	/* On vérifie que la clé publique contenue dans l'accréditation est bien celle de la DGFiP */
	/* (contrôle facultatif puisque cette clé publique est unique et donc toujours la même...) */
	/* ======================================================================================= */
	if ( strcmp ( cle_publique_bin, CLE_PUBLIQUE_DGFIP_PEDI ) )
	{
          	fprintf ( stderr, "La clé publique lue dans l'accréditation ne correspond pas à la clé publique DGFiP \"partenaire EDI\".\n" ) ;
		  
		fprintf ( stderr, "Clé publique lue      => %s\n", cle_publique_bin ) ;
		fprintf ( stderr, "Clé publique attendue => %s\n", CLE_PUBLIQUE_DGFIP_PEDI ) ;

		unlink ( fichierGF ) ;
		  
		exit ( 1 ) ;
	}
	
	/* Construction de la clé publique au format PEM */
	/* ============================================= */
	sprintf ( cle_publique_pem, "-----BEGIN PUBLIC KEY-----" ) ;
	for ( compteur = 0 ; compteur < LG_CLE_PUBLIQUE ; compteur++ )
	{
		if ( (compteur % 64) == 0 )
		{
			strcat ( cle_publique_pem, "\n" ) ;
		}
		strncat ( cle_publique_pem, cle_publique_bin + compteur, 1 ) ;
	}
	strcat ( cle_publique_pem, "\n-----END PUBLIC KEY-----" ) ;

	/* Initialisation */
	/* ============== */
	OpenSSL_add_all_ciphers() ;
	memset ( hash, 0, sizeof ( hash ) ) ;
	
	/* Lecture de la clé publique */
	/* ========================== */
	ptrClePublique = BIO_new_mem_buf ( cle_publique_pem, -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 ) ;
		unlink ( fichierGF ) ;

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

		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 ( SHA256_Update ( & contexteSHA256, buffer_lecture, nb_car_lus ) != OK )
		{
			fclose ( ptrFichier ) ;
			fprintf ( stderr, "Erreur de mise à jour du contexte pour le calcul de l'empreinte SHA256\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 ( SHA256_Final ( hash, & contexteSHA256 ) != 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 + SHA256_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_PKCS1_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 */
	/* =========================================================================== */
	if ( memcmp ( hash, signDecrypt, SHA_DIGEST_LENGTH + LG_DATE_HEURE ) )
	{
		fprintf ( stdout, "Signature KO !\n" ) ;

		fprintf ( stderr, "Hash calculé        => " ) ;
		for ( compteur = 0 ; compteur < SHA_DIGEST_LENGTH + LG_DATE_HEURE ; compteur++ )
		{
			fprintf ( stderr, "%02X", hash[compteur] ) ;
		}
		fprintf ( stderr, "\nSignature décryptée => " ) ;
		for ( compteur = 0 ; compteur < SHA_DIGEST_LENGTH + LG_DATE_HEURE ; compteur++ )
		{
			fprintf ( stderr, "%02X", signDecrypt[compteur] ) ;
		}
		fprintf ( stderr, "\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 de la clé publique contenue dans l'accréditation, en hexadécimal
 *	
 *	Renvoie OK ou KO
 */

int recuperer_elements_signature ( char * fichierAControler, char * fichierTmp, char * date_heure, char * signature, char * cle_publique )
{
	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 )
	{
		fprintf ( stderr, "Erreur d'ouverture du fichier %s\n", fichierAControler ) ;
		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 )
	{
		fprintf ( stderr, "Erreur : premier segment UNG (début des données signées) non trouvé\n" ) ;
		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 )
	{
		fprintf ( stderr, "Erreur : premier segment UNE (fin des données signées) non trouvé\n" ) ;
		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 )
	{
		fprintf ( stderr, "Erreur : fin du premier segment UNE (données signées) non trouvée\n" ) ;
		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 )
	{
		fprintf ( stderr, "Erreur de création du fichier temporaire %s\n", fichierTmp ) ;
		fclose ( vl_psInter_Trav ) ;

		return ( KO ) ;
	}

	/* Repositionnement du pointeur de fichier */
	/* --------------------------------------- */
	if ( fseek ( vl_psInter_Trav, vl_iPos_Deb + 1, SEEK_SET ) )
	{
		fprintf ( stderr, "Erreur de positionnement sur le début du premier segment UNG (début des données signées)\n" ) ;
		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 )
		{
			fprintf ( stderr, "Erreur d'écriture dans le fichier temporaire %s\n", fichierTmp ) ;
			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 ) )
	{
		fprintf ( stderr, "Erreur de positionnement sur le début de la date de signature (dans l'AUTACK)\n" ) ;
		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 )
	{
		fprintf ( stderr, "Erreur de lecture de la date de signature (dans l'AUTACK)\n" ) ;
		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 ) )
	{
		fprintf ( stderr, "Erreur de positionnement sur l'heure de signature (dans l'AUTACK)\n" ) ;

		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 )
	{
		fprintf ( stderr, "Erreur de lecture de l'heure de signature (dans l'AUTACK)\n" ) ;
		fclose ( vl_psInter_Trav ) ;
		fclose ( vl_psFich_Extrait ) ;
		unlink ( fichierTmp ) ;
	
		return ( KO ) ;
	}
  
	/* Pour récupérer la clé publique contenue dans l'accrédidation,            */
	/* il faut conserver la chaîne de longueur 784 et commençant en position 72 */
	/* ======================================================================== */
	if ( trouver_chaine_dans_fichier ( vl_psInter_Trav, "'USR+", & vl_iPos_Deb ) == KO )
	{
		fprintf ( stderr, "Erreur : segment USR (contenant l'accréditation) introuvable\n" ) ;
		fclose ( vl_psInter_Trav ) ;
		fclose ( vl_psFich_Extrait ) ;
		unlink ( fichierTmp ) ;

		return ( KO ) ;
	}
  
	/* Repositionnement du pointeur de fichier              */
	/* (sur le 73ème caractère de l'accréd. du segment USR) */
	/* ---------------------------------------------------- */
	if ( fseek ( vl_psInter_Trav, vl_iPos_Deb + 79, SEEK_SET ) )
	{
		fprintf ( stderr, "Erreur de positionnement sur la clé publique contenue dans l'accréditation (segment USR)\n" ) ;
		fclose ( vl_psInter_Trav ) ;
		fclose ( vl_psFich_Extrait ) ;
		unlink ( fichierTmp ) ;

		return ( KO ) ;
	}
  
	/* On lit la clé publique */
	if ( fgets ( cle_publique, (LG_CLE_PUBLIQUE*2 + 1), vl_psInter_Trav ) == NULL )
	{
		fprintf ( stderr, "Erreur de lecture des %d caractères de la clé publique contenue dans l'accréditation (segment USR)\n", LG_CLE_PUBLIQUE*2 ) ;
		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 )
	{
		fprintf ( stderr, "Erreur : segment USY (contenant la signature) introuvable après lecture des %d caractères de la clé publique contenue dans l'accréditation (segment USR)\n", LG_CLE_PUBLIQUE*2 ) ;
		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 ) )
	{
		fprintf ( stderr, "Erreur de positionnement sur la signature (segment USY)\n" ) ;
		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 )
	{
		fprintf ( stderr, "Erreur de lecture des %d caractères de la signature (segment USY)\n", LG_SIGN*2 ) ;
		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 ) ;
}
