lcs Mixmaster Remailer (mix@anon.lcs.mit.edu)
16 Mar 1998 01:20:00 -0000
-----BEGIN PGP SIGNED MESSAGE-----
Here's my weekend hacking project. Hope somebody finds it useful.
Salvo Salasio
===========================================================================
/* SSPGP: parse an OpenPGP message
*
* This program parses a PGP message and describes the contents. It
* does not attempt to decrypt them. I wrote it to:
*
* (a) Show by example how to parse PGP message packets;
* (b) Verify gross legality of PGP messages;
* (c) Display the full recipient list of a PGP message;
* (d) Provide a partial reference implementation of the draft OpenPGP spec.
*
* Based on draft-ietf-openpgp-formats (Callas et al. exp. Aug 1998)
*
* Salvo Salasio (PGP key ID 0xFFFFFFFF), 1998.03.15
* Please report bugs via email to Salvo Salasio <CodherPlunks@toad.com>
*
* Usage: sspgp filename
* Compilation: gcc -o sspgp -O6 sspgp.c
* Environment: developed and tested on Linux
*
* Intellectual property status:
* This code is in the public domain, and thus may be used for any
* purpose, private or commercial. It was written without reference
* to PGP source code using the draft-ietf-openpgp-formats specification.
* Output of both licensed and free executable versions of PGP was used
* in testing.
*
* Export status:
* This version contains no crypto capability per se. However, since it
* might be argued that it provides crypto hooks in the form of a framework
* on which to hang an indepenent implementation of parts of PGP, I'm
* releasing the code pseudonymously to avoid, as Obi-wan Kenobi puts it,
* "Imperial entanglements."
*
* Version 0.1 - Ides of March 1998
*
* Potential extensions
* - Look up user names for keys for which a message has been encrypted
* - Parse algorithm-dependent public key data
* - Inflate compressed packets
* - Recognize more armored packet types
* - Verify signatures
* - Decrypt packets
* - Encrypt packets
* - Provide full PGP capability
*/
#include <stdio.h>
int next_packet(FILE *);
void initarmor(FILE *);
int getbyte(FILE *);
void ungetbyte(int, FILE *);
int freadpack(char *, int, FILE *);
char *pub_key_alg(int);
void die();
#define HIBIT 0x80 /* High bit of a tag is always on */
#define NEWBIT 0x40 /* Next highest bit determines new vs old */
#define OLDLENGTH 0x3 /* Bits 1-0 are length format in old style */
#define YES 1
#define NO 0
typedef unsigned char uchar;
typedef unsigned int uint;
#define MAXPACKET (1 << 18) /* Hey, memory's cheap these days */
uchar buffer[MAXPACKET];
#define MAXLINE 256
uchar line[MAXLINE];
uint msglen = 0; /* Used for radix64 parity (what's 3-valued parity?) */
int spout = -1; /* For ungetc() of an armored byte */
struct pubkey_session_key_pkt
{
uchar pskp_pkt_type_version;
uchar pskp_key_id[8]; /* ID this session key is encrypted to */
uchar pskp_pub_key_alg; /* Which public key algorithm used? */
uchar pskp_session[1]; /* The encrypted session key */
};
struct pubkey_pkt_2 /* Version 2 and version 3 public key fmt */
{
uchar pkp_pkt_type_version;
uchar pkp_creation[4];
uchar pkp_expire_days[2];
uchar pkp_pub_key_alg;
uchar pkp_key_material[1];
};
struct pubkey_pkt_4 /* Version 4 public key fmt is different */
{
uchar pkp_pkt_type_version;
uchar pkp_creation[4];
uchar pkp_pub_key_alg;
uchar pkp_key_material[1];
};
uint packet_number = 0;
uint partial = NO;
uint binary = YES;
int main(int argc, char *argv[])
{
FILE *pgpfile;
int c;
if (argc != 2) die("%s\n", "Usage: sspgp filename\n");
pgpfile = fopen(argv[1], "rb");
if (pgpfile == NULL) die("Can't read input file %s.\n", argv[1]);
binary = (c = getc(pgpfile)) & 0x80; /* Peek at first byte */
ungetc(c, pgpfile); /* If high bit off, armored */
if (!binary) initarmor(pgpfile); /* Scan for BEGIN PGP */
while (next_packet(pgpfile)); /* Scan the packets in the message */
fclose(pgpfile);
return 0;
}
int next_packet(FILE *pgpfile)
{
uchar packet_tag, packet_content, len1, *s;
uint length, i;
struct pubkey_session_key_pkt *pskp;
struct pubkey_pkt_2 *pkp_2;
struct pubkey_pkt_4 *pkp_4;
int c, read_length;
if (partial) goto partial_length; /* Middle of partial block chain */
/* Otherwise start of a full packet */
c = getbyte(pgpfile);
if (c == EOF) return 0;
/* Possible bug in the draft spec or in Win95 PGP?
* In one encrypted message sent to me a series of partial
* blocks ends with a normal block. After the legitimate
* pkts the final byte of the file is a 0xFF. This ought
* to be a sort of legal marker expressing a very large
* data block, but isn't. On the other hand, this
* is an unconfirmed sighting -- the file in question was
* encrypted on a Win95 box and sent through the mail, so
* it may have gotten that 0xFF added in transit.
*/
if (c == 0xFF)
{
c = getbyte(pgpfile); /* Are we out of data? */
if (c == EOF)
{
printf("Last bogus block is 0xFF only.\n");
return 1;
}
ungetbyte(c,pgpfile); /* Maybe not out of data... */
}
packet_tag = c;
printf("Packet %d: ", packet_number++);
if (! (packet_tag & HIBIT))
die("High bit of packet tag should be on: %02x\n", packet_tag);
if (packet_tag & NEWBIT)
{
packet_content = packet_tag & 0x3f;
partial_length: /* new-style length may have many partials */
partial = NO;
len1 = getbyte(pgpfile);
if (len1 <= 191)
length = len1;
else if (len1 >= 192 && len1 <= 223)
length = (len1 - 192) * 256 + getbyte(pgpfile) + 192;
else /* partial body length is a power of 2 */
{
length = 1 << (len1 & 0x1f);
partial = YES;
}
printf(" new-style %slength %d\n", partial? "partial ": "", length);
}
else
{
switch (packet_tag & OLDLENGTH)
{
case 0:
length = getbyte(pgpfile);
break;
case 1:
length = getbyte(pgpfile) * 256 +
getbyte(pgpfile);
break;
case 2:
length = getbyte(pgpfile) * 256 * 256 * 256 +
getbyte(pgpfile) * 256 * 256 +
getbyte(pgpfile) * 256 +
getbyte(pgpfile); /* Let compiler do it */
break;
case 3:
printf("\tHeader is of indeterminate length.\n");
break;
default:
die("%s\n", "Impossible error #1.\n");
}
printf("old-style length %d\n", length);
packet_content = (packet_tag & 0x3f) >> 2;
}
if ((read_length = freadpack(buffer, length, pgpfile)) != length)
die("Not as much data as claimed (%d).\n", read_length);
switch(packet_content)
{
case 0: printf("\tBogus (content type 0).\n"); break;
case 1:
printf("\tPubkey encrypted session key.\n");
pskp = (struct pubkey_session_key_pkt *) buffer;
if (pskp->pskp_pkt_type_version != 2 &&
pskp->pskp_pkt_type_version != 3)
printf("The packet has a bogus version ID: %d\n",
pskp->pskp_pkt_type_version);
printf("\tKey ID ["); /* PGP doesn't print the 1st part */
for (i = 0, s = pskp->pskp_key_id; i < 4; i++)
printf("%02x", *s++);
printf("] ");
for (; i < 8; i++)
printf("%02x", *s++);
printf("\n");
printf("\tAlgorithm %d: %s\n",
pskp->pskp_pub_key_alg,
pub_key_alg(pskp->pskp_pub_key_alg));
break;
case 2: printf("\tSignature.\n"); break;
case 3: printf("\tSymm-key encrypted session key.\n"); break;
case 4: printf("\tOne-pass sig.\n"); break;
case 5: printf("\tSecret key.\n"); break;
case 6:
/* I'm sure there's a clean way to do this. This isn't it. */
pkp_2 = (struct pubkey_pkt_2 *) buffer;
if (pkp_2->pkp_pkt_type_version == 4)
{
pkp_4 = (struct pubkey_pkt_4 *) buffer;
printf("\tPubkey pkt version %d, algorithm %d: %s\n",
pkp_4->pkp_pkt_type_version,
pkp_4->pkp_pub_key_alg,
pub_key_alg(pkp_4->pkp_pub_key_alg));
}
else
{
printf("\tPubkey pkt version %d, algorithm %d: %s\n",
pkp_2->pkp_pkt_type_version,
pkp_2->pkp_pub_key_alg,
pub_key_alg(pkp_2->pkp_pub_key_alg));
}
break;
case 7: printf("\tSecret subkey.\n"); break;
case 8:
printf("\tCompressed data: type %d.\n", buffer[0]);
break;
case 9: printf("\tSymmetric-encrypted data\n"); break;
case 10:
printf("\tMarker pkt: older PGP versions need not apply.\n");
if (strncmp("PGP", buffer, length) != 0)
die("Marker pkt value must be PGP, but it isn't.\n");
return 1;
case 11: printf("\tLiteral data.\n"); break;
case 12: printf("\tTrust %d\n", buffer[0]); break;
case 13:
printf("\tName: ");
for (i = 0; i < length; i++)
printf("%c", buffer[i]);
printf("\n");
break;
case 14: printf("\tSubkey.\n"); break;
case 15: printf("\tReserved value.\n"); break;
default: printf("\tPrivate or experimental value.\n"); break;
}
return 1;
}
char *pub_key_alg(int id)
{
switch(id)
{
case 1: return "RSA (Encrypt or Sign)";
case 2: return "RSA Encrypt-Only";
case 3: return "RSA Sign-Only";
case 16: return "ElGamal";
case 17: return "DSA";
case 18: return "Elliptic Curve";
case 19: return "ECDSA";
case 21: return "Diffie-Hellman (X9.42)";
default:
if (id >= 100 && id <= 110)
return "Private/experimental";
else
{
sprintf(line, "Bogus: Pub key type ID %d", id);
return line;
}
}
return "Fnord";
}
/* Simple-minded radix64 routine -- needs more generality and error checking.
* Could also check the CRC instead of quitting with an '='.
*/
uchar inverse[256];
#define NEXT_INPUT(inv) \
while ((c = getc(file)) == '\n' || c == '\r'); \
if (c == EOF || c == '=') return EOF; \
inv = inverse[c]
int getbyte(FILE *file) /* Read a real byte or a radix-64 one */
{
static int c;
static int inv1, inv2, inv3, inv4;
if (binary) return getc(file);
/* Did we push an armored byte back up the spout with ungetbyte()? */
if ((c = spout) != -1)
{
spout = -1;
return c;
}
/* Disarmor and return a byte */
switch(msglen++ % 3) /* Where are we in the 4->3 process? */
{
case 0:
NEXT_INPUT(inv1);
NEXT_INPUT(inv2);
c = ((inv1 << 2) & 0xfc) | ((inv2 >> 4) & 0x03);
break;
case 1:
NEXT_INPUT(inv3);
c = ((inv2 << 4) & 0xf0) | ((inv3 >> 2) & 0x0f);
break;
case 2:
NEXT_INPUT(inv4);
c = ((inv3 << 6) & 0xc0) | (inv4 & 0x3f);
break;
}
return c;
}
void ungetbyte(int c, FILE *file)
{
if (binary) ungetc(c, file);
else spout = c;
}
int freadpack(char *buffer, int length, FILE *pgpfile)
{
int i, c;
if (binary) return fread(buffer, 1, length, pgpfile);
for (i = 0; i < length; i++)
{
buffer[i] = (c = getbyte(pgpfile));
if (c == EOF) return i;
}
return length;
}
#define TOPMSG "-----BEGIN PGP"
#define SIGMSG "-----BEGIN PGP SIGNED"
uchar radix64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
void initarmor(FILE *pgpfile) /* Look for beginning of PGP armor */
{
int i;
char *v;
int toplen = strlen(TOPMSG);
int siglen = strlen(SIGMSG);
for (i = 0; i < 64; i++) inverse[radix64[i]] = i;
while ((v = fgets(line, MAXLINE, pgpfile)) != NULL)
if (strncmp(line, TOPMSG, toplen) == 0) break;
if (strncmp(line, SIGMSG, siglen) == 0)
{
printf("This is a PGP clear-signed file.\n");
die("That's all I can tell you about it.\n");
}
if (v == NULL)
die("Couldn't find PGP armor in non-binary input.\n");
while ((v = fgets(line, MAXLINE, pgpfile)) != NULL)
if (line[0] == '\n' || line[0] == '\r') break;
if (v == NULL)
die("Couldn't find a blank line in PGP armor.\n");
/* We're ready to start reading the base 64 data */
printf("ASCII-armored PGP message.\n");
}
void die(char *format, void *whine)
{
fprintf(stderr, format, whine);
exit(1);
}
===========================================================================
-----BEGIN PGP SIGNATURE-----
Version: PGP for Personal Privacy 5.0
Charset: noconv
iQEPAwUBNQxG58NH+A3/////AQHOZAfKAvXasVlwPTxsQp6fQrCEWKznTG1gJIso
YnQxBEFszxmnRH8ln9MoagBIaclMr4YLXzWTvOeAB3cnqtaAmS0P0Ulu/TgEzebn
/Qpcx61VDjwZLJ9PkuMVjYsCtK8XVeQKhGslcyi0Rc9USl8w7onaDXF38yMK2vjT
Vv496ZVDw+7SAIJxvSUFMaGT60/C+YM9s938YGIaa0516DEc5vqmTZQghT3cRvoS
28DjnNPwh7VYuPbt4+Y5FKgay/COpwJM0f1HrtqQrcdRRBJRDFzM++Pod2A9GwYU
DTJyP41CSAdLEMewOOqMYnobXYqCVoF+aFQ2VSV4ab5/1Q==
=IOon
-----END PGP SIGNATURE-----
The following archive was created by hippie-mail 7.98617-22 on Fri Aug 21 1998 - 17:15:59 ADT