/*********************************************************************
 *   pgp_write.cc -- This writes out a single PGP packet of your choice
 *   with the supplied information (ex. public key certificates, secret
 *   keys, userid packets...)  Written PGP packets can then be either used
 *   alone or `cat`ed together for use with PGP.
 *
 *   `./pgp_write -?`  for explanation of options.
 *
 *   Requires a large integer library (such as GMP) and a class named
 *   "Integer" that supports it (+, -, *, etc.) in the <Integer.h> file.
 *
 * Compile:
 *    g++ -o pgp_write pgp_write.cc libgmp.a
 * Invoke:
 *    ./pgp_write
 * Changelog:
 *   971024: Created - Paul Herman <a540pau@pslc.ucla.edu>
 *****************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <time.h>	// time stuff
#include <sys/stat.h>	// umask

#include "Integer.h"

#define MAX_PACKET_SIZE 16384	// Never this big, but just in case

/******** Flags in the first "CTB" byte ********/
#define CTB_ALWAYS (1<<7)

#define CTB_PUB_KEY (0x01<<2)		/* Public Key Encrypted x */
#define CTB_SEC_KEY (0x02<<2)		/* Secret Key Signature x */
#define CTB_SEC_CER (0x05<<2)		/* Secret Key info x */
#define CTB_PUB_CER (0x06<<2)		/* Public Key info x */
#define CTB_CMP_DAT (0x08<<2)		/* Compressed Data */
#define CTB_CNV_ENC (0x09<<2)		/* Conventionally Encrypted Data */
#define CTB_RAW_TXT (0x0b<<2)		/* Raw Plain Text */
#define CTB_KEY_TST (0x0c<<2)		/* Key Trust Packet */
#define CTB_USR_IDN (0x0d<<2)		/* User ID Packet x */
#define CTB_COM_MNT (0x0e<<2)		/* Comment Packet */

#define CTB_LEN_8	0x00
#define CTB_LEN_16	0x01
#define CTB_LEN_32	0x02
#define CTB_LEN_UNK 	0x03

/********* Types of Algorithms ********/
#define ALG_RSA		1
#define ALG_UNK		0

#define ALG_MD5		1

/********* Ways to hide secret exponents ********/
#define ENC_IDEA	1
#define ENC_NONE	0

/***** Few global variables *****/
int	verbose = 0;
int	stdoutput = 0;

/* default values */
int	PGP_version = 3;
int	PGP_validity = 0;
int	PGP_algorithm = ALG_RSA;
int	PGP_secret_encrypt = ENC_NONE;
unsigned int 	PGP_64bit_id[2] = { 0x12345678, 0x98765432 };
int	PGP_MDx_len = 5;
int	PGP_sig_classification = 1;
int	PGP_digest_algorithm = ALG_MD5;

char	user_id[256] = "fred";

Integer	public_modulus = 0;
Integer	public_exponent = 0;
Integer	secret_exponent = 0;
Integer	secret_p = 0;
Integer	secret_q = 0;
Integer	secret_u = 0;

Integer	encrypted_IDEA_key = 0;
Integer	sig_digest = 5;
time_t	tme;

/**** int_handler() - tidy up and exit ****/
static	void	int_handler(int unused) {
	printf("Caught an interrupt! Exiting...\n");
	exit(1);
}

void Panic(char *s) { fprintf(stderr, "%s", s); exit(1); }

void write_buff_to_file(char *filename, unsigned char *buff, int len) {
    FILE *fp;
    int i;

    if (stdoutput) {
	for (i=0; i<len; i++) printf("%c", buff[i]);
	return;
    }
    fp = fopen(filename, "a+");
    if (!fp) {free(buff); Panic("Could not open file for writing\n");}
    fwrite(buff, 1, len, fp);
    fclose(fp);
}

