////////////////////////////////////////////////////////////////////////////

// Extract hidden information from encrypted part of Type 1 font file 
// from EEXEC encryption and 
// from Subr and each CharString encryption

// Copyright © 2007-04-21

////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define REEXEC 55665 		// seed constant for EEXEC encryption
#define RCHARSTRING 4330 	// seed constant for Subr and CharString encryption

#define CRYPT_MUL 52845u	// pseudo-random number generator constant
#define CRYPT_ADD 22719u	// pseudo-random number generator constant

typedef unsigned char UCHAR;
typedef unsigned short USHORT;

/////////////////////////////////////////////////////////////////////////////

#define BUFFERLEN 1024

char buffer[BUFFERLEN];		// buffer to assemble tokens and strings

int verboseflag=1;

int traceflag=0;

int pfaflag=0;				// if file appears to be in PFA format

/////////////////////////////////////////////////////////////////////////////

void perrormod (char *s) {
	fprintf(stdout, "%s: %s\n", s, strerror(errno));
}

UCHAR encryptbyte (UCHAR plain, USHORT *crypter) {
	UCHAR cipher;
	cipher = (UCHAR) ((plain ^ (UCHAR) (*crypter >> 8)));
	*crypter = (USHORT) ((cipher + *crypter) * CRYPT_MUL + CRYPT_ADD);
	return cipher;
}

UCHAR decryptbyte (UCHAR cipher, USHORT *crypter) {
	UCHAR plain;
	plain = (UCHAR) ((cipher ^ (UCHAR) (*crypter >> 8)));
	*crypter = (USHORT) ((cipher + *crypter) * CRYPT_MUL + CRYPT_ADD);
	return plain;
} 

int hexadecimal (int n) {
	if (n >= '0' && n <= '9') return n - '0';
	else if (n >= 'A' && n <= 'F') return n - 'A' + 10;
	else if (n >= 'a' && n <= 'f') return n - 'a' + 10;
	else return -1;
}

int gethexadecimal (FILE *infile) {	// two characters for one byte
	int n, m;
	n = fgetc(infile);
	while (n <= ' ' && n >= 0) n = fgetc(infile);
	n = hexadecimal(n);
	m = fgetc(infile);
	while (m <= ' ' && m >= 0) m = fgetc(infile);
	m = hexadecimal(m);
	if (n < 0 || m < 0) return -1;
	return (n << 4) | m;
}

int savedbyteouter=-1;	// allow equivalent of ungetc(...)

int getnextbyteouter (FILE *infile, USHORT *pcryptee) {
	int n, m;
	if (savedbyteouter >= 0) {
		m = savedbyteouter;
		savedbyteouter = -1;
		return m;
	}
	if (pfaflag) n = gethexadecimal(infile);
	else n = fgetc(infile);
	if (n < 0) return -1;	// EOF or bad characters
	m = decryptbyte((UCHAR) n, pcryptee); // eexec decryption
	return m;
}

int getnextbyteinner (FILE *infile, USHORT *pcryptee, USHORT *pcryptcs) {
	int m, k;
	m = getnextbyteouter(infile, pcryptee);
	if (m < 0) return -1;	// EOF or bad characters
	k = decryptbyte((UCHAR) m, pcryptcs);  // Subr/CharString decryption
	return k;
}

int getnexttoken (FILE *infile, char *buffer, int nlen) {
	char *s=buffer;
	int c, count=0;

	c = fgetc(infile);		// in plain text
	while (! (c > 32 && c < 128) && c >= 0)
		c = fgetc(infile);	// advance over non-chars
	if (c < 0) return -1;	// hit EOF
	while ((c > 32 && c < 128) && count < nlen) {
		*s++ = (char) c;	// assemble a token
		count++;
		c = fgetc(infile);
		if (c == '/' || c == '{' || c == '}' || c == '[' || c == ']') {
			ungetc(c, infile);	// is part of next token
			break;
		}
	}
	*s = '\0';				// null terminate
	if (count == 0 && c < 0) return -1;	// EOF
	if (count == nlen) return -1;		// token too long
	return count;
}

