pgparmor.c

来自「著名的加密软件的应用于电子邮件中」· C语言 代码 · 共 1,332 行 · 第 1/3 页

C
1,332
字号
/*
 * pgpArmor.c -- a module to perform Ascii Armor
 *
 * Copyright (C) 1995-1997 Pretty Good Privacy, Inc.  All rights reserved.
 *
 * Written by:	Derek Atkins <warlord@MIT.EDU>
 *
 * $Id: pgpArmor.c,v 1.6.2.2.2.1 1997/07/16 18:51:41 colin Exp $
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <ctype.h>

#include "pgpDebug.h"
#include "pgpArmor.h"
#include "pgpCRC.h"
#include "pgpPktByte.h"
#include "pgpRadix64.h"
#include "pgpAnnotate.h"
#include "pgpFIFO.h"
#include "pgpHash.h"
#include "pgpJoin.h"
#include "pgpEnv.h"
#include "pgpMem.h"
#include "pgpEnv.h"
#include "pgpPipeline.h"
#include "pgpRndom.h"
#include "pgpSplit.h"
#include "pgpUsuals.h"

#define ARMORMAGIC	0xa4904f11

/* Max length of a line in MIME content-printable encoding */
#define MIMEMAX 76

/*
 * PGP-MIME headers.  We chose "=-" as boundary since it can't occur in
 * a quoted-printable encoding, nor in base64.
 */
#define MIMESEP "=--"
#define MIMESIGHDR "Mime-Version: 1.0\n" \
	"Content-Type: multipart/signed; boundary=\"" MIMESEP "\";\n" \
	"  protocol=\"application/pgp-signature\"; "
#define MIMEENCHDR "Mime-Version: 1.0\n" \
	"Content-Type: multipart/encrypted; boundary=\"" MIMESEP "\";\n" \
	"  protocol=\"application/pgp-encrypted\";\n\n" \
	"--" MIMESEP "\nContent-Type: application/pgp-encrypted\n\n" \
	"Version: 1\n\n--" MIMESEP "\n" \
	"Content-Type: application/octet-stream\n\n"
#define MIMEMIC "micalg=pgp-"
#define MIMEDFLTMIC "md5"
#define MIMETOPBOUND "\n\n--" MIMESEP "\n"
#define MIMEMIDBOUND "\n--" MIMESEP "\n" \
	"Content-Type: application/pgp-signature\n\n"
#define MIMEENDBOUND "\n--" MIMESEP "--\n"
#define MIMETOPHDR   "Content-Type: text/plain; charset=iso-8859-1\n"\
	"Content-Transfer-Encoding: quoted-printable\n\n"

/* Input size for a line of ASCII armor. */
#define ARMOR_LINE_INPUT 48

/*
 * Hairy context with lots of flags for the vsrious options.
 * The actual radix-64 encoding always takes the input[] array
 * as input and the produces a full line in the output[] array as
 * output.  Then armorFlushOutput() routine writes it out.
 */
struct Context {
	struct PgpPipeline *tail;
	struct PgpFifoDesc const *fd;
	struct PgpFifoContext *fifo;
	struct PgpFifoContext *header;
	unsigned long	crc;
	unsigned long	armorlines;	/* This must be an unsigned type */
	unsigned long	lineno;
	unsigned	thispart;
	unsigned	maxparts;
	int	scope_depth;
	byte	input[ARMOR_LINE_INPUT];
#if 0
	char	output[65];	/* Maximum 63 buyes + \r and/or \n */
#else
	char	output[MIMEMAX+2]; /* MIME line plus \n plus null */
#endif
	char *	outptr;		/* First unwritten byte in output[] */
	char *	messageid;	/* This is allocated in create */
	char const *	blocktype;
	char const *	comment;
	char const *	charset;
	PgpVersion	version;
	byte	inlen;		/* valid bytes in input[] */
	byte	outlen;		/* valid bytes in output[] */
	byte	clearsign;	/* boolean, suppresses final footer */
	byte	pgpmime;
#define PGPMIMESIG 1
#define PGPMIMEENC 2
	byte	didheader;	/* this part's header written to FIFO yet? */
	byte	didfooter;	/* this part's footer written to FIFO yet? */
	byte	pgpann_pending;	/* PGPANN_MULTIARMOR_END needs to be sent */
	byte	at_eof;		/* boolean, have got sizeAdvise(0) */
	byte	state;		/* Used by clearsign quoting */
	byte	linebuf;	/* Used by clearsign quoting */
};

/*
 * Armor 3 raw bytes into 4
 * If armoring n < 3 bytes, make the trailers zero, and
 * then overwrite the trailing 3-n bytes with '='
 */
static void
armorMorsel(byte const raw[3], char armor[4])
{
        armor[0] = armorTable[raw[0] >> 2 & 0x3f];
        armor[1] = armorTable[(raw[0] << 4 & 0x30) + (raw[1] >> 4 & 0x0f)];
        armor[2] = armorTable[(raw[1] << 2 & 0x3c) + (raw[2] >> 6 & 0x03)];
        armor[3] = armorTable[raw[2] & 0x3f];
}

