mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-29 22:49:41 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			1189 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1189 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * pgp-decrypt.c
 | |
|  *	  OpenPGP decrypt.
 | |
|  *
 | |
|  * Copyright (c) 2005 Marko Kreen
 | |
|  * All rights reserved.
 | |
|  *
 | |
|  * Redistribution and use in source and binary forms, with or without
 | |
|  * modification, are permitted provided that the following conditions
 | |
|  * are met:
 | |
|  * 1. Redistributions of source code must retain the above copyright
 | |
|  *	  notice, this list of conditions and the following disclaimer.
 | |
|  * 2. Redistributions in binary form must reproduce the above copyright
 | |
|  *	  notice, this list of conditions and the following disclaimer in the
 | |
|  *	  documentation and/or other materials provided with the distribution.
 | |
|  *
 | |
|  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 | |
|  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | |
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | |
|  * ARE DISCLAIMED.	IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 | |
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | |
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | |
|  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | |
|  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | |
|  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | |
|  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | |
|  * SUCH DAMAGE.
 | |
|  *
 | |
|  * $PostgreSQL: pgsql/contrib/pgcrypto/pgp-decrypt.c,v 1.8 2009/06/11 14:48:52 momjian Exp $
 | |
|  */
 | |
| 
 | |
| #include "postgres.h"
 | |
| 
 | |
| #include "px.h"
 | |
| #include "mbuf.h"
 | |
| #include "pgp.h"
 | |
| 
 | |
| #define NO_CTX_SIZE		0
 | |
| #define ALLOW_CTX_SIZE	1
 | |
| #define NO_COMPR		0
 | |
| #define ALLOW_COMPR		1
 | |
| #define NO_MDC			0
 | |
| #define NEED_MDC		1
 | |
| 
 | |
| #define PKT_NORMAL 1
 | |
| #define PKT_STREAM 2
 | |
| #define PKT_CONTEXT 3
 | |
| 
 | |
| #define MAX_CHUNK (16*1024*1024)
 | |
| 
 | |
| static int
 | |
| parse_new_len(PullFilter *src, int *len_p)
 | |
