/*
 * Copyright (c) 2003-2012
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * Assorted cryptographic support, including a convenience layer on top
 * of OpenSSL, secure hashing/digests, keyed hashes, symmetric encryption,
 * public key encryption, public/private keys and X.509 certificates, passwords,
 * key deriviation, digital signatures, and random numbers and strings.
 * Includes some self-testing using published test vectors.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: crypto.c 2606 2012-10-19 17:56:43Z brachman $";
#endif

#include "auth.h"
#include "dacs_crypto.h"

static char *log_module_name = "crypto";

#ifdef NOTDEF
/*
 * A string equality test that is resistant to timing attacks.
 * MAXLEN must be at least as long as the longest possible P or Q.
 * The goal is for the test to always take the same amount of time to perform,
 * as nearly as possible, regardless of P and Q.
 * In particular, an attacker should not be able to get information about where
 * P and Q begin to differ so as to be able to deduce their matching
 * prefixes.
 * Obviously, this is purposely inefficient.
 */
int
crypto_streq(const char *p, const char *q, size_t maxlen)
{
  int neq, end_i, end_j, i, j, k;

  neq = 0;
  i = j = k = 0;
  end_i = end_j = 0;
  while (k < maxlen) {
	neq |= (p[i] - q[j]);
	if (end_i)
	  i = (i + 1) % end_i;
	else if (p[i] == '\0') {
	  end_i = i;
	  i = 0;
	}
	else
	  i++;
	
	if (end_j)
	  j = (j + 1) % end_j;
	else if (q[j] == '\0') {
	  end_j = j;
	  j = 0;
	}
	else
	  j++;

	k++;
  }
  if (!end_i || !end_j) {
	log_msg((LOG_ERROR_LEVEL, "Configuration error"));
	dacs_abort("Bad crypto_streq() call");
	/*NOTREACHED*/
  }

  return(!neq);
}
#endif

void
crypto_log_error(void)
{
  long err;
  char msg[1024];

  while ((err = ERR_get_error()) != 0) {
	ERR_error_string_n(err, msg, sizeof(msg));
	log_msg((LOG_ERROR_LEVEL, "%s", msg));
  }
}

int
crypto_init(void)
{

  if (AUTH_MAX_RANDOM_PADDING_LENGTH > 255
	  || AUTH_MIN_RANDOM_PADDING_LENGTH > AUTH_MAX_RANDOM_PADDING_LENGTH) {
	/* Configuration error! */
	return(-1);
  }

  ERR_load_crypto_strings();

#ifdef CRYPT_ENGINE_NAME
  {
	ENGINE *e;

	if ((e = ENGINE_by_id(CRYPT_ENGINE_NAME)) == NULL) {
	  fprintf(stderr, "Error find ENGINE %s\n", CRYPT_ENGINE_NAME);
	  return(-1);
	}

	if (ENGINE_set_default(e, ENGINE_METHOD_ALL) == 0) {
	  fprintf(stderr, "Error setting default ENGINE %s\n", CRYPT_ENGINE_NAME);
	  return(-1);
	}
  }
#endif

  return(0);
}

static const EVP_MD *
crypto_lookup_digest_by_name(char *digest_name)
{
  const EVP_MD *evp;
  static int done_init = 0;

  if (!done_init) {
	OpenSSL_add_all_digests();
	done_init = 1;
  }

  if ((evp = EVP_get_digestbyname(digest_name)) == NULL) {
	crypto_log_error();
	if (digest_name != NULL)
	  log_msg((LOG_ERROR_LEVEL,
			   "Unrecognized digest name: \"%s\"", digest_name));
	return(NULL);
  }

  return(evp);
}

/*
 * STR is a PEM base64 encoded DER X.509 certificate.
 * Create a copy of STR with everything up to and including the start of
 * certificate header stripped and everything following and including the end
 * of certificate header stripped.
 * Return NULL if the expected header and trailer are absent.
 */
Ds *
pem_cert_strip_str(char *str)
{
  char *e, *p, *s;
  Ds *buf;

  if (str == NULL)
	return(NULL);

  if ((s = strstr(str, "CERTIFICATE-----")) == NULL)
	return(NULL);
  s += strlen("CERTIFICATE-----");

  if ((e = strstr(s, "-----END")) == NULL)
	return(NULL);

  buf = ds_init(NULL);
  for (p = s; p < e; p++) {
	if (*p != '\n' && *p != '\r' && *p != ' ')
	  ds_appendc(buf, (int) *p);
  }
  ds_appendc(buf, (int) '\0');

  return(buf);
}

Ds *
pem_cert_strip(Ds *ds)
{
  Ds *buf;

  if (ds == NULL)
	return(NULL);

  buf = pem_cert_strip_str(ds_buf(ds));

  return(buf);
}

/*
 * Convert BUFLEN bytes of BUF to hex and format them as a colon-separated,
 * null-terminated hex byte string (e.g., "00:0A:02:99")
 */
static Ds *
thumb_fmt(unsigned char *buf, size_t buflen)
{
  char *p, *s;
  Ds *ds;

  s = strbtohex(buf, buflen, STRBTOHEX_UPPER);

  ds = ds_init(NULL);
  for (p = s; *p != '\0'; p++) {
	if (p != s)
	  ds_appendc(ds, (int) ':');
	ds_appendc(ds, (int) *p++);
	ds_appendc(ds, (int) *p);
  }
  ds_appendc(ds, (int) '\0');

  return(ds);
}

/*
 * Compute the standard certificate thumbprint (hash) of STRIPPED_CERT, a PEM
 * base64 encoded DER X.509 cert with everything before the header and
 * following the trailer already removed.
 */
Ds *
crypto_cert_thumbprint(char *stripped_cert, char **fmt)
{
  unsigned char *dec;
  long len;
  Ds *digest;

  len = mime_decode_base64(stripped_cert, &dec);
  digest = sha1(dec, len);

  if (fmt != NULL) {
	Ds *fmt_digest;

	fmt_digest = thumb_fmt(ds_ucbuf(digest), ds_len(digest));
	*fmt = ds_buf(fmt_digest);
  }

  return(digest);
}

char *
pem_from_evp_pub_pkey(EVP_PKEY *public_key)
{
  char *ptr, *public_key_pem;
  BIO *mem;
  Ds ds;

  mem = BIO_new(BIO_s_mem());
  PEM_write_bio_PUBKEY(mem, public_key);
  /* WTF? Can't we get this null-terminated? */
  BIO_get_mem_data(mem, &ptr);
  ds_init(&ds);
  ds_copyb(&ds, ptr, BIO_number_written(mem), 0);
  ds_appendc(&ds, (int) '\0');
  public_key_pem = ds_buf(&ds);
  BIO_free(mem);

  return(public_key_pem);
}

char *
pem_from_evp_priv_pkey(EVP_PKEY *private_key)
{
  char *ptr, *private_key_pem;
  BIO *mem;
  Ds ds;

  mem = BIO_new(BIO_s_mem());
  PEM_write_bio_PrivateKey(mem, private_key, NULL, NULL, 0, NULL, NULL);
  /* WTF? Can't we get this null-terminated? */
  BIO_get_mem_data(mem, &ptr);
  ds_init(&ds);
  ds_copyb(&ds, ptr, BIO_number_written(mem), 0);
  ds_appendc(&ds, (int) '\0');
  private_key_pem = ds_buf(&ds);
  BIO_free(mem);

  return(private_key_pem);
}

/*
 * Return a base64 encoded thumbprint of CERT, a PEM base64 encoded DER X.509
 * cert.
 */
Ds *
pem_cert_thumbprint(char *cert)
{
  char *enc;
  Ds *digest, *ds, *tp;

  if ((ds = pem_cert_strip_str(cert)) == NULL)
	return(NULL);

  digest = crypto_cert_thumbprint(ds_buf(ds), NULL);

  mime_encode_base64(ds_ucbuf(digest), ds_len(digest), &enc);
  tp = ds_setn(NULL, (unsigned char *) enc, strlen(enc) + 1);

  return(tp);
}

/*
 * Return a hex-formatted string representation of the PEM certificate
 * CERT.
 */
char *
pem_cert_thumbprint_fmt_str(char *cert)
{
  Ds *digest, *ds, *fmt_digest;

  if ((ds = pem_cert_strip_str(cert)) == NULL)
	return(NULL);

  digest = crypto_cert_thumbprint(ds_buf(ds), NULL);

  fmt_digest = thumb_fmt(ds_ucbuf(digest), ds_len(digest));

  return(ds_buf(fmt_digest));
}

char *
pem_cert_thumbprint_fmt(Ds *tp)
{

  return(pem_cert_thumbprint_fmt_str(ds_buf(tp)));
}

Ds *
pem_cert_load_stripped(char *certfile)
{
  Ds *cert, *ds;

  if ((ds = ds_load_file(NULL, certfile)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not load server cert from %s", certfile));
	return(NULL);
  }

  cert = pem_cert_strip(ds);
  ds_free(ds);

  return(cert);
}

RSA *
pem_load_private_key_from_buf(char *buf, int buflen, char *passphrase)
{
  BIO *bio;
  RSA *priv_key;

  if ((bio = BIO_new_mem_buf(buf, buflen)) == NULL)
	return(NULL);

  priv_key = (RSA *) PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, passphrase);
  if (priv_key == NULL) {
	crypto_log_error();
	return(NULL);
  }

  return(priv_key);
}

RSA *
pem_load_private_key(char *keyfile, char *passphrase)
{
  BIO *bio;
  Ds *pkey;
  RSA *priv_key;

  if ((pkey = ds_load_file(NULL, keyfile)) == NULL)
	return(NULL);

  priv_key = pem_load_private_key_from_buf(ds_buf(pkey), ds_len(pkey) - 1,
										   passphrase);

  return(priv_key);
}

Ds *
pem_make_cert(char *enc_cert_str)
{
  int i;
  char *p;
  Ds *ds;

  ds = ds_init(NULL);
  ds_asprintf(ds, "-----BEGIN CERTIFICATE-----\n");
  i = 0;
  for (p = enc_cert_str; *p != '\0'; p++) {
	if (i == 78) {
	  ds_asprintf(ds, "\n");
	  i = 0;
	}
	ds_asprintf(ds, "%c", *p);
	i++;
  }
  ds_asprintf(ds, "\n");
  ds_asprintf(ds, "-----END CERTIFICATE-----");

  return(ds);
}

/*
 * Digest names, as recognized by OpenSSL (case-insensitive):
 *    "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512"
 *
 * XXX The interface to the message digest functions is too closely tied to
 * OpenSSL, via EVP_MD_CTX for instance.  There should be a generic layer
 * at the top that also allows hash functions in other libraries or custom
 * routines to be called.
 */
EVP_MD_CTX *
crypto_digest_open(char *digest_name)
{
  EVP_MD_CTX *ctx;
  const EVP_MD *evp;

  if ((evp = crypto_lookup_digest_by_name(digest_name)) == NULL) {
	crypto_log_error();
	if (digest_name != NULL)
	  log_msg((LOG_ERROR_LEVEL, "Unrecognized digest name: \"%s\"",
			   digest_name));
	return(NULL);
  }

  ctx = EVP_MD_CTX_create();
  if (!EVP_DigestInit_ex(ctx, evp, NULL)) {
    crypto_log_error();
	dacs_fatal(ds_xprintf("Unable to initialize digest name: \"%s\"",
						  digest_name));
	/*NOTREACHED*/
  }

  return(ctx);
}

void
crypto_digest_hash(EVP_MD_CTX *ctx, const void *input, size_t input_len)
{

  if (!EVP_DigestUpdate(ctx, input, input_len)) {
	crypto_log_error();
	dacs_fatal("EVP_DigestUpdate failed");
	/*NOTREACHED*/
  }
}

unsigned char *
crypto_digest_close(EVP_MD_CTX *ctx, unsigned char *output,
					unsigned int *output_len)
{
  unsigned char *outp;

  if (output == NULL)
	outp = (unsigned char *) malloc(EVP_MD_CTX_size(ctx));
  else
	outp = output;

  if (!EVP_DigestFinal(ctx, outp, output_len)) {
	crypto_log_error();
	dacs_fatal("EVP_DigestFinal failed");
	/*NOTREACHED*/
  }

  EVP_MD_CTX_destroy(ctx);
  return(outp);
}

unsigned char *
crypto_digest(char *digest_name, const void *input,
			  size_t input_len, unsigned char *output,
			  unsigned int *output_len)
{
  EVP_MD_CTX *ctx;

  ctx = crypto_digest_open(digest_name);
  crypto_digest_hash(ctx, input, input_len);
  return(crypto_digest_close(ctx, output, output_len));
}

/*
 * A convenience function to compute a SHA1 hash on BUF of length BUFLEN bytes.
 */
Ds *
sha1(unsigned char *buf, size_t buflen)
{
  unsigned char *digest;
  unsigned int digest_len;
  Ds *ds;

  digest = crypto_digest("SHA1", buf, buflen, NULL, &digest_len);
  ds = ds_setn(NULL, digest, digest_len);

  return(ds);
}

/*
 * A convenience function to compute a SHA-256 hash on BUF of length BUFLEN
 * bytes.
 */
Ds *
sha256(unsigned char *buf, size_t buflen)
{
  unsigned char *digest;
  unsigned int digest_len;
  Ds *ds;

  digest = crypto_digest("SHA256", buf, buflen, NULL, &digest_len);
  ds = ds_setn(NULL, digest, digest_len);

  return(ds);
}

/*
 * Compute a password-style digest of PASSWD, using DIGEST_NAME (and SALT,
 * if non-NULL).
 * Return it via OUTPUT as a DACS base-64 encoded string.
 *
 * XXX add iteration to slow computation and increase complexity
 */
void
crypto_passwd(char *digest_name, char *passwd, char *salt, char **output)
{
  unsigned char *outp;
  unsigned int outlen;
  EVP_MD_CTX *ctx;

  ctx = crypto_digest_open(digest_name);

  if (salt != NULL)
	crypto_digest_hash(ctx, salt, strlen(salt));

  crypto_digest_hash(ctx, passwd, strlen(passwd));
  outp = crypto_digest_close(ctx, NULL, &outlen);

  strba64(outp, outlen, output);
}

static Hmac_digest hmac_digests[] = {
  { "MD5",     HMAC_MD5,     64, 16 },		/* RFC 2104 */
  { "SHA1",    HMAC_SHA1,    64, 20 },
  { "SHA224",  HMAC_SHA224,  64, 28 },
  { "SHA256",  HMAC_SHA256,  64, 32 },
  { "SHA384",  HMAC_SHA384, 128, 48 },
  { "SHA512",  HMAC_SHA512, 128, 64 },
  { NULL,      HMAC_NONE,     0,  0 }
};

Hmac_digest *
hmac_lookup_digest_by_size(int size)
{
  int i;

  for (i = 0; hmac_digests[i].name != NULL; i++) {
	if (hmac_digests[i].hmac_size == size)
	  return(&hmac_digests[i]);
  }

  return(NULL);
}

Hmac_digest *
hmac_lookup_digest_by_name(char *digest_name)
{
  int i;

  for (i = 0; hmac_digests[i].name != NULL; i++) {
	if (strcaseeq(hmac_digests[i].name, digest_name))
	  return(&hmac_digests[i]);
  }

  return(NULL);
}

static Hmac_handle *
hmac_init(char *digest_name)
{
  Hmac_digest *hd;
  Hmac_handle *hmac;

  if ((hd = hmac_lookup_digest_by_name(digest_name)) == NULL)
	return(NULL);

  hmac = ALLOC(Hmac_handle);
  hmac->digest_name = strdup(hd->name);
  hmac->alg = hd->alg;
  hmac->block_size = hd->block_size;
  hmac->hmac_size = hd->hmac_size;

  hmac->hmac_key_ipad = (unsigned char *) malloc(hmac->block_size);
  hmac->hmac_key_opad = (unsigned char *) malloc(hmac->block_size);

  return(hmac);
}

/* FIPS 198 S3 */
int
crypto_hmac_key_length(char *digest_name)
{
  char *dn;
  Hmac_digest *hd;

  if ((dn = digest_name) == NULL)
	dn = CRYPTO_HMAC_DIGEST_ALG_NAME;

  if ((hd = hmac_lookup_digest_by_name(dn)) == NULL)
	return(-1);

  return(hd->hmac_size);
}

int
crypto_hmac_hmac_length(char *digest_name)
{
  char *dn;
  Hmac_digest *hd;

  if ((dn = digest_name) == NULL)
	dn = CRYPTO_HMAC_DIGEST_ALG_NAME;

  if ((hd = hmac_lookup_digest_by_name(dn)) == NULL)
	return(-1);

  return(hd->hmac_size);
}