int getnextinnertoken (FILE *infile, char *buffer, int nlen, USHORT *pcrypt) {
	char *s=buffer;
	int c, count=0;
	c = getnextbyteouter(infile, pcrypt);
	while (! (c > 32 && c < 128) && c >= 0)
		c = getnextbyteouter(infile, pcrypt);	// advance over non-chars
	if (c < 0) return -1;	// hit EOF
	while ((c > 32 && c < 128) && count < nlen) {
		*s++ = (char) c;	// assemble token
		count++;
		c = getnextbyteouter(infile, pcrypt);
		if (c == '/' || c == '{' || c == '}' || c == '[' || c == ']') {
			savedbyteouter = c;	// "unget" - is part of next token
			break;
		}
	}
	*s = '\0';				// null terminate
	if (count == 0 && c < 0) return -1;	// EOF
	if (count == nlen) return -1;		// token too long
	return count;
}

int readASCIIhead (FILE *infile) {	// try and read ASCII section head
	int c, d, e, f;
	pfaflag = 0;	// default is that its a PFB format file
	c = fgetc(infile);
	while (c <= ' ' && c >= 0) c = fgetc(infile);	// should not be needed
	d = fgetc(infile);
	if (c != 128 || d != 1) {
		if (c != '%' || d != '!') return -1;
		pfaflag = 1;	// maybe it is PFA format
		return 0;
	}
	c = fgetc(infile);
	d = fgetc(infile);
	e = fgetc(infile);
	f = fgetc(infile);
	return (((((f << 8) | e) << 8) | d) << 8) | c;	// length
}

int readbinaryhead (FILE *infile) {	// try and read binary section head
	int c, d, e, f;
	c = fgetc(infile);
	while (c <= ' ' && c >= 0) c = fgetc(infile);	// should not be needed
	if (pfaflag) {
		ungetc(c, infile);
		return 0;
	}
	d = fgetc(infile);
	if (c != 128 || d != 2) return -1;
	c = fgetc(infile);
	d = fgetc(infile);
	e = fgetc(infile);
	f = fgetc(infile);
	return (((((f << 8) | e) << 8) | d) << 8) | c;	// length
}

// scan up to "eexec" token (in PFB could use length of "ASCII" section)

int scantoeexec (FILE *infile) {
	int c;
	c = getnexttoken(infile, buffer, sizeof(buffer));
	while (c >= 0) {
		if (strcmp(buffer, "eexec") == 0) break;
		c = getnexttoken(infile, buffer, sizeof(buffer));
	}
	if (traceflag) printf("%s\n", buffer);
	return c;
}

// scan up to /Subrs section --- if any

int scantosubrs (FILE *infile, USHORT *pcryptee) {
	int c;
	c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
	while (c >= 0) {
		if (strcmp(buffer, "/Subrs") == 0) break;
		if (strcmp(buffer, "/CharStrings") == 0) return 0;	// ugh
		if (strcmp(buffer, "/FontName") == 0) return 0;	// ugh
		c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
	}
	if (traceflag) printf("%s\n", buffer);
	return c;
}

// Process the /Subrs