/* Classify the data in the input buffer, if it hasn't already been done */
static void
armorWriteClassify (struct Context *context)
{
	if (context->blocktype)
		return;

	if (context->inlen && PKTBYTE_TYPE(context->input[0]) == PKTBYTE_PUBKEY)
		context->blocktype = "PUBLIC KEY BLOCK";
	else
		context->blocktype = "MESSAGE";
}

/* Write out the output buffer (outptr, outlen) */
static int
armorFlushOutput (struct Context *context)
{
	int error;
	size_t retlen;

	pgpAssert (context);
	pgpAssert (context->tail);

	/* Try to flush anything that we have buffered. */
	while (context->outlen) {
		retlen = context->tail->write (context->tail,
					       (byte *)context->outptr,
					       context->outlen,
					       &error);
		context->outlen -= retlen;
		context->outptr += retlen;

		if (error)
			return error;
	}

	return 0;
}

/*
 * Try to write out all pending output, the output buffer, the header/footer
 * FIFO, and a pending PGPANN_MULTIARMOR_END annotation.
 */
static int
armorFlushPending (struct PgpPipeline *myself)
{
	struct Context *context = (struct Context *)myself->priv;
	byte const *ptr;
	unsigned len;
	int error;
	size_t retlen;

	/* Output buffer */
	error = armorFlushOutput (context);
	if (error)
		return error;

	/* header/footer FIFO */
	ptr = pgpFifoPeek (context->fd, context->header, &len);
	while (len) {
		retlen = context->tail->write (context->tail, ptr, len,
					       &error);
		pgpFifoSeek (context->fd, context->header, retlen);
		if (error)
			return error;

		ptr = pgpFifoPeek (context->fd, context->header, &len);
	}

	/* Annotation */
	if (context->pgpann_pending) {
		error = context->tail->annotate (context->tail, myself,
						 PGPANN_MULTIARMOR_END, 0, 0);
		if (error)
			return error;
		context->pgpann_pending = 0;
	}
	return 0;
}

/* Build a header and write it to the header FIFO */
static int
armorMakeHeader (struct PgpPipeline *myself)
{
	struct Context *context = (struct Context *)myself->priv;
	char temp[20];

	/* Write out the opening annotation, if needed */
	if (context->maxparts != 1) {
		unsigned thispart = context->thispart+1;
		int error;

		error = context->tail->annotate (context->tail, myself,
						 PGPANN_MULTIARMOR_BEGIN,
						 (byte const *)&thispart,
						 sizeof (thispart));
		if (error)
			return error;
	}

	/* Do the goodies */

	context->thispart++;
	context->lineno = 0;
	context->didfooter = 0;

	if (context->pgpmime == PGPMIMEENC) {
		pgpFifoWrite (context->fd, context->header,
			      (byte const *)MIMEENCHDR, strlen(MIMEENCHDR));
	}
	pgpFifoWrite (context->fd, context->header,
		      (byte const *)"-----BEGIN PGP ", 15);
	pgpFifoWrite (context->fd, context->header, (byte *)context->blocktype,
		      strlen (context->blocktype));

	if (context->maxparts != 1) {
		sprintf (temp, ", PART %02u", context->thispart);
		pgpFifoWrite (context->fd, context->header, (byte const *)temp,
			      strlen (temp));
		if (context->version <= PGPVERSION_2_6 ||
		    context->thispart == context->maxparts) {
			sprintf (temp, "/%02u", context->maxparts);
			pgpFifoWrite (context->fd, context->header,
				      (byte const *)temp, strlen (temp));
		}
	}

	pgpFifoWrite (context->fd, context->header,
		      (byte const *)"-----\nVersion: ", 15);
	pgpFifoWrite (context->fd, context->header,
		      (byte const *)pgpLibVersionString,
		      strlen (pgpLibVersionString));

	if (context->messageid) {
		pgpFifoWrite (context->fd, context->header,
			      (byte const *)"\nMessageID: ", 12);
		pgpFifoWrite (context->fd, context->header,
			      (byte const *)context->messageid,
			      strlen (context->messageid));
	}

	if (context->comment) {
		pgpFifoWrite (context->fd, context->header,
			      (byte const *)"\nComment: ", 10);
		pgpFifoWrite (context->fd, context->header,
			      (byte const *)context->comment,
			      strlen (context->comment));
	}

	if (context->charset) {
		pgpFifoWrite (context->fd, context->header,
			      (byte const *)"\nCharset: ", 10);
		pgpFifoWrite (context->fd, context->header,
			      (byte const *)context->charset,
			      strlen (context->charset));
	}

	pgpFifoWrite (context->fd, context->header, (byte *)"\n\n", 2);

	context->didheader = 1;
	return 0;	/* Success */
}