Hmac_handle *
crypto_hmac_open(char *digest_name, unsigned char *key, unsigned int keylen)
{
  int i;
  char *dn;
  unsigned char *k0;
  Hmac_handle *hmac;

  if ((dn = digest_name) == NULL)
	dn = CRYPTO_HMAC_DIGEST_ALG_NAME;

  if ((hmac = hmac_init(dn)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unrecognized digest algorithm for HMAC"));
	return(NULL);
  }

  /* Step 1 */
  if (keylen == hmac->block_size)
	k0 = key;
  else if (keylen > hmac->block_size) {
	unsigned char *k;
	EVP_MD_CTX *ctx;

	/* Step 2 */
	ctx = crypto_digest_open(dn);
	crypto_digest_hash(ctx, key, keylen);
	k = crypto_digest_close(ctx, NULL, &hmac->hmac_size);
	k0 = (unsigned char *) malloc(hmac->block_size);
	memcpy(k0, k, hmac->hmac_size);
	for (i = hmac->hmac_size; i < hmac->block_size; i++)
	  k0[i] = 0;
  }
  else {
	/* Step 3 */
	k0 = malloc(hmac->block_size);
	memcpy(k0, key, keylen);
	for (i = keylen; i < hmac->block_size; i++)
	  k0[i] = 0;
  }

  /* Steps 4 & 7 */
  for (i = 0; i < hmac->block_size; i++) {
	hmac->hmac_key_ipad[i] = k0[i] ^ 0x36;
	hmac->hmac_key_opad[i] = k0[i] ^ 0x5c;
  }

  if (keylen != hmac->block_size) {
	memzap(k0, hmac->block_size);
	free(k0);
  }

  /* Steps 5 & 6 */
  hmac->ctx = crypto_digest_open(dn);
  crypto_digest_hash(hmac->ctx, hmac->hmac_key_ipad, hmac->block_size);

  return(hmac);
}

/*
 * May be called repeatedly.
 */
void
crypto_hmac_hash(Hmac_handle *hmac, unsigned char *str, unsigned int len)
{

  /* Steps 5 & 6 */
  crypto_digest_hash(hmac->ctx, str, len);
}

/*
 * XXX could add optional argument to return "inner" hash value (tmp, below)
 * to the caller and an API that can use the value to complete the HMAC.
 */
unsigned char *
crypto_hmac_close(Hmac_handle *hmac, unsigned char *hmac_buf,
				  unsigned int *hmac_len)
{
  unsigned char *outp, *tmp;
  unsigned int tmp_len;
  EVP_MD_CTX *ctx;

  tmp = crypto_digest_close(hmac->ctx, NULL, &tmp_len);

  /* Steps 8 & 9 */
  ctx = crypto_digest_open(hmac->digest_name);
  crypto_digest_hash(ctx, hmac->hmac_key_opad, hmac->block_size);
  crypto_digest_hash(ctx, tmp, tmp_len);
  memzap(tmp, tmp_len);
  free(tmp);

  /* Step 10 */
  outp = crypto_digest_close(ctx, hmac_buf, hmac_len);

  memzap(hmac->hmac_key_ipad, hmac->block_size);
  memzap(hmac->hmac_key_opad, hmac->block_size);
  free(hmac->hmac_key_ipad);
  free(hmac->hmac_key_opad);  
  memzap(hmac, sizeof(Hmac_handle));
  free(hmac);

  return(outp);
}

static MAYBE_UNUSED void
hmac_show(FILE *fp, unsigned char *outbuf)
{
  int i;
  FILE *f;

  if ((f = fp) == NULL)
	f = stderr;

  for (i = 0; i < CRYPTO_HMAC_BYTE_LENGTH; i++) {
	fprintf(f, "%02x", outbuf[i]);
	if ((i + 1) % 32 == 0)
	  fprintf(f, "\n");
	else if ((i + 1) % 4 == 0)
	  fprintf(f, " ");
  }
}

unsigned char *
crypto_hmac(char *digest_name, unsigned char *key, unsigned int klen,
			unsigned char *value, unsigned int vlen,
			unsigned char **hmac, unsigned int *hlen)
{
  unsigned int kklen, vvlen;
  unsigned char *outp;
  Hmac_handle *h;

  if ((kklen = klen) == 0)
	kklen = strlen((char *) key);

  if ((vvlen = vlen) == 0)
	vvlen = strlen((char *) value);

  if ((h = crypto_hmac_open(digest_name, key, kklen)) == NULL)
	return(NULL);

  crypto_hmac_hash(h, value, vvlen);

  outp = crypto_hmac_close(h, NULL, hlen);
  if (hmac != NULL)
	*hmac = outp;

  return(outp);
}

typedef struct Hmac_vec {
  char *name;
  char *hashalg;
  unsigned char *key;
  unsigned int keylen;
  unsigned char *mesg;
  unsigned int mesglen;
  unsigned int taglen;
  char *expected_hmac;
} Hmac_vec;

/*
 * Refer to FIPS PUB 198
 * See also:
 *   http://csrc.nist.gov/groups/STM/cavp/documents/mac/hmactestvectors.zip
 */
static MAYBE_UNUSED void
hmac_test(void)
{
  char *p;
  unsigned char *outp;
  Hmac_vec *hv;
  static unsigned char key_A1[] = {
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f
  };
  static unsigned char key_A2[] = {
	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
	0x40, 0x41, 0x42, 0x43
  };
  static unsigned char key_A3[] = {
	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
	0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
	0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
	0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
	0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
	0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
	0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
	0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
	0xb0, 0xb1, 0xb2, 0xb3
  };
  static unsigned char key_A4[] = {
	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
	0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
	0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
	0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
	0xa0
  };
  static unsigned char key_MD5_1[] = {
	0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
	0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b
  };
  static unsigned char key_MD5_2[] = {
	"Jefe"
  };
  static unsigned char key_MD5_3[] = {
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa
  };
  static unsigned char msg_MD5_3[] = {
	0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
	0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
	0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
	0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
	0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
	0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
	0xdd, 0xdd
  };

  static Hmac_vec hmac_vecA[] = {
	{ "A.1", "SHA1", key_A1, sizeof(key_A1),
	  (unsigned char *) "Sample #1", 0,
	  20, "4f4ca3d5d68ba7cc0a1208c9c61e9c5da0403c0a" },

	{ "A.2", "SHA1", key_A2, sizeof(key_A2),
	  (unsigned char *) "Sample #2", 0,
	  20, "0922d3405faa3d194f82a45830737d5cc6c75d24" },

	{ "A.3", "SHA1", key_A3, sizeof(key_A3),
	  (unsigned char *) "Sample #3", 0,
	  20, "bcf41eab8bb2d802f3d05caf7cb092ecf8d1a3aa" },

	{ "A.4", "SHA1", key_A4, sizeof(key_A4),
	  (unsigned char *) "Sample #4", 0,
	  12, "9ea886efe268dbecce420c75" },

	{ "MD5.1", "MD5", key_MD5_1, sizeof(key_MD5_1),
	  (unsigned char *) "Hi There", 0,
	  16, "9294727a3638bb1c13f48ef8158bfc9d" },

	{ "MD5.2", "MD5", key_MD5_2, sizeof(key_MD5_2),
	  (unsigned char *) "what do ya want for nothing?", 0,
	  16, "750c783e6ab0b503eaa86e310a5db738" },

	{ "MD5.3", "MD5", key_MD5_3, sizeof(key_MD5_3),
	  msg_MD5_3, sizeof(msg_MD5_3),
	  16, "56be34521d144c88dbb8c733f0e8b3f6" },

	{ NULL, NULL, NULL, 0, NULL, 0, 0, NULL }
  };
 
  static Hmac_vec hmac_vecB[] = {
	{ "HMAC_All.1", "SHA1", NULL, 64,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  20, "5fd596ee78d5553c8ff4e72d266dfd192366da29" },

	{ "HMAC_All.2", "SHA1", NULL, 20,
	  (unsigned char *) "Sample message for keylen<blocklen", 0,
	  20, "4c99ff0cb1b31bd33f8431dbaf4d17fcd356a807" },

	{ "HMAC_All.3", "SHA1", NULL, 100,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  20, "2d51b2f7750e410584662e38f133435f4c4fd42a" },

	{ "HMAC_All.4", "SHA1", NULL, 49,
	  (unsigned char *) "Sample message for keylen<blocklen, with truncated tag", 0,
	  12, "fe3529565cd8e28c5fa79eac" },

	{ "HMAC_All.5", "SHA224", NULL, 64,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  28, "c7405e3ae058e8cd30b08b4140248581ed174cb34e1224bcc1efc81b" },

	{ "HMAC_All.6", "SHA224", NULL, 28,
	  (unsigned char *) "Sample message for keylen<blocklen", 0,
	  28, "e3d249a8cfb67ef8b7a169e9a0a599714a2cecba65999a51beb8fbbe" },

	{ "HMAC_All.7", "SHA224", NULL, 100,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  28, "91c52509e5af8531601ae6230099d90bef88aaefb961f4080abc014d" },

	{ "HMAC_All.8", "SHA224", NULL, 49,
	  (unsigned char *) "Sample message for keylen<blocklen, with truncated tag", 0,
	  16, "d522f1df596ca4b4b1c23d27bde067d6" },
	  
	{ "HMAC_All.9", "SHA256", NULL, 64,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  32, "8bb9a1db9806f20df7f77b82138c7914d174d59e13dc4d0169c9057b133e1d62" },

	{ "HMAC_All.10", "SHA256", NULL, 32,
	  (unsigned char *) "Sample message for keylen<blocklen", 0,
	  32, "a28cf43130ee696a98f14a37678b56bcfcbdd9e5cf69717fecf5480f0ebdf790" },

	{ "HMAC_All.11", "SHA256", NULL, 100,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  32, "bdccb6c72ddeadb500ae768386cb38cc41c63dbb0878ddb9c7a38a431b78378d" },

	{ "HMAC_All.12", "SHA256", NULL, 49,
	  (unsigned char *) "Sample message for keylen<blocklen, with truncated tag", 0,
	  16, "27a8b157839efeac98df070b331d5936" },

	{ "HMAC_All.13", "SHA384", NULL, 128,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  48, "63c5daa5e651847ca897c95814ab830bededc7d25e83eef9195cd45857a37f448947858f5af50cc2b1b730ddf29671a9" },

	{ "HMAC_All.14", "SHA384", NULL, 48,
	  (unsigned char *) "Sample message for keylen<blocklen", 0,
	  48, "6eb242bdbb582ca17bebfa481b1e23211464d2b7f8c20b9ff2201637b93646af5ae9ac316e98db45d9cae773675eeed0" },

	{ "HMAC_All.15", "SHA384", NULL, 200,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  48, "5b664436df69b0ca22551231a3f0a3d5b4f97991713cfa84bff4d0792eff96c27dccbbb6f79b65d548b40e8564cef594" },

	{ "HMAC_All.16", "SHA384", NULL, 49,
	  (unsigned char *) "Sample message for keylen<blocklen, with truncated tag", 0,
	  24, "c48130d3df703dd7cdaa56800dfbd2ba2458320e6e1f98fe" },

	{ "HMAC_All.17", "SHA512", NULL, 128,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  64, "fc25e240658ca785b7a811a8d3f7b4ca48cfa26a8a366bf2cd1f836b05fcb024bd36853081811d6cea4216ebad79da1cfcb95ea4586b8a0ce356596a55fb1347" },

	{ "HMAC_All.18", "SHA512", NULL, 64,
	  (unsigned char *) "Sample message for keylen<blocklen", 0,
	  64, "fd44c18bda0bb0a6ce0e82b031bf2818f6539bd56ec00bdc10a8a2d730b3634de2545d639b0f2cf710d0692c72a1896f1f211c2b922d1a96c392e07e7ea9fedc" },

	{ "HMAC_All.19", "SHA512", NULL, 200,
	  (unsigned char *) "Sample message for keylen=blocklen", 0,
	  64, "d93ec8d2de1ad2a9957cb9b83f14e76ad6b5e0cce285079a127d3b14bccb7aa7286d4ac0d4ce64215f2bc9e6870b33d97438be4aaa20cda5c5a912b48b8e27f3" },

	{ "HMAC_All.20", "SHA512", NULL, 49,
	  (unsigned char *) "Sample message for keylen<blocklen, with truncated tag", 0,
	  32, "00f3e9a77bb0f06de15f160603e42b5028758808596664c03e1ab8fb2b076778" },

	{ NULL, NULL, NULL, 0, NULL, 0, 0, NULL }
  };

  fprintf(stderr, "Doing FIPS PUB 198a Appendix A ");
  fprintf(stderr, "and RFC 2104 Appendix HMAC Examples:\n");
  fprintf(stderr,
		  "(http://csrc.nist.gov/publications/fips/fips198/fips-198a.pdf)\n");

  for (hv = &hmac_vecA[0]; hv->name != NULL; hv++) {
	fprintf(stderr, "%s: ", hv->name);
	outp = crypto_hmac(hv->hashalg, hv->key, hv->keylen,
					   hv->mesg, hv->mesglen, NULL, NULL);
	p = strbtohex(outp, hv->taglen, 0);
	fprintf(stderr, "%s... ", p);
	if (!streq(p, hv->expected_hmac)) {
	  fprintf(stderr, "Failed!\n");
	  exit(1);
	}
	fprintf(stderr, "ok\n");
  }

  fprintf(stderr, "\nDoing FIPS PUB 198-1 HMAC_All.pdf Examples:\n");
  fprintf(stderr,
		  "(http://csrc.nist.gov/groups/ST/toolkit/examples.html#aMsgAuth)\n");

  for (hv = &hmac_vecB[0]; hv->name != NULL; hv++) {
	int i;
	unsigned char k[1024];	/* XXX */

	for (i = 0; i < hv->keylen; i++)
	  k[i] = i;

	fprintf(stderr, "%s: ", hv->name);
	outp = crypto_hmac(hv->hashalg, k, hv->keylen,
					   hv->mesg, hv->mesglen, NULL, NULL);
	p = strbtohex(outp, hv->taglen, 0);
	fprintf(stderr, "%s... ", p);
	if (!streq(p, hv->expected_hmac)) {
	  fprintf(stderr, "Failed!\n");
	  exit(1);
	}
	fprintf(stderr, "ok\n");
  }

  fprintf(stderr, "All HMAC example tests succeeded\n");
}

/***************************************************************************/

static int enable_padding = 0;

int
crypto_cipher_set_padding(int enable)
{
  int prev;

  prev = enable_padding;
  enable_padding = enable;

  return(prev);
}

/*
 * Cipher names, as recognized by OpenSSL 0.9.8j (case-insensitive):
 * aes-[128|192|256]-cbc  (equiv: aes-[128|192|256])
 * aes-[128|192|256]-cfb
 * aes-[128|192|256]-cfb1
 * aes-[128|192|256]-cfb8
 * aes-[128|192|256]-ecb
 * aes-[128|192|256]-ofb
 * plus others...
 */
const EVP_CIPHER *
crypto_cipher_methodbyname(char *name)
{
  const EVP_CIPHER *evp;
  static int done_init = 0;

  if (!done_init) {
	OpenSSL_add_all_ciphers();
	done_init = 1;
  }

  if ((evp = EVP_get_cipherbyname(name)) == NULL) {
	crypto_log_error();
	log_msg((LOG_ERROR_LEVEL, "Unrecognized encryption algorithm: \"%s\"",
			 name));
	return(NULL);
  }

  return(evp);
}

/*
 * Find the IV length for CIPHER_NAME and set LEN to it.
 * Return -1 if an error occurs (such as if CIPHER_NAME is not recognized),
 * 0 otherwise.
 */
int
crypto_cipher_iv_length(char *cipher_name, unsigned int *len)
{
  const EVP_CIPHER *evp;

  if (len == NULL || (evp = crypto_cipher_methodbyname(cipher_name)) == NULL)
	return(-1);

  *len = EVP_CIPHER_iv_length(evp);

  return(0);
}

static Cipher_handle *
crypto_cipher_open(Crypto_function func, char *cipher_name,
				   unsigned char *key, unsigned char **ivp,
				   PKI_session_keys *sk)
{
  unsigned char *iv;
  unsigned int len;
  Cipher_handle *cipher;
  EVP_CIPHER_CTX *ctx;
  const EVP_CIPHER *evp;

  if ((evp = crypto_cipher_methodbyname(cipher_name)) == NULL)
	return(NULL);

  ctx = ALLOC(EVP_CIPHER_CTX);
  EVP_CIPHER_CTX_init(ctx);

  if (ivp == NULL)
	iv = NULL;
  else {
	if (*ivp == NULL) {
	  len = EVP_CIPHER_iv_length(evp);
	  *ivp = iv = (unsigned char *) malloc(len);
	  memset(iv, 0, len);
	}
	else
	  iv = *ivp;
  }

  if (func == CRYPTO_SYMMETRIC_ENCRYPT || func == CRYPTO_SYMMETRIC_DECRYPT) {
	if (!EVP_CipherInit_ex(ctx, evp, NULL, key, iv,
						   func == CRYPTO_SYMMETRIC_ENCRYPT)) {
	  crypto_log_error();
	  dacs_fatal("EVP_CipherInit_ex failed");
	  /*NOTREACHED*/
	}

	if (!EVP_CIPHER_CTX_set_padding(ctx, enable_padding)) {
	  crypto_log_error();
	  dacs_fatal("EVP_CIPHER_CTX_set_padding failed");
	  /*NOTREACHED*/
	}
	log_msg((LOG_TRACE_LEVEL, "Set padding for %s: %s",
			 cipher_name, enable_padding ? "enabled" : "disabled"));
	log_msg((LOG_TRACE_LEVEL, "Cipher block size is %d",
			 EVP_CIPHER_block_size(evp)));
  }
  else if (func == CRYPTO_PKI_ENCRYPT) {
	sk->nkeys = 1;
	sk->ek = (unsigned char **) malloc(sizeof(unsigned char *) * 1);
	sk->ekl = (int *) malloc(sizeof(int) * 1);
	sk->pubk = (EVP_PKEY **) malloc(sizeof(EVP_PKEY *) * 1);

	sk->pubk[0] = (EVP_PKEY *) key;
	sk->ek[0] = (unsigned char *) malloc(EVP_PKEY_size((EVP_PKEY *) key));

	if (!EVP_SealInit(ctx, evp, sk->ek, sk->ekl, iv, sk->pubk, sk->nkeys)) {
	  crypto_log_error();
	  dacs_fatal("EVP_SealInit failed");
	  /*NOTREACHED*/
	}
  }
  else if (func == CRYPTO_PKI_DECRYPT) {
	sk->pubk = (EVP_PKEY **) malloc(sizeof(EVP_PKEY *) * 1);
	sk->pubk[0] = (EVP_PKEY *) key;

	if (!EVP_OpenInit(ctx, evp, sk->ek[0], sk->ekl[0], iv, sk->pubk[0])) {
	  crypto_log_error();
	  dacs_fatal("EVP_OpenInit failed");
	  /*NOTREACHED*/
	}
  }
  else {
	/* ??? */
	dacs_fatal("Unrecognized Crypto_function argument?!");
	/*NOTREACHED*/
  }

  cipher = ALLOC(Cipher_handle);
  cipher->func = func;
  cipher->cipher_name = strdup(cipher_name);
  cipher->ctx = ctx;

  return(cipher);
}

/*
 * Note that EVP_CipherInit_ex() set the mode of operation (encrypt or
 * decrypt), so that this one function can be called by both the encrypt
 * and the decrypt API.
 */
static unsigned char *
internal_crypto_cipher(Cipher_handle *cipher, unsigned char *input,
					   int input_len, unsigned char *output,
					   int *output_len)
{
  int outp_len, st;
  unsigned char *outp;

  if (output == NULL)
	outp = (unsigned char *)
	  malloc(input_len + EVP_CIPHER_CTX_block_size(cipher->ctx));
  else
	outp = output;

  if (cipher->func == CRYPTO_PKI_ENCRYPT)
	st = EVP_SealUpdate(cipher->ctx, outp, &outp_len, input, input_len);
  else if (cipher->func == CRYPTO_PKI_DECRYPT)
	st = EVP_OpenUpdate(cipher->ctx, outp, &outp_len, input, input_len);
  else
	st = EVP_CipherUpdate(cipher->ctx, outp, &outp_len, input, input_len);

  if (st == 0) {
	crypto_log_error();
	dacs_fatal("Encryption failed");
	/*NOTREACHED*/
  }

  if (output_len != NULL)
	*output_len += outp_len;

  return(outp);
}

static unsigned char *
crypto_cipher_encrypt(Cipher_handle *cipher, unsigned char *input,
					  int input_len, unsigned char *output,
					  int *output_len)
{

  if (output_len != NULL)
	*output_len = 0;

  return(internal_crypto_cipher(cipher, input, input_len, output, output_len));
}

static unsigned char *
crypto_cipher_decrypt(Cipher_handle *cipher, unsigned char *input,
					  int input_len, unsigned char *output,
					  int *output_len)
{

  if (output_len != NULL)
	*output_len = 0;

  return(internal_crypto_cipher(cipher, input, input_len, output, output_len));
}

static unsigned char *
crypto_cipher_close(Cipher_handle *cipher, unsigned char *output,
					int *output_len)
{
  int outp_len, st;
  unsigned char *outp;

  if (output == NULL)
	outp = (unsigned char *) malloc(EVP_CIPHER_CTX_block_size(cipher->ctx));
  else
	outp = output;

  if (cipher->func == CRYPTO_PKI_ENCRYPT)
	st = EVP_SealFinal(cipher->ctx, outp, &outp_len);
  else if (cipher->func == CRYPTO_PKI_DECRYPT)
	st = EVP_OpenFinal(cipher->ctx, outp, &outp_len);
  else
	st = EVP_CipherFinal_ex(cipher->ctx, outp, &outp_len);

  if (st == 0) {
	crypto_log_error();
	dacs_fatal("Encryption finalization failed");
	/*NOTREACHED*/
  }

  if (!EVP_CIPHER_CTX_cleanup(cipher->ctx)) {
	crypto_log_error();
	dacs_fatal(NULL);
	/*NOTREACHED*/
  }
  free(cipher->ctx);
  free(cipher);

  if (output_len != NULL)
	*output_len += outp_len;

  return(outp);
}

static unsigned char *
internal_crypto_crypt(Crypto_function func, char *cipher_name,
					  unsigned char *key, unsigned char **iv,
					  PKI_session_keys *sk,
					  unsigned char *input, int input_len,
					  unsigned char *output, int *output_len)
{
  unsigned int len;
  unsigned char *outp;
  Cipher_handle *cipher;

  cipher = crypto_cipher_open(func, cipher_name, key, iv, sk);
  
  if (output == NULL) {
	len = input_len + EVP_CIPHER_CTX_block_size(cipher->ctx);
	outp = (unsigned char *) malloc(len);
  }
  else
	outp = output;

  if (sk != NULL)
	sk->cipher_name = strdup(cipher_name);

  if (func == CRYPTO_SYMMETRIC_ENCRYPT || func == CRYPTO_PKI_ENCRYPT)
	crypto_cipher_encrypt(cipher, input, input_len, outp, output_len);
  else
	crypto_cipher_decrypt(cipher, input, input_len, outp, output_len);

  crypto_cipher_close(cipher, outp, output_len);

  return(outp);
}

unsigned char *
crypto_encipher(Crypto_function func, char *cipher_name,
				unsigned char *key, unsigned char **ivp,
				PKI_session_keys *sk,
				unsigned char *input, int input_len,
				unsigned char *output, int *output_len)
{
  unsigned char *outp;

  outp = internal_crypto_crypt(func, cipher_name, key, ivp, sk,
							   input, input_len, output, output_len);
  return(outp);
}

unsigned char *
crypto_decipher(Crypto_function func, char *cipher_name,
				unsigned char *key, unsigned char **ivp,
				PKI_session_keys *sk,
				unsigned char *input, int input_len,
				unsigned char *output, int *output_len)
{
  unsigned char *outp;

  outp = internal_crypto_crypt(func, cipher_name, key, ivp, sk,
							   input, input_len, output, output_len);
  return(outp);
}

/*
 * Encrypt INPUT/INPUT_LEN using public key KEY and symmetric cipher
 * CIPHER_NAME and convert the result to text.
 * If INPUT_LEN is 0, then use the string length of INPUT.
 * The output includes the cipher name, encrypted session key, IV, and
 * ciphertext.
 * Additionally, if DIGEST_NAME is non-NULL, use it and SIGN_KEY to sign the
 * ciphertext (and DIGEST_NAME) and append the signature to the output.
 * Output format:
 *    <cipher-name>,<ek>,[<iv>],<cipher-text>[,<digest-name>,<signature>]
 */
char *
crypto_pki_encrypt(char *cipher_name, EVP_PKEY *key, unsigned char *input,
				   int input_len, char *digest_name, EVP_PKEY *sign_key)
{
  int enclen, len;
  char *enc_cipher, *enc_ek, *enc_iv, *enc_str, *enc_text;
  unsigned char *encp, *iv;
  Ds ds;
  PKI_session_keys sk;

  if (input_len == 0)
	len = strlen((char *) input) + 1;
  else
	len = input_len;

  iv = NULL;
  encp = crypto_encipher(CRYPTO_PKI_ENCRYPT, cipher_name,
						 (unsigned char *) key, &iv, &sk, input, len, NULL,
						 &enclen);
  strba64((unsigned char *) cipher_name, strlen(cipher_name) + 1, &enc_cipher);
  strba64(sk.ek[0], (unsigned int) sk.ekl[0], &enc_ek);
  if (iv != NULL) {
	int ivlen;
	const EVP_CIPHER *evp;

	if ((evp = crypto_cipher_methodbyname(cipher_name)) == NULL)
	  return(NULL);
	ivlen = EVP_CIPHER_iv_length(evp);
	strba64(iv, ivlen, &enc_iv);
  }
  else
	enc_iv = "";
  strba64(encp, enclen, &enc_text);

  ds_init(&ds);
  ds_asprintf(&ds, "%s,%s,%s,%s", enc_cipher, enc_ek, enc_iv, enc_text);

  if (digest_name != NULL) {
	char *enc_digest, *enc_sign;
	unsigned int sign_len;
	unsigned char *sign;

	strba64((unsigned char *) digest_name, strlen(digest_name) + 1,
			&enc_digest);
	ds_asprintf(&ds, ",%s", enc_digest);
	if (crypto_sign(digest_name, ds_ucbuf(&ds), ds_len(&ds),
					&sign, &sign_len, sign_key) == NULL)
	  return(NULL);
	strba64(sign, sign_len, &enc_sign);
	ds_asprintf(&ds, ",%s", enc_sign);
  }
  enc_str = ds_buf(&ds);

  return(enc_str);
}

/*
 * Decrypt INPUT, produced by crypto_pki_encrypt(), using public key KEY.
 * If a signature is included, verify it.
 * Return the plaintext and set OUTPUT_LEN, otherwise return NULL if an
 * error occurs.
 */
unsigned char *
crypto_pki_decrypt(EVP_PKEY *key, char *input, int *output_len)
{
  unsigned int enclen, ivlen, len;
  char *cipher_name, *enc_str_no_sign;
  char *enc_cipher, *enc_ek, *enc_iv, *enc_text;
  unsigned char *decp, *encp, *iv;
  Dsvec *dsv;
  PKI_session_keys sk;

  if ((dsv = strsplit(input, ",", 0)) == NULL
	  || (dsvec_len(dsv) != 4 && dsvec_len(dsv) != 6)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid exported PKI string"));
	return(NULL);
  }

  enc_cipher = (char *) dsvec_ptr_index(dsv, 0);
  enc_ek = (char *) dsvec_ptr_index(dsv, 1);
  enc_iv = (char *) dsvec_ptr_index(dsv, 2);
  enc_text = (char *) dsvec_ptr_index(dsv, 3);

  if (stra64b(enc_cipher, (unsigned char **) &cipher_name, &len) == NULL)
	return(NULL);
  if (cipher_name[len - 1] != '\0' || strlen(cipher_name) != len - 1)
	return(NULL);
  sk.ek = (unsigned char **) malloc(sizeof(unsigned char *) * 1);
  sk.ekl = (int *) malloc(sizeof(int *) * 1);
  if (stra64b(enc_ek, &sk.ek[0], (unsigned int *) &sk.ekl[0]) == NULL)
	return(NULL);
  if (*enc_iv == '\0')
	iv = NULL;
  else
	stra64b(enc_iv, &iv, &ivlen);
  if (stra64b(enc_text, &encp, &enclen) == NULL)
	return(NULL);

  if (dsvec_len(dsv) == 6) {
	int st;
	unsigned int sign_len;
	char *digest_name, *enc_digest, *enc_sign;
	unsigned char *sign;

	enc_digest = (char *) dsvec_ptr_index(dsv, 4);
	/*
	 * Rather than modify INPUT, recreate the string.  This might be
	 * expensive though.
	 */
	enc_str_no_sign = ds_xprintf("%s,%s,%s,%s,%s",
								 enc_cipher, enc_ek, enc_iv, enc_text,
								 enc_digest);

	if (stra64b(enc_digest, (unsigned char **) &digest_name, &len) == NULL)
	  return(NULL);
	if (digest_name[len - 1] != '\0' || strlen(digest_name) != len - 1)
	  return(NULL);

	enc_sign = (char *) dsvec_ptr_index(dsv, 5);
	if (stra64b(enc_sign, &sign, &sign_len) == NULL)
	  return(NULL);

	st = crypto_signv(digest_name, (unsigned char *) enc_str_no_sign, 0,
					  sign, sign_len, key);
	if (st != 1)
	  return(NULL);
  }
  else
	enc_str_no_sign = input;

  decp = crypto_decipher(CRYPTO_PKI_DECRYPT, cipher_name,
						 (unsigned char *) key, &iv, &sk, encp, enclen, NULL,
						 output_len);

  return(decp);
}

#include <openssl/rsa.h>

static MAYBE_UNUSED int
crypto_pki_test(char *cipher_name, EVP_PKEY *public_key, EVP_PKEY *private_key,
				char *filename)
{
  int data_len, in_len;
  char *data, *out;
  unsigned char *in;
  EVP_PKEY *sign_key;
  RSA *rrr;

  if (load_file(filename, &data, NULL) == -1)
	return(-1);

  rrr = RSAPrivateKey_dup(private_key->pkey.rsa);
  sign_key = EVP_PKEY_new();
  EVP_PKEY_set1_RSA(sign_key, rrr);

  data_len = strlen(data) + 1;
  if ((out = crypto_pki_encrypt(cipher_name, public_key,
								(unsigned char *) data, data_len,
								"sha256", sign_key)) == NULL)
	return(-1);

  if ((in = crypto_pki_decrypt(private_key, out, &in_len)) == NULL)
	return(-1);

  if (data_len == in_len && memcmp(data, in, data_len) == 0)
	return(1);

  return(0);
}

/*
 * See NIST Special Publication 800-38A, 2001 Edition
 * "Recommendation for Block Cipher Modes of Operation: Methods and Techniques"
 * Appendix C: Generation of Initialization Vectors
 *
 * N.B. This function must never generate the same IVBUF value more than once
 * for a particular encryption key.
 */
static void
make_iv(unsigned char *key, int len, unsigned char **ivbuf)
{
  int count_len, r;
  unsigned char *nonce;
  static time_t base_time = 0;
  static unsigned int count = 0;

  count_len = sizeof(base_time) + sizeof(count);
  if (CRYPTO_USE_RANDOMIZED_IV || count_len >= len) {
	unsigned char *outp;

	outp = (unsigned char *) malloc(len);
	crypto_randomize_buffer(outp, len);
	*ivbuf = outp;
	return;
  }

  if (base_time == 0 || ++count == 0)
	base_time = time(NULL);

  nonce = malloc(len);
  r = len - count_len;
  crypto_randomize_buffer(nonce, r);
  memcpy(nonce + r, &base_time, sizeof(base_time));
  memcpy(nonce + r + sizeof(base_time), &count, sizeof(count));

  *ivbuf = crypto_encipher(CRYPTO_SYMMETRIC_ENCRYPT, CRYPTO_IV_CIPHER_NAME,
						   key, NULL, NULL, nonce, len, NULL, NULL);

  memzap(nonce, len);
  free(nonce);
}

/*
 * Encrypt PLAINTEXT (LEN bytes) using KEY, setting ENCRYPTED to point
 * to the buffer allocated for the result.
 * The output is a buffer consisting of an IV followed by the enciphered:
 *   1. randomly-generated byte count (within a range),
 *   2. that number of random bytes,
 *   3. PLAINTEXT, and
 *   4. plaintext length (as an unsigned long in network byte order).
 * Following that is the (unencrypted) MAC of the IV and ciphertext.
 * Note that PLAINTEXT need not literally be ASCII text.
 * Return the total number of bytes copied into *ENCRYPTED.
 */
unsigned int
crypto_encrypt_string(Crypt_keys *keys, unsigned char *plaintext,
					  unsigned int len, unsigned char **encrypted)
{
  int n, m, nwritten, rlen;
  unsigned char *ep;
  unsigned int encrypted_length, xlen;
  unsigned char rvbuf[AUTH_MAX_RANDOM_PADDING_LENGTH], rv1;
  unsigned char *iv;
  Cipher_handle *cipher;
  Hmac_handle *hmac;

  log_msg((LOG_TRACE_LEVEL, "encrypting %u bytes", len));

  /*
   * The IV need not be secret, but it should be random and must be unique.
   * Its integrity is protected by the MAC.
   * See HAC, p. 231.
   * See AC, p. 201
   */
  make_iv(keys->auth_key, AUTH_CRYPT_KEY_LENGTH, &iv);

  cipher = crypto_cipher_open(CRYPTO_SYMMETRIC_ENCRYPT, CRYPTO_CRYPT_ALG_NAME,
							  keys->auth_key, &iv, NULL);

  /* Compute the maximum possible encrypted length. */
  encrypted_length = AUTH_CRYPT_KEY_LENGTH
	+ 1 + AUTH_MAX_RANDOM_PADDING_LENGTH
	+ len + sizeof(xlen)
	+ crypto_hmac_hmac_length(NULL);
  ep = *encrypted = malloc(encrypted_length);

  memcpy(ep, iv, AUTH_CRYPT_KEY_LENGTH);
  log_msg((LOG_TRACE_LEVEL, "IV:\n%s", hexdump(ep, AUTH_CRYPT_KEY_LENGTH)));
  ep += AUTH_CRYPT_KEY_LENGTH;
  nwritten = AUTH_CRYPT_KEY_LENGTH;

  /*
   * Insert some random padding.  This serves four important purposes:
   * 1) It boosts security somewhat because it's a little harder for an
   * attacker to tell where the payload starts.
   *
   * 2) It means that the same (and almost the same) plaintext will likely be
   * encrypted to different lengths on different calls, which might make it
   * more difficult for an attacker to draw some conclusions about the
   * contents merely from the length.
   *
   * 3) This and the block swap below are intended to make known-plaintext
   * attacks more difficult.  In particular, this is important wrt preventing
   * recovery of the key stream (just XOR ciphertext and known plaintext),
   * since then undetectable changes to the plaintext can be made.
   * Because the MAC's computation includes the random padding, an attacker who
   * knows the plaintext but not the random padding cannot recover all of the
   * key stream - if the ciphertext is changed a correct MAC cannot be
   * computed.
   * See HAC, Remark 9.86, p. 366.
   *
   * 4) It makes some kinds of brute force attack more difficult simply
   * because credentials have more bits.
   *
   * These basic ideas can be extended in various ways if necessary
   * (e.g., other permutations of the plaintext, maybe pre-whitening)
   */
  crypto_randomize_buffer(rvbuf, 1);
  rv1 = rvbuf[0]
	% (AUTH_MAX_RANDOM_PADDING_LENGTH - AUTH_MIN_RANDOM_PADDING_LENGTH + 1)
	+ AUTH_MIN_RANDOM_PADDING_LENGTH;
  log_msg((LOG_TRACE_LEVEL, "rvbuf[0]=%u, rv1=%u",
		   (unsigned int) rvbuf[0], (unsigned int) rv1));
  crypto_cipher_encrypt(cipher, &rv1, 1, ep, &rlen);
  if (rlen != 1)
	log_msg((LOG_ALERT_LEVEL, "???1 rv1: rlen=%d, not 1", rlen));
  ep++;
  nwritten += rlen;

  if (rv1) {
	int rlen;

	crypto_randomize_buffer(rvbuf, (int) rv1);
	crypto_cipher_encrypt(cipher, rvbuf, (int) rv1, ep, &rlen);
	if (rlen != (int) rv1)
	  log_msg((LOG_ALERT_LEVEL, "???2 rv1: rlen=%d, not %d",
			   rlen, (int) rv1));
	nwritten += rlen;
	log_msg((LOG_TRACE_LEVEL, "rlen=%d", rlen));
	log_msg((LOG_TRACE_LEVEL, "rvbuf plain:\n%s", hexdump(rvbuf, rv1)));
	log_msg((LOG_TRACE_LEVEL, "rvbuf encrypted:\n%s", hexdump(ep, rv1)));
	ep += rv1;
  }

  /*
   * Splitting the plaintext into two blocks and swapping them before
   * encryption can help to hide stereotypical formatting.
   * XXX Consider using an all-or-nothing transform
   */
  n = len / 2;
  m = len - n;
  crypto_cipher_encrypt(cipher, plaintext + n, m, ep, &rlen);
  nwritten += rlen;
  crypto_cipher_encrypt(cipher, plaintext, n, ep + m, &rlen);
  nwritten += rlen;
  log_msg((LOG_TRACE_LEVEL, "encrypted plaintext:\n%s", hexdump(ep, len)));
  ep += len;

  /*
   * Write the length of the plaintext, in network byte order.
   * Note that htonl() doesn't actually return a long.
   */
  xlen = htonl((uint32_t) len);
  crypto_cipher_encrypt(cipher, (unsigned char *) &xlen, sizeof(xlen),
						ep, &rlen);
  nwritten += rlen;

  log_msg((LOG_TRACE_LEVEL, "len = %u (xlen=%lu):\n%s",
		   len, xlen, hexdump((unsigned char *) &xlen, sizeof(xlen))));
  log_msg((LOG_TRACE_LEVEL, "encrypted xlen:\n%s", hexdump(ep, sizeof(xlen))));
  ep += sizeof(xlen);

  rlen = 0;
  crypto_cipher_close(cipher, ep, &rlen);
  nwritten += rlen;

  /* Double-check that we've written what we think we've written. */
  if (nwritten != (ep - *encrypted))
	log_msg((LOG_ALERT_LEVEL, "??? size mismatch %d != %d",
			 nwritten, ep - *encrypted));

  /*
   * Compute a MAC (HMAC, specifically) over the IV and all of the enciphered
   * message (i.e., the encrypt-then-authenticate method).
   * See "The order of encryption and authentication for protecting
   * communications (Or: how secure is SSL?)", Hugo Krawczyk, 2001.
   * See HAC, 9.6.5, p. 364-368
   */
  hmac = crypto_hmac_open(CRYPTO_HMAC_DIGEST_ALG_NAME, keys->hmac_key,
						  CRYPTO_HMAC_KEY_LENGTH);
  if (hmac == NULL) {
	log_msg((LOG_ALERT_LEVEL, "HMAC failed!"));
	return(0);
  }
  crypto_hmac_hash(hmac, *encrypted, ep - *encrypted);
  crypto_hmac_close(hmac, ep, NULL);

  log_msg((LOG_TRACE_LEVEL, "hmac:\n%s",
		   hexdump(ep, CRYPTO_HMAC_BYTE_LENGTH)));
  ep += CRYPTO_HMAC_BYTE_LENGTH;

  log_msg((LOG_TRACE_LEVEL, "Total encrypted length = %u", ep - *encrypted));

  return(ep - *encrypted);
}

/*
 * The inverse of crypto_encrypt_string().
 * Decrypt ENCRYPTED (the ciphertext), which is LEN bytes long.
 * The resulting plaintext is copied into an allocated buffer, which we set
 * PLAINTEXT to point to and set PLAINTEXT_LENGTH to its length in bytes.
 * Return 0 if all is well, -1 if decryption failed.
 */
int
crypto_decrypt_string(Crypt_keys *keys, unsigned char *encrypted,
					  unsigned int len, unsigned char **plaintext,
					  unsigned int *plaintext_length)
{
  int dlen, n, m;
  unsigned int min_len, olen, plen, xlen;
  unsigned char *ep, *iv, *pt, rv1;
  unsigned char rvbuf[AUTH_MAX_RANDOM_PADDING_LENGTH];
  unsigned char expected_hmac[CRYPTO_HMAC_BYTE_LENGTH];
  Cipher_handle *cipher;
  Hmac_handle *hmac;

  log_msg((LOG_TRACE_LEVEL, "decrypting %u bytes", len));

  /*
   * It must be at least as long as the IV + MAC + random count and
   * length fields.
   */
  min_len = AUTH_CRYPT_KEY_LENGTH + sizeof(unsigned char)
	+ CRYPTO_HMAC_BYTE_LENGTH + sizeof(unsigned int);

  if (len < min_len)
	return(-1);

  iv = ep = encrypted;
  cipher = crypto_cipher_open(CRYPTO_SYMMETRIC_DECRYPT, CRYPTO_CRYPT_ALG_NAME,
							  keys->auth_key, &iv, NULL);

  log_msg((LOG_TRACE_LEVEL, "IV:\n%s", hexdump(iv, AUTH_CRYPT_KEY_LENGTH)));

  ep += AUTH_CRYPT_KEY_LENGTH;

  crypto_cipher_decrypt(cipher, ep, 1, &rv1, NULL);
  log_msg((LOG_TRACE_LEVEL, "rv1=%u", (unsigned int) rv1));
  if (rv1 < AUTH_MIN_RANDOM_PADDING_LENGTH
	  || rv1 > AUTH_MAX_RANDOM_PADDING_LENGTH) {
	crypto_cipher_close(cipher, NULL, NULL);
	log_msg((LOG_ERROR_LEVEL, "Padding error, rv1"));
	return(-1);
  }

  ep++;
  min_len += rv1;

  /* Check again. */
  if (len < min_len) {
	log_msg((LOG_ALERT_LEVEL, "Error: len=%u (min_len=%u)", len, min_len));
	crypto_cipher_close(cipher, NULL, NULL);
	return(-1);
  }

  if (rv1) {
	int rlen;

	crypto_cipher_decrypt(cipher, ep, (int) rv1, rvbuf, &rlen);

	log_msg((LOG_TRACE_LEVEL, "rlen=%d", rlen));
	log_msg((LOG_TRACE_LEVEL, "rvbuf plain (%d):\n%s",
			 rlen, hexdump(rvbuf, rv1)));
	log_msg((LOG_TRACE_LEVEL, "rvbuf encrypted (%d):\n%s",
			 rlen, hexdump(ep, rv1)));
	ep += rv1;
  }

  plen = len - AUTH_CRYPT_KEY_LENGTH - sizeof(unsigned char)
	- rv1 - sizeof(olen) - CRYPTO_HMAC_BYTE_LENGTH;
  n = plen / 2;
  m = plen - n;
  log_msg((LOG_TRACE_LEVEL, "plen=%u, n=%d, m=%d", plen, n, m));

  pt = *plaintext = malloc(plen);

  crypto_cipher_decrypt(cipher, ep, m, pt + n, &dlen);
  log_msg((LOG_TRACE_LEVEL, "dlen=%d", dlen));
  crypto_cipher_decrypt(cipher, ep + m, n, pt, &dlen);
  log_msg((LOG_TRACE_LEVEL, "dlen=%d", dlen));
  ep += plen;

  crypto_cipher_decrypt(cipher, ep, sizeof(xlen), (unsigned char *) &xlen,
						&dlen);
  /* Note that ntohl() doesn't actually return a long */
  olen = ntohl((uint32_t) xlen);
  log_msg((LOG_TRACE_LEVEL, "xlen=%u, olen=%u, plen=%u, dlen=%d",
		   xlen, olen, plen, dlen));

  if (olen != plen) {
	/* The length of the plaintext has been altered. */
	log_msg((LOG_ALERT_LEVEL, "Error: olen=%u, xlen=%lu, plen=%u",
			 olen, xlen, plen));
	crypto_cipher_close(cipher, NULL, NULL);
	memzap(pt, plen);
	free(pt);
	return(-1);
  }
  ep += sizeof(olen);

  /* Validate the MAC */
  hmac = crypto_hmac_open(CRYPTO_HMAC_DIGEST_ALG_NAME, keys->hmac_key,
						  CRYPTO_HMAC_KEY_LENGTH);
  if (hmac == NULL) {
	log_msg((LOG_ALERT_LEVEL, "HMAC failed!"));
	return(-1);
  }
  crypto_hmac_hash(hmac, encrypted, ep - encrypted);
  crypto_hmac_close(hmac, expected_hmac, NULL);

  log_msg((LOG_TRACE_LEVEL, "hmac:\n%s",
		   hexdump(expected_hmac, CRYPTO_HMAC_BYTE_LENGTH)));

  if (memcmp(expected_hmac, ep, CRYPTO_HMAC_BYTE_LENGTH)) {
	log_msg((LOG_ALERT_LEVEL, "Error: expected_hmac != ep"));
	crypto_cipher_close(cipher, NULL, NULL);
	memzap(pt, plen);
	free(pt);
	return(-1);
  }
  ep += CRYPTO_HMAC_BYTE_LENGTH;

  crypto_cipher_close(cipher, NULL, NULL);

  if ((ep - encrypted) != len) {
	log_msg((LOG_ALERT_LEVEL, "Error: ep - encrypted = %u, len = %u",
			  ep - encrypted, len));
	memzap(pt, plen);
	free(pt);
	return(-1);
  }

  if (plaintext_length != NULL)
	*plaintext_length = plen;
  
  log_msg((LOG_TRACE_LEVEL, "Length = %u", plen));

  return(0);
}

/***************************************************************************/

/*
 * Fill BUF, a buffer of LEN bytes, with random material.
 * Return 0 if ok, -1 otherwise.
 */
int
crypto_randomize_buffer(unsigned char *buf, unsigned int len)
{

  if (!RAND_bytes(buf, len)) {
	crypto_log_error();
	return(-1);
  }

  return(0);
}

/*
 * Generate a random unsigned integer betwee LO and HI, inclusive, and
 * store it in UINT.
 * Return 0 if ok, -1 otherwise.
 */
int
crypto_random_uint(unsigned int lo, unsigned int hi, unsigned int *uint)
{
  unsigned int r;

  if (lo >= hi)
	return(-1);

  if (!RAND_bytes((unsigned char *) &r, sizeof(r))) {
	crypto_log_error();
	return(-1);
  }

  *uint = (r % (hi - lo + 1)) + lo;

  return(0);
}

/*
 * Return a buffer comprised of LEN bytes of random material, or NULL if an
 * error occurs.
 */
unsigned char *
crypto_make_random_buffer(unsigned int len)
{
  unsigned char *ptr;

  ptr = (unsigned char *) malloc(len);
  if (!RAND_bytes(ptr, len)) {
	crypto_log_error();
	return(NULL);
  }

  return(ptr);
}

/*
 * Generate NBYTES bytes of random material, then create a printable string
 * consisting of PREFIX (if non-NULL) followed by NBYTES pairs of hex digits,
 * null terminated.
 * We require cryptographically strong random numbers, not run of the mill
 * junk.
 */
char *
crypto_make_random_string(char *prefix, int nbytes)
{
  int i;
  unsigned char *r;
  Ds ds;

  r = crypto_make_random_buffer(nbytes);

  ds_init(&ds);
  if (prefix != NULL && prefix[0] != '\0')
	ds_asprintf(&ds, "%s", prefix);

  for (i = 0; i < nbytes; i++)
	ds_asprintf(&ds, "%02x", r[i] & 0xff);

  memzap(r, nbytes);
  free(r);

  return(ds_buf(&ds));
}

/*
 * Like crypto_make_random_string() except format the random component
 * as a DACS base64 string.
 */
char *
crypto_make_random_a64(char *prefix, int nbytes)
{
  char *p, *s;
  unsigned char *r;

  r = crypto_make_random_buffer(nbytes);

  strba64(r, nbytes, &p);
  if (prefix != NULL && prefix[0] != '\0') {
	s = ds_xprintf("%s%s", prefix, p);
	free(p);
  }
  else
	s = p;

  memzap(r, nbytes);
  free(r);

  return(s);
}

/*
 * Generate a random string of length NBYTES from characters in TR_SPEC,
 * a tr(1) specification, or its complement (if CFLAG is non-zero).
 * Only printable characters, excluding the space, are allowed, regardless
 * of TR_SPEC.
 * Return the string, or NULL if an error occurs.
 */
char *
crypto_make_random_string_from_spec(char *tr_spec, int nbytes, int cflag)
{
  int ch, i, n;
  unsigned int r;
  char chars[256];
  Ds ds;

  if (nbytes <= 0)
	return(NULL);
	
  n = 0;
  for (ch = 041; ch < 0177; ch++) {
	if (strtr_char(ch, tr_spec, cflag))
	  chars[n++] = ch;
  }

  ds_init(&ds);
  for (i = 0; i < nbytes; i++) {
	if (crypto_random_uint(0, n - 1, &r) == -1)
	  return(NULL);
	ds_appendc(&ds, (int) chars[r]);
  }
  ds_appendc(&ds, (int) '\0');

  return(ds_buf(&ds));
}

/*
 * Suboptimal but adequate.
 */
static int
append_random_chars(Ds *ds, int fmt, unsigned int count)
{
  unsigned int i;
  unsigned long long rval;

  for (i = 0; i < count; i++) {
	if (RAND_bytes((unsigned char *) &rval, sizeof(rval)) != 1)
	  return(-1);

	/*
	 * XXX Selecting an integer randomly in this way (by remainder)
	 * does not always result in a perfect distribution, but the error
	 * is not terrible if the modulus is small relative to dividend.
	 * For example, if you are generating numbers in the range 0..2
	 * from 4 random bits (0..15) using val = Random % 3, you can get:
	 *   0%3=0,  1%3=1,  2%3=2,  3%3=0,  4%3=1,  5%3=2,  6%3=0, 7%3=1, 8%3=2,
	 *   9%3=0, 10%3=1, 11%3=2, 12%3=0, 13%3=1, 14%3=2, 15%3=0
	 * The probability of getting a 0 here is 6/16, but the probability of
	 * getting a 1 is 5/16, and 2 is 5/16.  But in this example the modulus
	 * is not small relative to the dividend.
	 *
	 * See NIST SP 800-90 (June 2006), Appendix B.5.1.3
	 */
	if (fmt == 'l')
	  ds_appendc(ds, (int) ('a' + (rval % 26)));
	else if (fmt == 'L')
	  ds_appendc(ds, (int) ('A' + (rval % 26)));
	else if (fmt == 'a') {
	  unsigned int w;

	  w = (unsigned int) (rval % 52);
	  if (w < 26)
		ds_appendc(ds, (int) ('A' + w));
	  else
		ds_appendc(ds, (int) ('a' + w - 26));
	}
	else if (fmt == 'A') {
	  unsigned int w;

	  w = (unsigned int) (rval % 62);
	  if (w < 26)
		ds_appendc(ds, (int) ('A' + w));
	  else if (w < 52)
		ds_appendc(ds, (int) ('a' + w - 26));
	  else
		ds_appendc(ds, (int) ('0' + w - 52));
	}
	else if (fmt == 'x' || fmt == 'X') {
	  unsigned int w;

	  w = (unsigned int) (rval % 16);
	  if (w < 10)
		ds_appendc(ds, (int) ('0' + w));
	  else if (fmt == 'x')
		ds_appendc(ds, (int) ('a' + w - 10));
	  else
		ds_appendc(ds, (int) ('A' + w - 10));
	}
	else if (fmt == 'd')
	  ds_appendc(ds, (int) ('0' + (rval % 10)));
	else
	  ds_appendc(ds, fmt);
  }

  return(0);
}

/*
 * Return a random string according to the format string TEMPLATE.
 * TEMPLATE specifies the format of the corresponding
 * character in the output:
 *   '%l' - a lowercase letter
 *   '%L' - an uppercase letter
 *   '%d' - a digit
 *   '%x' - a lowercase hex digit
 *   '%X' - an uppercase hex digit
 *   '%a' - a lowercase or uppercase letter
 *   '%A' - a lowercase or uppercase letter, or a digit
 * A % can be followed by an unsigned integer, which is a repeat count
 * for the format character that follows.
 * A '%' followed by any other character copies that character.
 * A '%' at the end of the template is mapped to itself.
 * Other characters are copied verbatim.
 * Example: the template "%l-%d-%L" asks for a random lowercase letter,
 * hyphen, random digit, hyphen, and lastly a random uppercase character.
 * Example: "%2l%4d" asks for two lowercase letters followed by 4 digits.
 *
 * XXX should extend this to
 *   o recognize tr(1) type specifiers;
 *     e.g., for two random hex digits separated by a colon:
 *     "[0-9a-f]:[0-9a-f]"
 */
char *
crypto_make_random_string_from_template(char *template)
{
  char *t;
  unsigned int count;
  Ds ds;

  ds_init(&ds);
  for (t = template; *t != '\0'; t++) {
	if (*t == '%') {
	  if (*(t + 1) != '\0')
		t++;
	  if (isdigit((int) *t)) {
		char *endp;

		if (strnumx(t, STRNUM_UI, &count, &endp) == -1)
		  return(NULL);
		t = endp;
	  }
	  else
		count = 1;

	  if (append_random_chars(&ds, (int) *t, count) == -1)
		return(NULL);
	}
	else
	  ds_appendc(&ds, (int) *t);
  }

  ds_appendc(&ds, (int) '\0');

  return(ds_buf(&ds));
}

/***************************************************************************/

/*
 * XXX I'd like to implement NIST SP 800-90...
 * Recommendation for Random Number Generation Using Deterministic Random Bit
 * Generators
 */

#ifdef NOTDEF
static Rng_state *
rng_get_entropy_input(int min_entropy, int min_length, int max_length)
{

  return(NULL);
}

static int
rng_instantiate_function(Rng_state *state, int security_strength,
						 int prediction_resistance_flag,
						 char *personalization_str)
{

  return(-1);
}
#endif

Rng_state *
rng_init(char *seed)
{

  return(NULL);
}

Rng_state *
rng_reinit(char *new_seed, Rng_state *state)
{

  return(NULL);
}

int
rng_generate(Rng_state *state, unsigned char *buf, size_t len)
{

  if (state == NULL)
	return(RAND_bytes(buf, len) != 1 ? -1 : 0);

  return(-1);
}

char *
rng_generate_string(Rng_state *state, char *prefix, size_t len)
{
  int i;
  unsigned char *buf;
  Ds ds;

  buf = (unsigned char *) malloc(len);

  if (state == NULL) {
	if (!RAND_bytes(buf, len))
	  return(NULL);
  }
  else
	return(NULL);

  ds_init(&ds);
  if (prefix != NULL)
	ds_asprintf(&ds, "%s", prefix);

  for (i = 0; i < len; i++)
    ds_asprintf(&ds, "%02x", buf[i] & 0xff);

  memzap(buf, len);
  free(buf);
  return(ds_buf(&ds));
}

void
rng_end(Rng_state *state)
{

}

#ifdef NOTDEF
/*
 * http://php.net/uniqid
 */
Ds *
crypto_uniqid(char *prefix)
{

  return(NULL);
}
#endif

/***************************************************************************/

/* Public key signing and verifying */

EVP_MD_CTX *
crypto_sign_open(char *digest_name)
{
  EVP_MD_CTX *ctx;
  const EVP_MD *evp;

  if ((evp = crypto_lookup_digest_by_name(digest_name)) == NULL)
	return(NULL);

  ctx = EVP_MD_CTX_create();
  if (EVP_SignInit_ex(ctx, evp, NULL) == 0)
	return(NULL);

  return(ctx);
}

int
crypto_sign_update(EVP_MD_CTX *ctx, unsigned char *buf, unsigned int len)
{

  if (EVP_SignUpdate(ctx, buf, len) == 0)
	return(-1);

  return(0);
}

unsigned char *
crypto_sign_close(EVP_MD_CTX *ctx, unsigned char *sign_buf,
				  unsigned int *sign_len, EVP_PKEY *private_key)
{

  if (EVP_SignFinal(ctx, sign_buf, sign_len, private_key) == 0)
	return(NULL);

  return(sign_buf);
}

/*
 * Compute a digital signature for data BUF/LEN (use strlen(BUF) if LEN is 0)
 * using using DIGEST_NAME and SIGN_KEY.
 * Return a pointer to the signature and set SIGN_BUF (if non-NULL) and
 * SIGN_LEN; otherwise return NULL if an error occurs.
 */
unsigned char *
crypto_sign(char *digest_name, unsigned char *buf, unsigned int buflen,
			unsigned char **sign_buf, unsigned int *sign_len,
			EVP_PKEY *sign_key)
{
  unsigned int len, slen;
  unsigned char *sbuf;
  EVP_MD_CTX *ctx;
  
  if ((ctx = crypto_sign_open(digest_name)) == NULL)
	return(NULL);

  if (buflen == 0)
	len = strlen((char *) buf) + 1;
  else
	len = buflen;

  if (crypto_sign_update(ctx, buf, len) == -1)
	return(NULL);

  slen = EVP_PKEY_size(sign_key);
  sbuf = (unsigned char *) malloc(slen);
  if (sign_buf != NULL)
	*sign_buf = sbuf;

  if (crypto_sign_close(ctx, sbuf, sign_len, sign_key) == NULL)
	return(NULL);

  EVP_MD_CTX_destroy(ctx);

  return(sbuf);
}

/*
 * Generate a digital signature for DATA using private key PRIV_KEY.
 * If MAX_DATALEN is zero, compute over all of DATA, otherwise compute over
 * the smaller of MAX_DATALEN and the actual length.
 * This assumes that DATA is null-terminated.
 * If successful, the signature is returned (NOT null-terminated), otherwise
 * NULL is returned.
 */
Ds *
crypto_sign_buf_len(Ds *data, RSA *priv_key, unsigned int max_datalen)
{
  unsigned int datalen, siglen;
  unsigned char *sigbuf;
  Ds *ds;
  EVP_PKEY *pkey;

  pkey = EVP_PKEY_new();
  EVP_PKEY_assign_RSA(pkey, priv_key); 

  if ((datalen = ds_len(data) - 1) > max_datalen && max_datalen != 0)
	datalen = max_datalen;

  if (crypto_sign("SHA1", ds_ucbuf(data), datalen,
				  &sigbuf, &siglen, pkey) == NULL)
	return(NULL);

  ds = ds_setn(NULL, sigbuf, siglen);

  return(ds);
}

/*
 * A wrapper for a commonly used digital signature method.
 * It is assumed that DATA is null-terminated, and the null character is
 * not included in the signature.
 * RFC 3447
 */
Ds *
crypto_sign_buf(Ds *data, RSA *priv_key)
{
  Ds *ds;

  ds = crypto_sign_buf_len(data, priv_key, 0);

  return(ds);
}

/*
 * Sign BUF using SHA1 and the private key in CK.
 * Return the signature string base64 encoded, or NULL.
 */
static MAYBE_UNUSED char *
crypto_sign_test(char *buf, Crypt_keys *ck, unsigned int max_buflen)
{
  char *sbuf;
  Ds *ds, *sign;
  EVP_PKEY *sign_key;

  sign_key = ck->private_key;

  ds = ds_set(NULL, buf);
  if ((sign = crypto_sign_buf_len(ds, sign_key->pkey.rsa, max_buflen)) == NULL)
	return(NULL);

  /* The signature is not null-terminated. */
  mime_encode_base64(ds_ucbuf(sign), ds_len(sign), &sbuf);

  return(sbuf);
}

EVP_MD_CTX *
crypto_signv_open(char *digest_name)
{
  EVP_MD_CTX *ctx;
  const EVP_MD *evp;

  if ((evp = crypto_lookup_digest_by_name(digest_name)) == NULL)
	return(NULL);

  ctx = EVP_MD_CTX_create();
  if (EVP_VerifyInit_ex(ctx, evp, NULL) == 0)
	return(NULL);

  return(ctx);
}

int
crypto_signv_update(EVP_MD_CTX *ctx, unsigned char *buf, unsigned int len)
{

  if (EVP_VerifyUpdate(ctx, buf, len) == 0)
	return(-1);

  return(0);
}

int
crypto_signv_close(EVP_MD_CTX *ctx, unsigned char *sign_buf,
				   unsigned int sign_len, EVP_PKEY *public_key)
{
  int st;

  st = EVP_VerifyFinal(ctx, sign_buf, sign_len, public_key);

  return(st);
}

/*
 * Public key signature verification
 * Return 1 if SIGN_BUF/SIGN_LEN is a valid signature for BUF/LEN
 * using DIGEST_NAME and PUBLIC_KEY.
 * Return 0 if the verification fails, and -1 if an error occurs.
 */
int
crypto_signv(char *digest_name, unsigned char *buf, unsigned int blen,
			unsigned char *sign_buf, unsigned int sign_len,
			EVP_PKEY *public_key)
{
  int st;
  unsigned int len;
  EVP_MD_CTX *ctx;

  if ((ctx = crypto_signv_open(digest_name)) == NULL)
	return(-1);

  if (blen == 0)
	len = strlen((char *) buf) + 1;
  else
	len = blen;
  if (crypto_signv_update(ctx, buf, len) == -1)
	return(-1);

  st = crypto_signv_close(ctx, sign_buf, sign_len, public_key);

  EVP_MD_CTX_destroy(ctx);

  return(st);
}

/*
 * Sign the text in BUF using DIGEST_NAME and then verify the signature.
 */
static MAYBE_UNUSED int
crypto_signv_test(Crypt_keys *ck, char *digest_name, char *buf)
{
  int st;
  unsigned int sign_len;
  unsigned char *sign;

  fprintf(stderr, "Testing public key signature1... ");
  if (crypto_sign(digest_name, (unsigned char *) buf, strlen(buf),
				  &sign, &sign_len, ck->private_key) == NULL) {
	fprintf(stderr, "crypto_sign failed\n");
	return(-1);
  }

  st = crypto_signv(digest_name, (unsigned char *) buf, strlen(buf),
					sign, sign_len, ck->public_key);

  if (st == 1)
	fprintf(stderr, "ok\n");
  else if (st == 0)
	fprintf(stderr, "failed - signature did not verify\n");
  else
	fprintf(stderr, "failed - an error occurred\n");

  return(st);
}

/***************************************************************************/

/*
 * XXX THIS IS PURELY EXPERIMENTAL - THE QUALITY OF THE OUTPUT HASN'T BEEN
 * EVALUATED
 * XXX See PKCS #5 v2 or PKCS #12
 * ftp://ftp.rfc-editor.org/in-notes/rfc2898.txt
 *
 * Given PPLEN bytes of passphrase PP (which may be binary), generate
 * NEEDED pseudorandom bytes into an allocated buffer and set RBUF to point
 * to it.
 * The basic approach is to create an initial HMAC key from a SHA-1 of the
 * entire passphrase, then iterate through equal-sized chunks of the passphrase
 * using HMAC and a unique key for each iteration.
 */
void
crypto_make_randomized_from_passphrase(unsigned char *pp, unsigned int pplen,
									   unsigned int needed,
									   unsigned char **rbuf)
{
  unsigned int i, n, plen, rlen;
  unsigned char *ptr, *rptr;
  unsigned char *md, *sha1_key, *sha1_key2;
  unsigned int hmac_len, sha1_key_len, sha1_key2_len;
  Hmac_handle *hmac;

  /* Create an initial key using a digest of the passphrase. */
  sha1_key = crypto_digest("SHA1", pp, pplen, NULL, &sha1_key_len);

  /* Allocate space for the result. */
  rlen = CRYPTO_HMAC_BYTE_LENGTH * ((needed + CRYPTO_HMAC_BYTE_LENGTH)
									/ CRYPTO_HMAC_BYTE_LENGTH);
  *rbuf = rptr = (unsigned char *) malloc(rlen);

  /* How many HMAC computations are required? */
  n = (needed + CRYPTO_HMAC_BYTE_LENGTH + 1) / CRYPTO_HMAC_BYTE_LENGTH;
  ptr = pp;
  /* XXX The remainder is lost, but the SHA1 is on the entire passphrase... */
  plen = pplen / n;

  sha1_key2_len = sha1_key_len + 8 + 1;
  sha1_key2 = (unsigned char *) malloc(sha1_key2_len);

  log_msg((LOG_TRACE_LEVEL,
		   "%d iteration%s, %d bytes each (%d)",
		   n, (n == 1) ? "" : "s", plen, sha1_key2_len));

  for (i = 0; i < n; i++) {
	memcpy(sha1_key2, sha1_key, sha1_key_len);
	sprintf((char *) (sha1_key2 + sha1_key_len), "%8.8u", i);
	/*
	log_msg((LOG_TRACE_LEVEL, "iter%d:\n%s",
			 i, sha1_key2 + sha1_key_len));
	log_msg((LOG_TRACE_LEVEL, "sha1_key (len=%d):\n%s",
			 sha1_key2_len, hexdump(sha1_key, sha1_key2_len)));
	*/
	if ((hmac = crypto_hmac_open("SHA1", sha1_key2, sha1_key2_len)) == NULL) {
	  log_msg((LOG_ALERT_LEVEL, "HMAC failed"));
	  goto done;
	}
	crypto_hmac_hash(hmac, ptr, plen);
	md = crypto_hmac_close(hmac, NULL, &hmac_len);
	memcpy(rptr, md, hmac_len);

	/*
	  log_msg((LOG_TRACE_LEVEL, "rptr(%d):\n%s",
	  i, hexdump(rptr, hmac_len)));
	*/
	memzap(md, hmac_len);
	free(md);
	rptr += hmac_len;
	ptr += plen;
  }

 done:
  memzap(sha1_key2, sha1_key2_len);
  free(sha1_key2);
  memzap(sha1_key, sha1_key_len);
  free(sha1_key);
  /*
	log_msg((LOG_TRACE_LEVEL, "result:\n%s",
	hexdump(*rbuf, needed)));
  */
}

/*
 * This assumes the 4-byte counter used by PBKDF2.
 */
static unsigned char *
pbkdf2_set_counter(unsigned char *counter, unsigned int value)
{
  int cbyte, vbits;
  unsigned int b;

  memset(counter, 0, 4);
  vbits = 32;
  cbyte = 3;
  for (b = 1; b < vbits; b <<= 1) {
	if (value & b) {
	  if (cbyte < 0)
		return(NULL);
	  counter[cbyte] |= b;
	}

	if ((b % 8) == 0)
	  cbyte--;
  }

  return(counter);
}

static void
pbkdf2_xor(unsigned char *dst, unsigned char *src, unsigned int hlen)
{
  unsigned int i;

  for (i = 0; i < hlen; i++)
	dst[i] ^= src[i];

}

static unsigned char *
pbkdf2_f(unsigned char *pwd, unsigned int pwdlen,
		 unsigned char *salt, unsigned int saltlen,
		 unsigned int count, unsigned int block_index)
{
  unsigned int hlen, i, tlen;
  unsigned char *f, *text, *u, *u_prev;

  tlen = saltlen + 4;
  text = (unsigned char *) malloc(tlen);
  memcpy(text, salt, saltlen);
  pbkdf2_set_counter(text + saltlen, block_index);
  u_prev = NULL;
  f = NULL;

  for (i = 0; i < count; i++) {
	u = crypto_hmac("SHA1", pwd, pwdlen, text, tlen, NULL, &hlen);

	if (u_prev == NULL) {
	  f = (unsigned char *) malloc(hlen);
	  memcpy(f, u, hlen);
	  tlen = hlen;
	  free(text);
	}
	else {
	  pbkdf2_xor(f, u, hlen);
	  free(u_prev);
	}

	text = u_prev = u;
  }

  return(f);
}

/*
 * PBKDF2 - Password-Based Key Derivation Function
 *   RFC 2898, S5.2 (http://www.rfc-editor.org/rfc/rfc2898.txt)
 *   RFC 3962, App B test vectors (http://www.rfc-editor.org/rfc/rfc3962.txt)
 *
 * This implementation assumes HMAC-SHA-1 is the PRF.
 * XXX investigate generalizing to HMAC-SHA-XXX
 *
 * COUNT is the iteration count, which increases the amount of effort
 * needed by an attacker to search for a key but which also increases the
 * amount of time needed to generate a derived key.
 * DKLEN is the length, in bytes, of the derived key.
 */
unsigned char *
crypto_pbkdf2(unsigned char *pwd, int pwdlen,
			  unsigned char *salt, int saltlen,
			  unsigned int count, unsigned int dklen)
{
  unsigned int ell, hlen, i, plen, r, rem, slen;
  unsigned char *dk, *dkp;

  /* For HMAC-SHA1, hlen is 20 bytes */
  hlen = 20;

#ifdef NOTDEF
  if (dklen > (2^32 - 1) * hLen) {
	log_msg((LOG_ERROR_LEVEL, "derived key too long"));
	return(NULL);
  }
#endif

  if (pwdlen == -1)
	plen = strlen((char *) pwd);
  else
	plen = (unsigned int) pwdlen;

  if (saltlen == -1)
	slen = strlen((char *) salt);
  else
	slen = (unsigned int) saltlen;

  ell = (dklen + hlen - 1) / hlen;
  r = dklen - (ell - 1) * hlen;
  dk = (unsigned char *) malloc(dklen);

  dkp = dk;
  rem = dklen;
  for (i = 1; i <= ell; i++) {
	unsigned int len;
	unsigned char *u;

	u = pbkdf2_f(pwd, plen, salt, slen, count, i);
	len = (rem > hlen) ? hlen : rem;
	memcpy(dkp, u, len);
	dkp += len;
	rem -= len;
  }

  return(dk);
}

/*
 * Derive a key from other key material.
 * NBITS is the number of bits (which must be a multiple of 8) of derived
 * key required.
 *
 * One intended use of this within DACS is to support multiple symmetric
 * encryption key lengths by generating all of them from a common source.
 * Pre-generating keys of the various sizes seems unnecessary and inflexible,
 * and simply truncating a single long key to the desired length is probably
 * a weak approach.
 * Applying PBKDF2 appears to be the best recommended practice at present.
 * The mkkey utility should generate a very long, random key (say, 1024 bits)
 * from which keys of length 128-bits, 192-bits, 256-bits, and so on can be
 * created on-the-fly based on the run-time configuration.  This should not
 * impose a significant cost if the COUNT argument is suitably tuned.
 */
unsigned char *
crypto_kdf(unsigned char *pwd, int pwdlen,
		   unsigned char *salt, int saltlen,
		   unsigned int count, unsigned int nbits)
{
  unsigned char *dk;

  dk = crypto_pbkdf2(pwd, pwdlen, salt, saltlen, count, nbits / 8);

  return(dk);
}

static int debug_oauth = 0;

static char *
oauth_encode(char *str)
{
  char *enc_str;
  static char *oauth_chars =
	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";

  enc_str = percent_encode_other_chars(str, oauth_chars, 0);

  return(enc_str);
}

static int
oauth_param_compar(const void *ap, const void *bp)
{
  int st;
  Uri_query_arg *a, *b;

  a = *(Uri_query_arg **) ap;
  b = *(Uri_query_arg **) bp;

  if ((st = strcmp(a->name, b->name)) == 0)
	st = strcmp(a->value, b->value);

  return(st);
}

static char *
oauth_normalize_params(Dsvec *dsv_args, Dsvec *dsv_normalized_args)
{
  int i;
  Ds ds;
  Uri_query_arg *arg, *enc_arg;

  for (i = 0; i < dsvec_len(dsv_args); i++) {
	arg = (Uri_query_arg *) dsvec_ptr_index(dsv_args, i);
	enc_arg = ALLOC(Uri_query_arg);
	enc_arg->name = oauth_encode(arg->name);
	enc_arg->value = oauth_encode(arg->value);
	dsvec_add_ptr(dsv_normalized_args, enc_arg);
	if (debug_oauth > 1) {
	  fprintf(stderr,
			  "RawArg%d: %s=\"%s\"\n", i, arg->name, arg->value);
	  fprintf(stderr,
			  "EncArg%d: %s=\"%s\"\n", i, enc_arg->name, enc_arg->value);
	}
  }

  dsvec_sort(dsv_normalized_args, oauth_param_compar);

  ds_init(&ds);
  for (i = 0; i < dsvec_len(dsv_normalized_args); i++) {
	char *arg_str;

	arg = (Uri_query_arg *) dsvec_ptr_index(dsv_normalized_args, i);
	arg_str = ds_xprintf("%s=%s",
						 arg->name, (arg->value == NULL) ? "" : arg->value);
	ds_asprintf(&ds, "%s%s", (i == 0) ? "" : "&", arg_str);
  }

  return(ds_buf(&ds));
}

typedef enum {
  OAUTH_HMAC_SHA1 = 0,
  OAUTH_RSA_SHA1  = 1,
  OAUTH_PLAINTEXT = 2
} Oauth_sig_method;

/*
 * RFC 5849 S3.4
 *
 * XXX
 * http://hueniverse.com/2012/07/oauth-2-0-and-the-road-to-hell/
 * http://tech.slashdot.org/story/12/07/28/1538212/oauth-20-standard-editor-quits-takes-name-off-spec
 * 
 */
static char *
crypto_oauth_signature(char *method, Uri *uri, Dsvec *oauth_args,
					   char *body_arg_str, Dsvec *normalized_oauth_args,
					   Oauth_sig_method sig_method, char *client_shared_secret,
					   char *token_shared_secret, char *private_key_file,
					   char *passphrase)
{
  int i;
  unsigned int outp_len;
  char *base_string_uri, *enc, *key;
  char *normalized_param_string, *signature_base_string;
  unsigned char *outp;
  Ds ds;
  Dsvec *args, *body_args;

  ds_init(&ds);
  ds_asprintf(&ds, "%s://%s", uri->scheme, uri->host);
  if (uri->port_given != NULL) {
	if (streq(uri->scheme, "http")) {
	  if (uri->port != HTTP_DEFAULT_PORT)
		ds_asprintf(&ds, ":%s", uri->port_given);
	}
	else if (streq(uri->scheme, "https")) {
	  if (uri->port != HTTP_DEFAULT_SSL_PORT)
		ds_asprintf(&ds, ":%s", uri->port_given);
	}
	else
	  ds_asprintf(&ds, ":%s", uri->port_given);
  }
  if (uri->path == NULL || *uri->path == '\0')
	ds_asprintf(&ds, "/");
  else
	ds_asprintf(&ds, "%s", uri->path);

  base_string_uri = ds_buf(&ds);
  if (debug_oauth)
	fprintf(stderr, "base_string_uri=\"%s\"\n", base_string_uri);

  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  dsvec_append(args, uri->query_args);
  dsvec_append(args, oauth_args);
  if (body_arg_str != NULL) {
	if ((body_args = parse_query_string(strdup(body_arg_str))) == NULL)
	  return(NULL);
	dsvec_append(args, body_args);
  }

  normalized_param_string = oauth_normalize_params(args, normalized_oauth_args);
  if (debug_oauth)
	fprintf(stderr, "normalized_params=\"%s\"\n", normalized_param_string);

  ds_reset_buf(&ds);
  ds_asprintf(&ds, "%s&%s&%s",
			  oauth_encode(strtoupper(method)),
			  oauth_encode(base_string_uri),
			  oauth_encode(normalized_param_string));
			  

  signature_base_string = ds_buf(&ds);
  if (debug_oauth)
	fprintf(stderr, "signature_base_string=\"%s\"\n", signature_base_string);

  if (sig_method == OAUTH_HMAC_SHA1) {
	key = ds_xprintf("%s&%s",
					 oauth_encode(client_shared_secret),
					 oauth_encode(token_shared_secret));
	outp = crypto_hmac("SHA1", (unsigned char *) key, strlen(key),
					   ds_ucbuf(&ds), ds_len(&ds) - 1,
					   NULL, &outp_len);
	mime_encode_base64(outp, outp_len, &enc);
	if (debug_oauth)
	  fprintf(stderr, "HMAC-SHA1(\"%s\", \"%s\") = \"%s\"\n",
			  key, signature_base_string, enc);
  }
  else if (sig_method == OAUTH_RSA_SHA1) {
	Ds *sig;
	RSA *priv_key;

	priv_key = pem_load_private_key(private_key_file, NULL);
	if (priv_key == NULL)
	  return(NULL);
	if ((sig = crypto_sign_buf(&ds, priv_key)) == NULL)
	  return(NULL);
	mime_encode_base64(ds_ucbuf(sig), ds_len(sig), &enc);
	if (debug_oauth)
	  fprintf(stderr, "RSA-SHA1(\"%s\", \"%s\") = \"%s\"\n",
			  private_key_file, signature_base_string, enc);
  }
  else {
	enc = ds_xprintf("%s&%s",
					 oauth_encode(client_shared_secret),
					 oauth_encode(token_shared_secret));
	if (debug_oauth)
	  fprintf(stderr, "PLAINTEXT(\"%s\") = \"%s\"\n",
			  signature_base_string, enc);
  }

  return(enc);
}

/*
 * Create a query argument, without encoding.
 */
Uri_query_arg *
uri_make_query_arg(char *name, char *value)
{
  Uri_query_arg *arg;

  arg = ALLOC(Uri_query_arg);
  arg->name = name;
  arg->value = value;

  return(arg);
}

/*
 * Create and add a query argument element, without encoding anything.
 */
Uri_query_arg *
uri_add_query_arg(Dsvec *dsv, char *name, char *value)
{
  Uri_query_arg *arg;

  arg = uri_make_query_arg(name, value);
  dsvec_add_ptr(dsv, arg);

  return(arg);
}

/*
 * Also see:
 * http://oauth.googlecode.com/svn/code/javascript/example/signature.html
 */
static int
oauth_test(void)
{
  int i, testnum;
  unsigned int outp_len;
  char *client_shared_secret, *enc, *signature, *token_shared_secret;
  char *oauth_signature;
  unsigned char *outp;
  Dsvec *args, *normalized_args;
  Uri *uri;
  Uri_query_arg *arg;

  testnum = 0;

  /*
   * Tests from: http://wiki.oauth.net/TestCases
   */
  /* Test S3.1 and 3.4.1.1 */
  fprintf(stderr, "Test %d (HMAC-SHA1)... ", ++testnum);
  outp = crypto_hmac("SHA1", (unsigned char *) "cs&", 3,
					 (unsigned char *) "bs", 2, NULL, &outp_len);
  mime_encode_base64(outp, outp_len, &enc);
  if (!streq(enc, "egQqG5AJep5sJ7anhXju1unge2I=")) {
	fprintf(stderr, "failed\n");
	return(-1);
  }
  fprintf(stderr, "ok\n");

  fprintf(stderr, "Test %d (HMAC-SHA1)... ", ++testnum);
  outp = crypto_hmac("SHA1", (unsigned char *) "cs&ts", 5,
					 (unsigned char *) "bs", 2, NULL, &outp_len);
  mime_encode_base64(outp, outp_len, &enc);
  if (!streq(enc, "VZVjXceV7JgPq/dOTnNmEfO0Fv8=")) {
	fprintf(stderr, "failed\n");
	return(-1);
  }
  fprintf(stderr, "ok\n");

  fprintf(stderr, "Test %d (vacation.jpg)... ", ++testnum);
  client_shared_secret = "kd94hf93k423kf44";
  token_shared_secret = "pfkkdhi9sl3r4s00";
  uri = uri_parse("http://photos.example.net/photos");
  uri_add_query_arg(uri->query_args, "file", "vacation.jpg");
  uri_add_query_arg(uri->query_args, "size", "original");

  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri_add_query_arg(args, "oauth_consumer_key", "dpf43f3p2l4k3l03");
  uri_add_query_arg(args, "oauth_token", "nnch734d00sl2jdk");
  uri_add_query_arg(args, "oauth_signature_method", "HMAC-SHA1");
  uri_add_query_arg(args, "oauth_timestamp", "1191242096");
  uri_add_query_arg(args, "oauth_nonce", "kllo9940pd9333jh");
  uri_add_query_arg(args, "oauth_version", "1.0");

  normalized_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  signature = crypto_oauth_signature("GET", uri, args,
									 NULL,
									 normalized_args, OAUTH_HMAC_SHA1,
									 client_shared_secret, token_shared_secret,
									 NULL, NULL);
  if (debug_oauth) {
	fprintf(stderr, "Authorization: OAuth\n");
	for (i = 0; i < dsvec_len(normalized_args); i++) {
	  arg = dsvec_ptr_index(normalized_args, i);
	  fprintf(stderr, "  %s=\"%s\",\n", arg->name, arg->value);
	}
  }
  if (!streq(signature, "tR3+Ty81lMeYAr/Fid0kMTYa/WM=")) {
	fprintf(stderr, "failed\n");
	fprintf(stderr, "client_shared_secret=\"%s\", token_shared_secret=\"%s\"\n",
			client_shared_secret, token_shared_secret);
	return(-1);
  }
  fprintf(stderr, "ok\n");

  fprintf(stderr, "Test %d (vacaction.jpg)... ", ++testnum);
  client_shared_secret = "";
  token_shared_secret = "";
  uri = uri_parse("http://photos.example.net/photos");
  uri_add_query_arg(uri->query_args, "file", "vacaction.jpg");
  uri_add_query_arg(uri->query_args, "size", "original");

  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri_add_query_arg(args, "oauth_consumer_key", "dpf43f3p2l4k3l03");
  uri_add_query_arg(args, "oauth_signature_method", "RSA-SHA1");
  uri_add_query_arg(args, "oauth_timestamp", "1196666512");
  uri_add_query_arg(args, "oauth_nonce", "13917289812797014437");
  uri_add_query_arg(args, "oauth_version", "1.0");

  normalized_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  signature = crypto_oauth_signature("GET", uri, args,
									 NULL,
									 normalized_args, OAUTH_RSA_SHA1,
									 client_shared_secret, token_shared_secret,
									 "tests/oauth_test_private_key.pem", NULL);
  if (debug_oauth) {
	fprintf(stderr, "Authorization: OAuth\n");
	for (i = 0; i < dsvec_len(normalized_args); i++) {
	  arg = dsvec_ptr_index(normalized_args, i);
	  fprintf(stderr, "  %s=\"%s\",\n", arg->name, arg->value);
	}
  }
  if (!streq(signature, "jvTp/wX1TYtByB1m+Pbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2/9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW//e+RinhejgCuzoH26dyF8iY2ZZ/5D1ilgeijhV/vBka5twt399mXwaYdCwFYE=")) {
	fprintf(stderr, "failed\n");
	fprintf(stderr, "client_shared_secret=\"%s\", token_shared_secret=\"%s\"\n",
			client_shared_secret, token_shared_secret);
	return(-1);
  }
  fprintf(stderr, "ok\n");

  /* RFC 5849 test - S2.1 */
  fprintf(stderr, "Test %d (S2.1)... ", ++testnum);
  client_shared_secret = "ja893SD9";
  token_shared_secret = "";

  uri = uri_parse("https://server.example.com/request_temp_credentials");

  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri_add_query_arg(args, "oauth_consumer_key", "jd83jd92dhsh93js");
  uri_add_query_arg(args, "oauth_signature_method", "PLAINTEXT");
  uri_add_query_arg(args, "oauth_callback", "http://client.example.net/cb?x=1");

  normalized_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  signature = crypto_oauth_signature("POST", uri, args,
									 NULL,
									 normalized_args, OAUTH_PLAINTEXT,
									 client_shared_secret, token_shared_secret,
									 NULL, NULL);
  oauth_signature = oauth_encode(signature);
  if (debug_oauth) {
	fprintf(stderr, "Authorization: OAuth\n");
	for (i = 0; i < dsvec_len(normalized_args); i++) {
	  arg = dsvec_ptr_index(normalized_args, i);
	  fprintf(stderr, "  %s=\"%s\",\n", arg->name, arg->value);
	}
	fprintf(stderr, "  oauth_signature=\"%s\"\n", oauth_signature);
  }
  if (!streq(oauth_signature, "ja893SD9%26")) {
	fprintf(stderr, "failed\n");
	fprintf(stderr, "client_shared_secret=\"%s\", token_shared_secret=\"%s\"\n",
			client_shared_secret, token_shared_secret);
	return(-1);
  }
  fprintf(stderr, "ok\n");

  /* RFC 5849 test - S2.3 */
  fprintf(stderr, "Test %d (S2.3)... ", ++testnum);
  client_shared_secret = "ja893SD9";
  token_shared_secret = "xyz4992k83j47x0b";

  uri = uri_parse("https://server.example.com/request_token");

  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri_add_query_arg(args, "oauth_consumer_key", "jd83jd92dhsh93js");
  uri_add_query_arg(args, "oauth_token", "hdk48Djdsa");
  uri_add_query_arg(args, "oauth_signature_method", "PLAINTEXT");
  uri_add_query_arg(args, "oauth_verifier", "473f82d3");

  normalized_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  signature = crypto_oauth_signature("POST", uri, args,
									 NULL,
									 normalized_args, OAUTH_PLAINTEXT,
									 client_shared_secret, token_shared_secret,
									 NULL, NULL);
  oauth_signature = oauth_encode(signature);
  if (debug_oauth) {
	fprintf(stderr, "Authorization: OAuth\n");
	for (i = 0; i < dsvec_len(normalized_args); i++) {
	  arg = dsvec_ptr_index(normalized_args, i);
	  fprintf(stderr, "  %s=\"%s\",\n", arg->name, arg->value);
	}
	fprintf(stderr, "  oauth_signature=\"%s\"\n", oauth_signature);
  }
  if (!streq(oauth_signature, "ja893SD9%26xyz4992k83j47x0b")) {
	fprintf(stderr, "failed\n");
	fprintf(stderr, "client_shared_secret=\"%s\", token_shared_secret=\"%s\"\n",
			client_shared_secret, token_shared_secret);
	return(-1);
  }
  fprintf(stderr, "ok\n");

  /* RFC 5849 test - S3.1 and 3.4.1.1 */
  fprintf(stderr, "Test %d (S3.1/3.4.1.1)... ", ++testnum);
  client_shared_secret = "j49sk3j29djd";
  token_shared_secret = "dh893hdasih9";

  uri = uri_parse("http://example.com/request");
  uri_add_query_arg(uri->query_args, "b5", "=%3D");
  uri_add_query_arg(uri->query_args, "a3", "a");
  uri_add_query_arg(uri->query_args, "c@", "");
  uri_add_query_arg(uri->query_args, "a2", "r b");

  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri_add_query_arg(args, "oauth_consumer_key", "9djdj82h48djs9d2");
  uri_add_query_arg(args, "oauth_token", "kkk9d7dh3k39sjv7");
  uri_add_query_arg(args, "oauth_signature_method", "HMAC-SHA1");
  uri_add_query_arg(args, "oauth_timestamp", "137131201");
  uri_add_query_arg(args, "oauth_nonce", "7d8f3e4a");

  normalized_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  signature = crypto_oauth_signature("POST", uri, args,
									 "c2&a3=2 q",
									 normalized_args, OAUTH_HMAC_SHA1,
									 client_shared_secret, token_shared_secret,
									 NULL, NULL);
  oauth_signature = oauth_encode(signature);
  if (debug_oauth) {
	fprintf(stderr, "Authorization: OAuth\n");
	for (i = 0; i < dsvec_len(normalized_args); i++) {
	  arg = dsvec_ptr_index(normalized_args, i);
	  fprintf(stderr, "  %s=\"%s\",\n", arg->name, arg->value);
	}
	fprintf(stderr, "  oauth_signature=\"%s\"\n", oauth_signature);
  }
  if (!streq(oauth_signature, "r6%2FTJjbCOr97%2F%2BUU0NsvSne7s5g%3D")) {
	fprintf(stderr, "failed\n");
	fprintf(stderr, "client_shared_secret=\"%s\", token_shared_secret=\"%s\"\n",
			client_shared_secret, token_shared_secret);
	return(-1);
  }
  fprintf(stderr, "ok\n");

  /* RFC 5849 test - S1.2 */
  fprintf(stderr, "Test %d (S1.2-1)... ", ++testnum);
  client_shared_secret = "kd94hf93k423kf44";
  token_shared_secret = "";

  uri = uri_parse("https://photos.example.net/initiate");
  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri_add_query_arg(args, "oauth_consumer_key", "dpf43f3p2l4k3l03");
  uri_add_query_arg(args, "oauth_signature_method", "HMAC-SHA1");
  uri_add_query_arg(args, "oauth_timestamp", "137131200");
  uri_add_query_arg(args, "oauth_nonce", "wIjqoS");
  uri_add_query_arg(args, "oauth_callback", "http://printer.example.com/ready");

  normalized_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  signature = crypto_oauth_signature("post", uri, args, NULL,
									 normalized_args, OAUTH_HMAC_SHA1,
									 client_shared_secret, token_shared_secret,
									 NULL, NULL);
  oauth_signature = oauth_encode(signature);
  if (debug_oauth) {
	fprintf(stderr, "Authorization: OAuth\n");
	for (i = 0; i < dsvec_len(normalized_args); i++) {
	  arg = dsvec_ptr_index(normalized_args, i);
	  fprintf(stderr, "  %s=\"%s\",\n", arg->name, arg->value);
	}
	fprintf(stderr, "  oauth_signature=\"%s\"\n", oauth_signature);
  }
  if (!streq(oauth_signature, "74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D")) {
	fprintf(stderr, "failed\n");
	return(-1);
  }
  fprintf(stderr, "ok\n");

  /* RFC 5849 test -  S1.2 */
  fprintf(stderr, "Test %d (S1.2-2)... ", ++testnum);
  client_shared_secret = "kd94hf93k423kf44";
  token_shared_secret = "hdhd0244k9j7ao03";
  uri = uri_parse("https://photos.example.net/token");
  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri_add_query_arg(args, "oauth_consumer_key", "dpf43f3p2l4k3l03");
  uri_add_query_arg(args, "oauth_token", "hh5s93j4hdidpola");
  uri_add_query_arg(args, "oauth_signature_method", "HMAC-SHA1");
  uri_add_query_arg(args, "oauth_timestamp", "137131201");
  uri_add_query_arg(args, "oauth_nonce", "walatlh");
  uri_add_query_arg(args, "oauth_verifier", "hfdp7dh39dks9884");

  normalized_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  signature = crypto_oauth_signature("post", uri, args, NULL,
									 normalized_args, OAUTH_HMAC_SHA1,
									 client_shared_secret, token_shared_secret,
									 NULL, NULL);
  oauth_signature = oauth_encode(signature);
  if (debug_oauth) {
	fprintf(stderr, "Authorization: OAuth\n");
	for (i = 0; i < dsvec_len(normalized_args); i++) {
	  arg = dsvec_ptr_index(normalized_args, i);
	  fprintf(stderr, "  %s=\"%s\",\n", arg->name, arg->value);
	}
	fprintf(stderr, "  oauth_signature=\"%s\"\n", oauth_signature);
  }
  if (!streq(oauth_signature, "gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D")) {
	fprintf(stderr, "failed\n");
	fprintf(stderr, "client_shared_secret=\"%s\", token_shared_secret=\"%s\"\n",
			client_shared_secret, token_shared_secret);
	return(-1);
  }
  fprintf(stderr, "ok\n");

  /* RFC 5849 test -  S1.2 */
  fprintf(stderr, "Test %d (S1.2-3)... ", ++testnum);
  client_shared_secret = "kd94hf93k423kf44";
  token_shared_secret = "pfkkdhi9sl3r4s00";
  uri = uri_parse("http://photos.example.net/photos");
  uri_add_query_arg(uri->query_args, "file", "vacation.jpg");
  uri_add_query_arg(uri->query_args, "size", "original");
  args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri_add_query_arg(args, "oauth_consumer_key", "dpf43f3p2l4k3l03");
  uri_add_query_arg(args, "oauth_token", "nnch734d00sl2jdk");
  uri_add_query_arg(args, "oauth_signature_method", "HMAC-SHA1");
  uri_add_query_arg(args, "oauth_timestamp", "137131202");
  uri_add_query_arg(args, "oauth_nonce", "chapoH");

  normalized_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  signature = crypto_oauth_signature("get", uri, args, NULL,
									 normalized_args, OAUTH_HMAC_SHA1,
									 client_shared_secret, token_shared_secret,
									 NULL, NULL);
  oauth_signature = oauth_encode(signature);
  if (debug_oauth) {
	fprintf(stderr, "Authorization: OAuth\n");
	for (i = 0; i < dsvec_len(normalized_args); i++) {
	  arg = dsvec_ptr_index(normalized_args, i);
	  fprintf(stderr, "  %s=\"%s\",\n", arg->name, arg->value);
	}
	fprintf(stderr, "  oauth_signature=\"%s\"\n", oauth_signature);
  }
  if (!streq(oauth_signature, "MdpQcU8iPSUjWoN%2FUDMsK2sui9I%3D")) {
	fprintf(stderr, "failed\n");
	fprintf(stderr, "client_shared_secret=\"%s\", token_shared_secret=\"%s\"\n",
			client_shared_secret, token_shared_secret);
	return(-1);
  }
  fprintf(stderr, "ok\n");

  return(0);
}

char *
dacs_approval_create(char *uri, char *method, char *ident, char *dn,
					 EVP_PKEY *private_key)
{
  char *approval, *astr, *astr_enc, *signature, *stamp;
  unsigned int s_astr_len;
  unsigned char *s_astr;

  if (dn == NULL)
	dn = "SHA1";

  if ((stamp = ustamp_clock(dacs_current_jurisdiction(), NULL)) == NULL)
	return(NULL);

  astr
	= ds_xprintf("j=\"%s\"\nh=\"%s\"\ns=\"%s\"\nu=\"%s\"\nm=\"%s\"\ni=\"%s\"",
				 dacs_current_jurisdiction(),
				 dn, strtolower(stamp), uri, strtolower(method),
				 (ident == NULL) ? "unauth" : ident);
  if ((s_astr = crypto_sign(dn, (unsigned char *) astr, strlen(astr), NULL,
							&s_astr_len, private_key)) == NULL)
	return(NULL);

  strba64(s_astr, s_astr_len, &signature);
  strba64((unsigned char *) astr, strlen(astr) + 1, &astr_enc);
  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
		   "Approval signature(%s)=%s", astr, signature));

  approval = ds_xprintf("a=\"%s\", s=\"%s\"", astr_enc, signature);
  log_msg((LOG_TRACE_LEVEL, "Approval: %s", approval));

  return(approval);
}