int processsubrs (FILE *infile, USHORT *pcryptee, USHORT *pcryptcs) {
	int c, n, m, k;

//	printf("Entering processsubrs\n");
	c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
	while (c >= 0) {
		if (strcmp(buffer, "/CharStrings") == 0) break;	// end of Subrs
		if (strcmp(buffer, "def") == 0) break;	// end of Subrs
		if (strcmp(buffer, "end") == 0) break;	// end of Subrs
		if (strcmp(buffer, "FontDirectory") == 0) break;	// end of Subrs
		if (strcmp(buffer, "dup") == 0) {
			c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
			if (c < 0) break;
			if (sscanf(buffer, "%d", &n) < 1) {	// Subr number
				printf("\n");
				printf("ERROR: can't parse Subr number: %s\n", buffer);
				return -1;
			}
			c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
			if (c < 0) break;
			if (sscanf(buffer, "%d", &m) < 1 || m < 4) {	// length of program
				printf("ERROR: can't parse Subr length: %s (%d)\n", buffer, n);
				return -1;
			}
//			printf("\ndup %d %d\n", n, m);	// trace
			c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
			if (c < 0) break;	// -| or RD e.g.
			*pcryptcs = RCHARSTRING;
			for (k = 0; k < 4; k++) {	// read encryption primer
				c = getnextbyteinner(infile, pcryptee, pcryptcs);
				printf("%c", c);	// show it
			}
			for (k = 4; k < m; k++) {	// step over Subr program
				c = getnextbyteouter(infile, pcryptee);
//				c = getnextbyteinner(infile, pcryptee, pcryptcs);
			}
		}
		c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
	}
	if (traceflag) printf("\n%s\n", buffer);
	return c;
}

// Process the /CharStrings

int processcharstrings (FILE *infile, USHORT *pcryptee, USHORT *pcryptcs) {
	int c, m, k;
	char str[256];

//	printf("Entering processcharstrings\n");
	c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
	while (c >= 0) {
		if (strcmp(buffer, "/FontName") == 0) break;	// end of chars
		if (strcmp(buffer, "definefont") == 0) break;	// end of chars
		if (strcmp(buffer, "currentfile") == 0) break;	// end of chars
		if (strcmp(buffer, "closefile") == 0) break;	// end of chars
		if (strcmp(buffer, "cleartomark") == 0) break;	// end of chars
		if (strcmp(buffer, "FontDirectory") == 0) return 0;	// nested font
		if (buffer[0] == '/') {		// glyphname
			strcpy(str, buffer);	// remember name
			c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
			if (c < 0) break;
			if (strcmp(str, "/CharStrings") == 0) continue;
			if (sscanf(buffer, "%d", &m) < 1 || m < 4) {	// length of program
				printf("\n");
//				printf("ERROR: can't parse CharString length: %s\n", buffer);
				printf("ERROR: can't parse CharString length: %s (%s %d)\n",
					   buffer, str, m);
				return -1;
			}
//			printf("%s %d\n", str, m);	// trace
			c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
			if (c < 0) break;	// -| or RD e.g.
			*pcryptcs = RCHARSTRING;
			for (k = 0; k < 4; k++) {	// read encryption primer
				c = getnextbyteinner(infile, pcryptee, pcryptcs);
				printf("%c", c);	// show it
			}
			for (k = 4; k < m; k++) {	// step over Char program
				c = getnextbyteouter(infile, pcryptee);
//				c = getnextbyteinner(infile, pcryptee, pcryptcs);
			}
		}
		c = getnextinnertoken(infile, buffer, sizeof(buffer), pcryptee);
	}
	if (traceflag) printf("token %s c %d\n", buffer, c);
	return c;
}

// Try various variations on given file name to find the PFB file

FILE *findPFBfile (char *pfbfilename) {
	FILE *infile;
	char *s;
	int n, k;
	char infilename[FILENAME_MAX];	// place to construct file name

	strcpy(infilename, pfbfilename);
	if (traceflag) printf("Trying %s\n", infilename);
	infile = fopen(infilename, "rb");
	if (infile != NULL) {
		if (verboseflag) printf("Opened %s\n", infilename);
		return infile;
	}
//	If failed, try various variations on the file name supplied...
	s = strchr(infilename, '.');
	if (s == NULL) {	// no extension
		strcat(infilename, ".pfb");	// add extension
		infile = findPFBfile(infilename);
	}
	if (infile != NULL) {
		if (verboseflag) printf("Opened %s\n", infilename);
		return infile;
	}
	s = strchr(infilename, '.');
	if (s != NULL) *s='\0';
	n = strlen(infilename);
	if (n < 8) {	// short file name
		for (k = n; k < 8; k++) infilename[k] = '_';
		infilename[8] = '\0';
		strcat(infilename, ".pfb");
		infile = findPFBfile(infilename);
	}
	if (infile != NULL) {
		if (verboseflag) printf("Opened %s\n", infilename);
		return infile;
	}
	if (strchr(pfbfilename, '\\') == NULL &&
		  strchr(pfbfilename, ':') == NULL) {	// unqualified name
		strcpy(infilename, "c:\\windows\\fonts");	// standard place
		strcat(infilename, pfbfilename);
		infile = findPFBfile(infilename);
	}
	if (infile != NULL) {
		if (verboseflag) printf("Opened %s\n", infilename);
		return infile;
	}
	return NULL;
}

