Logo Search packages:      
Sourcecode: postgresql-8.4 version File versions

pgp-encrypt.c

/*
 * 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.
 *
 * $PostgreSQL$
 */

#include "postgres.h"

#include <time.h>

#include "mbuf.h"
#include "px.h"
#include "pgp.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);
      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 = px_alloc(sizeof(*st));
      memset(st, 0, 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;

      memset(st, 0, sizeof(*st));
      px_free(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 = px_alloc(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;

      memset(st, 0, sizeof(*st));
      px_free(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);
      res = px_get_random_bytes(prefix, bs);
      if (res < 0)
            return res;

      prefix[bs + 0] = prefix[bs - 2];
      prefix[bs + 1] = prefix[bs - 1];

      res = pushf_write(dst, prefix, bs + 2);
      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);

      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);
      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)
{
      int               res;

      if (ctx->use_sess_key || ctx->pub_key)
      {
            ctx->sess_key_len = pgp_get_cipher_key_size(ctx->cipher_algo);
            res = px_get_random_bytes(ctx->sess_key, ctx->sess_key_len);
            if (res < 0)
                  return res;
      }
      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 symkey
       */
      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;
}

Generated by  Doxygen 1.6.0   Back to index