#ifdef PROG

#include "dacs.h"

#define CRYPTO_TEST_CIPHER_NAME		"AES-128-CFB"

typedef struct Hmac_test {
  int size;
  int count;
  unsigned int key_len;
  unsigned int hmac_len;
  char *key_str;
  char *msg_str;
  char *hmac_str;
} Hmac_test;

static int
do_hmac_test(Hmac_test *test)
{
  char *outp_str;
  unsigned int key_len, msg_len;
  unsigned char *key, *msg, *outp;
  Hmac_digest *hd;
  Hmac_handle *hmac;

  if ((key = strhextob(test->key_str, &key_len)) == NULL) {
	fprintf(stderr, "Invalid Key hex string\n");
	return(-1);
  }

  if (key_len != test->key_len) {
	fprintf(stderr, "Key length mismatch\n");
	return(-1);
  }

  if ((msg = strhextob(test->msg_str, &msg_len)) == NULL) {
	fprintf(stderr, "Invalid Msg hex string\n");
	return(-1);
  }
  
  if ((hd = hmac_lookup_digest_by_size(test->size)) == NULL) {
	fprintf(stderr, "HMAC lookup failed for size %d\n", test->size);
	return(-1);
  }

  if ((hmac = crypto_hmac_open(hd->name, key, key_len)) == NULL)
	return(-1);
  crypto_hmac_hash(hmac, msg, msg_len);
  outp = crypto_hmac_close(hmac, NULL, NULL);
  outp_str = strbtohex(outp, test->hmac_len, 0);
  if (!strneq(outp_str, test->hmac_str, test->hmac_len)) {
	fprintf(stderr, "HMAC comparison failed\n");
	fprintf(stderr, "Want: %s\n", test->hmac_str);
	fprintf(stderr, "Got:  %s\n", outp_str);
	return(-1);
  }

  return(0);
}

