/* * pgp-encrypt.c * OpenPGP encrypt. * * 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-encrypt.c */ #include "postgres.h" #include #include "mbuf.h" #include "pgp.h" #include "px.h" #define MDC_DIGEST_LEN 20 #define STREAM_ID 0xE0 #define STREAM_BLOCK_SHIFT 14 static uint8 * render_newlen(uint8 *h, int len) { if (len <= 191) { *h++ = len & 255; } else if (len > 191 && len <= 8383) { *h++ = ((len - 192) >> 8) + 192; *h++ = (len - 192) & 255; } else { *h++ = 255; *h++ = (len >> 24) & 255; *h++ = (len >> 16) & 255; *h++ = (len >> 8) & 255; *h++ = len & 255; } return h; } static int write_tag_only(PushFilter *dst, int tag) { uint8 hdr = 0xC0 | tag; return pushf_write(dst, &hdr, 1); } static int write_normal_header(PushFilter *dst, int tag, int len) { uint8 hdr[8]; uint8 *h = hdr; *h++ = 0xC0 | tag; h = render_newlen(h, len); return pushf_write(dst, hdr, h - hdr); } /* * MAC writer */ static int mdc_init(PushFilter *dst, void *init_arg, void **priv_p) { int res; PX_MD *md; res = pgp_load_digest(PGP_DIGEST_SHA1, &md); if (res < 0) return res; *priv_p = md; return 0; } static int mdc_write(PushFilter *dst, void *priv, const uint8 *data, int len) { PX_MD *md = priv; px_md_update(md, data, len); return pushf_write(dst, data, len); } static int mdc_flush(PushFilter *dst, void *priv) { int res; uint8 pkt[2 + MDC_DIGEST_LEN]; PX_MD *md = priv; /* * create mdc pkt */ pkt[0] = 0xD3; pkt[1] = 0x14; /* MDC_DIGEST_LEN */ px_md_update(md, pkt, 2); px_md_finish(md, pkt + 2); res = pushf_write(dst, pkt, 2 + MDC_DIGEST_LEN); px_memset(pkt, 0, 2 + MDC_DIGEST_LEN); return res; } static void mdc_free(void *priv) { PX_MD *md = priv; px_md_free(md); } static const PushFilterOps mdc_filter = { mdc_init, mdc_write, mdc_flush, mdc_free }; /* * Encrypted pkt writer */ #define ENCBUF 8192 struct EncStat { PGP_CFB *ciph; uint8 buf[ENCBUF]; }; static int encrypt_init(PushFilter *next, void *init_arg, void **priv_p) { struct EncStat *st; PGP_Context *ctx = init_arg; PGP_CFB *ciph; int resync = 1; int res; /* should we use newer packet format? */ if (ctx->disable_mdc == 0) { uint8 ver = 1; resync = 0; res = pushf_write(next, &ver, 1); if (res < 0) return res; } res = pgp_cfb_create(&ciph, ctx->cipher_algo, ctx->sess_key, ctx->sess_key_len, resync, NULL); if (res < 0) return res; st = palloc0(sizeof(*st)); st->ciph = ciph; *priv_p = st; return ENCBUF; } static int encrypt_process(PushFilter *next, void *priv, const uint8 *data, int len) { int res; struct EncStat *st = priv; int avail = len; while (avail > 0) { int tmplen = avail > ENCBUF ? ENCBUF : avail; res = pgp_cfb_encrypt(st->ciph, data, tmplen, st->buf); if (res < 0) return res; res = pushf_write(next, st->buf, tmplen); if (res < 0) return res; data += tmplen; avail -= tmplen; } return 0; } static void encrypt_free(void *priv) { struct EncStat *st = priv; if (st->ciph) pgp_cfb_free(st->ciph); px_memset(st, 0, sizeof(*st)); pfree(st); } static const PushFilterOps encrypt_filter = { encrypt_init, encrypt_process, NULL, encrypt_free }; /* * Write Streamable pkts */ struct PktStreamStat { int final_done; int pkt_block; }; static int pkt_stream_init(PushFilter *next, void *init_arg, void **priv_p) { struct PktStreamStat *st; st = palloc(sizeof(*st)); st->final_done = 0; st->pkt_block = 1 << STREAM_BLOCK_SHIFT; *priv_p = st; return st->pkt_block; } static int pkt_stream_process(PushFilter *next, void *priv, const uint8 *data, int len) { int res; uint8 hdr[8]; uint8 *h = hdr; struct PktStreamStat *st = priv; if (st->final_done) return PXE_BUG; if (len == st->pkt_block) *h++ = STREAM_ID | STREAM_BLOCK_SHIFT; else { h = render_newlen(h, len); st->final_done = 1; } res = pushf_write(next, hdr, h - hdr); if (res < 0) return res; return pushf_write(next, data, len); } static int pkt_stream_flush(PushFilter *next, void *priv) { int res; uint8 hdr[8]; uint8 *h = hdr; struct PktStreamStat *st = priv; /* stream MUST end with normal packet. */ if (!st->final_done) { h = render_newlen(h, 0); res = pushf_write(next, hdr, h - hdr); if (res < 0) return res; st->final_done = 1; } return 0; } static void pkt_stream_free(void *priv) { struct PktStreamStat *st = priv; px_memset(st, 0, sizeof(*st)); pfree(st); } static const PushFilterOps pkt_stream_filter = { pkt_stream_init, pkt_stream_process, pkt_stream_flush, pkt_stream_free }; int pgp_create_pkt_writer(PushFilter *dst, int tag, PushFilter **res_p) { int res; res = write_tag_only(dst, tag); if (res < 0) return res; return pushf_create(res_p, &pkt_stream_filter, NULL, dst); } /* * Text conversion filter */ static int crlf_process(PushFilter *dst, void *priv, const uint8 *data, int len) { const uint8 *data_end = data + len; const uint8 *p2, *p1 = data; int line_len; static const uint8 crlf[] = {'\r', '\n'}; int res = 0; while (p1 < data_end) { p2 = memchr(p1, '\n', data_end - p1); if (p2 == NULL) p2 = data_end; line_len = p2 - p1; /* write data */ res = 0; if (line_len > 0) { res = pushf_write(dst, p1, line_len); if (res < 0) break; p1 += line_len; } /* write crlf */ while (p1 < data_end && *p1 == '\n') { res = pushf_write(dst, crlf, 2); if (res < 0) break; p1++; } } return res; } static const PushFilterOps crlf_filter = { NULL, crlf_process, NULL, NULL }; /* * Initialize literal data packet */ static int init_litdata_packet(PushFilter **pf_res, PGP_Context *ctx, PushFilter *dst) { int res; int hdrlen; uint8 hdr[6]; uint32 t; PushFilter *pkt; int type; /* * Create header */ if (ctx->text_mode) type = ctx->unicode_mode ? 'u' : 't'; else type = 'b'; /* * Store the creation time into packet. The goal is to have as few known * bytes as possible. */ t = (uint32) time(NULL); hdr[0] = type; hdr[1] = 0; hdr[2] = (t >> 24) & 255; hdr[3] = (t >> 16) & 255; hdr[4] = (t >> 8) & 255; hdr[5] = t & 255; hdrlen = 6; res = write_tag_only(dst, PGP_PKT_LITERAL_DATA); if (res < 0) return res; res = pushf_create(&pkt, &pkt_stream_filter, ctx, dst); if (res < 0) return res; res = pushf_write(pkt, hdr, hdrlen); if (res < 0) { pushf_free(pkt); return res; } *pf_res = pkt; return 0; } /* * Initialize compression filter */ static int init_compress(PushFilter **pf_res, PGP_Context *ctx, PushFilter *dst) { int res; uint8 type = ctx->compress_algo; PushFilter *pkt; res = write_tag_only(dst, PGP_PKT_COMPRESSED_DATA); if (res < 0) return res; res = pushf_create(&pkt, &pkt_stream_filter, ctx, dst); if (res < 0) return res; res = pushf_write(pkt, &type, 1); if (res >= 0) res = pgp_compress_filter(pf_res, ctx, pkt); if (res < 0) pushf_free(pkt); return res; } /* * Initialize encdata packet */ static int init_encdata_packet(PushFilter **pf_res, PGP_Context *ctx, PushFilter *dst) { int res; int tag; if (ctx->disable_mdc) tag = PGP_PKT_SYMENCRYPTED_DATA; else tag = PGP_PKT_SYMENCRYPTED_DATA_MDC; res = write_tag_only(dst, tag); if (res < 0) return res; return pushf_create(pf_res, &pkt_stream_filter, ctx, dst); } /* * write prefix */ static int write_prefix(PGP_Context *ctx, PushFilter *dst) { uint8 prefix[PGP_MAX_BLOCK + 2]; int res, bs; bs = pgp_get_cipher_block_size(ctx->cipher_algo); if (!pg_strong_random(prefix, bs)) return PXE_NO_RANDOM; prefix[bs + 0] = prefix[bs - 2]; prefix[bs + 1] = prefix[bs - 1]; res = pushf_write(dst, prefix, bs + 2); px_memset(prefix, 0, bs + 2); return res < 0 ? res : 0; } /* * write symmetrically encrypted session key packet */ static int symencrypt_sesskey(PGP_Context *ctx, uint8 *dst) { int res; PGP_CFB *cfb; uint8 algo = ctx->cipher_algo; 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_encrypt(cfb, &algo, 1, dst); pgp_cfb_encrypt(cfb, ctx->sess_key, ctx->sess_key_len, dst + 1); pgp_cfb_free(cfb); return ctx->sess_key_len + 1; } /* 5.3: Symmetric-Key Encrypted Session-Key */ static int write_symenc_sesskey(PGP_Context *ctx, PushFilter *dst) { uint8 pkt[256]; int pktlen; int res; uint8 *p = pkt; *p++ = 4; /* 5.3 - version number */ *p++ = ctx->s2k_cipher_algo; *p++ = ctx->s2k.mode; *p++ = ctx->s2k.digest_algo; if (ctx->s2k.mode > 0) { memcpy(p, ctx->s2k.salt, 8); p += 8; } if (ctx->s2k.mode == 3) *p++ = ctx->s2k.iter; if (ctx->use_sess_key) { res = symencrypt_sesskey(ctx, p); if (res < 0) return res; p += res; } pktlen = p - pkt; res = write_normal_header(dst, PGP_PKT_SYMENCRYPTED_SESSKEY, pktlen); if (res >= 0) res = pushf_write(dst, pkt, pktlen); px_memset(pkt, 0, pktlen); return res; } /* * key setup */ static int init_s2k_key(PGP_Context *ctx) { int res; if (ctx->s2k_cipher_algo < 0) ctx->s2k_cipher_algo = ctx->cipher_algo; res = pgp_s2k_fill(&ctx->s2k, ctx->s2k_mode, ctx->s2k_digest_algo, ctx->s2k_count); if (res < 0) return res; return pgp_s2k_process(&ctx->s2k, ctx->s2k_cipher_algo, ctx->sym_key, ctx->sym_key_len); } static int init_sess_key(PGP_Context *ctx) { if (ctx->use_sess_key || ctx->pub_key) { ctx->sess_key_len = pgp_get_cipher_key_size(ctx->cipher_algo); if (!pg_strong_random(ctx->sess_key, ctx->sess_key_len)) return PXE_NO_RANDOM; } else { ctx->sess_key_len = ctx->s2k.key_len; memcpy(ctx->sess_key, ctx->s2k.key, ctx->s2k.key_len); } return 0; } /* * combine */ int pgp_encrypt(PGP_Context *ctx, MBuf *src, MBuf *dst) { int res; int len; uint8 *buf; PushFilter *pf, *pf_tmp; /* * do we have any key */ if (!ctx->sym_key && !ctx->pub_key) return PXE_ARGUMENT_ERROR; /* MBuf writer */ res = pushf_create_mbuf_writer(&pf, dst); if (res < 0) goto out; /* * initialize sym_key */ if (ctx->sym_key) { res = init_s2k_key(ctx); if (res < 0) goto out; } res = init_sess_key(ctx); if (res < 0) goto out; /* * write keypkt */ if (ctx->pub_key) res = pgp_write_pubenc_sesskey(ctx, pf); else res = write_symenc_sesskey(ctx, pf); if (res < 0) goto out; /* encrypted data pkt */ res = init_encdata_packet(&pf_tmp, ctx, pf); if (res < 0) goto out; pf = pf_tmp; /* encrypter */ res = pushf_create(&pf_tmp, &encrypt_filter, ctx, pf); if (res < 0) goto out; pf = pf_tmp; /* hasher */ if (ctx->disable_mdc == 0) { res = pushf_create(&pf_tmp, &mdc_filter, ctx, pf); if (res < 0) goto out; pf = pf_tmp; } /* prefix */ res = write_prefix(ctx, pf); if (res < 0) goto out; /* compressor */ if (ctx->compress_algo > 0 && ctx->compress_level > 0) { res = init_compress(&pf_tmp, ctx, pf); if (res < 0) goto out; pf = pf_tmp; } /* data streamer */ res = init_litdata_packet(&pf_tmp, ctx, pf); if (res < 0) goto out; pf = pf_tmp; /* text conversion? */ if (ctx->text_mode && ctx->convert_crlf) { res = pushf_create(&pf_tmp, &crlf_filter, ctx, pf); if (res < 0) goto out; pf = pf_tmp; } /* * chain complete */ len = mbuf_grab(src, mbuf_avail(src), &buf); res = pushf_write(pf, buf, len); if (res >= 0) res = pushf_flush(pf); out: pushf_free_all(pf); return res; }