unsigned int write_mpi(Integer intgr, unsigned char *p) {
    unsigned int bits, bytes;
    int i;
    Integer tmp, tmp2;

    if (intgr == 0) { *p++ = 0; *p++ = 0; return 2; }

    bits = digits(intgr, 2);	/* Number of bits in 'i' */
    bytes = (bits+7)/8;
    *p++ = (bits >> 8) & 0xff;
    *p++ = bits & 0xff;
    tmp = 256;
    for (i=bytes-1; i>-1; i--) {
	tmp2 = intgr & (tmp-1);
	p[i] = ((int) (tmp2/ (tmp/256))) & 0x000000ff ;
	tmp *= 256;
	}
    return bytes+2;
}

void store_len(unsigned char *buff, int len) {

    if ( (*buff & 0x03) == CTB_LEN_8) {
	buff[1] = len & 0x000000ff;
	}
    if ( (*buff & 0x03) == CTB_LEN_16) {
	buff[1] = (len >> 8) & 0x000000ff;
	buff[2] = len & 0x000000ff;
	}
    if ( (*buff & 0x03) == CTB_LEN_32) {
	buff[1] = (len >> 24) & 0x000000ff;
	buff[2] = (len >> 16) & 0x000000ff;
	buff[3] = (len >> 8) & 0x000000ff;
	buff[4] = len & 0x000000ff;
	}
}

void stamp_time(unsigned char *buff) {
    buff[0] = (tme & 0xff000000) >> 24;
    buff[1] = (tme & 0x00ff0000) >> 16;
    buff[2] = (tme & 0x0000ff00) >> 8;
    buff[3] = tme & 0x000000ff;
}

void stamp_64bits(unsigned char *buff, unsigned int *data) {

    buff[0] = (data[0] & 0xff000000) >> 24;
    buff[1] = (data[0] & 0x00ff0000) >> 16;
    buff[2] = (data[0] & 0x0000ff00) >> 8;
    buff[3] =  data[0] & 0x000000ff;
    buff[4] = (data[1] & 0xff000000) >> 24;
    buff[5] = (data[1] & 0x00ff0000) >> 16;
    buff[6] = (data[1] & 0x0000ff00) >> 8;
    buff[7] =  data[1] & 0x000000ff;
}

/******************************************************************/

void write_rsa_packet(char *filename) {
    unsigned char *buff, *ptr;
    unsigned int bt, len;

    buff = (unsigned char *) malloc(MAX_PACKET_SIZE);
    if (buff == 0) Panic("Not enough memory!\n");

    ptr = buff; len = 0;

	/* CTB byte */
    bt = CTB_ALWAYS | CTB_PUB_KEY | CTB_LEN_16;
    *ptr++ = bt;

	/* Length of Packet */
    *ptr++ = 0; *ptr++ = 0;	/* will write later */

	/* Version Byte */
    *ptr++ = PGP_version; len++;

	/* 64-bit ID */
    stamp_64bits(ptr, PGP_64bit_id);
    ptr += 2*sizeof(int); len += 2*sizeof(int);

	/* PGP Algorithm RSA=1, ??=Other */
    *ptr++ = PGP_algorithm; len++;
    
	/* Public Modulus, N */
    bt = write_mpi(encrypted_IDEA_key, ptr); ptr += bt; len += bt;
    
    store_len(buff, len);

    write_buff_to_file(filename, buff, ptr-buff);
    free(buff);
}

void write_sig_packet(char *filename) {
    unsigned char *buff, *ptr;
    unsigned int bt, len;

    buff = (unsigned char *) malloc(MAX_PACKET_SIZE);
    if (buff == 0) Panic("Not enough memory!\n");

    ptr = buff; len = 0;

	/* CTB byte */
    bt = CTB_ALWAYS | CTB_SEC_KEY | CTB_LEN_16;
    *ptr++ = bt;

	/* Length of Packet */
    *ptr++ = 0; *ptr++ = 0;	/* will write later */

	/* Version Byte */
    *ptr++ = PGP_version; len++;

	/* Digest length (usually 5, as in "MD5") */
    *ptr++ = PGP_MDx_len; len++;

	/* Signature Classification Field */
    *ptr++ = PGP_sig_classification; len++;

	/* Time Stamp */
    stamp_time(ptr); ptr += sizeof(time_t); len += sizeof(time_t);

	/* 64-bit ID */
    stamp_64bits(ptr, PGP_64bit_id);
    ptr += 2*sizeof(int); len += 2*sizeof(int);

	/* PGP Algorithm RSA=1, ??=Other */
    *ptr++ = PGP_algorithm; len++;
    
	/* Digest Algorithm MD5=1, ??=Other */
    *ptr++ = PGP_digest_algorithm; len++;
    
	/* First two bytes of digest */
    *ptr++ = 0; *ptr++ = 0; len += 2;

	/* RSA encrypted digest, in MPI form */
    bt = write_mpi(sig_digest, ptr); ptr += bt; len += bt;
    
    store_len(buff, len);

    write_buff_to_file(filename, buff, ptr-buff);
    free(buff);
}