static int
do_hmac_tests(FILE *fp)
{
  int in_test, n;
  char *p;
  Ds *ds;
  Hmac_test test;

  ds = ds_init(NULL);
  ds->delnl_flag = 1;

  in_test = 0;
  n = 0;
  while (1) {
	if (ds_gets(ds, fp) == NULL)
	  break;
	p = ds_buf(ds);
	if (*p == '#')
	  continue;

	if (*p == '\0') {
	  if (in_test) {
		if (do_hmac_test(&test) == -1) {
		  fprintf(stderr, "HMAC test failed!\n");
		  fprintf(stderr, "L     = %d\n", test.size);
		  fprintf(stderr, "Count = %d\n", test.count);
		  fprintf(stderr, "Klen  = %d\n", test.key_len);
		  fprintf(stderr, "Tlen  = %d\n", test.hmac_len);
		  return(-1);
		}
		fprintf(stderr, ".");
		n++;
		test.count = 0;
		test.key_len = test.hmac_len = 0;
		test.key_str = test.msg_str = test.hmac_str = NULL;
		in_test = 0;
	  }
	  continue;
	}

	if (strprefix(p, "[L=") != NULL) {
	  test.size = atoi(p + 3);
	  fprintf(stderr, "%s[L=%d] ", n ? "\n" : "", test.size);
	  continue;
	}

	in_test = 1;
	if (strprefix(p, "Count = ") != NULL)
	  test.count = atoi(p + 8);
	else if (strprefix(p, "Klen = ") != NULL)
	  test.key_len = atoi(p + 7);
	else if (strprefix(p, "Tlen = ") != NULL)
	  test.hmac_len = atoi(p + 7);
	else if (strprefix(p, "Key = ") != NULL)
	  test.key_str = strdup(p + 6);
	else if (strprefix(p, "Msg = ") != NULL)
	  test.msg_str = strdup(p + 6);
	else if (strprefix(p, "Mac = ") != NULL)
	  test.hmac_str = strdup(p + 6);
	else {
	  fprintf(stderr, "Parse error: %s\n", p);
	  return(-1);
	}
  }

  fprintf(stderr, "\n");
  return(0);
}