/* Build a footer (CRC onwards) and write it to the header FIFO */
static void
armorMakeFooter (struct Context *context)
{
	char temp[20];

	/* What to do with 0 bytes of data? */
	if (!context->didheader)
		return;

	/* Emit CRC, MSB-first */
	temp[0] = '=';
	temp[5] = (byte)(context->crc >> 16);
	temp[6] = (byte)(context->crc >> 8);
	temp[7] = (byte)context->crc;
	armorMorsel (temp+5, temp+1);

	pgpFifoWrite (context->fd, context->header, (byte const *)temp, 5);

	/* Now emit the -----END */
	pgpFifoWrite (context->fd, context->header,
		      (byte const *)"\n-----END PGP ", 14);
	pgpFifoWrite (context->fd, context->header,
		      (byte const *)context->blocktype,
		      strlen (context->blocktype));

	if (context->maxparts != 1) {
		sprintf (temp, ", PART %02u", context->thispart);
		pgpFifoWrite (context->fd, context->header,
			      (byte const *)temp, strlen (temp));
		if (context->version <= PGPVERSION_2_6 ||
		    context->thispart == context->maxparts) {
			sprintf (temp, "/%02u", context->maxparts);
			pgpFifoWrite (context->fd, context->header,
				      (byte const *)temp, strlen (temp));
		}
	}
	pgpFifoWrite (context->fd, context->header, (byte const *)"-----\n", 6);

	if (context->pgpmime) {
		pgpFifoWrite (context->fd, context->header,
		       (byte const *)MIMEENDBOUND, strlen(MIMEENDBOUND));
	}

	context->didfooter = 1;
	if (context->maxparts != 1)
		context->pgpann_pending = 1;	/* Send annotate afterwards! */
	context->crc = CRC_INIT;
}

/*
 * The method here to do the armoring is somewhat tricky.
 * Most lines just have inlen = 48 which maps to 48*4/3 = 64
 * output characters.  But the last line has a short inlen.
 * This leads to a truncated last group, which looks like one of:
 * xx== (if the last group contains 1 byte - 4 bits of padding are zero)
 * xxx= (if the last group contains 2 bytes - 2 bits of padding are zero)
 * xxxx (if the last group contains 3 bytes)
 * To do this, we make sure that we've added an extra 0 byte to the
 * end of the input, then encode it in blocks of 3 bytes, then note by
 * how much the encoding overshot the input length, len - inlen.
 * This is 2, 1, or 0.  Overwrite that many trailing characters with '='.
 * Then a newline can be appended for output.
 */
static int
armorLine (byte const *in, unsigned inlen, char *out)
{
	unsigned len;
	int t;
	char const *out0 = out;

	/* Fill the output buffer from the input buffer */
	for (len = 0; len < inlen; len += 3) {
		armorMorsel (in, out);
		in += 3;
		out += 4;
	}

	/* Now back up and erase any overrun */
	t = (int)(inlen - len);		/* Zero or negative */
	while (t)
		out[t++] = '=';

	return (out - out0);
}

/*
 * Generate a line of output based on the current input buffer.
 * Generate a header if required, then a line of output, writing both out.
 * This should only be called when the input buffer is full, except for
 * the last partial line.   If it is called with a partial line, it
 * must be terminated with a trailing 0 byte.
 */
static int
armorWriteLine (struct PgpPipeline *myself)
{
	struct Context *context = (struct Context *)myself->priv;
	int error = 0;

	/* Make a header if required */
	if (!context->didheader) {
		armorWriteClassify (context);
		error = armorMakeHeader (myself);
		if (error)
			return error;
		error = armorFlushPending (myself);
		if (error)
			return error;
	}

	/* Update CRC */
	context->crc = crcUpdate (context->crc, context->input,
				  context->inlen);

	/* Refill the output buffer from the input buffer */
	context->outlen = armorLine (context->input, context->inlen,
				     context->output);
	context->output [context->outlen++] = '\n';
	context->outptr = context->output;

	/* Mark input as processed */
	context->inlen = 0;
	context->lineno++;

	/* Write a footer, if required (note tricky "-1") */
	if (context->lineno > context->armorlines - 1) {
		if (!context->didfooter)
			armorMakeFooter (context);
		context->didheader = 0;
	}

	return armorFlushPending (myself);
}

/*
 * Write out a final partial line in the input buffer.
 */
static int
armorWritePartial (struct PgpPipeline *myself)
{
	struct Context *context = (struct Context *)myself->priv;
	int error = 0;

	error = armorFlushPending (myself);
	if (error)
		return error;

	if (context->inlen) {
		/* Ensure that padding byte is there! */
		if (context->inlen < sizeof(context->input))
			context->input[context->inlen] = 0;
		error = armorWriteLine (myself);
	}

	return error;
}

/*
 * Write out as many full lines as possible, leaving the rest in the

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?