void write_secret_key(char *filename) {
    unsigned char *buff, *ptr;
    unsigned int bt, len;

    buff = (unsigned char *) malloc(MAX_PACKET_SIZE);
    if (buff == 0) Panic("Not enough memory!\n");

    ptr = buff; len = 0;

	/* CTB byte */
    bt = CTB_ALWAYS | CTB_SEC_CER | CTB_LEN_16;
    *ptr++ = bt;

	/* Length of Packet */
    *ptr++ = 0; *ptr++ = 0;	/* will write later */

	/* Version Byte */
    *ptr++ = PGP_version; len++;

	/* Time Stamp */
    stamp_time(ptr); ptr += sizeof(time_t); len += sizeof(time_t);
    
	/* Validity in days */
    *ptr++ = (PGP_validity >> 8) & 0xff;
    *ptr++ = PGP_validity & 0xff;
    len+=2;
    
	/* PGP Algorithm RSA=1, ??=Other */
    *ptr++ = PGP_algorithm; len++;
    
	/* Public Modulus, N */
    bt = write_mpi(public_modulus, ptr); ptr += bt; len += bt;
    
	/* Public Exponent, E */
    bt = write_mpi(public_exponent, ptr); ptr += bt; len += bt;
    
	/* Secret Key hiding alg.  Unencrypted=0, IDEA=1 */
    *ptr++ = PGP_secret_encrypt; len++;

	/* Secret Exponent, D */
    bt = write_mpi(secret_exponent, ptr); ptr += bt; len += bt;
    
	/* Secret Factor, P */
    bt = write_mpi(secret_p, ptr); ptr += bt; len += bt;
    
	/* Secret Factor, Q */
    bt = write_mpi(secret_q, ptr); ptr += bt; len += bt;
    
	/* Secret Multiplicative Inverse, U */
    bt = write_mpi(secret_u, ptr); ptr += bt; len += bt;
    
	/* 16-bit Checksum of all secret bytes */
    *ptr++ = 0; *ptr++ = 0; len += 2;	/* Not right!  FIX!!!! */

    store_len(buff, len);

    write_buff_to_file(filename, buff, ptr-buff);
    free(buff);
}

void write_public_key(char *filename) {
    unsigned char *buff, *ptr;
    unsigned int bt, len;

    buff = (unsigned char *) malloc(MAX_PACKET_SIZE);
    if (buff == 0) Panic("Not enough memory!\n");

    ptr = buff; len = 0;

	/* CTB byte */
    bt = CTB_ALWAYS | CTB_PUB_CER | CTB_LEN_16;
    *ptr++ = bt;

	/* Length of Packet */
    *ptr++ = 0; *ptr++ = 0;	/* will write later */

	/* Version Byte */
    *ptr++ = PGP_version; len++;

	/* Time Stamp */
    stamp_time(ptr); ptr += sizeof(time_t); len += sizeof(time_t);
    
	/* Validity in days */
    *ptr++ = (PGP_validity >> 8) & 0xff;
    *ptr++ = PGP_validity & 0xff;
    len+=2;
    
	/* PGP Algorithm RSA=1, ??=Other */
    *ptr++ = PGP_algorithm; len++;
    
	/* Public Modulus, N */
    bt = write_mpi(public_modulus, ptr); ptr += bt; len += bt;
    
	/* Public Exponent, E */
    bt = write_mpi(public_exponent, ptr); ptr += bt; len += bt;
    
    store_len(buff, len);

    write_buff_to_file(filename, buff, ptr-buff);
    free(buff);
}