typedef struct RFC4231_hmac_test_data {
  char *key_str;
  char *msg_str;
  struct {
	char *digest_name;
	char *hmac_str;
	int match_prefix;
  } t[5];
} RFC4231_hmac_test_data;

static RFC4231_hmac_test_data rfc4231_hmac_test_data[] = {
  { /* 4.2. Test Case 1 */
	"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
	"4869205468657265",
	{
	  { "SHA224",
		"896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", 0
	  },
	  { "SHA256",
		"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", 0
	  },
	  { "SHA384",
		"afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6", 0
	  },
	  { "SHA512",
		"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854", 0
	  },
	  { NULL, NULL, 0 }
	}
  },

  { /* 4.3. Test Case 2 */
	"4a656665",
	"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
	{
	  { "SHA224",
		"a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44", 0
	  },
	  { "SHA256",
		"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", 0
	  },
	  { "SHA384",
		"af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649", 0
	  },
	  { "SHA512",
		"164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737", 0
	  },
	  { NULL, NULL, 0 }
	}
  },
	  
  { /* 4.4. Test Case 3 */
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
	{
	  { "SHA224",
		"7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea", 0
	  },
	  { "SHA256",
		"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", 0
	  },
	  { "SHA384",
		"88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27", 0
	  },
	  { "SHA512",
		"fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb", 0
	  },
	  { NULL, NULL, 0 }
	}
  },

  { /* 4.5. Test Case 4 */
	"0102030405060708090a0b0c0d0e0f10111213141516171819",
	"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
	{
	  { "SHA224",
		"6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a", 0
	  },
	  { "SHA256",
		"82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", 0
	  },
	  { "SHA384",
		"3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb", 0
	  },
	  { "SHA512",
		"b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd", 0
	  },
	  { NULL, NULL, 0 }
	}
  },

  { /* 4.6. Test Case 5 */
	"0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
	"546573742057697468205472756e636174696f6e",
	{
	  { "SHA224",
		"0e2aea68a90c8d37c988bcdb9fca6fa8", 1
	  },
	  { "SHA256",
		"a3b6167473100ee06e0c796c2955552b", 1
	  },
	  { "SHA384",
		"3abf34c3503b2a23a46efc619baef897", 1
	  },
	  { "SHA512",
		"415fad6271580a531d4179bc891d87a6", 1
	  },
	  { NULL, NULL, 0 }
	}
  },

  { /* 4.7. Test Case 6 */
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	"54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374",
	{
	  { "SHA224",
		"95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e", 0
	  },
	  { "SHA256",
		"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", 0
	  },
	  { "SHA384",
		"4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952", 0
	  },
	  { "SHA512",
		"80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598", 0
	  },
	  { NULL, NULL, 0 }
	}
  },

  { /* 4.8. Test Case 7 */
	"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
	"5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e",
	{
	  { "SHA224",
		"3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1", 0
	  },
	  { "SHA256",
		"9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", 0
	  },
	  { "SHA384",
		"6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e", 0
	  },
	  { "SHA512",
		"e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58", 0
	  },
	  { NULL, NULL, 0 }
	}
  },

  { NULL, NULL, { { NULL, NULL, 0 } } }
};

