mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-23 21:27:20 +02:00
comment line where output as too long, and update typedefs for /lib directory. Also fix case where identifiers were used as variable names in the backend, but as typedefs in ecpg (favor the backend for indenting). Backpatch to 8.1.X.
1189 lines
23 KiB
C
1189 lines
23 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.7 2005/11/22 18:17:04 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;
|
|
}
|