void write_user_id(char *filename) {
    unsigned char *buff, *ptr;
    unsigned int bt, len;

    buff = (unsigned char *) malloc(MAX_PACKET_SIZE);
    if (buff == 0) Panic("Not enough memory!\n");

    ptr = buff; len = 0;

	/* CTB byte */    /* Want to or (|) #defines */
    bt = CTB_ALWAYS | CTB_USR_IDN | CTB_LEN_8;
    *ptr++ = bt;

	/* Length of Packet */
    *ptr++ = 0;		/* will write later */

    sprintf((char *)ptr, "%s", user_id);
    len += strlen((char *)ptr); ptr += strlen((char *)ptr);

	/* Finally write the len */
    buff[1] = len & 0x000000ff;
    
    write_buff_to_file(filename, buff, ptr-buff);
    free(buff);
}

void write_comment_packet(char *filename) {
    unsigned char *buff, *ptr;
    unsigned int bt, len;

    buff = (unsigned char *) malloc(MAX_PACKET_SIZE);
    if (buff == 0) Panic("Not enough memory!\n");

    ptr = buff; len = 0;

	/* CTB byte */    /* Want to or (|) #defines */
    bt = CTB_ALWAYS | CTB_COM_MNT | CTB_LEN_8;
    *ptr++ = bt;

	/* Length of Packet */
    *ptr++ = 0;		/* will write later */

    sprintf((char *)ptr, "%s", user_id);
    len += strlen((char *)ptr); ptr += strlen((char *)ptr);

	/* Finally write the len */
    buff[1] = len & 0x000000ff;
    
    write_buff_to_file(filename, buff, ptr-buff);
    free(buff);
}

int are_equal(char *a, char *b) {
	int r=1, i=0;
	if (!*a && !*b) return 1;
	if (!*a) return 0;
	for (i=0; i<strlen(b) && *a; i++)
		r &= (tolower(a[i]) == tolower(b[i]));
	if (a[i-1]) return r;
	else return 0;
}

void set_64bit(unsigned int *i, char *c) {
   if (are_equal(c, "0x") || are_equal(c, "0X") ) { 	// in hex
	}
   else {	// in decimal
	}
}

#define USAGE "usage: %s [-options] outputfile\n\
    outputfile\t- name of file to write ('-' for stdout)\n\n\
Where [-options] are:\n\
    -h,?\t- help\n\
    -v\t- verbose\n\

    Packet Types:\n\
    -prs\t- write RSA packet\n\
    -psk\t- write secret key packet\n\
    -ppk\t- write public key packet\n\
    -psp\t- write signature packet\n\
    -pid\t- write UserID packet\n\
    -pcm\t- write comment packet\n\

    Set Values:\n\
    -spm [N]\t- Set public modulus  = N\n\
    -spe [E]\t- Set public exponent = E\n\
    -sse [D]\t- Set secret exponent = D\n\
    -ssp [P]\t- Set secret P factor = P\n\
    -ssq [Q]\t- Set secret Q factor = Q\n\
    -ssu [U]\t- Set secret inverse  = U\n\

    -svr [v]\t- Set version = v\n\
    -sal [a]\t- Set algorithm = a\n\
    -sid [i]\t- Set 64-bit ID = i\n\
    -smd [x]\t- Set MDx length = x\n\
    -sda [a]\t- Set digest alg = a\n\
    -sui [name]\t- Set UserID = name\n\
    -scm [text]\t- Set comment = text\n\
    -sik [i]\t- Set IDEA Key = i\n\
"
void show_usage(char *prog) { fprintf(stderr, USAGE, prog); }