#ifdef NOTDEF

#endif

/*
 * http://www.rfc-editor.org/rfc/rfc4231.txt
 */
static int
rfc4231_hmac_test(void)
{
  int i, matched;
  char *outp_str;
  unsigned int key_len, hmac_len, msg_len;
  unsigned char *key, *msg, *outp;
  Hmac_digest *hd;
  Hmac_handle *hmac;
  RFC4231_hmac_test_data *data;

  fprintf(stderr, "\nDoing RFC 4231 HMAC test vector tests:\n");
  fprintf(stderr, "(see http://www.rfc-editor.org/rfc/rfc4231.txt)\n");

  for (data = &rfc4231_hmac_test_data[0]; data->key_str != NULL; data++) {
	if ((key = strhextob(data->key_str, &key_len)) == NULL) {
	  fprintf(stderr, "Invalid Key hex string\n");
	  exit(1);
	}

	if ((msg = strhextob(data->msg_str, &msg_len)) == NULL) {
	  fprintf(stderr, "Invalid Msg hex string\n");
	  exit(1);
	}
  
	for (i = 0; data->t[i].digest_name != NULL; i++) {
	  if ((hd = hmac_lookup_digest_by_name(data->t[i].digest_name)) == NULL) {
		fprintf(stderr, "HMAC lookup failed for %s\n", data->t[i].digest_name);
		exit(1);
	  }

	  if ((hmac = crypto_hmac_open(hd->name, key, key_len)) == NULL)
		exit(1);
	  crypto_hmac_hash(hmac, msg, msg_len);
	  outp = crypto_hmac_close(hmac, NULL, &hmac_len);
	  outp_str = strbtohex(outp, hmac_len, 0);
	  if (data->t[i].match_prefix)
		matched = (strprefix(outp_str, data->t[i].hmac_str)) != NULL;
	  else
		matched = streq(outp_str, data->t[i].hmac_str);
	  if (!matched) {
		fprintf(stderr, "HMAC comparison failed\n");
		fprintf(stderr, "Want: %s\n", data->t[i].hmac_str);
		fprintf(stderr, "Got:  %s\n", outp_str);
		exit(1);
	  }
	  fprintf(stderr, ".");
	}
  }

  fprintf(stderr, "\nAll HMAC test vectors succeeded\n\n");
  return(0);
}

