text: /*
   * px.c
   *		Various cryptographic stuff for PostgreSQL.
   *
   * Copyright (c) 2001 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/px.c
   */
  
  #include "postgres.h"
  
  #include "px.h"
  
  struct error_desc
  {
  	int			err;
  	const char *desc;
  };
  
  static const struct error_desc px_err_list[] = {
  	{PXE_OK, "Everything ok"},
  	{PXE_ERR_GENERIC, "Some PX error (not specified)"},
  	{PXE_NO_HASH, "No such hash algorithm"},
  	{PXE_NO_CIPHER, "No such cipher algorithm"},
  	{PXE_NOTBLOCKSIZE, "Data not a multiple of block size"},
  	{PXE_BAD_OPTION, "Unknown option"},
  	{PXE_BAD_FORMAT, "Badly formatted type"},
  	{PXE_KEY_TOO_BIG, "Key was too big"},
  	{PXE_CIPHER_INIT, "Cipher cannot be initialized ?"},
  	{PXE_HASH_UNUSABLE_FOR_HMAC, "This hash algorithm is unusable for HMAC"},
  	{PXE_DEV_READ_ERROR, "Error reading from random device"},
  	{PXE_BUG, "pgcrypto bug"},
  	{PXE_ARGUMENT_ERROR, "Illegal argument to function"},
  	{PXE_UNKNOWN_SALT_ALGO, "Unknown salt algorithm"},
  	{PXE_BAD_SALT_ROUNDS, "Incorrect number of rounds"},
  	{PXE_MCRYPT_INTERNAL, "mcrypt internal error"},
  	{PXE_NO_RANDOM, "Failed to generate strong random bits"},
  	{PXE_DECRYPT_FAILED, "Decryption failed"},
  	{PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"},
  	{PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"},
  	{PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"},
  	{PXE_PGP_UNSUPPORTED_CIPHER, "Unsupported cipher algorithm"},
  	{PXE_PGP_UNSUPPORTED_HASH, "Unsupported digest algorithm"},
  	{PXE_PGP_COMPRESSION_ERROR, "Compression error"},
  	{PXE_PGP_NOT_TEXT, "Not text data"},
  	{PXE_PGP_UNEXPECTED_PKT, "Unexpected packet in key data"},
  	{PXE_PGP_MATH_FAILED, "Math operation failed"},
  	{PXE_PGP_SHORT_ELGAMAL_KEY, "Elgamal keys must be at least 1024 bits long"},
  	{PXE_PGP_UNKNOWN_PUBALGO, "Unknown public-key encryption algorithm"},
  	{PXE_PGP_WRONG_KEY, "Wrong key"},
  	{PXE_PGP_MULTIPLE_KEYS,
  	"Several keys given - pgcrypto does not handle keyring"},
  	{PXE_PGP_EXPECT_PUBLIC_KEY, "Refusing to encrypt with secret key"},
  	{PXE_PGP_EXPECT_SECRET_KEY, "Cannot decrypt with public key"},
  	{PXE_PGP_NOT_V4_KEYPKT, "Only V4 key packets are supported"},
  	{PXE_PGP_KEYPKT_CORRUPT, "Corrupt key packet"},
  	{PXE_PGP_NO_USABLE_KEY, "No encryption key found"},
  	{PXE_PGP_NEED_SECRET_PSW, "Need password for secret key"},
  	{PXE_PGP_BAD_S2K_MODE, "Bad S2K mode"},
  	{PXE_PGP_UNSUPPORTED_PUBALGO, "Unsupported public key algorithm"},
  	{PXE_PGP_MULTIPLE_SUBKEYS, "Several subkeys not supported"},
  
  	{0, NULL},
  };
  
  /*
   * Call ereport(ERROR, ...), with an error code and message corresponding to
   * the PXE_* error code given as argument.
   *
   * This is similar to px_strerror(err), but for some errors, we fill in the
   * error code and detail fields more appropriately.
   */
  void
  px_THROW_ERROR(int err)
  {
  	if (err == PXE_NO_RANDOM)
  	{
  		ereport(ERROR,
  				(errcode(ERRCODE_INTERNAL_ERROR),
  				 errmsg("could not generate a random number")));
  	}
  	else
  	{
  		/* For other errors, use the message from the above list. */
  		ereport(ERROR,
  				(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
  				 errmsg("%s", px_strerror(err))));
  	}
  }
  
  const char *
  px_strerror(int err)
  {
  	const struct error_desc *e;
  
  	for (e = px_err_list; e->desc; e++)
  		if (e->err == err)
  			return e->desc;
  	return "Bad error code";
  }
  
  /* memset that must not be optimized away */
  void
  px_memset(void *ptr, int c, size_t len)
  {
  	memset(ptr, c, len);
  }
  
  const char *
  px_resolve_alias(const PX_Alias *list, const char *name)
  {
  	while (list->name)
  	{
  		if (pg_strcasecmp(list->alias, name) == 0)
  			return list->name;
  		list++;
  	}
  	return name;
  }
  
  static void (*debug_handler) (const char *) = NULL;
  
  void
  px_set_debug_handler(void (*handler) (const char *))
  {
  	debug_handler = handler;
  }
  
  void
  px_debug(const char *fmt,...)
  {
  	va_list		ap;
  
  	va_start(ap, fmt);
  	if (debug_handler)
  	{
  		char		buf[512];
  
  		vsnprintf(buf, sizeof(buf), fmt, ap);
  		debug_handler(buf);
  	}
  	va_end(ap);
  }
  
  /*
   * combo - cipher + padding (+ checksum)
   */
  
  static unsigned
  combo_encrypt_len(PX_Combo *cx, unsigned dlen)
  {
  	return dlen + 512;
  }
  
  static unsigned
  combo_decrypt_len(PX_Combo *cx, unsigned dlen)
  {
  	return dlen;
  }
  
  static int
  combo_init(PX_Combo *cx, const uint8 *key, unsigned klen,
  		   const uint8 *iv, unsigned ivlen)
  {
  	int			err;
  	unsigned	ks,
  				ivs;
  	PX_Cipher  *c = cx->cipher;
  	uint8	   *ivbuf = NULL;
  	uint8	   *keybuf;
  
  	ks = px_cipher_key_size(c);
  
  	ivs = px_cipher_iv_size(c);
  	if (ivs > 0)
  	{
  		ivbuf = px_alloc(ivs);
  		memset(ivbuf, 0, ivs);
  		if (ivlen > ivs)
  			memcpy(ivbuf, iv, ivs);
  		else
  			memcpy(ivbuf, iv, ivlen);
  	}
  
  	if (klen > ks)
  		klen = ks;
  	keybuf = px_alloc(ks);
  	memset(keybuf, 0, ks);
  	memcpy(keybuf, key, klen);
  
  	err = px_cipher_init(c, keybuf, klen, ivbuf);
  
  	if (ivbuf)
  		px_free(ivbuf);
  	px_free(keybuf);
  
  	return err;
  }
  
  static int
  combo_encrypt(PX_Combo *cx, const uint8 *data, unsigned dlen,
  			  uint8 *res, unsigned *rlen)
  {
  	int			err = 0;
  	uint8	   *bbuf;
  	unsigned	bs,
  				bpos,
  				i,
  				pad;
  
  	PX_Cipher  *c = cx->cipher;
  
  	bbuf = NULL;
  	bs = px_cipher_block_size(c);
  
  	/* encrypt */
  	if (bs > 1)
  	{
  		bbuf = px_alloc(bs * 4);
  		bpos = dlen % bs;
  		*rlen = dlen - bpos;
  		memcpy(bbuf, data + *rlen, bpos);
  
  		/* encrypt full-block data */
  		if (*rlen)
  		{
  			err = px_cipher_encrypt(c, data, *rlen, res);
  			if (err)
  				goto out;
  		}
  
  		/* bbuf has now bpos bytes of stuff */
  		if (cx->padding)
  		{
  			pad = bs - (bpos % bs);
  			for (i = 0; i < pad; i++)
  				bbuf[bpos++] = pad;
  		}
  		else if (bpos % bs)
  		{
  			/* ERROR? */
  			pad = bs - (bpos % bs);
  			for (i = 0; i < pad; i++)
  				bbuf[bpos++] = 0;
  		}
  
  		/* encrypt the rest - pad */
  		if (bpos)
  		{
  			err = px_cipher_encrypt(c, bbuf, bpos, res + *rlen);
  			*rlen += bpos;
  		}
  	}
  	else
  	{
  		/* stream cipher/mode - no pad needed */
  		err = px_cipher_encrypt(c, data, dlen, res);
  		if (err)
  			goto out;
  		*rlen = dlen;
  	}
  out:
  	if (bbuf)
  		px_free(bbuf);
  
  	return err;
  }
  
  static int
  combo_decrypt(PX_Combo *cx, const uint8 *data, unsigned dlen,
  			  uint8 *res, unsigned *rlen)
  {
  	unsigned	bs,
  				i,
  				pad;
  	unsigned	pad_ok;
  
  	PX_Cipher  *c = cx->cipher;
  
  	/* decide whether zero-length input is allowed */
  	if (dlen == 0)
  	{
  		/* with padding, empty ciphertext is not allowed */
  		if (cx->padding)
  			return PXE_DECRYPT_FAILED;
  
  		/* without padding, report empty result */
  		*rlen = 0;
  		return 0;
  	}
  
  	bs = px_cipher_block_size(c);
  	if (bs > 1 && (dlen % bs) != 0)
  		goto block_error;
  
  	/* decrypt */
  	*rlen = dlen;
  	px_cipher_decrypt(c, data, dlen, res);
  
  	/* unpad */
  	if (bs > 1 && cx->padding)
  	{
  		pad = res[*rlen - 1];
  		pad_ok = 0;
  		if (pad > 0 && pad <= bs && pad <= *rlen)
  		{
  			pad_ok = 1;
  			for (i = *rlen - pad; i < *rlen; i++)
  				if (res[i] != pad)
  				{
  					pad_ok = 0;
  					break;
  				}
  		}
  
  		if (pad_ok)
  			*rlen -= pad;
  	}
  
  	return 0;
  
  block_error:
  	return PXE_NOTBLOCKSIZE;
  }
  
  static void
  combo_free(PX_Combo *cx)
  {
  	if (cx->cipher)
  		px_cipher_free(cx->cipher);
  	px_memset(cx, 0, sizeof(*cx));
  	px_free(cx);
  }
  
  /* PARSER */
  
  static int
  parse_cipher_name(char *full, char **cipher, char **pad)
  {
  	char	   *p,
  			   *p2,
  			   *q;
  
  	*cipher = full;
  	*pad = NULL;
  
  	p = strchr(full, '/');
  	if (p != NULL)
  		*p++ = 0;
  	while (p != NULL)
  	{
  		if ((q = strchr(p, '/')) != NULL)
  			*q++ = 0;
  
  		if (!*p)
  		{
  			p = q;
  			continue;
  		}
  		p2 = strchr(p, ':');
  		if (p2 != NULL)
  		{
  			*p2++ = 0;
  			if (strcmp(p, "pad") == 0)
  				*pad = p2;
  			else
  				return PXE_BAD_OPTION;
  		}
  		else
  			return PXE_BAD_FORMAT;
  
  		p = q;
  	}
  	return 0;
  }
  
  /* provider */
  
  int
  px_find_combo(const char *name, PX_Combo **res)
  {
  	int			err;
  	char	   *buf,
  			   *s_cipher,
  			   *s_pad;
  
  	PX_Combo   *cx;
  
  	cx = px_alloc(sizeof(*cx));
  	memset(cx, 0, sizeof(*cx));
  
  	buf = px_alloc(strlen(name) + 1);
  	strcpy(buf, name);
  
  	err = parse_cipher_name(buf, &s_cipher, &s_pad);
  	if (err)
  	{
  		px_free(buf);
  		px_free(cx);
  		return err;
  	}
  
  	err = px_find_cipher(s_cipher, &cx->cipher);
  	if (err)
  		goto err1;
  
  	if (s_pad != NULL)
  	{
  		if (strcmp(s_pad, "pkcs") == 0)
  			cx->padding = 1;
  		else if (strcmp(s_pad, "none") == 0)
  			cx->padding = 0;
  		else
  			goto err1;
  	}
  	else
  		cx->padding = 1;
  
  	cx->init = combo_init;
  	cx->encrypt = combo_encrypt;
  	cx->decrypt = combo_decrypt;
  	cx->encrypt_len = combo_encrypt_len;
  	cx->decrypt_len = combo_decrypt_len;
  	cx->free = combo_free;
  
  	px_free(buf);
  
  	*res = cx;
  
  	return 0;
  
  err1:
  	if (cx->cipher)
  		px_cipher_free(cx->cipher);
  	px_free(cx);
  	px_free(buf);
  	return PXE_NO_CIPHER;
  }
  