main(int argc, char **argv) {
    int		arg = 1, i;
    char	*p;
    void	(*write_packet)(char *);

	/* Set the umask for any files we may create */
    umask(077);

    signal(SIGINT, int_handler);
    write_packet = 0;

    time(&tme);

	/* Check command line parameters (FIX, make a separate fcn) */
    while (arg != argc && argv [arg][0] == '-') {
	if (!argv[arg][1]) stdoutput++;
	for (i = 1; argv[arg][i]; i++) {
		// Set Packet Types
	    if      (are_equal(&argv[arg][i], "prs"))
		{ write_packet=write_rsa_packet; break; }
	    else if (are_equal(&argv[arg][i], "psk"))
		{ write_packet=write_secret_key; break; }
	    else if (are_equal(&argv[arg][i], "ppk"))
		{ write_packet=write_public_key; break; }
	    else if (are_equal(&argv[arg][i], "psp"))
		{ write_packet=write_sig_packet; break; }
	    else if (are_equal(&argv[arg][i], "pid"))
		{ write_packet=write_user_id; break; }
	    else if (are_equal(&argv[arg][i], "pcm"))
		{ write_packet=write_comment_packet; break; }

		// Set Values
	    else if (are_equal(&argv[arg][i], "spm") && argv[arg+1][0] )
		{ public_modulus = argv[++arg]; break; }
	    else if (are_equal(&argv[arg][i], "spe") && argv[arg+1][0] )
		{ public_exponent = argv[++arg]; break; }
	    else if (are_equal(&argv[arg][i], "sse") && argv[arg+1][0] )
		{ secret_exponent = argv[++arg]; break; }
	    else if (are_equal(&argv[arg][i], "ssp") && argv[arg+1][0] )
		{ secret_p = argv[++arg]; break; }
	    else if (are_equal(&argv[arg][i], "ssq") && argv[arg+1][0] )
		{ secret_q = argv[++arg]; break; }
	    else if (are_equal(&argv[arg][i], "ssu") && argv[arg+1][0] )
		{ secret_u = argv[++arg]; break; }

	    else if (are_equal(&argv[arg][i], "svr") && argv[arg+1][0] )
		{ PGP_version = atol(argv[++arg]); break; }
	    else if (are_equal(&argv[arg][i], "sal") && argv[arg+1][0] )
		{ PGP_algorithm = atol(argv[++arg]); break; }
	    else if (are_equal(&argv[arg][i], "sid") && argv[arg+1][0] )
		{ set_64bit(PGP_64bit_id, argv[++arg]); break; }
	    else if (are_equal(&argv[arg][i], "smd") && argv[arg+1][0] )
		{ PGP_MDx_len = atol(argv[++arg]); break; }
	    else if (are_equal(&argv[arg][i], "sda") && argv[arg+1][0] )
		{ PGP_digest_algorithm = atol(argv[++arg]); break; }
	    else if (are_equal(&argv[arg][i], "sui") && argv[arg+1][0] )
		{ sprintf(user_id, "%s", argv[++arg]); break; }
	    else if (are_equal(&argv[arg][i], "scm") && argv[arg+1][0] )
		{ sprintf(user_id, "%s", argv[++arg]); break; }
	    else if (are_equal(&argv[arg][i], "sik") && argv[arg+1][0] )
		{ encrypted_IDEA_key = argv[++arg]; break; }

		// Normal options
	    else if (are_equal(&argv[arg][i], "v"))
		{ verbose++; }
	    else if (are_equal(&argv[arg][i], "h") ||
			are_equal(&argv[arg][i], "?") || 
			are_equal(&argv[arg][i], "help") ) {
		    show_usage(argv[0]);
		    exit(0);
		}
	    else {
		    fprintf(stderr, "Problem with option %s (%c)\n",
			argv[arg], argv[arg][i]);
		    exit(1);
		    }
	    } /* for i */
	arg++;
    }
    if (arg == argc && !stdoutput) {show_usage(argv[0]); exit(1);}

    if (write_packet) write_packet(argv[arg]);
    else fprintf(stderr, "No packet type specified!\n");

    exit(0);
}