/*
 * See: http://csrc.nist.gov/groups/STM/cavp/documents/mac/hmactestvectors.zip
 */
static int
nist_hmac_test(char *filename)
{
  FILE *fp;

  if ((fp = fopen(filename, "r")) == NULL) {
	fprintf(stderr, "Cannot open \"%s\"\n", filename);
	return(0);
  }

  fprintf(stderr, "\nDoing NIST HMAC test vector tests:\n");
  fprintf(stderr,
		  "(see http://csrc.nist.gov/groups/STM/cavp/documents/mac/hmactestvectors.zip)\n");

  if (do_hmac_tests(fp) == -1)
	exit(1);

  fclose(fp);
  fprintf(stderr, "All HMAC test vectors succeeded\n");
  return(0);
}

static Crypt_keys *
get_keys(char *filename)
{
  char *buf;
  Crypt_keys *ck;

  if (load_file("tests/test_keys", &buf, NULL) == -1)
	return(NULL);

  if (parse_xml_crypt_keys(buf, &ck) == -1)
	return(NULL);

  free(buf);

  return(ck);
}

static int
write_privatekey(Crypt_keys *ck, char *filename, const EVP_CIPHER *enc,
				 unsigned char *kstr, int klen, pem_password_cb callback,
				 void *cb_arg)
{
  FILE *fp;

  if ((fp = fopen(filename, "w")) == NULL) {
	log_err((LOG_ERROR_LEVEL, "write_privatekey: \"%s\"", filename));
	return(-1);
  }

  if (PEM_write_RSAPrivateKey(fp, ck->private_key->pkey.rsa,
							  enc, kstr, klen, callback, cb_arg) == 0) {
	crypto_log_error();
	return(-1);
  }

  fclose(fp);
  return(0);
}

