mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-21 08:29:39 +08:00
e3860ffa4d
The new indent version includes numerous fixes thanks to Piotr Stefaniak. The main changes visible in this commit are: * Nicer formatting of function-pointer declarations. * No longer unexpectedly removes spaces in expressions using casts, sizeof, or offsetof. * No longer wants to add a space in "struct structname *varname", as well as some similar cases for const- or volatile-qualified pointers. * Declarations using PG_USED_FOR_ASSERTS_ONLY are formatted more nicely. * Fixes bug where comments following declarations were sometimes placed with no space separating them from the code. * Fixes some odd decisions for comments following case labels. * Fixes some cases where comments following code were indented to less than the expected column 33. On the less good side, it now tends to put more whitespace around typedef names that are not listed in typedefs.list. This might encourage us to put more effort into typedef name collection; it's not really a bug in indent itself. There are more changes coming after this round, having to do with comment indentation and alignment of lines appearing within parentheses. I wanted to limit the size of the diffs to something that could be reviewed without one's eyes completely glazing over, so it seemed better to split up the changes as much as practical. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
1211 lines
24 KiB
C
1211 lines
24 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.
|
|
*
|
|
* contrib/pgcrypto/pgp-decrypt.c
|
|
*/
|
|
|
|
#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);
|
|
|
|
while (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;
|
|
|
|
px_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
|
|
* https://tools.ietf.org/html/rfc4880#section-5.7
|
|
* https://tools.ietf.org/html/rfc4880#section-5.13
|
|
*/
|
|
|
|
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");
|
|
px_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");
|
|
/* report error in pgp_decrypt() */
|
|
ctx->corrupt_prefix = 1;
|
|
}
|
|
px_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)
|
|
{
|
|
int res;
|
|
uint8 hash[20];
|
|
uint8 tmpbuf[20];
|
|
uint8 *data;
|
|
|
|
/* should not happen */
|
|
if (ctx->use_mdcbuf_filter)
|
|
return PXE_BUG;
|
|
|
|
/* It's SHA1 */
|
|
if (len != 20)
|
|
return PXE_PGP_CORRUPT_DATA;
|
|
|
|
/* mdc_read should not call md_update */
|
|
ctx->in_mdc_pkt = 1;
|
|
|
|
/* read data */
|
|
res = pullf_read_max(src, len, &data, tmpbuf);
|
|
if (res < 0)
|
|
return res;
|
|
if (res == 0)
|
|
{
|
|
px_debug("no mdc");
|
|
return PXE_PGP_CORRUPT_DATA;
|
|
}
|
|
|
|
/* 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, 20);
|
|
px_memset(hash, 0, 20);
|
|
px_memset(tmpbuf, 0, sizeof(tmpbuf));
|
|
if (res != 0)
|
|
{
|
|
px_debug("mdc_finish: mdc failed");
|
|
return PXE_PGP_CORRUPT_DATA;
|
|
}
|
|
ctx->mdc_checked = 1;
|
|
return 0;
|
|
}
|
|
|
|
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 || ctx->in_mdc_pkt)
|
|
return pullf_read(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);
|
|
px_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;
|
|
px_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_count = s2k_decode_count(ctx->s2k.iter);
|
|
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);
|
|
}
|
|
|
|
px_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;
|
|
}
|
|
px_memset(tmpbuf, 0, sizeof(tmpbuf));
|
|
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;
|
|
}
|
|
px_memset(tmpbuf, 0, 4);
|
|
|
|
/*
|
|
* If called from an SQL function that returns text, pgp_decrypt() rejects
|
|
* inputs not self-identifying as text.
|
|
*/
|
|
if (ctx->text_mode)
|
|
if (type != 't' && type != 'u')
|
|
{
|
|
px_debug("parse_literal_data: data type=%c", type);
|
|
ctx->unexpected_binary = true;
|
|
}
|
|
|
|
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;
|
|
uint8 *discard_buf;
|
|
|
|
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");
|
|
/* report error in pgp_decrypt() */
|
|
ctx->unsupported_compr = 1;
|
|
|
|
/*
|
|
* Discard the compressed data, allowing it to first affect any
|
|
* MDC digest computation.
|
|
*/
|
|
while (1)
|
|
{
|
|
res = pullf_read(pkt, 32 * 1024, &discard_buf);
|
|
if (res <= 0)
|
|
break;
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
res = mdc_finish(ctx, pkt, len);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* expect to be at packet end, any data is error
|
|
*/
|
|
int
|
|
pgp_expect_packet_end(PullFilter *pkt)
|
|
{
|
|
int res;
|
|
uint8 *tmp;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
/*
|
|
* Report a failure of the prefix_init() "quick check" now, rather than
|
|
* upon detection, to hinder timing attacks. pgcrypto is not generally
|
|
* secure against timing attacks, but this helps.
|
|
*/
|
|
if (!got_data || ctx->corrupt_prefix)
|
|
return PXE_PGP_CORRUPT_DATA;
|
|
|
|
/*
|
|
* Code interpreting purportedly-decrypted data prior to this stage shall
|
|
* report no error other than PXE_PGP_CORRUPT_DATA. (PXE_BUG is okay so
|
|
* long as it remains unreachable.) This ensures that an attacker able to
|
|
* choose a ciphertext and receive a corresponding decryption error
|
|
* message cannot use that oracle to gather clues about the decryption
|
|
* key. See "An Attack on CFB Mode Encryption As Used By OpenPGP" by
|
|
* Serge Mister and Robert Zuccherato.
|
|
*
|
|
* A problematic value in the first octet of a Literal Data or Compressed
|
|
* Data packet may indicate a simple user error, such as the need to call
|
|
* pgp_sym_decrypt_bytea instead of pgp_sym_decrypt. Occasionally,
|
|
* though, it is the first symptom of the encryption key not matching the
|
|
* decryption key. When this was the only problem encountered, report a
|
|
* specific error to guide the user; otherwise, we will have reported
|
|
* PXE_PGP_CORRUPT_DATA before now. A key mismatch makes the other errors
|
|
* into red herrings, and this avoids leaking clues to attackers.
|
|
*/
|
|
if (ctx->unsupported_compr)
|
|
return PXE_PGP_UNSUPPORTED_COMPR;
|
|
if (ctx->unexpected_binary)
|
|
return PXE_PGP_NOT_TEXT;
|
|
|
|
return res;
|
|
}
|