| {
 | |
| 	uint8		b;
 | |
| 	int			len;
 | |
| 	int			pkttype = PKT_NORMAL;
 | |
| 
 | |
| 	GETBYTE(src, b);
 | |
| 	if (b <= 191)
 | |
| 		len = b;
 | |
| 	else if (b >= 192 && b <= 223)
 | |
| 	{
 | |
| 		len = ((unsigned) (b) - 192) << 8;
 | |
| 		GETBYTE(src, b);
 | |
| 		len += 192 + b;
 | |
| 	}
 | |
| 	else if (b == 255)
 | |
| 	{
 | |
| 		GETBYTE(src, b);
 | |
| 		len = b;
 | |
| 		GETBYTE(src, b);
 | |
| 		len = (len << 8) | b;
 | |
| 		GETBYTE(src, b);
 | |
| 		len = (len << 8) | b;
 | |
| 		GETBYTE(src, b);
 | |
| 		len = (len << 8) | b;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		len = 1 << (b & 0x1F);
 | |
| 		pkttype = PKT_STREAM;
 | |
| 	}
 | |
| 
 | |
| 	if (len < 0 || len > MAX_CHUNK)
 | |
| 	{
 | |
| 		px_debug("parse_new_len: weird length");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 
 | |
| 	*len_p = len;
 | |
| 	return pkttype;
 | |
| }
 | |
| 
 | |
| static int
 | |
| parse_old_len(PullFilter *src, int *len_p, int lentype)
 | |
| {
 | |
| 	uint8		b;
 | |
| 	int			len;
 | |
| 
 | |
| 	GETBYTE(src, b);
 | |
| 	len = b;
 | |
| 
 | |
| 	if (lentype == 1)
 | |
| 	{
 | |
| 		GETBYTE(src, b);
 | |
| 		len = (len << 8) | b;
 | |
| 	}
 | |
| 	else if (lentype == 2)
 | |
| 	{
 | |
| 		GETBYTE(src, b);
 | |
| 		len = (len << 8) | b;
 | |
| 		GETBYTE(src, b);
 | |
| 		len = (len << 8) | b;
 | |
| 		GETBYTE(src, b);
 | |
| 		len = (len << 8) | b;
 | |
| 	}
 | |
| 
 | |
| 	if (len < 0 || len > MAX_CHUNK)
 | |
| 	{
 | |
| 		px_debug("parse_old_len: weird length");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	*len_p = len;
 | |
| 	return PKT_NORMAL;
 | |
| }
 | |
| 
 | |
| /* returns pkttype or 0 on eof */
 | |
| int
 | |
| pgp_parse_pkt_hdr(PullFilter *src, uint8 *tag, int *len_p, int allow_ctx)
 | |
| {
 | |
| 	int			lentype;
 | |
| 	int			res;
 | |
| 	uint8	   *p;
 | |
| 
 | |
| 	/* EOF is normal here, thus we dont use GETBYTE */
 | |
| 	res = pullf_read(src, 1, &p);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 	if (res == 0)
 | |
| 		return 0;
 | |
| 
 | |
| 	if ((*p & 0x80) == 0)
 | |
| 	{
 | |
| 		px_debug("pgp_parse_pkt_hdr: not pkt hdr");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 
 | |
| 	if (*p & 0x40)
 | |
| 	{
 | |
| 		*tag = *p & 0x3f;
 | |
| 		res = parse_new_len(src, len_p);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		lentype = *p & 3;
 | |
| 		*tag = (*p >> 2) & 0x0F;
 | |
| 		if (lentype == 3)
 | |
| 			res = allow_ctx ? PKT_CONTEXT : PXE_PGP_CORRUPT_DATA;
 | |
| 		else
 | |
| 			res = parse_old_len(src, len_p, lentype);
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Packet reader
 | |
|  */
 | |
| struct PktData
 | |
| {
 | |
| 	int			type;
 | |
| 	int			len;
 | |
| };
 | |
| 
 | |
| static int
 | |
| pktreader_pull(void *priv, PullFilter *src, int len,
 | |
| 			   uint8 **data_p, uint8 *buf, int buflen)
 | |
| {
 | |
| 	int			res;
 | |
| 	struct PktData *pkt = priv;
 | |
| 
 | |
| 	/* PKT_CONTEXT means: whatever there is */
 | |
| 	if (pkt->type == PKT_CONTEXT)
 | |
| 		return pullf_read(src, len, data_p);
 | |
| 
 | |
| 	if (pkt->len == 0)
 | |
| 	{
 | |
| 		/* this was last chunk in stream */
 | |
| 		if (pkt->type == PKT_NORMAL)
 | |
| 			return 0;
 | |
| 
 | |
| 		/* next chunk in stream */
 | |
| 		res = parse_new_len(src, &pkt->len);
 | |
| 		if (res < 0)
 | |
| 			return res;
 | |
| 		pkt->type = res;
 | |
| 	}
 | |
| 
 | |
| 	if (len > pkt->len)
 | |
| 		len = pkt->len;
 | |
| 
 | |
| 	res = pullf_read(src, len, data_p);
 | |
| 	if (res > 0)
 | |
| 		pkt->len -= res;
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static void
 | |
| pktreader_free(void *priv)
 | |
| {
 | |
| 	struct PktData *pkt = priv;
 | |
| 
 | |
| 	memset(pkt, 0, sizeof(*pkt));
 | |
| 	px_free(pkt);
 | |
| }
 | |
| 
 | |
| static struct PullFilterOps pktreader_filter = {
 | |
| 	NULL, pktreader_pull, pktreader_free
 | |
| };
 | |
| 
 | |
| /* needs helper function to pass several parameters */
 | |
| int
 | |
| pgp_create_pkt_reader(PullFilter **pf_p, PullFilter *src, int len,
 | |
| 					  int pkttype, PGP_Context *ctx)
 | |
| {
 | |
| 	int			res;
 | |
| 	struct PktData *pkt = px_alloc(sizeof(*pkt));
 | |
| 
 | |
| 	pkt->type = pkttype;
 | |
| 	pkt->len = len;
 | |
| 	res = pullf_create(pf_p, &pktreader_filter, pkt, src);
 | |
| 	if (res < 0)
 | |
| 		px_free(pkt);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Prefix check filter
 | |
|  */
 | |
| 
 | |
| static int
 | |
| prefix_init(void **priv_p, void *arg, PullFilter *src)
 | |
| {
 | |
| 	PGP_Context *ctx = arg;
 | |
| 	int			len;
 | |
| 	int			res;
 | |
| 	uint8	   *buf;
 | |
| 	uint8		tmpbuf[PGP_MAX_BLOCK + 2];
 | |
| 
 | |
| 	len = pgp_get_cipher_block_size(ctx->cipher_algo);
 | |
| 	if (len > sizeof(tmpbuf))
 | |
| 		return PXE_BUG;
 | |
| 
 | |
| 	res = pullf_read_max(src, len + 2, &buf, tmpbuf);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 	if (res != len + 2)
 | |
| 	{
 | |
| 		px_debug("prefix_init: short read");
 | |
| 		memset(tmpbuf, 0, sizeof(tmpbuf));
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 
 | |
| 	if (buf[len - 2] != buf[len] || buf[len - 1] != buf[len + 1])
 | |
| 	{
 | |
| 		px_debug("prefix_init: corrupt prefix");
 | |
| 
 | |
| 		/*
 | |
| 		 * The original purpose of the 2-byte check was to show user a
 | |
| 		 * friendly "wrong key" message. This made following possible:
 | |
| 		 *
 | |
| 		 * "An Attack on CFB Mode Encryption As Used By OpenPGP" by Serge
 | |
| 		 * Mister and Robert Zuccherato
 | |
| 		 *
 | |
| 		 * To avoid being 'oracle', we delay reporting, which basically means
 | |
| 		 * we prefer to run into corrupt packet header.
 | |
| 		 *
 | |
| 		 * We _could_ throw PXE_PGP_CORRUPT_DATA here, but there is
 | |
| 		 * possibility of attack via timing, so we don't.
 | |
| 		 */
 | |
| 		ctx->corrupt_prefix = 1;
 | |
| 	}
 | |
| 	memset(tmpbuf, 0, sizeof(tmpbuf));
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct PullFilterOps prefix_filter = {
 | |
| 	prefix_init, NULL, NULL
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Decrypt filter
 | |
|  */
 | |
| 
 | |
| static int
 | |
| decrypt_init(void **priv_p, void *arg, PullFilter *src)
 | |
| {
 | |
| 	PGP_CFB    *cfb = arg;
 | |
| 
 | |
| 	*priv_p = cfb;
 | |
| 
 | |
| 	/* we need to write somewhere, so ask for a buffer */
 | |
| 	return 4096;
 | |
| }
 | |
| 
 | |
| static int
 | |
| decrypt_read(void *priv, PullFilter *src, int len,
 | |
| 			 uint8 **data_p, uint8 *buf, int buflen)
 | |
| {
 | |
| 	PGP_CFB    *cfb = priv;
 | |
| 	uint8	   *tmp;
 | |
| 	int			res;
 | |
| 
 | |
| 	res = pullf_read(src, len, &tmp);
 | |
| 	if (res > 0)
 | |
| 	{
 | |
| 		pgp_cfb_decrypt(cfb, tmp, res, buf);
 | |
| 		*data_p = buf;
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| struct PullFilterOps pgp_decrypt_filter = {
 | |
| 	decrypt_init, decrypt_read, NULL
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * MDC hasher filter
 | |
|  */
 | |
| 
 | |
| static int
 | |
| mdc_init(void **priv_p, void *arg, PullFilter *src)
 | |
| {
 | |
| 	PGP_Context *ctx = arg;
 | |
| 
 | |
| 	*priv_p = ctx;
 | |
| 	return pgp_load_digest(PGP_DIGEST_SHA1, &ctx->mdc_ctx);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mdc_free(void *priv)
 | |
| {
 | |
| 	PGP_Context *ctx = priv;
 | |
| 
 | |
| 	if (ctx->use_mdcbuf_filter)
 | |
| 		return;
 | |
| 	px_md_free(ctx->mdc_ctx);
 | |
| 	ctx->mdc_ctx = NULL;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mdc_finish(PGP_Context *ctx, PullFilter *src,
 | |
| 		   int len, uint8 **data_p)
 | |
| {
 | |
| 	int			res;
 | |
| 	uint8		hash[20];
 | |
| 	uint8		tmpbuf[22];
 | |
| 
 | |
| 	if (len + 1 > sizeof(tmpbuf))
 | |
| 		return PXE_BUG;
 | |
| 
 | |
| 	/* read data */
 | |
| 	res = pullf_read_max(src, len + 1, data_p, tmpbuf);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 	if (res == 0)
 | |
| 	{
 | |
| 		if (ctx->mdc_checked == 0)
 | |
| 		{
 | |
| 			px_debug("no mdc");
 | |
| 			return PXE_PGP_CORRUPT_DATA;
 | |
| 		}
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/* safety check */
 | |
| 	if (ctx->in_mdc_pkt > 1)
 | |
| 	{
 | |
| 		px_debug("mdc_finish: several times here?");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	ctx->in_mdc_pkt++;
 | |
| 
 | |
| 	/* is the packet sane? */
 | |
| 	if (res != 20)
 | |
| 	{
 | |
| 		px_debug("mdc_finish: read failed, res=%d", res);
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * ok, we got the hash, now check
 | |
| 	 */
 | |
| 	px_md_finish(ctx->mdc_ctx, hash);
 | |
| 	res = memcmp(hash, *data_p, 20);
 | |
| 	memset(hash, 0, 20);
 | |
| 	memset(tmpbuf, 0, sizeof(tmpbuf));
 | |
| 	if (res != 0)
 | |
| 	{
 | |
| 		px_debug("mdc_finish: mdc failed");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	ctx->mdc_checked = 1;
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mdc_read(void *priv, PullFilter *src, int len,
 | |
| 		 uint8 **data_p, uint8 *buf, int buflen)
 | |
| {
 | |
| 	int			res;
 | |
| 	PGP_Context *ctx = priv;
 | |
| 
 | |
| 	/* skip this filter? */
 | |
| 	if (ctx->use_mdcbuf_filter)
 | |
| 		return pullf_read(src, len, data_p);
 | |
| 
 | |
| 	if (ctx->in_mdc_pkt)
 | |
| 		return mdc_finish(ctx, src, len, data_p);
 | |
| 
 | |
| 	res = pullf_read(src, len, data_p);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 	if (res == 0)
 | |
| 	{
 | |
| 		px_debug("mdc_read: unexpected eof");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	px_md_update(ctx->mdc_ctx, *data_p, res);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static struct PullFilterOps mdc_filter = {
 | |
| 	mdc_init, mdc_read, mdc_free
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Combined Pkt reader and MDC hasher.
 | |
|  *
 | |
|  * For the case of SYMENCRYPTED_MDC packet, where
 | |
|  * the data part has 'context length', which means
 | |
|  * that data packet ends 22 bytes before end of parent
 | |
|  * packet, which is silly.
 | |
|  */
 | |
| #define MDCBUF_LEN 8192
 | |
| struct MDCBufData
 | |
| {
 | |
| 	PGP_Context *ctx;
 | |
| 	int			eof;
 | |
| 	int			buflen;
 | |
| 	int			avail;
 | |
| 	uint8	   *pos;
 | |
| 	int			mdc_avail;
 | |
| 	uint8		mdc_buf[22];
 | |
| 	uint8		buf[MDCBUF_LEN];
 | |
| };
 | |
| 
 | |
| static int
 | |
| mdcbuf_init(void **priv_p, void *arg, PullFilter *src)
 | |
| {
 | |
| 	PGP_Context *ctx = arg;
 | |
| 	struct MDCBufData *st;
 | |
| 
 | |
| 	st = px_alloc(sizeof(*st));
 | |
| 	memset(st, 0, sizeof(*st));
 | |
| 	st->buflen = sizeof(st->buf);
 | |
| 	st->ctx = ctx;
 | |
| 	*priv_p = st;
 | |
| 
 | |
| 	/* take over the work of mdc_filter */
 | |
| 	ctx->use_mdcbuf_filter = 1;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mdcbuf_finish(struct MDCBufData * st)
 | |
| {
 | |
| 	uint8		hash[20];
 | |
| 	int			res;
 | |
| 
 | |
| 	st->eof = 1;
 | |
| 
 | |
| 	if (st->mdc_buf[0] != 0xD3 || st->mdc_buf[1] != 0x14)
 | |
| 	{
 | |
| 		px_debug("mdcbuf_finish: bad MDC pkt hdr");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	px_md_update(st->ctx->mdc_ctx, st->mdc_buf, 2);
 | |
| 	px_md_finish(st->ctx->mdc_ctx, hash);
 | |
| 	res = memcmp(hash, st->mdc_buf + 2, 20);
 | |
| 	memset(hash, 0, 20);
 | |
| 	if (res)
 | |
| 	{
 | |
| 		px_debug("mdcbuf_finish: MDC does not match");
 | |
| 		res = PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mdcbuf_load_data(struct MDCBufData * st, uint8 *src, int len)
 | |
| {
 | |
| 	uint8	   *dst = st->pos + st->avail;
 | |
| 
 | |
| 	memcpy(dst, src, len);
 | |
| 	px_md_update(st->ctx->mdc_ctx, src, len);
 | |
| 	st->avail += len;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mdcbuf_load_mdc(struct MDCBufData * st, uint8 *src, int len)
 | |
| {
 | |
| 	memmove(st->mdc_buf + st->mdc_avail, src, len);
 | |
| 	st->mdc_avail += len;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mdcbuf_refill(struct MDCBufData * st, PullFilter *src)
 | |
| {
 | |
| 	uint8	   *data;
 | |
| 	int			res;
 | |
| 	int			need;
 | |
| 
 | |
| 	/* put avail data in start */
 | |
| 	if (st->avail > 0 && st->pos != st->buf)
 | |
| 		memmove(st->buf, st->pos, st->avail);
 | |
| 	st->pos = st->buf;
 | |
| 
 | |
| 	/* read new data */
 | |
| 	need = st->buflen + 22 - st->avail - st->mdc_avail;
 | |
| 	res = pullf_read(src, need, &data);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 	if (res == 0)
 | |
| 		return mdcbuf_finish(st);
 | |
| 
 | |
| 	/* add to buffer */
 | |
| 	if (res >= 22)
 | |
| 	{
 | |
| 		mdcbuf_load_data(st, st->mdc_buf, st->mdc_avail);
 | |
| 		st->mdc_avail = 0;
 | |
| 
 | |
| 		mdcbuf_load_data(st, data, res - 22);
 | |
| 		mdcbuf_load_mdc(st, data + res - 22, 22);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		int			canmove = st->mdc_avail + res - 22;
 | |
| 
 | |
| 		if (canmove > 0)
 | |
| 		{
 | |
| 			mdcbuf_load_data(st, st->mdc_buf, canmove);
 | |
| 			st->mdc_avail -= canmove;
 | |
| 			memmove(st->mdc_buf, st->mdc_buf + canmove, st->mdc_avail);
 | |
| 		}
 | |
| 		mdcbuf_load_mdc(st, data, res);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mdcbuf_read(void *priv, PullFilter *src, int len,
 | |
| 			uint8 **data_p, uint8 *buf, int buflen)
 | |
| {
 | |
| 	struct MDCBufData *st = priv;
 | |
| 	int			res;
 | |
| 
 | |
| 	if (!st->eof && len > st->avail)
 | |
| 	{
 | |
| 		res = mdcbuf_refill(st, src);
 | |
| 		if (res < 0)
 | |
| 			return res;
 | |
| 	}
 | |
| 
 | |
| 	if (len > st->avail)
 | |
| 		len = st->avail;
 | |
| 
 | |
| 	*data_p = st->pos;
 | |
| 	st->pos += len;
 | |
| 	st->avail -= len;
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mdcbuf_free(void *priv)
 | |
| {
 | |
| 	struct MDCBufData *st = priv;
 | |
| 
 | |
| 	px_md_free(st->ctx->mdc_ctx);
 | |
| 	st->ctx->mdc_ctx = NULL;
 | |
| 	memset(st, 0, sizeof(*st));
 | |
| 	px_free(st);
 | |
| }
 | |
| 
 | |
| static struct PullFilterOps mdcbuf_filter = {
 | |
| 	mdcbuf_init, mdcbuf_read, mdcbuf_free
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Decrypt separate session key
 | |
|  */
 | |
| static int
 | |
| decrypt_key(PGP_Context *ctx, const uint8 *src, int len)
 | |
| {
 | |
| 	int			res;
 | |
| 	uint8		algo;
 | |
| 	PGP_CFB    *cfb;
 | |
| 
 | |
| 	res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo,
 | |
| 						 ctx->s2k.key, ctx->s2k.key_len, 0, NULL);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 
 | |
| 	pgp_cfb_decrypt(cfb, src, 1, &algo);
 | |
| 	src++;
 | |
| 	len--;
 | |
| 
 | |
| 	pgp_cfb_decrypt(cfb, src, len, ctx->sess_key);
 | |
| 	pgp_cfb_free(cfb);
 | |
| 	ctx->sess_key_len = len;
 | |
| 	ctx->cipher_algo = algo;
 | |
| 
 | |
| 	if (pgp_get_cipher_key_size(algo) != len)
 | |
| 	{
 | |
| 		px_debug("sesskey bad len: algo=%d, expected=%d, got=%d",
 | |
| 				 algo, pgp_get_cipher_key_size(algo), len);
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Handle key packet
 | |
|  */
 | |
| static int
 | |
| parse_symenc_sesskey(PGP_Context *ctx, PullFilter *src)
 | |
| {
 | |
| 	uint8	   *p;
 | |
| 	int			res;
 | |
| 	uint8		tmpbuf[PGP_MAX_KEY + 2];
 | |
| 	uint8		ver;
 | |
| 
 | |
| 	GETBYTE(src, ver);
 | |
| 	GETBYTE(src, ctx->s2k_cipher_algo);
 | |
| 	if (ver != 4)
 | |
| 	{
 | |
| 		px_debug("bad key pkt ver");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * read S2K info
 | |
| 	 */
 | |
| 	res = pgp_s2k_read(src, &ctx->s2k);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 	ctx->s2k_mode = ctx->s2k.mode;
 | |
| 	ctx->s2k_digest_algo = ctx->s2k.digest_algo;
 | |
| 
 | |
| 	/*
 | |
| 	 * generate key from password
 | |
| 	 */
 | |
| 	res = pgp_s2k_process(&ctx->s2k, ctx->s2k_cipher_algo,
 | |
| 						  ctx->sym_key, ctx->sym_key_len);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 
 | |
| 	/*
 | |
| 	 * do we have separate session key?
 | |
| 	 */
 | |
| 	res = pullf_read_max(src, PGP_MAX_KEY + 2, &p, tmpbuf);
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 
 | |
| 	if (res == 0)
 | |
| 	{
 | |
| 		/*
 | |
| 		 * no, s2k key is session key
 | |
| 		 */
 | |
| 		memcpy(ctx->sess_key, ctx->s2k.key, ctx->s2k.key_len);
 | |
| 		ctx->sess_key_len = ctx->s2k.key_len;
 | |
| 		ctx->cipher_algo = ctx->s2k_cipher_algo;
 | |
| 		res = 0;
 | |
| 		ctx->use_sess_key = 0;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		/*
 | |
| 		 * yes, decrypt it
 | |
| 		 */
 | |
| 		if (res < 17 || res > PGP_MAX_KEY + 1)
 | |
| 		{
 | |
| 			px_debug("expect key, but bad data");
 | |
| 			return PXE_PGP_CORRUPT_DATA;
 | |
| 		}
 | |
| 		ctx->use_sess_key = 1;
 | |
| 		res = decrypt_key(ctx, p, res);
 | |
| 	}
 | |
| 
 | |
| 	memset(tmpbuf, 0, sizeof(tmpbuf));
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int
 | |
| copy_crlf(MBuf *dst, uint8 *data, int len, int *got_cr)
 | |
| {
 | |
| 	uint8	   *data_end = data + len;
 | |
| 	uint8		tmpbuf[1024];
 | |
| 	uint8	   *tmp_end = tmpbuf + sizeof(tmpbuf);
 | |
| 	uint8	   *p;
 | |
| 	int			res;
 | |
| 
 | |
| 	p = tmpbuf;
 | |
| 	if (*got_cr)
 | |
| 	{
 | |
| 		if (*data != '\n')
 | |
| 			*p++ = '\r';
 | |
| 		*got_cr = 0;
 | |
| 	}
 | |
| 	while (data < data_end)
 | |
| 	{
 | |
| 		if (*data == '\r')
 | |
| 		{
 | |
| 			if (data + 1 < data_end)
 | |
| 			{
 | |
| 				if (*(data + 1) == '\n')
 | |
| 					data++;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				*got_cr = 1;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		*p++ = *data++;
 | |
| 		if (p >= tmp_end)
 | |
| 		{
 | |
| 			res = mbuf_append(dst, tmpbuf, p - tmpbuf);
 | |
| 			if (res < 0)
 | |
| 				return res;
 | |
| 			p = tmpbuf;
 | |
| 		}
 | |
| 	}
 | |
| 	if (p - tmpbuf > 0)
 | |
| 	{
 | |
| 		res = mbuf_append(dst, tmpbuf, p - tmpbuf);
 | |
| 		if (res < 0)
 | |
| 			return res;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| parse_literal_data(PGP_Context *ctx, MBuf *dst, PullFilter *pkt)
 | |
| {
 | |
| 	int			type;
 | |
| 	int			name_len;
 | |
| 	int			res;
 | |
| 	uint8	   *buf;
 | |
| 	uint8		tmpbuf[4];
 | |
| 	int			got_cr = 0;
 | |
| 
 | |
| 	GETBYTE(pkt, type);
 | |
| 	GETBYTE(pkt, name_len);
 | |
| 
 | |
| 	/* skip name */
 | |
| 	while (name_len > 0)
 | |
| 	{
 | |
| 		res = pullf_read(pkt, name_len, &buf);
 | |
| 		if (res < 0)
 | |
| 			return res;
 | |
| 		if (res == 0)
 | |
| 			break;
 | |
| 		name_len -= res;
 | |
| 	}
 | |
| 	if (name_len > 0)
 | |
| 	{
 | |
| 		px_debug("parse_literal_data: unexpected eof");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 
 | |
| 	/* skip date */
 | |
| 	res = pullf_read_max(pkt, 4, &buf, tmpbuf);
 | |
| 	if (res != 4)
 | |
| 	{
 | |
| 		px_debug("parse_literal_data: unexpected eof");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	memset(tmpbuf, 0, 4);
 | |
| 
 | |
| 	/* check if text */
 | |
| 	if (ctx->text_mode)
 | |
| 		if (type != 't' && type != 'u')
 | |
| 		{
 | |
| 			px_debug("parse_literal_data: data type=%c", type);
 | |
| 			return PXE_PGP_NOT_TEXT;
 | |
| 		}
 | |
| 
 | |
| 	ctx->unicode_mode = (type == 'u') ? 1 : 0;
 | |
| 
 | |
| 	/* read data */
 | |
| 	while (1)
 | |
| 	{
 | |
| 		res = pullf_read(pkt, 32 * 1024, &buf);
 | |
| 		if (res <= 0)
 | |
| 			break;
 | |
| 
 | |
| 		if (ctx->text_mode && ctx->convert_crlf)
 | |
| 			res = copy_crlf(dst, buf, res, &got_cr);
 | |
| 		else
 | |
| 			res = mbuf_append(dst, buf, res);
 | |
| 		if (res < 0)
 | |
| 			break;
 | |
| 	}
 | |
| 	if (res >= 0 && got_cr)
 | |
| 		res = mbuf_append(dst, (const uint8 *) "\r", 1);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /* process_data_packets and parse_compressed_data call each other */
 | |
| static int process_data_packets(PGP_Context *ctx, MBuf *dst,
 | |
| 					 PullFilter *src, int allow_compr, int need_mdc);
 | |
| 
 | |
| static int
 | |
| parse_compressed_data(PGP_Context *ctx, MBuf *dst, PullFilter *pkt)
 | |
| {
 | |
| 	int			res;
 | |
| 	uint8		type;
 | |
| 	PullFilter *pf_decompr;
 | |
| 
 | |
| 	GETBYTE(pkt, type);
 | |
| 
 | |
| 	ctx->compress_algo = type;
 | |
| 	switch (type)
 | |
| 	{
 | |
| 		case PGP_COMPR_NONE:
 | |
| 			res = process_data_packets(ctx, dst, pkt, NO_COMPR, NO_MDC);
 | |
| 			break;
 | |
| 
 | |
| 		case PGP_COMPR_ZIP:
 | |
| 		case PGP_COMPR_ZLIB:
 | |
| 			res = pgp_decompress_filter(&pf_decompr, ctx, pkt);
 | |
| 			if (res >= 0)
 | |
| 			{
 | |
| 				res = process_data_packets(ctx, dst, pf_decompr,
 | |
| 										   NO_COMPR, NO_MDC);
 | |
| 				pullf_free(pf_decompr);
 | |
| 			}
 | |
| 			break;
 | |
| 
 | |
| 		case PGP_COMPR_BZIP2:
 | |
| 			px_debug("parse_compressed_data: bzip2 unsupported");
 | |
| 			res = PXE_PGP_UNSUPPORTED_COMPR;
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
| 			px_debug("parse_compressed_data: unknown compr type");
 | |
| 			res = PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int
 | |
| process_data_packets(PGP_Context *ctx, MBuf *dst, PullFilter *src,
 | |
| 					 int allow_compr, int need_mdc)
 | |
| {
 | |
| 	uint8		tag;
 | |
| 	int			len,
 | |
| 				res;
 | |
| 	int			got_data = 0;
 | |
| 	int			got_mdc = 0;
 | |
| 	PullFilter *pkt = NULL;
 | |
| 	uint8	   *tmp;
 | |
| 
 | |
| 	while (1)
 | |
| 	{
 | |
| 		res = pgp_parse_pkt_hdr(src, &tag, &len, ALLOW_CTX_SIZE);
 | |
| 		if (res <= 0)
 | |
| 			break;
 | |
| 
 | |
| 
 | |
| 		/* mdc packet should be last */
 | |
| 		if (got_mdc)
 | |
| 		{
 | |
| 			px_debug("process_data_packets: data after mdc");
 | |
| 			res = PXE_PGP_CORRUPT_DATA;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		/* context length inside SYMENC_MDC needs special handling */
 | |
| 		if (need_mdc && res == PKT_CONTEXT)
 | |
| 			res = pullf_create(&pkt, &mdcbuf_filter, ctx, src);
 | |
| 		else
 | |
| 			res = pgp_create_pkt_reader(&pkt, src, len, res, ctx);
 | |
| 		if (res < 0)
 | |
| 			break;
 | |
| 
 | |
| 		switch (tag)
 | |
| 		{
 | |
| 			case PGP_PKT_LITERAL_DATA:
 | |
| 				got_data = 1;
 | |
| 				res = parse_literal_data(ctx, dst, pkt);
 | |
| 				break;
 | |
| 			case PGP_PKT_COMPRESSED_DATA:
 | |
| 				if (allow_compr == 0)
 | |
| 				{
 | |
| 					px_debug("process_data_packets: unexpected compression");
 | |
| 					res = PXE_PGP_CORRUPT_DATA;
 | |
| 				}
 | |
| 				else if (got_data)
 | |
| 				{
 | |
| 					/*
 | |
| 					 * compr data must be alone
 | |
| 					 */
 | |
| 					px_debug("process_data_packets: only one cmpr pkt allowed");
 | |
| 					res = PXE_PGP_CORRUPT_DATA;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					got_data = 1;
 | |
| 					res = parse_compressed_data(ctx, dst, pkt);
 | |
| 				}
 | |
| 				break;
 | |
| 			case PGP_PKT_MDC:
 | |
| 				if (need_mdc == NO_MDC)
 | |
| 				{
 | |
| 					px_debug("process_data_packets: unexpected MDC");
 | |
| 					res = PXE_PGP_CORRUPT_DATA;
 | |
| 					break;
 | |
| 				}
 | |
| 
 | |
| 				/* notify mdc_filter */
 | |
| 				ctx->in_mdc_pkt = 1;
 | |
| 
 | |
| 				res = pullf_read(pkt, 8192, &tmp);
 | |
| 				if (res > 0)
 | |
| 					got_mdc = 1;
 | |
| 				break;
 | |
| 			default:
 | |
| 				px_debug("process_data_packets: unexpected pkt tag=%d", tag);
 | |
| 				res = PXE_PGP_CORRUPT_DATA;
 | |
| 		}
 | |
| 
 | |
| 		pullf_free(pkt);
 | |
| 		pkt = NULL;
 | |
| 
 | |
| 		if (res < 0)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	if (pkt)
 | |
| 		pullf_free(pkt);
 | |
| 
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 
 | |
| 	if (!got_data)
 | |
| 	{
 | |
| 		px_debug("process_data_packets: no data");
 | |
| 		res = PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	if (need_mdc && !got_mdc && !ctx->use_mdcbuf_filter)
 | |
| 	{
 | |
| 		px_debug("process_data_packets: got no mdc");
 | |
| 		res = PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int
 | |
| parse_symenc_data(PGP_Context *ctx, PullFilter *pkt, MBuf *dst)
 | |
| {
 | |
| 	int			res;
 | |
| 	PGP_CFB    *cfb = NULL;
 | |
| 	PullFilter *pf_decrypt = NULL;
 | |
| 	PullFilter *pf_prefix = NULL;
 | |
| 
 | |
| 	res = pgp_cfb_create(&cfb, ctx->cipher_algo,
 | |
| 						 ctx->sess_key, ctx->sess_key_len, 1, NULL);
 | |
| 	if (res < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	res = pullf_create(&pf_decrypt, &pgp_decrypt_filter, cfb, pkt);
 | |
| 	if (res < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	res = pullf_create(&pf_prefix, &prefix_filter, ctx, pf_decrypt);
 | |
| 	if (res < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	res = process_data_packets(ctx, dst, pf_prefix, ALLOW_COMPR, NO_MDC);
 | |
| 
 | |
| out:
 | |
| 	if (pf_prefix)
 | |
| 		pullf_free(pf_prefix);
 | |
| 	if (pf_decrypt)
 | |
| 		pullf_free(pf_decrypt);
 | |
| 	if (cfb)
 | |
| 		pgp_cfb_free(cfb);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int
 | |
| parse_symenc_mdc_data(PGP_Context *ctx, PullFilter *pkt, MBuf *dst)
 | |
| {
 | |
| 	int			res;
 | |
| 	PGP_CFB    *cfb = NULL;
 | |
| 	PullFilter *pf_decrypt = NULL;
 | |
| 	PullFilter *pf_prefix = NULL;
 | |
| 	PullFilter *pf_mdc = NULL;
 | |
| 	uint8		ver;
 | |
| 
 | |
| 	GETBYTE(pkt, ver);
 | |
| 	if (ver != 1)
 | |
| 	{
 | |
| 		px_debug("parse_symenc_mdc_data: pkt ver != 1");
 | |
| 		return PXE_PGP_CORRUPT_DATA;
 | |
| 	}
 | |
| 
 | |
| 	res = pgp_cfb_create(&cfb, ctx->cipher_algo,
 | |
| 						 ctx->sess_key, ctx->sess_key_len, 0, NULL);
 | |
| 	if (res < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	res = pullf_create(&pf_decrypt, &pgp_decrypt_filter, cfb, pkt);
 | |
| 	if (res < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	res = pullf_create(&pf_mdc, &mdc_filter, ctx, pf_decrypt);
 | |
| 	if (res < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	res = pullf_create(&pf_prefix, &prefix_filter, ctx, pf_mdc);
 | |
| 	if (res < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	res = process_data_packets(ctx, dst, pf_prefix, ALLOW_COMPR, NEED_MDC);
 | |
| 
 | |
| out:
 | |
| 	if (pf_prefix)
 | |
| 		pullf_free(pf_prefix);
 | |
| 	if (pf_mdc)
 | |
| 		pullf_free(pf_mdc);
 | |
| 	if (pf_decrypt)
 | |
| 		pullf_free(pf_decrypt);
 | |
| 	if (cfb)
 | |
| 		pgp_cfb_free(cfb);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * skip over packet contents
 | |
|  */
 | |
| int
 | |
| pgp_skip_packet(PullFilter *pkt)
 | |
| {
 | |
| 	int			res = 1;
 | |
| 	uint8	   *tmp;
 | |
| 
 | |
| 	while (res > 0)
 | |
| 		res = pullf_read(pkt, 32 * 1024, &tmp);
 | |
| 	return res < 0 ? res : 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * expect to be at packet end, any data is error
 | |
|  */
 | |
| int
 | |
| pgp_expect_packet_end(PullFilter *pkt)
 | |
| {
 | |
| 	int			res = 1;
 | |
| 	uint8	   *tmp;
 | |
| 
 | |
| 	while (res > 0)
 | |
| 	{
 | |
| 		res = pullf_read(pkt, 32 * 1024, &tmp);
 | |
| 		if (res > 0)
 | |
| 		{
 | |
| 			px_debug("pgp_expect_packet_end: got data");
 | |
| 			return PXE_PGP_CORRUPT_DATA;
 | |
| 		}
 | |
| 	}
 | |
| 	return res < 0 ? res : 0;
 | |
| }
 | |
| 
 | |
| int
 | |
| pgp_decrypt(PGP_Context *ctx, MBuf *msrc, MBuf *mdst)
 | |
| {
 | |
| 	int			res;
 | |
| 	PullFilter *src = NULL;
 | |
| 	PullFilter *pkt = NULL;
 | |
| 	uint8		tag;
 | |
| 	int			len;
 | |
| 	int			got_key = 0;
 | |
| 	int			got_data = 0;
 | |
| 
 | |
| 	res = pullf_create_mbuf_reader(&src, msrc);
 | |
| 
 | |
| 	while (res >= 0)
 | |
| 	{
 | |
| 		res = pgp_parse_pkt_hdr(src, &tag, &len, NO_CTX_SIZE);
 | |
| 		if (res <= 0)
 | |
| 			break;
 | |
| 
 | |
| 		res = pgp_create_pkt_reader(&pkt, src, len, res, ctx);
 | |
| 		if (res < 0)
 | |
| 			break;
 | |
| 
 | |
| 		res = PXE_PGP_CORRUPT_DATA;
 | |
| 		switch (tag)
 | |
| 		{
 | |
| 			case PGP_PKT_MARKER:
 | |
| 				res = pgp_skip_packet(pkt);
 | |
| 				break;
 | |
| 			case PGP_PKT_PUBENCRYPTED_SESSKEY:
 | |
| 				/* fixme: skip those */
 | |
| 				res = pgp_parse_pubenc_sesskey(ctx, pkt);
 | |
| 				got_key = 1;
 | |
| 				break;
 | |
| 			case PGP_PKT_SYMENCRYPTED_SESSKEY:
 | |
| 				if (got_key)
 | |
| 
 | |
| 					/*
 | |
| 					 * Theoretically, there could be several keys, both public
 | |
| 					 * and symmetric, all of which encrypt same session key.
 | |
| 					 * Decrypt should try with each one, before failing.
 | |
| 					 */
 | |
| 					px_debug("pgp_decrypt: using first of several keys");
 | |
| 				else
 | |
| 				{
 | |
| 					got_key = 1;
 | |
| 					res = parse_symenc_sesskey(ctx, pkt);
 | |
| 				}
 | |
| 				break;
 | |
| 			case PGP_PKT_SYMENCRYPTED_DATA:
 | |
| 				if (!got_key)
 | |
| 					px_debug("pgp_decrypt: have data but no key");
 | |
| 				else if (got_data)
 | |
| 					px_debug("pgp_decrypt: got second data packet");
 | |
| 				else
 | |
| 				{
 | |
| 					got_data = 1;
 | |
| 					ctx->disable_mdc = 1;
 | |
| 					res = parse_symenc_data(ctx, pkt, mdst);
 | |
| 				}
 | |
| 				break;
 | |
| 			case PGP_PKT_SYMENCRYPTED_DATA_MDC:
 | |
| 				if (!got_key)
 | |
| 					px_debug("pgp_decrypt: have data but no key");
 | |
| 				else if (got_data)
 | |
| 					px_debug("pgp_decrypt: several data pkts not supported");
 | |
| 				else
 | |
| 				{
 | |
| 					got_data = 1;
 | |
| 					ctx->disable_mdc = 0;
 | |
| 					res = parse_symenc_mdc_data(ctx, pkt, mdst);
 | |
| 				}
 | |
| 				break;
 | |
| 			default:
 | |
| 				px_debug("pgp_decrypt: unknown tag: 0x%02x", tag);
 | |
| 		}
 | |
| 		pullf_free(pkt);
 | |
| 		pkt = NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (pkt)
 | |
| 		pullf_free(pkt);
 | |
| 
 | |
| 	if (src)
 | |
| 		pullf_free(src);
 | |
| 
 | |
| 	if (res < 0)
 | |
| 		return res;
 | |
| 
 | |
| 	if (!got_data || ctx->corrupt_prefix)
 | |
| 		res = PXE_PGP_CORRUPT_DATA;
 | |
| 
 | |
| 	return res;
 | |
| }
 |