static int
write_publickey(Crypt_keys *ck, char *filename)
{
  FILE *fp;

  if ((fp = fopen(filename, "w")) == NULL) {
	log_err((LOG_ERROR_LEVEL, "write_publickey: \"%s\"", filename));
	return(-1);
  }

  if (PEM_write_RSAPublicKey(fp, ck->public_key->pkey.rsa) == 0) {
	crypto_log_error();
	return(-1);
  }

  fclose(fp);
  return(0);
}

typedef struct Pbkdf2_test {
  unsigned char *pwd;
  int pwdlen;
  unsigned char *salt;
  int saltlen;
  unsigned int count;
  unsigned int dklen;
  char *result;
} Pbkdf2_test;

static unsigned char pbkdf2_salt1[] = {
  0x12, 0x34, 0x56, 0x78, 0x78, 0x56, 0x34, 0x12
};

static unsigned char pbkdf2_g_clef[] = {
  0xf0, 0x9d, 0x84, 0x9e
};

typedef unsigned char *UC;

static Pbkdf2_test pbkdf2_vec[] = {
  /*  1 */
  { (UC) "password", -1,
	(unsigned char *) "ATHENA.MIT.EDUraeburn", -1, 1, 128 / 8,
	"cdedb5281bb2f801565a1122b2563515" },
  /*  2 */
  { (unsigned char *) "password", -1,
	(unsigned char *) "ATHENA.MIT.EDUraeburn", -1, 1, 256 / 8,
	"cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837" },
  /*  3 */
  { (unsigned char *) "password", -1,
	(unsigned char *) "ATHENA.MIT.EDUraeburn", -1, 2, 128 / 8,
	"01dbee7f4a9e243e988b62c73cda935d" },
  /*  4 */
  { (unsigned char *) "password", -1,
	(unsigned char *) "ATHENA.MIT.EDUraeburn", -1, 2, 256 / 8,
	"01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86" },
  /*  5 */
  { (unsigned char *) "password", -1,
	(unsigned char *) "ATHENA.MIT.EDUraeburn", -1, 1200, 128 / 8,
	"5c08eb61fdf71e4e4ec3cf6ba1f5512b" },
  /*  6 */
  { (unsigned char *) "password", -1,
	(unsigned char *) "ATHENA.MIT.EDUraeburn", -1, 1200, 256 / 8,
	"5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13" },
  /*  7 */
  { (unsigned char *) "password", -1,
	pbkdf2_salt1, sizeof(pbkdf2_salt1), 5, 128 / 8,
	"d1daa78615f287e6a1c8b120d7062a49" },
  /*  8 */
  { (unsigned char *) "password", -1,
	pbkdf2_salt1, sizeof(pbkdf2_salt1), 5, 256 / 8,
	"d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee" },
  /*  9 */
  { (unsigned char *)
	"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", -1,
	(unsigned char *) "pass phrase equals block size", -1, 1200,  128 / 8,
	"139c30c0966bc32ba55fdbf212530ac9" },
  /* 10 */
  { (unsigned char *)
	"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", -1,
	(unsigned char *) "pass phrase equals block size", -1, 1200,  256 / 8,
	"139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1" },
  /* 11 */
  { (unsigned char *)
	"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", -1,
	(unsigned char *) "pass phrase exceeds block size", -1, 1200,  128 / 8,
	"9ccad6d468770cd51b10e6a68721be61" },
  /* 12 */
  { (unsigned char *)
	"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", -1,
	(unsigned char *) "pass phrase exceeds block size", -1, 1200,  256 / 8,
	"9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a" },
  /* 13 */
  { pbkdf2_g_clef, sizeof(pbkdf2_g_clef),
	(unsigned char *) "EXAMPLE.COMpianist", -1, 50, 128 / 8,
	"6b9cf26d45455a43a5b8bb276a403b39" },
  /* 14 */
  { pbkdf2_g_clef, sizeof(pbkdf2_g_clef),
	(unsigned char *) "EXAMPLE.COMpianist", -1, 50, 256 / 8,
	"6b9cf26d45455a43a5b8bb276a403b39e7fe37a0c41e02c281ff3069e1e94f52" },
  /* 15 */
  { (unsigned char *) "password", -1,
	(unsigned char *) "salt", -1, 1, 20,
	"0c60c80f961f0e71f3a9b524af6012062fe037a6" },
  /* 16 */
  { (unsigned char *) "password", -1,
	(unsigned char *) "salt", -1, 2, 20,
	"ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957" },
  /* 17 */
  { (unsigned char *) "password", -1,
	(unsigned char *) "salt", -1, 4096, 20,
	"4b007901b765489abead49d926f721d065a429c1" },
#ifdef NOTDEF
  /* Takes too long... */
  { (unsigned char *) "password", -1,
	(unsigned char *) "salt", -1, 16777216, 20,
	"eefe3d61cd4da4e4e9945b3d6ba2158c2634e984" },
#endif
  { NULL, -1, NULL, -1, 0, 0, NULL }
};

static int
do_pbkdf2_test(int num, Pbkdf2_test *test)
{
  char *p;
  unsigned char *kdf2;

  kdf2 = crypto_pbkdf2(test->pwd, test->pwdlen, test->salt, test->saltlen,
					   test->count, test->dklen);
  p = strbtohex(kdf2, test->dklen, 0);
  fprintf(stderr, " %d. %s... ", num, p);
  if (!streq(p, test->result)) {
	fprintf(stderr, "Failed!\n");
	return(-1);
  }
  fprintf(stderr, "ok\n");
  return(0);
}

/*
 * This signature value was obtained using:
 *   echo "Please sign me" | openssl dgst -sha1 \
 *     -sign tests/private_test_key.pem -binary | gbase64
 */
static char *sign_test2_sig_value =
  "UjYbp3PDQK25XALNU+yl5sk9KC0nEQrvIfEX3atg/P8ITNUSC+hUG8T7q4mWqTVpQJAzes55gmHLm/t4IFmWLMyWJ1wtu6gSl18uXqJXRNA9dIuQ3DpXRaJ19SLMQEfp9HIPHZX/R/U3W2ZhsZb+UCz6ychVVefJuyXoZbQDoKY=";

int
main(int argc, char **argv)
{
  int i, declen, enclen;
  char *buf, *errmsg, *p;
  unsigned char *decp, *encp;
  Crypt_keys *ck;

  errmsg = NULL;
  if (dacs_init(DACS_STANDALONE, &argc, &argv, NULL, &errmsg) == -1) {
  fail:
    if (errmsg != NULL)
      fprintf(stderr, "crypto: %s\n", errmsg);

	exit(1);
  }

  fprintf(stderr, "Testing crypto...\n");

  fprintf(stderr, "Doing OAuth tests:\n");
  if (oauth_test() == -1)
	exit(1);
  fprintf(stderr, "All OAuth tests succeeded\n\n");

  /*
   * Test PBKDF2 against RFC 3962
   * and http://www.ietf.org/id/draft-josefsson-pbkdf2-test-vectors-00.txt
   */
  fprintf(stderr, "Doing PBKDF2 tests:\n");
  for (i = 0; pbkdf2_vec[i].pwd != NULL; i++) {
	if (do_pbkdf2_test(i + 1, pbkdf2_vec + i) == -1)
	  exit(1);
  }
  fprintf(stderr, "All PBKDF2 tests succeeded\n\n");

  fprintf(stderr, "Random IVs:\n");
  for (i = 0; i < 4; i++) {
	make_iv((unsigned char *) "hello, world", 20, &encp);
	fprintf(stderr, "%s", hexdump(encp, 20));
  }

  fprintf(stderr, "\n");
  hmac_test();
  nist_hmac_test("tests/HMAC.txt");
  rfc4231_hmac_test();

  fprintf(stderr, "Testing symmetric encryption... ");
  encp = crypto_encipher(CRYPTO_SYMMETRIC_ENCRYPT, CRYPTO_TEST_CIPHER_NAME,
						 (unsigned char *) "hello, world", NULL,  NULL,
						 (unsigned char *) "abcdefghijkl", 13, NULL, &enclen);
  decp = crypto_decipher(CRYPTO_SYMMETRIC_DECRYPT, CRYPTO_TEST_CIPHER_NAME,
						 (unsigned char *) "hello, world", NULL, NULL,
						 encp, enclen, NULL, &declen);
  p = ds_xprintf("*%s*%d", decp, declen);
  if (!streq(p, "*abcdefghijkl*13")) {
	fprintf(stderr, "Failed!\n");
	exit(1);
  }
  fprintf(stderr, "ok\n");

  if ((ck = get_keys("tests/test_keys")) == NULL)
	fprintf(stderr, "Cannot load test keys for PKI testing\n");
  else {
	int st;
	char *d_tmp, *e_tmp, *fn, *plain, *sig;
	unsigned char *iv;
	PKI_session_keys sk;

	/* Write the test keys in PEM format. */
	fprintf(stderr, "Writing test keys in PEM format... ");
	fn = "tests/private_test_key.pem";
	st = write_privatekey(ck, fn, NULL, NULL, 0, NULL, NULL);
	if (st == -1) {
	  fprintf(stderr, "Could not write private test key to %s\n", fn);
	  exit(1);
	}
	fn = "tests/public_test_key.pem";
	st = write_publickey(ck, fn);
	if (st == -1) {
	  fprintf(stderr, "Could not write public test key to %s\n", fn);
	  exit(1);
	}
	fprintf(stderr, "ok\n");

	crypto_signv_test(ck, "sha256", "Please sign me");

	fprintf(stderr, "Testing public key signature2... ");
	if ((sig = crypto_sign_test("Please sign me\n", ck, 0)) == NULL) {
	  fprintf(stderr, "Failed!\n");
	  exit(1);
	}
	if (!streq(sig, sign_test2_sig_value)) {
	  fprintf(stderr, "Failed!\n");
	  exit(1);
	}
	fprintf(stderr, "ok, sig=%s\n", sig);

	fprintf(stderr, "Testing public key encryption... ");
	plain = "abcdefghijkl";
	iv = NULL;
	encp = crypto_encipher(CRYPTO_PKI_ENCRYPT, CRYPTO_TEST_CIPHER_NAME,
						   (unsigned char *) ck->public_key, &iv, &sk,
						   (unsigned char *) plain, strlen(plain) + 1,
						   NULL, &enclen);
	strba64(encp, enclen, &e_tmp);
	decp = crypto_decipher(CRYPTO_PKI_DECRYPT, CRYPTO_TEST_CIPHER_NAME,
						   (unsigned char *) ck->private_key, &iv, &sk,
						   encp, enclen, NULL, &declen);
	if (declen != strlen(plain) + 1 || memcmp(plain, decp, declen)) {
	  fprintf(stderr, "Failed!\n");
	  exit(1);
	}
	fprintf(stderr, "ok\n");

	fprintf(stderr, "Testing public key encryption export/import... ");
	if ((st = crypto_pki_test(CRYPTO_TEST_CIPHER_NAME, ck->public_key,
							  ck->private_key, "tests/HMAC.txt")) == 1)
	  fprintf(stderr, "ok\n");
	else {
	  fprintf(stderr, "Failed!\n");
	  exit(1);
	}

  }

  fprintf(stderr, "All crypto tests succeeded!\n");

  exit(0);
}
#endif
