diff options
Diffstat (limited to 'contrib/pgcrypto/pgp-decrypt.c')
-rw-r--r-- | contrib/pgcrypto/pgp-decrypt.c | 1156 |
1 files changed, 1156 insertions, 0 deletions
diff --git a/contrib/pgcrypto/pgp-decrypt.c b/contrib/pgcrypto/pgp-decrypt.c new file mode 100644 index 0000000000..edffc6a344 --- /dev/null +++ b/contrib/pgcrypto/pgp-decrypt.c @@ -0,0 +1,1156 @@ +/* + * 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.1 2005/07/10 13:46:28 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; +} + +// fixme: clarify +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; + } + + if (ctx->in_mdc_pkt > 1) + { + px_debug("mdc_finish: several times here?"); + return PXE_PGP_CORRUPT_DATA; + } + ctx->in_mdc_pkt++; + + 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, "\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; +} + |