OpenPGP format example program

New Message Reply About this list Date view Thread view Subject view Author view

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


New Message Reply About this list Date view Thread view Subject view Author view

 
All trademarks and copyrights are the property of their respective owners.

Other Directory Sites: SeekWonder | Directory Owners Forum

The following archive was created by hippie-mail 7.98617-22 on Fri Aug 21 1998 - 17:15:59 ADT