// Scan a PFB file

int scanPFBfile (char *pfbfilename) {
	FILE *infile;
	int c, k, nascii, nbinary;
	USHORT cryptee; 	// current seed for eexec encryption 
	USHORT cryptcs; 	// current seed for Subr/CharString encryption 

	char *flagstring="***********************************";

	printf("%s%s\n", flagstring, flagstring);
	infile = findPFBfile(pfbfilename);
	if (infile == NULL) {
		perrormod(pfbfilename);
		return -1;
	}
	nascii = readASCIIhead(infile);	// length of initial "ASCII" section
	if (nascii < 0) {
		printf("ERROR: %s not a PFB file\n", pfbfilename);
		fclose(infile);
		return -1;
	}
	c = scantoeexec(infile);
	if (c < 0) {
		printf("ERROR: Hit EOF in %s\n", pfbfilename);
		fclose(infile);
		return -1;
	}
	nbinary = readbinaryhead(infile);	// length of "binary" section
	if (nbinary < 0) {
		printf("ERROR: %s is not properly formatted\n", pfbfilename);
		fclose(infile);
		return -1;
	}
	cryptee = REEXEC;	// encryption seed for eexec coded stuff 
	printf("EEXEC HEAD: ");
	for (k = 0; k < 4; k++) {
		c = getnextbyteouter(infile, &cryptee);
		if (c < 0) break;
		printf("%c", c);
	}
	printf("\n");
	c = scantosubrs(infile, &cryptee);
	if (c < 0) {
		printf("ERROR: %s has no /Subrs\n", pfbfilename);
		fclose(infile);
		return -1;
	}
	if (c > 0) {	// should always happen
		c = processsubrs(infile, &cryptee, &cryptcs);
		if (c < 0) {
			printf("ERROR: %s has bad /Subrs\n", pfbfilename);
			fclose(infile);
			return -1;
		}
	}
	c = processcharstrings(infile, &cryptee, &cryptcs);
	if (c == 0) {
		printf("\n");
		printf("ERROR: %s is compound/nested font\n", pfbfilename);
		fclose(infile);
		return -1;
	}
	else if (c < 0) {
		printf("\n");
		printf("ERROR: %s has bad /CharStrings\n", pfbfilename);
		fclose(infile);
		return -1;
	}
	fclose(infile);
	printf("\n\n");
	return 0;
}

int main (int argc, char *argv[]) {
	int k;
	if (argc < 2) printf("Useage: %s <PFB-file-names>\n", argv[0]);
	for (k = 1; k < argc; k++) (void) scanPFBfile(argv[k]);
	return 0;
}

///////////////////////////////////////////////////////////////////////////

// Assumes plain vanilla Adobe Type 1 font file format (PFB)
// See: "Adobe Type 1 Font Format" by Adobe Systems Incorporated
//		Copyright (C) 1990	ISBN 0-201-57044-0  
// Does not handle "nested" fonts
//		(e.g. obliques defined in terms of roman)
//		(e.g. narrows defined in terms of roman)
// Does not handle binary section broken into multiple parts
// May handle extraneous white space after "eexec" token...
// May handle case of missing /Subrs...
// May handle PFA files...

/////////////////////////////////////////////////////////////////////////


