wpa_supplicant: Initial Revision 0.8.X

Based on:
commit 0725cc7b7efc434910e89865c42eda7ce61bbf08
Author: Jouni Malinen <j@w1.fi>
Date:   Thu Apr 21 20:41:01 2011 +0300

    Enable CONFIG_DRIVER_NL80211=y in the default configuration

    nl80211 should be preferred over WEXT with any recent Linux
    kernel version.

Change-Id: I26aec5afbbd4f4a1f5fd900912545b6f5050de64
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/src/tls/.gitignore b/src/tls/.gitignore
new file mode 100644
index 0000000..d43242d
--- /dev/null
+++ b/src/tls/.gitignore
@@ -0,0 +1 @@
+libtls.a
diff --git a/src/tls/Makefile b/src/tls/Makefile
new file mode 100644
index 0000000..a2da096
--- /dev/null
+++ b/src/tls/Makefile
@@ -0,0 +1,37 @@
+all: libtls.a
+
+clean:
+	rm -f *~ *.o *.d libtls.a
+
+install:
+	@echo Nothing to be made.
+
+
+include ../lib.rules
+
+CFLAGS += -DCONFIG_INTERNAL_LIBTOMMATH
+CFLAGS += -DCONFIG_CRYPTO_INTERNAL
+
+LIB_OBJS= \
+	asn1.o \
+	bignum.o \
+	pkcs1.o \
+	pkcs5.o \
+	pkcs8.o \
+	rsa.o \
+	tlsv1_client.o \
+	tlsv1_client_read.o \
+	tlsv1_client_write.o \
+	tlsv1_common.o \
+	tlsv1_cred.o \
+	tlsv1_record.o \
+	tlsv1_server.o \
+	tlsv1_server_read.o \
+	tlsv1_server_write.o \
+	x509v3.o
+
+
+libtls.a: $(LIB_OBJS)
+	$(AR) crT $@ $?
+
+-include $(OBJS:%.o=%.d)
diff --git a/src/tls/asn1.c b/src/tls/asn1.c
new file mode 100644
index 0000000..3391245
--- /dev/null
+++ b/src/tls/asn1.c
@@ -0,0 +1,212 @@
+/*
+ * ASN.1 DER parsing
+ * Copyright (c) 2006, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "asn1.h"
+
+int asn1_get_next(const u8 *buf, size_t len, struct asn1_hdr *hdr)
+{
+	const u8 *pos, *end;
+	u8 tmp;
+
+	os_memset(hdr, 0, sizeof(*hdr));
+	pos = buf;
+	end = buf + len;
+
+	hdr->identifier = *pos++;
+	hdr->class = hdr->identifier >> 6;
+	hdr->constructed = !!(hdr->identifier & (1 << 5));
+
+	if ((hdr->identifier & 0x1f) == 0x1f) {
+		hdr->tag = 0;
+		do {
+			if (pos >= end) {
+				wpa_printf(MSG_DEBUG, "ASN.1: Identifier "
+					   "underflow");
+				return -1;
+			}
+			tmp = *pos++;
+			wpa_printf(MSG_MSGDUMP, "ASN.1: Extended tag data: "
+				   "0x%02x", tmp);
+			hdr->tag = (hdr->tag << 7) | (tmp & 0x7f);
+		} while (tmp & 0x80);
+	} else
+		hdr->tag = hdr->identifier & 0x1f;
+
+	tmp = *pos++;
+	if (tmp & 0x80) {
+		if (tmp == 0xff) {
+			wpa_printf(MSG_DEBUG, "ASN.1: Reserved length "
+				   "value 0xff used");
+			return -1;
+		}
+		tmp &= 0x7f; /* number of subsequent octets */
+		hdr->length = 0;
+		if (tmp > 4) {
+			wpa_printf(MSG_DEBUG, "ASN.1: Too long length field");
+			return -1;
+		}
+		while (tmp--) {
+			if (pos >= end) {
+				wpa_printf(MSG_DEBUG, "ASN.1: Length "
+					   "underflow");
+				return -1;
+			}
+			hdr->length = (hdr->length << 8) | *pos++;
+		}
+	} else {
+		/* Short form - length 0..127 in one octet */
+		hdr->length = tmp;
+	}
+
+	if (end < pos || hdr->length > (unsigned int) (end - pos)) {
+		wpa_printf(MSG_DEBUG, "ASN.1: Contents underflow");
+		return -1;
+	}
+
+	hdr->payload = pos;
+	return 0;
+}
+
+
+int asn1_parse_oid(const u8 *buf, size_t len, struct asn1_oid *oid)
+{
+	const u8 *pos, *end;
+	unsigned long val;
+	u8 tmp;
+
+	os_memset(oid, 0, sizeof(*oid));
+
+	pos = buf;
+	end = buf + len;
+
+	while (pos < end) {
+		val = 0;
+
+		do {
+			if (pos >= end)
+				return -1;
+			tmp = *pos++;
+			val = (val << 7) | (tmp & 0x7f);
+		} while (tmp & 0x80);
+
+		if (oid->len >= ASN1_MAX_OID_LEN) {
+			wpa_printf(MSG_DEBUG, "ASN.1: Too long OID value");
+			return -1;
+		}
+		if (oid->len == 0) {
+			/*
+			 * The first octet encodes the first two object
+			 * identifier components in (X*40) + Y formula.
+			 * X = 0..2.
+			 */
+			oid->oid[0] = val / 40;
+			if (oid->oid[0] > 2)
+				oid->oid[0] = 2;
+			oid->oid[1] = val - oid->oid[0] * 40;
+			oid->len = 2;
+		} else
+			oid->oid[oid->len++] = val;
+	}
+
+	return 0;
+}
+
+
+int asn1_get_oid(const u8 *buf, size_t len, struct asn1_oid *oid,
+		 const u8 **next)
+{
+	struct asn1_hdr hdr;
+
+	if (asn1_get_next(buf, len, &hdr) < 0 || hdr.length == 0)
+		return -1;
+
+	if (hdr.class != ASN1_CLASS_UNIVERSAL || hdr.tag != ASN1_TAG_OID) {
+		wpa_printf(MSG_DEBUG, "ASN.1: Expected OID - found class %d "
+			   "tag 0x%x", hdr.class, hdr.tag);
+		return -1;
+	}
+
+	*next = hdr.payload + hdr.length;
+
+	return asn1_parse_oid(hdr.payload, hdr.length, oid);
+}
+
+
+void asn1_oid_to_str(struct asn1_oid *oid, char *buf, size_t len)
+{
+	char *pos = buf;
+	size_t i;
+	int ret;
+
+	if (len == 0)
+		return;
+
+	buf[0] = '\0';
+
+	for (i = 0; i < oid->len; i++) {
+		ret = os_snprintf(pos, buf + len - pos,
+				  "%s%lu",
+				  i == 0 ? "" : ".", oid->oid[i]);
+		if (ret < 0 || ret >= buf + len - pos)
+			break;
+		pos += ret;
+	}
+	buf[len - 1] = '\0';
+}
+
+
+static u8 rotate_bits(u8 octet)
+{
+	int i;
+	u8 res;
+
+	res = 0;
+	for (i = 0; i < 8; i++) {
+		res <<= 1;
+		if (octet & 1)
+			res |= 1;
+		octet >>= 1;
+	}
+
+	return res;
+}
+
+
+unsigned long asn1_bit_string_to_long(const u8 *buf, size_t len)
+{
+	unsigned long val = 0;
+	const u8 *pos = buf;
+
+	/* BER requires that unused bits are zero, so we can ignore the number
+	 * of unused bits */
+	pos++;
+
+	if (len >= 2)
+		val |= rotate_bits(*pos++);
+	if (len >= 3)
+		val |= ((unsigned long) rotate_bits(*pos++)) << 8;
+	if (len >= 4)
+		val |= ((unsigned long) rotate_bits(*pos++)) << 16;
+	if (len >= 5)
+		val |= ((unsigned long) rotate_bits(*pos++)) << 24;
+	if (len >= 6)
+		wpa_printf(MSG_DEBUG, "X509: %s - some bits ignored "
+			   "(BIT STRING length %lu)",
+			   __func__, (unsigned long) len);
+
+	return val;
+}
diff --git a/src/tls/asn1.h b/src/tls/asn1.h
new file mode 100644
index 0000000..2ff571e
--- /dev/null
+++ b/src/tls/asn1.h
@@ -0,0 +1,72 @@
+/*
+ * ASN.1 DER parsing
+ * Copyright (c) 2006, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef ASN1_H
+#define ASN1_H
+
+#define ASN1_TAG_EOC		0x00 /* not used with DER */
+#define ASN1_TAG_BOOLEAN	0x01
+#define ASN1_TAG_INTEGER	0x02
+#define ASN1_TAG_BITSTRING	0x03
+#define ASN1_TAG_OCTETSTRING	0x04
+#define ASN1_TAG_NULL		0x05
+#define ASN1_TAG_OID		0x06
+#define ASN1_TAG_OBJECT_DESCRIPTOR	0x07 /* not yet parsed */
+#define ASN1_TAG_EXTERNAL	0x08 /* not yet parsed */
+#define ASN1_TAG_REAL		0x09 /* not yet parsed */
+#define ASN1_TAG_ENUMERATED	0x0A /* not yet parsed */
+#define ASN1_TAG_UTF8STRING	0x0C /* not yet parsed */
+#define ANS1_TAG_RELATIVE_OID	0x0D
+#define ASN1_TAG_SEQUENCE	0x10 /* shall be constructed */
+#define ASN1_TAG_SET		0x11
+#define ASN1_TAG_NUMERICSTRING	0x12 /* not yet parsed */
+#define ASN1_TAG_PRINTABLESTRING	0x13
+#define ASN1_TAG_TG1STRING	0x14 /* not yet parsed */
+#define ASN1_TAG_VIDEOTEXSTRING	0x15 /* not yet parsed */
+#define ASN1_TAG_IA5STRING	0x16
+#define ASN1_TAG_UTCTIME	0x17
+#define ASN1_TAG_GENERALIZEDTIME	0x18 /* not yet parsed */
+#define ASN1_TAG_GRAPHICSTRING	0x19 /* not yet parsed */
+#define ASN1_TAG_VISIBLESTRING	0x1A
+#define ASN1_TAG_GENERALSTRING	0x1B /* not yet parsed */
+#define ASN1_TAG_UNIVERSALSTRING	0x1C /* not yet parsed */
+#define ASN1_TAG_BMPSTRING	0x1D /* not yet parsed */
+
+#define ASN1_CLASS_UNIVERSAL		0
+#define ASN1_CLASS_APPLICATION		1
+#define ASN1_CLASS_CONTEXT_SPECIFIC	2
+#define ASN1_CLASS_PRIVATE		3
+
+
+struct asn1_hdr {
+	const u8 *payload;
+	u8 identifier, class, constructed;
+	unsigned int tag, length;
+};
+
+#define ASN1_MAX_OID_LEN 20
+struct asn1_oid {
+	unsigned long oid[ASN1_MAX_OID_LEN];
+	size_t len;
+};
+
+
+int asn1_get_next(const u8 *buf, size_t len, struct asn1_hdr *hdr);
+int asn1_parse_oid(const u8 *buf, size_t len, struct asn1_oid *oid);
+int asn1_get_oid(const u8 *buf, size_t len, struct asn1_oid *oid,
+		 const u8 **next);
+void asn1_oid_to_str(struct asn1_oid *oid, char *buf, size_t len);
+unsigned long asn1_bit_string_to_long(const u8 *buf, size_t len);
+
+#endif /* ASN1_H */
diff --git a/src/tls/bignum.c b/src/tls/bignum.c
new file mode 100644
index 0000000..5c0fc62
--- /dev/null
+++ b/src/tls/bignum.c
@@ -0,0 +1,230 @@
+/*
+ * Big number math
+ * Copyright (c) 2006, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "bignum.h"
+
+#ifdef CONFIG_INTERNAL_LIBTOMMATH
+#include "libtommath.c"
+#else /* CONFIG_INTERNAL_LIBTOMMATH */
+#include <tommath.h>
+#endif /* CONFIG_INTERNAL_LIBTOMMATH */
+
+
+/*
+ * The current version is just a wrapper for LibTomMath library, so
+ * struct bignum is just typecast to mp_int.
+ */
+
+/**
+ * bignum_init - Allocate memory for bignum
+ * Returns: Pointer to allocated bignum or %NULL on failure
+ */
+struct bignum * bignum_init(void)
+{
+	struct bignum *n = os_zalloc(sizeof(mp_int));
+	if (n == NULL)
+		return NULL;
+	if (mp_init((mp_int *) n) != MP_OKAY) {
+		os_free(n);
+		n = NULL;
+	}
+	return n;
+}
+
+
+/**
+ * bignum_deinit - Free bignum
+ * @n: Bignum from bignum_init()
+ */
+void bignum_deinit(struct bignum *n)
+{
+	if (n) {
+		mp_clear((mp_int *) n);
+		os_free(n);
+	}
+}
+
+
+/**
+ * bignum_get_unsigned_bin - Get length of bignum as an unsigned binary buffer
+ * @n: Bignum from bignum_init()
+ * Returns: Length of n if written to a binary buffer
+ */
+size_t bignum_get_unsigned_bin_len(struct bignum *n)
+{
+	return mp_unsigned_bin_size((mp_int *) n);
+}
+
+
+/**
+ * bignum_get_unsigned_bin - Set binary buffer to unsigned bignum
+ * @n: Bignum from bignum_init()
+ * @buf: Buffer for the binary number
+ * @len: Length of the buffer, can be %NULL if buffer is known to be long
+ * enough. Set to used buffer length on success if not %NULL.
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_get_unsigned_bin(const struct bignum *n, u8 *buf, size_t *len)
+{
+	size_t need = mp_unsigned_bin_size((mp_int *) n);
+	if (len && need > *len) {
+		*len = need;
+		return -1;
+	}
+	if (mp_to_unsigned_bin((mp_int *) n, buf) != MP_OKAY) {
+		wpa_printf(MSG_DEBUG, "BIGNUM: %s failed", __func__);
+		return -1;
+	}
+	if (len)
+		*len = need;
+	return 0;
+}
+
+
+/**
+ * bignum_set_unsigned_bin - Set bignum based on unsigned binary buffer
+ * @n: Bignum from bignum_init(); to be set to the given value
+ * @buf: Buffer with unsigned binary value
+ * @len: Length of buf in octets
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_set_unsigned_bin(struct bignum *n, const u8 *buf, size_t len)
+{
+	if (mp_read_unsigned_bin((mp_int *) n, (u8 *) buf, len) != MP_OKAY) {
+		wpa_printf(MSG_DEBUG, "BIGNUM: %s failed", __func__);
+		return -1;
+	}
+	return 0;
+}
+
+
+/**
+ * bignum_cmp - Signed comparison
+ * @a: Bignum from bignum_init()
+ * @b: Bignum from bignum_init()
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_cmp(const struct bignum *a, const struct bignum *b)
+{
+	return mp_cmp((mp_int *) a, (mp_int *) b);
+}
+
+
+/**
+ * bignum_cmd_d - Compare bignum to standard integer
+ * @a: Bignum from bignum_init()
+ * @b: Small integer
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_cmp_d(const struct bignum *a, unsigned long b)
+{
+	return mp_cmp_d((mp_int *) a, b);
+}
+
+
+/**
+ * bignum_add - c = a + b
+ * @a: Bignum from bignum_init()
+ * @b: Bignum from bignum_init()
+ * @c: Bignum from bignum_init(); used to store the result of a + b
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_add(const struct bignum *a, const struct bignum *b,
+	       struct bignum *c)
+{
+	if (mp_add((mp_int *) a, (mp_int *) b, (mp_int *) c) != MP_OKAY) {
+		wpa_printf(MSG_DEBUG, "BIGNUM: %s failed", __func__);
+		return -1;
+	}
+	return 0;
+}
+
+
+/**
+ * bignum_sub - c = a - b
+ * @a: Bignum from bignum_init()
+ * @b: Bignum from bignum_init()
+ * @c: Bignum from bignum_init(); used to store the result of a - b
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_sub(const struct bignum *a, const struct bignum *b,
+	       struct bignum *c)
+{
+	if (mp_sub((mp_int *) a, (mp_int *) b, (mp_int *) c) != MP_OKAY) {
+		wpa_printf(MSG_DEBUG, "BIGNUM: %s failed", __func__);
+		return -1;
+	}
+	return 0;
+}
+
+
+/**
+ * bignum_mul - c = a * b
+ * @a: Bignum from bignum_init()
+ * @b: Bignum from bignum_init()
+ * @c: Bignum from bignum_init(); used to store the result of a * b
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_mul(const struct bignum *a, const struct bignum *b,
+	       struct bignum *c)
+{
+	if (mp_mul((mp_int *) a, (mp_int *) b, (mp_int *) c) != MP_OKAY) {
+		wpa_printf(MSG_DEBUG, "BIGNUM: %s failed", __func__);
+		return -1;
+	}
+	return 0;
+}
+
+
+/**
+ * bignum_mulmod - d = a * b (mod c)
+ * @a: Bignum from bignum_init()
+ * @b: Bignum from bignum_init()
+ * @c: Bignum from bignum_init(); modulus
+ * @d: Bignum from bignum_init(); used to store the result of a * b (mod c)
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_mulmod(const struct bignum *a, const struct bignum *b,
+		  const struct bignum *c, struct bignum *d)
+{
+	if (mp_mulmod((mp_int *) a, (mp_int *) b, (mp_int *) c, (mp_int *) d)
+	    != MP_OKAY) {
+		wpa_printf(MSG_DEBUG, "BIGNUM: %s failed", __func__);
+		return -1;
+	}
+	return 0;
+}
+
+
+/**
+ * bignum_exptmod - Modular exponentiation: d = a^b (mod c)
+ * @a: Bignum from bignum_init(); base
+ * @b: Bignum from bignum_init(); exponent
+ * @c: Bignum from bignum_init(); modulus
+ * @d: Bignum from bignum_init(); used to store the result of a^b (mod c)
+ * Returns: 0 on success, -1 on failure
+ */
+int bignum_exptmod(const struct bignum *a, const struct bignum *b,
+		   const struct bignum *c, struct bignum *d)
+{
+	if (mp_exptmod((mp_int *) a, (mp_int *) b, (mp_int *) c, (mp_int *) d)
+	    != MP_OKAY) {
+		wpa_printf(MSG_DEBUG, "BIGNUM: %s failed", __func__);
+		return -1;
+	}
+	return 0;
+}
diff --git a/src/tls/bignum.h b/src/tls/bignum.h
new file mode 100644
index 0000000..f25e267
--- /dev/null
+++ b/src/tls/bignum.h
@@ -0,0 +1,38 @@
+/*
+ * Big number math
+ * Copyright (c) 2006, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef BIGNUM_H
+#define BIGNUM_H
+
+struct bignum;
+
+struct bignum * bignum_init(void);
+void bignum_deinit(struct bignum *n);
+size_t bignum_get_unsigned_bin_len(struct bignum *n);
+int bignum_get_unsigned_bin(const struct bignum *n, u8 *buf, size_t *len);
+int bignum_set_unsigned_bin(struct bignum *n, const u8 *buf, size_t len);
+int bignum_cmp(const struct bignum *a, const struct bignum *b);
+int bignum_cmp_d(const struct bignum *a, unsigned long b);
+int bignum_add(const struct bignum *a, const struct bignum *b,
+	       struct bignum *c);
+int bignum_sub(const struct bignum *a, const struct bignum *b,
+	       struct bignum *c);
+int bignum_mul(const struct bignum *a, const struct bignum *b,
+	       struct bignum *c);
+int bignum_mulmod(const struct bignum *a, const struct bignum *b,
+		  const struct bignum *c, struct bignum *d);
+int bignum_exptmod(const struct bignum *a, const struct bignum *b,
+		   const struct bignum *c, struct bignum *d);
+
+#endif /* BIGNUM_H */
diff --git a/src/tls/libtommath.c b/src/tls/libtommath.c
new file mode 100644
index 0000000..1374264
--- /dev/null
+++ b/src/tls/libtommath.c
@@ -0,0 +1,3381 @@
+/*
+ * Minimal code for RSA support from LibTomMath 0.41
+ * http://libtom.org/
+ * http://libtom.org/files/ltm-0.41.tar.bz2
+ * This library was released in public domain by Tom St Denis.
+ *
+ * The combination in this file may not use all of the optimized algorithms
+ * from LibTomMath and may be considerable slower than the LibTomMath with its
+ * default settings. The main purpose of having this version here is to make it
+ * easier to build bignum.c wrapper without having to install and build an
+ * external library.
+ *
+ * If CONFIG_INTERNAL_LIBTOMMATH is defined, bignum.c includes this
+ * libtommath.c file instead of using the external LibTomMath library.
+ */
+
+#ifndef CHAR_BIT
+#define CHAR_BIT 8
+#endif
+
+#define BN_MP_INVMOD_C
+#define BN_S_MP_EXPTMOD_C /* Note: #undef in tommath_superclass.h; this would
+			   * require BN_MP_EXPTMOD_FAST_C instead */
+#define BN_S_MP_MUL_DIGS_C
+#define BN_MP_INVMOD_SLOW_C
+#define BN_S_MP_SQR_C
+#define BN_S_MP_MUL_HIGH_DIGS_C /* Note: #undef in tommath_superclass.h; this
+				 * would require other than mp_reduce */
+
+#ifdef LTM_FAST
+
+/* Use faster div at the cost of about 1 kB */
+#define BN_MP_MUL_D_C
+
+/* Include faster exptmod (Montgomery) at the cost of about 2.5 kB in code */
+#define BN_MP_EXPTMOD_FAST_C
+#define BN_MP_MONTGOMERY_SETUP_C
+#define BN_FAST_MP_MONTGOMERY_REDUCE_C
+#define BN_MP_MONTGOMERY_CALC_NORMALIZATION_C
+#define BN_MP_MUL_2_C
+
+/* Include faster sqr at the cost of about 0.5 kB in code */
+#define BN_FAST_S_MP_SQR_C
+
+#else /* LTM_FAST */
+
+#define BN_MP_DIV_SMALL
+#define BN_MP_INIT_MULTI_C
+#define BN_MP_CLEAR_MULTI_C
+#define BN_MP_ABS_C
+#endif /* LTM_FAST */
+
+/* Current uses do not require support for negative exponent in exptmod, so we
+ * can save about 1.5 kB in leaving out invmod. */
+#define LTM_NO_NEG_EXP
+
+/* from tommath.h */
+
+#ifndef MIN
+   #define MIN(x,y) ((x)<(y)?(x):(y))
+#endif
+
+#ifndef MAX
+   #define MAX(x,y) ((x)>(y)?(x):(y))
+#endif
+
+#define  OPT_CAST(x)
+
+typedef unsigned long mp_digit;
+typedef u64 mp_word;
+
+#define DIGIT_BIT          28
+#define MP_28BIT
+
+
+#define XMALLOC  os_malloc
+#define XFREE    os_free
+#define XREALLOC os_realloc
+
+
+#define MP_MASK          ((((mp_digit)1)<<((mp_digit)DIGIT_BIT))-((mp_digit)1))
+
+#define MP_LT        -1   /* less than */
+#define MP_EQ         0   /* equal to */
+#define MP_GT         1   /* greater than */
+
+#define MP_ZPOS       0   /* positive integer */
+#define MP_NEG        1   /* negative */
+
+#define MP_OKAY       0   /* ok result */
+#define MP_MEM        -2  /* out of mem */
+#define MP_VAL        -3  /* invalid input */
+
+#define MP_YES        1   /* yes response */
+#define MP_NO         0   /* no response */
+
+typedef int           mp_err;
+
+/* define this to use lower memory usage routines (exptmods mostly) */
+#define MP_LOW_MEM
+
+/* default precision */
+#ifndef MP_PREC
+   #ifndef MP_LOW_MEM
+      #define MP_PREC                 32     /* default digits of precision */
+   #else
+      #define MP_PREC                 8      /* default digits of precision */
+   #endif   
+#endif
+
+/* size of comba arrays, should be at least 2 * 2**(BITS_PER_WORD - BITS_PER_DIGIT*2) */
+#define MP_WARRAY               (1 << (sizeof(mp_word) * CHAR_BIT - 2 * DIGIT_BIT + 1))
+
+/* the infamous mp_int structure */
+typedef struct  {
+    int used, alloc, sign;
+    mp_digit *dp;
+} mp_int;
+
+
+/* ---> Basic Manipulations <--- */
+#define mp_iszero(a) (((a)->used == 0) ? MP_YES : MP_NO)
+#define mp_iseven(a) (((a)->used > 0 && (((a)->dp[0] & 1) == 0)) ? MP_YES : MP_NO)
+#define mp_isodd(a)  (((a)->used > 0 && (((a)->dp[0] & 1) == 1)) ? MP_YES : MP_NO)
+
+
+/* prototypes for copied functions */
+#define s_mp_mul(a, b, c) s_mp_mul_digs(a, b, c, (a)->used + (b)->used + 1)
+static int s_mp_exptmod(mp_int * G, mp_int * X, mp_int * P, mp_int * Y, int redmode);
+static int s_mp_mul_digs (mp_int * a, mp_int * b, mp_int * c, int digs);
+static int s_mp_sqr(mp_int * a, mp_int * b);
+static int s_mp_mul_high_digs(mp_int * a, mp_int * b, mp_int * c, int digs);
+
+static int fast_s_mp_mul_digs (mp_int * a, mp_int * b, mp_int * c, int digs);
+
+#ifdef BN_MP_INIT_MULTI_C
+static int mp_init_multi(mp_int *mp, ...);
+#endif
+#ifdef BN_MP_CLEAR_MULTI_C
+static void mp_clear_multi(mp_int *mp, ...);
+#endif
+static int mp_lshd(mp_int * a, int b);
+static void mp_set(mp_int * a, mp_digit b);
+static void mp_clamp(mp_int * a);
+static void mp_exch(mp_int * a, mp_int * b);
+static void mp_rshd(mp_int * a, int b);
+static void mp_zero(mp_int * a);
+static int mp_mod_2d(mp_int * a, int b, mp_int * c);
+static int mp_div_2d(mp_int * a, int b, mp_int * c, mp_int * d);
+static int mp_init_copy(mp_int * a, mp_int * b);
+static int mp_mul_2d(mp_int * a, int b, mp_int * c);
+#ifndef LTM_NO_NEG_EXP
+static int mp_div_2(mp_int * a, mp_int * b);
+static int mp_invmod(mp_int * a, mp_int * b, mp_int * c);
+static int mp_invmod_slow(mp_int * a, mp_int * b, mp_int * c);
+#endif /* LTM_NO_NEG_EXP */
+static int mp_copy(mp_int * a, mp_int * b);
+static int mp_count_bits(mp_int * a);
+static int mp_div(mp_int * a, mp_int * b, mp_int * c, mp_int * d);
+static int mp_mod(mp_int * a, mp_int * b, mp_int * c);
+static int mp_grow(mp_int * a, int size);
+static int mp_cmp_mag(mp_int * a, mp_int * b);
+#ifdef BN_MP_ABS_C
+static int mp_abs(mp_int * a, mp_int * b);
+#endif
+static int mp_sqr(mp_int * a, mp_int * b);
+static int mp_reduce_2k_l(mp_int *a, mp_int *n, mp_int *d);
+static int mp_reduce_2k_setup_l(mp_int *a, mp_int *d);
+static int mp_2expt(mp_int * a, int b);
+static int mp_reduce_setup(mp_int * a, mp_int * b);
+static int mp_reduce(mp_int * x, mp_int * m, mp_int * mu);
+static int mp_init_size(mp_int * a, int size);
+#ifdef BN_MP_EXPTMOD_FAST_C
+static int mp_exptmod_fast (mp_int * G, mp_int * X, mp_int * P, mp_int * Y, int redmode);
+#endif /* BN_MP_EXPTMOD_FAST_C */
+#ifdef BN_FAST_S_MP_SQR_C
+static int fast_s_mp_sqr (mp_int * a, mp_int * b);
+#endif /* BN_FAST_S_MP_SQR_C */
+#ifdef BN_MP_MUL_D_C
+static int mp_mul_d (mp_int * a, mp_digit b, mp_int * c);
+#endif /* BN_MP_MUL_D_C */
+
+
+
+/* functions from bn_<func name>.c */
+
+
+/* reverse an array, used for radix code */
+static void bn_reverse (unsigned char *s, int len)
+{
+  int     ix, iy;
+  unsigned char t;
+
+  ix = 0;
+  iy = len - 1;
+  while (ix < iy) {
+    t     = s[ix];
+    s[ix] = s[iy];
+    s[iy] = t;
+    ++ix;
+    --iy;
+  }
+}
+
+
+/* low level addition, based on HAC pp.594, Algorithm 14.7 */
+static int s_mp_add (mp_int * a, mp_int * b, mp_int * c)
+{
+  mp_int *x;
+  int     olduse, res, min, max;
+
+  /* find sizes, we let |a| <= |b| which means we have to sort
+   * them.  "x" will point to the input with the most digits
+   */
+  if (a->used > b->used) {
+    min = b->used;
+    max = a->used;
+    x = a;
+  } else {
+    min = a->used;
+    max = b->used;
+    x = b;
+  }
+
+  /* init result */
+  if (c->alloc < max + 1) {
+    if ((res = mp_grow (c, max + 1)) != MP_OKAY) {
+      return res;
+    }
+  }
+
+  /* get old used digit count and set new one */
+  olduse = c->used;
+  c->used = max + 1;
+
+  {
+    register mp_digit u, *tmpa, *tmpb, *tmpc;
+    register int i;
+
+    /* alias for digit pointers */
+
+    /* first input */
+    tmpa = a->dp;
+
+    /* second input */
+    tmpb = b->dp;
+
+    /* destination */
+    tmpc = c->dp;
+
+    /* zero the carry */
+    u = 0;
+    for (i = 0; i < min; i++) {
+      /* Compute the sum at one digit, T[i] = A[i] + B[i] + U */
+      *tmpc = *tmpa++ + *tmpb++ + u;
+
+      /* U = carry bit of T[i] */
+      u = *tmpc >> ((mp_digit)DIGIT_BIT);
+
+      /* take away carry bit from T[i] */
+      *tmpc++ &= MP_MASK;
+    }
+
+    /* now copy higher words if any, that is in A+B 
+     * if A or B has more digits add those in 
+     */
+    if (min != max) {
+      for (; i < max; i++) {
+        /* T[i] = X[i] + U */
+        *tmpc = x->dp[i] + u;
+
+        /* U = carry bit of T[i] */
+        u = *tmpc >> ((mp_digit)DIGIT_BIT);
+
+        /* take away carry bit from T[i] */
+        *tmpc++ &= MP_MASK;
+      }
+    }
+
+    /* add carry */
+    *tmpc++ = u;
+
+    /* clear digits above oldused */
+    for (i = c->used; i < olduse; i++) {
+      *tmpc++ = 0;
+    }
+  }
+
+  mp_clamp (c);
+  return MP_OKAY;
+}
+
+
+/* low level subtraction (assumes |a| > |b|), HAC pp.595 Algorithm 14.9 */
+static int s_mp_sub (mp_int * a, mp_int * b, mp_int * c)
+{
+  int     olduse, res, min, max;
+
+  /* find sizes */
+  min = b->used;
+  max = a->used;
+
+  /* init result */
+  if (c->alloc < max) {
+    if ((res = mp_grow (c, max)) != MP_OKAY) {
+      return res;
+    }
+  }
+  olduse = c->used;
+  c->used = max;
+
+  {
+    register mp_digit u, *tmpa, *tmpb, *tmpc;
+    register int i;
+
+    /* alias for digit pointers */
+    tmpa = a->dp;
+    tmpb = b->dp;
+    tmpc = c->dp;
+
+    /* set carry to zero */
+    u = 0;
+    for (i = 0; i < min; i++) {
+      /* T[i] = A[i] - B[i] - U */
+      *tmpc = *tmpa++ - *tmpb++ - u;
+
+      /* U = carry bit of T[i]
+       * Note this saves performing an AND operation since
+       * if a carry does occur it will propagate all the way to the
+       * MSB.  As a result a single shift is enough to get the carry
+       */
+      u = *tmpc >> ((mp_digit)(CHAR_BIT * sizeof (mp_digit) - 1));
+
+      /* Clear carry from T[i] */
+      *tmpc++ &= MP_MASK;
+    }
+
+    /* now copy higher words if any, e.g. if A has more digits than B  */
+    for (; i < max; i++) {
+      /* T[i] = A[i] - U */
+      *tmpc = *tmpa++ - u;
+
+      /* U = carry bit of T[i] */
+      u = *tmpc >> ((mp_digit)(CHAR_BIT * sizeof (mp_digit) - 1));
+
+      /* Clear carry from T[i] */
+      *tmpc++ &= MP_MASK;
+    }
+
+    /* clear digits above used (since we may not have grown result above) */
+    for (i = c->used; i < olduse; i++) {
+      *tmpc++ = 0;
+    }
+  }
+
+  mp_clamp (c);
+  return MP_OKAY;
+}
+
+
+/* init a new mp_int */
+static int mp_init (mp_int * a)
+{
+  int i;
+
+  /* allocate memory required and clear it */
+  a->dp = OPT_CAST(mp_digit) XMALLOC (sizeof (mp_digit) * MP_PREC);
+  if (a->dp == NULL) {
+    return MP_MEM;
+  }
+
+  /* set the digits to zero */
+  for (i = 0; i < MP_PREC; i++) {
+      a->dp[i] = 0;
+  }
+
+  /* set the used to zero, allocated digits to the default precision
+   * and sign to positive */
+  a->used  = 0;
+  a->alloc = MP_PREC;
+  a->sign  = MP_ZPOS;
+
+  return MP_OKAY;
+}
+
+
+/* clear one (frees)  */
+static void mp_clear (mp_int * a)
+{
+  int i;
+
+  /* only do anything if a hasn't been freed previously */
+  if (a->dp != NULL) {
+    /* first zero the digits */
+    for (i = 0; i < a->used; i++) {
+        a->dp[i] = 0;
+    }
+
+    /* free ram */
+    XFREE(a->dp);
+
+    /* reset members to make debugging easier */
+    a->dp    = NULL;
+    a->alloc = a->used = 0;
+    a->sign  = MP_ZPOS;
+  }
+}
+
+
+/* high level addition (handles signs) */
+static int mp_add (mp_int * a, mp_int * b, mp_int * c)
+{
+  int     sa, sb, res;
+
+  /* get sign of both inputs */
+  sa = a->sign;
+  sb = b->sign;
+
+  /* handle two cases, not four */
+  if (sa == sb) {
+    /* both positive or both negative */
+    /* add their magnitudes, copy the sign */
+    c->sign = sa;
+    res = s_mp_add (a, b, c);
+  } else {
+    /* one positive, the other negative */
+    /* subtract the one with the greater magnitude from */
+    /* the one of the lesser magnitude.  The result gets */
+    /* the sign of the one with the greater magnitude. */
+    if (mp_cmp_mag (a, b) == MP_LT) {
+      c->sign = sb;
+      res = s_mp_sub (b, a, c);
+    } else {
+      c->sign = sa;
+      res = s_mp_sub (a, b, c);
+    }
+  }
+  return res;
+}
+
+
+/* high level subtraction (handles signs) */
+static int mp_sub (mp_int * a, mp_int * b, mp_int * c)
+{
+  int     sa, sb, res;
+
+  sa = a->sign;
+  sb = b->sign;
+
+  if (sa != sb) {
+    /* subtract a negative from a positive, OR */
+    /* subtract a positive from a negative. */
+    /* In either case, ADD their magnitudes, */
+    /* and use the sign of the first number. */
+    c->sign = sa;
+    res = s_mp_add (a, b, c);
+  } else {
+    /* subtract a positive from a positive, OR */
+    /* subtract a negative from a negative. */
+    /* First, take the difference between their */
+    /* magnitudes, then... */
+    if (mp_cmp_mag (a, b) != MP_LT) {
+      /* Copy the sign from the first */
+      c->sign = sa;
+      /* The first has a larger or equal magnitude */
+      res = s_mp_sub (a, b, c);
+    } else {
+      /* The result has the *opposite* sign from */
+      /* the first number. */
+      c->sign = (sa == MP_ZPOS) ? MP_NEG : MP_ZPOS;
+      /* The second has a larger magnitude */
+      res = s_mp_sub (b, a, c);
+    }
+  }
+  return res;
+}
+
+
+/* high level multiplication (handles sign) */
+static int mp_mul (mp_int * a, mp_int * b, mp_int * c)
+{
+  int     res, neg;
+  neg = (a->sign == b->sign) ? MP_ZPOS : MP_NEG;
+
+  /* use Toom-Cook? */
+#ifdef BN_MP_TOOM_MUL_C
+  if (MIN (a->used, b->used) >= TOOM_MUL_CUTOFF) {
+    res = mp_toom_mul(a, b, c);
+  } else 
+#endif
+#ifdef BN_MP_KARATSUBA_MUL_C
+  /* use Karatsuba? */
+  if (MIN (a->used, b->used) >= KARATSUBA_MUL_CUTOFF) {
+    res = mp_karatsuba_mul (a, b, c);
+  } else 
+#endif
+  {
+    /* can we use the fast multiplier?
+     *
+     * The fast multiplier can be used if the output will 
+     * have less than MP_WARRAY digits and the number of 
+     * digits won't affect carry propagation
+     */
+#ifdef BN_FAST_S_MP_MUL_DIGS_C
+    int     digs = a->used + b->used + 1;
+
+    if ((digs < MP_WARRAY) &&
+        MIN(a->used, b->used) <= 
+        (1 << ((CHAR_BIT * sizeof (mp_word)) - (2 * DIGIT_BIT)))) {
+      res = fast_s_mp_mul_digs (a, b, c, digs);
+    } else 
+#endif
+#ifdef BN_S_MP_MUL_DIGS_C
+      res = s_mp_mul (a, b, c); /* uses s_mp_mul_digs */
+#else
+#error mp_mul could fail
+      res = MP_VAL;
+#endif
+
+  }
+  c->sign = (c->used > 0) ? neg : MP_ZPOS;
+  return res;
+}
+
+
+/* d = a * b (mod c) */
+static int mp_mulmod (mp_int * a, mp_int * b, mp_int * c, mp_int * d)
+{
+  int     res;
+  mp_int  t;
+
+  if ((res = mp_init (&t)) != MP_OKAY) {
+    return res;
+  }
+
+  if ((res = mp_mul (a, b, &t)) != MP_OKAY) {
+    mp_clear (&t);
+    return res;
+  }
+  res = mp_mod (&t, c, d);
+  mp_clear (&t);
+  return res;
+}
+
+
+/* c = a mod b, 0 <= c < b */
+static int mp_mod (mp_int * a, mp_int * b, mp_int * c)
+{
+  mp_int  t;
+  int     res;
+
+  if ((res = mp_init (&t)) != MP_OKAY) {
+    return res;
+  }
+
+  if ((res = mp_div (a, b, NULL, &t)) != MP_OKAY) {
+    mp_clear (&t);
+    return res;
+  }
+
+  if (t.sign != b->sign) {
+    res = mp_add (b, &t, c);
+  } else {
+    res = MP_OKAY;
+    mp_exch (&t, c);
+  }
+
+  mp_clear (&t);
+  return res;
+}
+
+
+/* this is a shell function that calls either the normal or Montgomery
+ * exptmod functions.  Originally the call to the montgomery code was
+ * embedded in the normal function but that wasted alot of stack space
+ * for nothing (since 99% of the time the Montgomery code would be called)
+ */
+static int mp_exptmod (mp_int * G, mp_int * X, mp_int * P, mp_int * Y)
+{
+  int dr;
+
+  /* modulus P must be positive */
+  if (P->sign == MP_NEG) {
+     return MP_VAL;
+  }
+
+  /* if exponent X is negative we have to recurse */
+  if (X->sign == MP_NEG) {
+#ifdef LTM_NO_NEG_EXP
+        return MP_VAL;
+#else /* LTM_NO_NEG_EXP */
+#ifdef BN_MP_INVMOD_C
+     mp_int tmpG, tmpX;
+     int err;
+
+     /* first compute 1/G mod P */
+     if ((err = mp_init(&tmpG)) != MP_OKAY) {
+        return err;
+     }
+     if ((err = mp_invmod(G, P, &tmpG)) != MP_OKAY) {
+        mp_clear(&tmpG);
+        return err;
+     }
+
+     /* now get |X| */
+     if ((err = mp_init(&tmpX)) != MP_OKAY) {
+        mp_clear(&tmpG);
+        return err;
+     }
+     if ((err = mp_abs(X, &tmpX)) != MP_OKAY) {
+        mp_clear_multi(&tmpG, &tmpX, NULL);
+        return err;
+     }
+
+     /* and now compute (1/G)**|X| instead of G**X [X < 0] */
+     err = mp_exptmod(&tmpG, &tmpX, P, Y);
+     mp_clear_multi(&tmpG, &tmpX, NULL);
+     return err;
+#else 
+#error mp_exptmod would always fail
+     /* no invmod */
+     return MP_VAL;
+#endif
+#endif /* LTM_NO_NEG_EXP */
+  }
+
+/* modified diminished radix reduction */
+#if defined(BN_MP_REDUCE_IS_2K_L_C) && defined(BN_MP_REDUCE_2K_L_C) && defined(BN_S_MP_EXPTMOD_C)
+  if (mp_reduce_is_2k_l(P) == MP_YES) {
+     return s_mp_exptmod(G, X, P, Y, 1);
+  }
+#endif
+
+#ifdef BN_MP_DR_IS_MODULUS_C
+  /* is it a DR modulus? */
+  dr = mp_dr_is_modulus(P);
+#else
+  /* default to no */
+  dr = 0;
+#endif
+
+#ifdef BN_MP_REDUCE_IS_2K_C
+  /* if not, is it a unrestricted DR modulus? */
+  if (dr == 0) {
+     dr = mp_reduce_is_2k(P) << 1;
+  }
+#endif
+    
+  /* if the modulus is odd or dr != 0 use the montgomery method */
+#ifdef BN_MP_EXPTMOD_FAST_C
+  if (mp_isodd (P) == 1 || dr !=  0) {
+    return mp_exptmod_fast (G, X, P, Y, dr);
+  } else {
+#endif
+#ifdef BN_S_MP_EXPTMOD_C
+    /* otherwise use the generic Barrett reduction technique */
+    return s_mp_exptmod (G, X, P, Y, 0);
+#else
+#error mp_exptmod could fail
+    /* no exptmod for evens */
+    return MP_VAL;
+#endif
+#ifdef BN_MP_EXPTMOD_FAST_C
+  }
+#endif
+}
+
+
+/* compare two ints (signed)*/
+static int mp_cmp (mp_int * a, mp_int * b)
+{
+  /* compare based on sign */
+  if (a->sign != b->sign) {
+     if (a->sign == MP_NEG) {
+        return MP_LT;
+     } else {
+        return MP_GT;
+     }
+  }
+  
+  /* compare digits */
+  if (a->sign == MP_NEG) {
+     /* if negative compare opposite direction */
+     return mp_cmp_mag(b, a);
+  } else {
+     return mp_cmp_mag(a, b);
+  }
+}
+
+
+/* compare a digit */
+static int mp_cmp_d(mp_int * a, mp_digit b)
+{
+  /* compare based on sign */
+  if (a->sign == MP_NEG) {
+    return MP_LT;
+  }
+
+  /* compare based on magnitude */
+  if (a->used > 1) {
+    return MP_GT;
+  }
+
+  /* compare the only digit of a to b */
+  if (a->dp[0] > b) {
+    return MP_GT;
+  } else if (a->dp[0] < b) {
+    return MP_LT;
+  } else {
+    return MP_EQ;
+  }
+}
+
+
+#ifndef LTM_NO_NEG_EXP
+/* hac 14.61, pp608 */
+static int mp_invmod (mp_int * a, mp_int * b, mp_int * c)
+{
+  /* b cannot be negative */
+  if (b->sign == MP_NEG || mp_iszero(b) == 1) {
+    return MP_VAL;
+  }
+
+#ifdef BN_FAST_MP_INVMOD_C
+  /* if the modulus is odd we can use a faster routine instead */
+  if (mp_isodd (b) == 1) {
+    return fast_mp_invmod (a, b, c);
+  }
+#endif
+
+#ifdef BN_MP_INVMOD_SLOW_C
+  return mp_invmod_slow(a, b, c);
+#endif
+
+#ifndef BN_FAST_MP_INVMOD_C
+#ifndef BN_MP_INVMOD_SLOW_C
+#error mp_invmod would always fail
+#endif
+#endif
+  return MP_VAL;
+}
+#endif /* LTM_NO_NEG_EXP */
+
+
+/* get the size for an unsigned equivalent */
+static int mp_unsigned_bin_size (mp_int * a)
+{
+  int     size = mp_count_bits (a);
+  return (size / 8 + ((size & 7) != 0 ? 1 : 0));
+}
+
+
+#ifndef LTM_NO_NEG_EXP
+/* hac 14.61, pp608 */
+static int mp_invmod_slow (mp_int * a, mp_int * b, mp_int * c)
+{
+  mp_int  x, y, u, v, A, B, C, D;
+  int     res;
+
+  /* b cannot be negative */
+  if (b->sign == MP_NEG || mp_iszero(b) == 1) {
+    return MP_VAL;
+  }
+
+  /* init temps */
+  if ((res = mp_init_multi(&x, &y, &u, &v, 
+                           &A, &B, &C, &D, NULL)) != MP_OKAY) {
+     return res;
+  }
+
+  /* x = a, y = b */
+  if ((res = mp_mod(a, b, &x)) != MP_OKAY) {
+      goto LBL_ERR;
+  }
+  if ((res = mp_copy (b, &y)) != MP_OKAY) {
+    goto LBL_ERR;
+  }
+
+  /* 2. [modified] if x,y are both even then return an error! */
+  if (mp_iseven (&x) == 1 && mp_iseven (&y) == 1) {
+    res = MP_VAL;
+    goto LBL_ERR;
+  }
+
+  /* 3. u=x, v=y, A=1, B=0, C=0,D=1 */
+  if ((res = mp_copy (&x, &u)) != MP_OKAY) {
+    goto LBL_ERR;
+  }
+  if ((res = mp_copy (&y, &v)) != MP_OKAY) {
+    goto LBL_ERR;
+  }
+  mp_set (&A, 1);
+  mp_set (&D, 1);
+
+top:
+  /* 4.  while u is even do */
+  while (mp_iseven (&u) == 1) {
+    /* 4.1 u = u/2 */
+    if ((res = mp_div_2 (&u, &u)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+    /* 4.2 if A or B is odd then */
+    if (mp_isodd (&A) == 1 || mp_isodd (&B) == 1) {
+      /* A = (A+y)/2, B = (B-x)/2 */
+      if ((res = mp_add (&A, &y, &A)) != MP_OKAY) {
+         goto LBL_ERR;
+      }
+      if ((res = mp_sub (&B, &x, &B)) != MP_OKAY) {
+         goto LBL_ERR;
+      }
+    }
+    /* A = A/2, B = B/2 */
+    if ((res = mp_div_2 (&A, &A)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+    if ((res = mp_div_2 (&B, &B)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+  }
+
+  /* 5.  while v is even do */
+  while (mp_iseven (&v) == 1) {
+    /* 5.1 v = v/2 */
+    if ((res = mp_div_2 (&v, &v)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+    /* 5.2 if C or D is odd then */
+    if (mp_isodd (&C) == 1 || mp_isodd (&D) == 1) {
+      /* C = (C+y)/2, D = (D-x)/2 */
+      if ((res = mp_add (&C, &y, &C)) != MP_OKAY) {
+         goto LBL_ERR;
+      }
+      if ((res = mp_sub (&D, &x, &D)) != MP_OKAY) {
+         goto LBL_ERR;
+      }
+    }
+    /* C = C/2, D = D/2 */
+    if ((res = mp_div_2 (&C, &C)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+    if ((res = mp_div_2 (&D, &D)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+  }
+
+  /* 6.  if u >= v then */
+  if (mp_cmp (&u, &v) != MP_LT) {
+    /* u = u - v, A = A - C, B = B - D */
+    if ((res = mp_sub (&u, &v, &u)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+
+    if ((res = mp_sub (&A, &C, &A)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+
+    if ((res = mp_sub (&B, &D, &B)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+  } else {
+    /* v - v - u, C = C - A, D = D - B */
+    if ((res = mp_sub (&v, &u, &v)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+
+    if ((res = mp_sub (&C, &A, &C)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+
+    if ((res = mp_sub (&D, &B, &D)) != MP_OKAY) {
+      goto LBL_ERR;
+    }
+  }
+
+  /* if not zero goto step 4 */
+  if (mp_iszero (&u) == 0)
+    goto top;
+
+  /* now a = C, b = D, gcd == g*v */
+
+  /* if v != 1 then there is no inverse */
+  if (mp_cmp_d (&v, 1) != MP_EQ) {
+    res = MP_VAL;
+    goto LBL_ERR;
+  }
+
+  /* if its too low */
+  while (mp_cmp_d(&C, 0) == MP_LT) {
+      if ((res = mp_add(&C, b, &C)) != MP_OKAY) {
+         goto LBL_ERR;
+      }
+  }
+  
+  /* too big */
+  while (mp_cmp_mag(&C, b) != MP_LT) {
+      if ((res = mp_sub(&C, b, &C)) != MP_OKAY) {
+         goto LBL_ERR;
+      }
+  }
+  
+  /* C is now the inverse */
+  mp_exch (&C, c);
+  res = MP_OKAY;
+LBL_ERR:mp_clear_multi (&x, &y, &u, &v, &A, &B, &C, &D, NULL);
+  return res;
+}
+#endif /* LTM_NO_NEG_EXP */
+
+
+/* compare maginitude of two ints (unsigned) */
+static int mp_cmp_mag (mp_int * a, mp_int * b)
+{
+  int     n;
+  mp_digit *tmpa, *tmpb;
+
+  /* compare based on # of non-zero digits */
+  if (a->used > b->used) {
+    return MP_GT;
+  }
+  
+  if (a->used < b->used) {
+    return MP_LT;
+  }
+
+  /* alias for a */
+  tmpa = a->dp + (a->used - 1);
+
+  /* alias for b */
+  tmpb = b->dp + (a->used - 1);
+
+  /* compare based on digits  */
+  for (n = 0; n < a->used; ++n, --tmpa, --tmpb) {
+    if (*tmpa > *tmpb) {
+      return MP_GT;
+    }
+
+    if (*tmpa < *tmpb) {
+      return MP_LT;
+    }
+  }
+  return MP_EQ;
+}
+
+
+/* reads a unsigned char array, assumes the msb is stored first [big endian] */
+static int mp_read_unsigned_bin (mp_int * a, const unsigned char *b, int c)
+{
+  int     res;
+
+  /* make sure there are at least two digits */
+  if (a->alloc < 2) {
+     if ((res = mp_grow(a, 2)) != MP_OKAY) {
+        return res;
+     }
+  }
+
+  /* zero the int */
+  mp_zero (a);
+
+  /* read the bytes in */
+  while (c-- > 0) {
+    if ((res = mp_mul_2d (a, 8, a)) != MP_OKAY) {
+      return res;
+    }
+
+#ifndef MP_8BIT
+      a->dp[0] |= *b++;
+      a->used += 1;
+#else
+      a->dp[0] = (*b & MP_MASK);
+      a->dp[1] |= ((*b++ >> 7U) & 1);
+      a->used += 2;
+#endif
+  }
+  mp_clamp (a);
+  return MP_OKAY;
+}
+
+
+/* store in unsigned [big endian] format */
+static int mp_to_unsigned_bin (mp_int * a, unsigned char *b)
+{
+  int     x, res;
+  mp_int  t;
+
+  if ((res = mp_init_copy (&t, a)) != MP_OKAY) {
+    return res;
+  }
+
+  x = 0;
+  while (mp_iszero (&t) == 0) {
+#ifndef MP_8BIT
+      b[x++] = (unsigned char) (t.dp[0] & 255);
+#else
+      b[x++] = (unsigned char) (t.dp[0] | ((t.dp[1] & 0x01) << 7));
+#endif
+    if ((res = mp_div_2d (&t, 8, &t, NULL)) != MP_OKAY) {
+      mp_clear (&t);
+      return res;
+    }
+  }
+  bn_reverse (b, x);
+  mp_clear (&t);
+  return MP_OKAY;
+}
+
+
+/* shift right by a certain bit count (store quotient in c, optional remainder in d) */
+static int mp_div_2d (mp_int * a, int b, mp_int * c, mp_int * d)
+{
+  mp_digit D, r, rr;
+  int     x, res;
+  mp_int  t;
+
+
+  /* if the shift count is <= 0 then we do no work */
+  if (b <= 0) {
+    res = mp_copy (a, c);
+    if (d != NULL) {
+      mp_zero (d);
+    }
+    return res;
+  }
+
+  if ((res = mp_init (&t)) != MP_OKAY) {
+    return res;
+  }
+
+  /* get the remainder */
+  if (d != NULL) {
+    if ((res = mp_mod_2d (a, b, &t)) != MP_OKAY) {
+      mp_clear (&t);
+      return res;
+    }
+  }
+
+  /* copy */
+  if ((res = mp_copy (a, c)) != MP_OKAY) {
+    mp_clear (&t);
+    return res;
+  }
+
+  /* shift by as many digits in the bit count */
+  if (b >= (int)DIGIT_BIT) {
+    mp_rshd (c, b / DIGIT_BIT);
+  }
+
+  /* shift any bit count < DIGIT_BIT */
+  D = (mp_digit) (b % DIGIT_BIT);
+  if (D != 0) {
+    register mp_digit *tmpc, mask, shift;
+
+    /* mask */
+    mask = (((mp_digit)1) << D) - 1;
+
+    /* shift for lsb */
+    shift = DIGIT_BIT - D;
+
+    /* alias */
+    tmpc = c->dp + (c->used - 1);
+
+    /* carry */
+    r = 0;
+    for (x = c->used - 1; x >= 0; x--) {
+      /* get the lower  bits of this word in a temp */
+      rr = *tmpc & mask;
+
+      /* shift the current word and mix in the carry bits from the previous word */
+      *tmpc = (*tmpc >> D) | (r << shift);
+      --tmpc;
+
+      /* set the carry to the carry bits of the current word found above */
+      r = rr;
+    }
+  }
+  mp_clamp (c);
+  if (d != NULL) {
+    mp_exch (&t, d);
+  }
+  mp_clear (&t);
+  return MP_OKAY;
+}
+
+
+static int mp_init_copy (mp_int * a, mp_int * b)
+{
+  int     res;
+
+  if ((res = mp_init (a)) != MP_OKAY) {
+    return res;
+  }
+  return mp_copy (b, a);
+}
+
+
+/* set to zero */
+static void mp_zero (mp_int * a)
+{
+  int       n;
+  mp_digit *tmp;
+
+  a->sign = MP_ZPOS;
+  a->used = 0;
+
+  tmp = a->dp;
+  for (n = 0; n < a->alloc; n++) {
+     *tmp++ = 0;
+  }
+}
+
+
+/* copy, b = a */
+static int mp_copy (mp_int * a, mp_int * b)
+{
+  int     res, n;
+
+  /* if dst == src do nothing */
+  if (a == b) {
+    return MP_OKAY;
+  }
+
+  /* grow dest */
+  if (b->alloc < a->used) {
+     if ((res = mp_grow (b, a->used)) != MP_OKAY) {
+        return res;
+     }
+  }
+
+  /* zero b and copy the parameters over */
+  {
+    register mp_digit *tmpa, *tmpb;
+
+    /* pointer aliases */
+
+    /* source */
+    tmpa = a->dp;
+
+    /* destination */
+    tmpb = b->dp;
+
+    /* copy all the digits */
+    for (n = 0; n < a->used; n++) {
+      *tmpb++ = *tmpa++;
+    }
+
+    /* clear high digits */
+    for (; n < b->used; n++) {
+      *tmpb++ = 0;
+    }
+  }
+
+  /* copy used count and sign */
+  b->used = a->used;
+  b->sign = a->sign;
+  return MP_OKAY;
+}
+
+
+/* shift right a certain amount of digits */
+static void mp_rshd (mp_int * a, int b)
+{
+  int     x;
+
+  /* if b <= 0 then ignore it */
+  if (b <= 0) {
+    return;
+  }
+
+  /* if b > used then simply zero it and return */
+  if (a->used <= b) {
+    mp_zero (a);
+    return;
+  }
+
+  {
+    register mp_digit *bottom, *top;
+
+    /* shift the digits down */
+
+    /* bottom */
+    bottom = a->dp;
+
+    /* top [offset into digits] */
+    top = a->dp + b;
+
+    /* this is implemented as a sliding window where 
+     * the window is b-digits long and digits from 
+     * the top of the window are copied to the bottom
+     *
+     * e.g.
+
+     b-2 | b-1 | b0 | b1 | b2 | ... | bb |   ---->
+                 /\                   |      ---->
+                  \-------------------/      ---->
+     */
+    for (x = 0; x < (a->used - b); x++) {
+      *bottom++ = *top++;
+    }
+
+    /* zero the top digits */
+    for (; x < a->used; x++) {
+      *bottom++ = 0;
+    }
+  }
+  
+  /* remove excess digits */
+  a->used -= b;
+}
+
+
+/* swap the elements of two integers, for cases where you can't simply swap the 
+ * mp_int pointers around
+ */
+static void mp_exch (mp_int * a, mp_int * b)
+{
+  mp_int  t;
+
+  t  = *a;
+  *a = *b;
+  *b = t;
+}
+
+
+/* trim unused digits 
+ *
+ * This is used to ensure that leading zero digits are
+ * trimed and the leading "used" digit will be non-zero
+ * Typically very fast.  Also fixes the sign if there
+ * are no more leading digits
+ */
+static void mp_clamp (mp_int * a)
+{
+  /* decrease used while the most significant digit is
+   * zero.
+   */
+  while (a->used > 0 && a->dp[a->used - 1] == 0) {
+    --(a->used);
+  }
+
+  /* reset the sign flag if used == 0 */
+  if (a->used == 0) {
+    a->sign = MP_ZPOS;
+  }
+}
+
+
+/* grow as required */
+static int mp_grow (mp_int * a, int size)
+{
+  int     i;
+  mp_digit *tmp;
+
+  /* if the alloc size is smaller alloc more ram */
+  if (a->alloc < size) {
+    /* ensure there are always at least MP_PREC digits extra on top */
+    size += (MP_PREC * 2) - (size % MP_PREC);
+
+    /* reallocate the array a->dp
+     *
+     * We store the return in a temporary variable
+     * in case the operation failed we don't want
+     * to overwrite the dp member of a.
+     */
+    tmp = OPT_CAST(mp_digit) XREALLOC (a->dp, sizeof (mp_digit) * size);
+    if (tmp == NULL) {
+      /* reallocation failed but "a" is still valid [can be freed] */
+      return MP_MEM;
+    }
+
+    /* reallocation succeeded so set a->dp */
+    a->dp = tmp;
+
+    /* zero excess digits */
+    i        = a->alloc;
+    a->alloc = size;
+    for (; i < a->alloc; i++) {
+      a->dp[i] = 0;
+    }
+  }
+  return MP_OKAY;
+}
+
+
+#ifdef BN_MP_ABS_C
+/* b = |a| 
+ *
+ * Simple function copies the input and fixes the sign to positive
+ */
+static int mp_abs (mp_int * a, mp_int * b)
+{
+  int     res;
+
+  /* copy a to b */
+  if (a != b) {
+     if ((res = mp_copy (a, b)) != MP_OKAY) {
+       return res;
+     }
+  }
+
+  /* force the sign of b to positive */
+  b->sign = MP_ZPOS;
+
+  return MP_OKAY;
+}
+#endif
+
+
+/* set to a digit */
+static void mp_set (mp_int * a, mp_digit b)
+{
+  mp_zero (a);
+  a->dp[0] = b & MP_MASK;
+  a->used  = (a->dp[0] != 0) ? 1 : 0;
+}
+
+
+#ifndef LTM_NO_NEG_EXP
+/* b = a/2 */
+static int mp_div_2(mp_int * a, mp_int * b)
+{
+  int     x, res, oldused;
+
+  /* copy */
+  if (b->alloc < a->used) {
+    if ((res = mp_grow (b, a->used)) != MP_OKAY) {
+      return res;
+    }
+  }
+
+  oldused = b->used;
+  b->used = a->used;
+  {
+    register mp_digit r, rr, *tmpa, *tmpb;
+
+    /* source alias */
+    tmpa = a->dp + b->used - 1;
+
+    /* dest alias */
+    tmpb = b->dp + b->used - 1;
+
+    /* carry */
+    r = 0;
+    for (x = b->used - 1; x >= 0; x--) {
+      /* get the carry for the next iteration */
+      rr = *tmpa & 1;
+
+      /* shift the current digit, add in carry and store */
+      *tmpb-- = (*tmpa-- >> 1) | (r << (DIGIT_BIT - 1));
+
+      /* forward carry to next iteration */
+      r = rr;
+    }
+
+    /* zero excess digits */
+    tmpb = b->dp + b->used;
+    for (x = b->used; x < oldused; x++) {
+      *tmpb++ = 0;
+    }
+  }
+  b->sign = a->sign;
+  mp_clamp (b);
+  return MP_OKAY;
+}
+#endif /* LTM_NO_NEG_EXP */
+
+
+/* shift left by a certain bit count */
+static int mp_mul_2d (mp_int * a, int b, mp_int * c)
+{
+  mp_digit d;
+  int      res;
+
+  /* copy */
+  if (a != c) {
+     if ((res = mp_copy (a, c)) != MP_OKAY) {
+       return res;
+     }
+  }
+
+  if (c->alloc < (int)(c->used + b/DIGIT_BIT + 1)) {
+     if ((res = mp_grow (c, c->used + b / DIGIT_BIT + 1)) != MP_OKAY) {
+       return res;
+     }
+  }
+
+  /* shift by as many digits in the bit count */
+  if (b >= (int)DIGIT_BIT) {
+    if ((res = mp_lshd (c, b / DIGIT_BIT)) != MP_OKAY) {
+      return res;
+    }
+  }
+
+  /* shift any bit count < DIGIT_BIT */
+  d = (mp_digit) (b % DIGIT_BIT);
+  if (d != 0) {
+    register mp_digit *tmpc, shift, mask, r, rr;
+    register int x;
+
+    /* bitmask for carries */
+    mask = (((mp_digit)1) << d) - 1;
+
+    /* shift for msbs */
+    shift = DIGIT_BIT - d;
+
+    /* alias */
+    tmpc = c->dp;
+
+    /* carry */
+    r    = 0;
+    for (x = 0; x < c->used; x++) {
+      /* get the higher bits of the current word */
+      rr = (*tmpc >> shift) & mask;
+
+      /* shift the current word and OR in the carry */
+      *tmpc = ((*tmpc << d) | r) & MP_MASK;
+      ++tmpc;
+
+      /* set the carry to the carry bits of the current word */
+      r = rr;
+    }
+    
+    /* set final carry */
+    if (r != 0) {
+       c->dp[(c->used)++] = r;
+    }
+  }
+  mp_clamp (c);
+  return MP_OKAY;
+}
+
+
+#ifdef BN_MP_INIT_MULTI_C
+static int mp_init_multi(mp_int *mp, ...) 
+{
+    mp_err res = MP_OKAY;      /* Assume ok until proven otherwise */
+    int n = 0;                 /* Number of ok inits */
+    mp_int* cur_arg = mp;
+    va_list args;
+
+    va_start(args, mp);        /* init args to next argument from caller */
+    while (cur_arg != NULL) {
+        if (mp_init(cur_arg) != MP_OKAY) {
+            /* Oops - error! Back-track and mp_clear what we already
+               succeeded in init-ing, then return error.
+            */
+            va_list clean_args;
+            
+            /* end the current list */
+            va_end(args);
+            
+            /* now start cleaning up */            
+            cur_arg = mp;
+            va_start(clean_args, mp);
+            while (n--) {
+                mp_clear(cur_arg);
+                cur_arg = va_arg(clean_args, mp_int*);
+            }
+            va_end(clean_args);
+            res = MP_MEM;
+            break;
+        }
+        n++;
+        cur_arg = va_arg(args, mp_int*);
+    }
+    va_end(args);
+    return res;                /* Assumed ok, if error flagged above. */
+}
+#endif
+
+
+#ifdef BN_MP_CLEAR_MULTI_C
+static void mp_clear_multi(mp_int *mp, ...) 
+{
+    mp_int* next_mp = mp;
+    va_list args;
+    va_start(args, mp);
+    while (next_mp != NULL) {
+        mp_clear(next_mp);
+        next_mp = va_arg(args, mp_int*);
+    }
+    va_end(args);
+}
+#endif
+
+
+/* shift left a certain amount of digits */
+static int mp_lshd (mp_int * a, int b)
+{
+  int     x, res;
+
+  /* if its less than zero return */
+  if (b <= 0) {
+    return MP_OKAY;
+  }
+
+  /* grow to fit the new digits */
+  if (a->alloc < a->used + b) {
+     if ((res = mp_grow (a, a->used + b)) != MP_OKAY) {
+       return res;
+     }
+  }
+
+  {
+    register mp_digit *top, *bottom;
+
+    /* increment the used by the shift amount then copy upwards */
+    a->used += b;
+
+    /* top */
+    top = a->dp + a->used - 1;
+
+    /* base */
+    bottom = a->dp + a->used - 1 - b;
+
+    /* much like mp_rshd this is implemented using a sliding window
+     * except the window goes the otherway around.  Copying from
+     * the bottom to the top.  see bn_mp_rshd.c for more info.
+     */
+    for (x = a->used - 1; x >= b; x--) {
+      *top-- = *bottom--;
+    }
+
+    /* zero the lower digits */
+    top = a->dp;
+    for (x = 0; x < b; x++) {
+      *top++ = 0;
+    }
+  }
+  return MP_OKAY;
+}
+
+
+/* returns the number of bits in an int */
+static int mp_count_bits (mp_int * a)
+{
+  int     r;
+  mp_digit q;
+
+  /* shortcut */
+  if (a->used == 0) {
+    return 0;
+  }
+
+  /* get number of digits and add that */
+  r = (a->used - 1) * DIGIT_BIT;
+  
+  /* take the last digit and count the bits in it */
+  q = a->dp[a->used - 1];
+  while (q > ((mp_digit) 0)) {
+    ++r;
+    q >>= ((mp_digit) 1);
+  }
+  return r;
+}
+
+
+/* calc a value mod 2**b */
+static int mp_mod_2d (mp_int * a, int b, mp_int * c)
+{
+  int     x, res;
+
+  /* if b is <= 0 then zero the int */
+  if (b <= 0) {
+    mp_zero (c);
+    return MP_OKAY;
+  }
+
+  /* if the modulus is larger than the value than return */
+  if (b >= (int) (a->used * DIGIT_BIT)) {
+    res = mp_copy (a, c);
+    return res;
+  }
+
+  /* copy */
+  if ((res = mp_copy (a, c)) != MP_OKAY) {
+    return res;
+  }
+
+  /* zero digits above the last digit of the modulus */
+  for (x = (b / DIGIT_BIT) + ((b % DIGIT_BIT) == 0 ? 0 : 1); x < c->used; x++) {
+    c->dp[x] = 0;
+  }
+  /* clear the digit that is not completely outside/inside the modulus */
+  c->dp[b / DIGIT_BIT] &=
+    (mp_digit) ((((mp_digit) 1) << (((mp_digit) b) % DIGIT_BIT)) - ((mp_digit) 1));
+  mp_clamp (c);
+  return MP_OKAY;
+}
+
+
+#ifdef BN_MP_DIV_SMALL
+
+/* slower bit-bang division... also smaller */
+static int mp_div(mp_int * a, mp_int * b, mp_int * c, mp_int * d)
+{
+   mp_int ta, tb, tq, q;
+   int    res, n, n2;
+
+  /* is divisor zero ? */
+  if (mp_iszero (b) == 1) {
+    return MP_VAL;
+  }
+
+  /* if a < b then q=0, r = a */
+  if (mp_cmp_mag (a, b) == MP_LT) {
+    if (d != NULL) {
+      res = mp_copy (a, d);
+    } else {
+      res = MP_OKAY;
+    }
+    if (c != NULL) {
+      mp_zero (c);
+    }
+    return res;
+  }
+	
+  /* init our temps */
+  if ((res = mp_init_multi(&ta, &tb, &tq, &q, NULL) != MP_OKAY)) {
+     return res;
+  }
+
+
+  mp_set(&tq, 1);
+  n = mp_count_bits(a) - mp_count_bits(b);
+  if (((res = mp_abs(a, &ta)) != MP_OKAY) ||
+      ((res = mp_abs(b, &tb)) != MP_OKAY) || 
+      ((res = mp_mul_2d(&tb, n, &tb)) != MP_OKAY) ||
+      ((res = mp_mul_2d(&tq, n, &tq)) != MP_OKAY)) {
+      goto LBL_ERR;
+  }
+
+  while (n-- >= 0) {
+     if (mp_cmp(&tb, &ta) != MP_GT) {
+        if (((res = mp_sub(&ta, &tb, &ta)) != MP_OKAY) ||
+            ((res = mp_add(&q, &tq, &q)) != MP_OKAY)) {
+           goto LBL_ERR;
+        }
+     }
+     if (((res = mp_div_2d(&tb, 1, &tb, NULL)) != MP_OKAY) ||
+         ((res = mp_div_2d(&tq, 1, &tq, NULL)) != MP_OKAY)) {
+           goto LBL_ERR;
+     }
+  }
+
+  /* now q == quotient and ta == remainder */
+  n  = a->sign;
+  n2 = (a->sign == b->sign ? MP_ZPOS : MP_NEG);
+  if (c != NULL) {
+     mp_exch(c, &q);
+     c->sign  = (mp_iszero(c) == MP_YES) ? MP_ZPOS : n2;
+  }
+  if (d != NULL) {
+     mp_exch(d, &ta);
+     d->sign = (mp_iszero(d) == MP_YES) ? MP_ZPOS : n;
+  }
+LBL_ERR:
+   mp_clear_multi(&ta, &tb, &tq, &q, NULL);
+   return res;
+}
+
+#else
+
+/* integer signed division. 
+ * c*b + d == a [e.g. a/b, c=quotient, d=remainder]
+ * HAC pp.598 Algorithm 14.20
+ *
+ * Note that the description in HAC is horribly 
+ * incomplete.  For example, it doesn't consider 
+ * the case where digits are removed from 'x' in 
+ * the inner loop.  It also doesn't consider the 
+ * case that y has fewer than three digits, etc..
+ *
+ * The overall algorithm is as described as 
+ * 14.20 from HAC but fixed to treat these cases.
+*/
+static int mp_div (mp_int * a, mp_int * b, mp_int * c, mp_int * d)
+{
+  mp_int  q, x, y, t1, t2;
+  int     res, n, t, i, norm, neg;
+
+  /* is divisor zero ? */
+  if (mp_iszero (b) == 1) {
+    return MP_VAL;
+  }
+
+  /* if a < b then q=0, r = a */
+  if (mp_cmp_mag (a, b) == MP_LT) {
+    if (d != NULL) {
+      res = mp_copy (a, d);
+    } else {
+      res = MP_OKAY;
+    }
+    if (c != NULL) {
+      mp_zero (c);
+    }
+    return res;
+  }
+
+  if ((res = mp_init_size (&q, a->used + 2)) != MP_OKAY) {
+    return res;
+  }
+  q.used = a->used + 2;
+
+  if ((res = mp_init (&t1)) != MP_OKAY) {
+    goto LBL_Q;
+  }
+
+  if ((res = mp_init (&t2)) != MP_OKAY) {
+    goto LBL_T1;
+  }
+
+  if ((res = mp_init_copy (&x, a)) != MP_OKAY) {
+    goto LBL_T2;
+  }
+
+  if ((res = mp_init_copy (&y, b)) != MP_OKAY) {
+    goto LBL_X;
+  }
+
+  /* fix the sign */
+  neg = (a->sign == b->sign) ? MP_ZPOS : MP_NEG;
+  x.sign = y.sign = MP_ZPOS;
+
+  /* normalize both x and y, ensure that y >= b/2, [b == 2**DIGIT_BIT] */
+  norm = mp_count_bits(&y) % DIGIT_BIT;
+  if (norm < (int)(DIGIT_BIT-1)) {
+     norm = (DIGIT_BIT-1) - norm;
+     if ((res = mp_mul_2d (&x, norm, &x)) != MP_OKAY) {
+       goto LBL_Y;
+     }
+     if ((res = mp_mul_2d (&y, norm, &y)) != MP_OKAY) {
+       goto LBL_Y;
+     }
+  } else {
+     norm = 0;
+  }
+
+  /* note hac does 0 based, so if used==5 then its 0,1,2,3,4, e.g. use 4 */
+  n = x.used - 1;
+  t = y.used - 1;
+
+  /* while (x >= y*b**n-t) do { q[n-t] += 1; x -= y*b**{n-t} } */
+  if ((res = mp_lshd (&y, n - t)) != MP_OKAY) { /* y = y*b**{n-t} */
+    goto LBL_Y;
+  }
+
+  while (mp_cmp (&x, &y) != MP_LT) {
+    ++(q.dp[n - t]);
+    if ((res = mp_sub (&x, &y, &x)) != MP_OKAY) {
+      goto LBL_Y;
+    }
+  }
+
+  /* reset y by shifting it back down */
+  mp_rshd (&y, n - t);
+
+  /* step 3. for i from n down to (t + 1) */
+  for (i = n; i >= (t + 1); i--) {
+    if (i > x.used) {
+      continue;
+    }
+
+    /* step 3.1 if xi == yt then set q{i-t-1} to b-1, 
+     * otherwise set q{i-t-1} to (xi*b + x{i-1})/yt */
+    if (x.dp[i] == y.dp[t]) {
+      q.dp[i - t - 1] = ((((mp_digit)1) << DIGIT_BIT) - 1);
+    } else {
+      mp_word tmp;
+      tmp = ((mp_word) x.dp[i]) << ((mp_word) DIGIT_BIT);
+      tmp |= ((mp_word) x.dp[i - 1]);
+      tmp /= ((mp_word) y.dp[t]);
+      if (tmp > (mp_word) MP_MASK)
+        tmp = MP_MASK;
+      q.dp[i - t - 1] = (mp_digit) (tmp & (mp_word) (MP_MASK));
+    }
+
+    /* while (q{i-t-1} * (yt * b + y{t-1})) > 
+             xi * b**2 + xi-1 * b + xi-2 
+     
+       do q{i-t-1} -= 1; 
+    */
+    q.dp[i - t - 1] = (q.dp[i - t - 1] + 1) & MP_MASK;
+    do {
+      q.dp[i - t - 1] = (q.dp[i - t - 1] - 1) & MP_MASK;
+
+      /* find left hand */
+      mp_zero (&t1);
+      t1.dp[0] = (t - 1 < 0) ? 0 : y.dp[t - 1];
+      t1.dp[1] = y.dp[t];
+      t1.used = 2;
+      if ((res = mp_mul_d (&t1, q.dp[i - t - 1], &t1)) != MP_OKAY) {
+        goto LBL_Y;
+      }
+
+      /* find right hand */
+      t2.dp[0] = (i - 2 < 0) ? 0 : x.dp[i - 2];
+      t2.dp[1] = (i - 1 < 0) ? 0 : x.dp[i - 1];
+      t2.dp[2] = x.dp[i];
+      t2.used = 3;
+    } while (mp_cmp_mag(&t1, &t2) == MP_GT);
+
+    /* step 3.3 x = x - q{i-t-1} * y * b**{i-t-1} */
+    if ((res = mp_mul_d (&y, q.dp[i - t - 1], &t1)) != MP_OKAY) {
+      goto LBL_Y;
+    }
+
+    if ((res = mp_lshd (&t1, i - t - 1)) != MP_OKAY) {
+      goto LBL_Y;
+    }
+
+    if ((res = mp_sub (&x, &t1, &x)) != MP_OKAY) {
+      goto LBL_Y;
+    }
+
+    /* if x < 0 then { x = x + y*b**{i-t-1}; q{i-t-1} -= 1; } */
+    if (x.sign == MP_NEG) {
+      if ((res = mp_copy (&y, &t1)) != MP_OKAY) {
+        goto LBL_Y;
+      }
+      if ((res = mp_lshd (&t1, i - t - 1)) != MP_OKAY) {
+        goto LBL_Y;
+      }
+      if ((res = mp_add (&x, &t1, &x)) != MP_OKAY) {
+        goto LBL_Y;
+      }
+
+      q.dp[i - t - 1] = (q.dp[i - t - 1] - 1UL) & MP_MASK;
+    }
+  }
+
+  /* now q is the quotient and x is the remainder 
+   * [which we have to normalize] 
+   */
+  
+  /* get sign before writing to c */
+  x.sign = x.used == 0 ? MP_ZPOS : a->sign;
+
+  if (c != NULL) {
+    mp_clamp (&q);
+    mp_exch (&q, c);
+    c->sign = neg;
+  }
+
+  if (d != NULL) {
+    mp_div_2d (&x, norm, &x, NULL);
+    mp_exch (&x, d);
+  }
+
+  res = MP_OKAY;
+
+LBL_Y:mp_clear (&y);
+LBL_X:mp_clear (&x);
+LBL_T2:mp_clear (&t2);
+LBL_T1:mp_clear (&t1);
+LBL_Q:mp_clear (&q);
+  return res;
+}
+
+#endif
+
+
+#ifdef MP_LOW_MEM
+   #define TAB_SIZE 32
+#else
+   #define TAB_SIZE 256
+#endif
+
+static int s_mp_exptmod (mp_int * G, mp_int * X, mp_int * P, mp_int * Y, int redmode)
+{
+  mp_int  M[TAB_SIZE], res, mu;
+  mp_digit buf;
+  int     err, bitbuf, bitcpy, bitcnt, mode, digidx, x, y, winsize;
+  int (*redux)(mp_int*,mp_int*,mp_int*);
+
+  /* find window size */
+  x = mp_count_bits (X);
+  if (x <= 7) {
+    winsize = 2;
+  } else if (x <= 36) {
+    winsize = 3;
+  } else if (x <= 140) {
+    winsize = 4;
+  } else if (x <= 450) {
+    winsize = 5;
+  } else if (x <= 1303) {
+    winsize = 6;
+  } else if (x <= 3529) {
+    winsize = 7;
+  } else {
+    winsize = 8;
+  }
+
+#ifdef MP_LOW_MEM
+    if (winsize > 5) {
+       winsize = 5;
+    }
+#endif
+
+  /* init M array */
+  /* init first cell */
+  if ((err = mp_init(&M[1])) != MP_OKAY) {
+     return err; 
+  }
+
+  /* now init the second half of the array */
+  for (x = 1<<(winsize-1); x < (1 << winsize); x++) {
+    if ((err = mp_init(&M[x])) != MP_OKAY) {
+      for (y = 1<<(winsize-1); y < x; y++) {
+        mp_clear (&M[y]);
+      }
+      mp_clear(&M[1]);
+      return err;
+    }
+  }
+
+  /* create mu, used for Barrett reduction */
+  if ((err = mp_init (&mu)) != MP_OKAY) {
+    goto LBL_M;
+  }
+  
+  if (redmode == 0) {
+     if ((err = mp_reduce_setup (&mu, P)) != MP_OKAY) {
+        goto LBL_MU;
+     }
+     redux = mp_reduce;
+  } else {
+     if ((err = mp_reduce_2k_setup_l (P, &mu)) != MP_OKAY) {
+        goto LBL_MU;
+     }
+     redux = mp_reduce_2k_l;
+  }    
+
+  /* create M table
+   *
+   * The M table contains powers of the base, 
+   * e.g. M[x] = G**x mod P
+   *
+   * The first half of the table is not 
+   * computed though accept for M[0] and M[1]
+   */
+  if ((err = mp_mod (G, P, &M[1])) != MP_OKAY) {
+    goto LBL_MU;
+  }
+
+  /* compute the value at M[1<<(winsize-1)] by squaring 
+   * M[1] (winsize-1) times 
+   */
+  if ((err = mp_copy (&M[1], &M[1 << (winsize - 1)])) != MP_OKAY) {
+    goto LBL_MU;
+  }
+
+  for (x = 0; x < (winsize - 1); x++) {
+    /* square it */
+    if ((err = mp_sqr (&M[1 << (winsize - 1)], 
+                       &M[1 << (winsize - 1)])) != MP_OKAY) {
+      goto LBL_MU;
+    }
+
+    /* reduce modulo P */
+    if ((err = redux (&M[1 << (winsize - 1)], P, &mu)) != MP_OKAY) {
+      goto LBL_MU;
+    }
+  }
+
+  /* create upper table, that is M[x] = M[x-1] * M[1] (mod P)
+   * for x = (2**(winsize - 1) + 1) to (2**winsize - 1)
+   */
+  for (x = (1 << (winsize - 1)) + 1; x < (1 << winsize); x++) {
+    if ((err = mp_mul (&M[x - 1], &M[1], &M[x])) != MP_OKAY) {
+      goto LBL_MU;
+    }
+    if ((err = redux (&M[x], P, &mu)) != MP_OKAY) {
+      goto LBL_MU;
+    }
+  }
+
+  /* setup result */
+  if ((err = mp_init (&res)) != MP_OKAY) {
+    goto LBL_MU;
+  }
+  mp_set (&res, 1);
+
+  /* set initial mode and bit cnt */
+  mode   = 0;
+  bitcnt = 1;
+  buf    = 0;
+  digidx = X->used - 1;
+  bitcpy = 0;
+  bitbuf = 0;
+
+  for (;;) {
+    /* grab next digit as required */
+    if (--bitcnt == 0) {
+      /* if digidx == -1 we are out of digits */
+      if (digidx == -1) {
+        break;
+      }
+      /* read next digit and reset the bitcnt */
+      buf    = X->dp[digidx--];
+      bitcnt = (int) DIGIT_BIT;
+    }
+
+    /* grab the next msb from the exponent */
+    y     = (buf >> (mp_digit)(DIGIT_BIT - 1)) & 1;
+    buf <<= (mp_digit)1;
+
+    /* if the bit is zero and mode == 0 then we ignore it
+     * These represent the leading zero bits before the first 1 bit
+     * in the exponent.  Technically this opt is not required but it
+     * does lower the # of trivial squaring/reductions used
+     */
+    if (mode == 0 && y == 0) {
+      continue;
+    }
+
+    /* if the bit is zero and mode == 1 then we square */
+    if (mode == 1 && y == 0) {
+      if ((err = mp_sqr (&res, &res)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+      if ((err = redux (&res, P, &mu)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+      continue;
+    }
+
+    /* else we add it to the window */
+    bitbuf |= (y << (winsize - ++bitcpy));
+    mode    = 2;
+
+    if (bitcpy == winsize) {
+      /* ok window is filled so square as required and multiply  */
+      /* square first */
+      for (x = 0; x < winsize; x++) {
+        if ((err = mp_sqr (&res, &res)) != MP_OKAY) {
+          goto LBL_RES;
+        }
+        if ((err = redux (&res, P, &mu)) != MP_OKAY) {
+          goto LBL_RES;
+        }
+      }
+
+      /* then multiply */
+      if ((err = mp_mul (&res, &M[bitbuf], &res)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+      if ((err = redux (&res, P, &mu)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+
+      /* empty window and reset */
+      bitcpy = 0;
+      bitbuf = 0;
+      mode   = 1;
+    }
+  }
+
+  /* if bits remain then square/multiply */
+  if (mode == 2 && bitcpy > 0) {
+    /* square then multiply if the bit is set */
+    for (x = 0; x < bitcpy; x++) {
+      if ((err = mp_sqr (&res, &res)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+      if ((err = redux (&res, P, &mu)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+
+      bitbuf <<= 1;
+      if ((bitbuf & (1 << winsize)) != 0) {
+        /* then multiply */
+        if ((err = mp_mul (&res, &M[1], &res)) != MP_OKAY) {
+          goto LBL_RES;
+        }
+        if ((err = redux (&res, P, &mu)) != MP_OKAY) {
+          goto LBL_RES;
+        }
+      }
+    }
+  }
+
+  mp_exch (&res, Y);
+  err = MP_OKAY;
+LBL_RES:mp_clear (&res);
+LBL_MU:mp_clear (&mu);
+LBL_M:
+  mp_clear(&M[1]);
+  for (x = 1<<(winsize-1); x < (1 << winsize); x++) {
+    mp_clear (&M[x]);
+  }
+  return err;
+}
+
+
+/* computes b = a*a */
+static int mp_sqr (mp_int * a, mp_int * b)
+{
+  int     res;
+
+#ifdef BN_MP_TOOM_SQR_C
+  /* use Toom-Cook? */
+  if (a->used >= TOOM_SQR_CUTOFF) {
+    res = mp_toom_sqr(a, b);
+  /* Karatsuba? */
+  } else 
+#endif
+#ifdef BN_MP_KARATSUBA_SQR_C
+if (a->used >= KARATSUBA_SQR_CUTOFF) {
+    res = mp_karatsuba_sqr (a, b);
+  } else 
+#endif
+  {
+#ifdef BN_FAST_S_MP_SQR_C
+    /* can we use the fast comba multiplier? */
+    if ((a->used * 2 + 1) < MP_WARRAY && 
+         a->used < 
+         (1 << (sizeof(mp_word) * CHAR_BIT - 2*DIGIT_BIT - 1))) {
+      res = fast_s_mp_sqr (a, b);
+    } else
+#endif
+#ifdef BN_S_MP_SQR_C
+      res = s_mp_sqr (a, b);
+#else
+#error mp_sqr could fail
+      res = MP_VAL;
+#endif
+  }
+  b->sign = MP_ZPOS;
+  return res;
+}
+
+
+/* reduces a modulo n where n is of the form 2**p - d 
+   This differs from reduce_2k since "d" can be larger
+   than a single digit.
+*/
+static int mp_reduce_2k_l(mp_int *a, mp_int *n, mp_int *d)
+{
+   mp_int q;
+   int    p, res;
+   
+   if ((res = mp_init(&q)) != MP_OKAY) {
+      return res;
+   }
+   
+   p = mp_count_bits(n);    
+top:
+   /* q = a/2**p, a = a mod 2**p */
+   if ((res = mp_div_2d(a, p, &q, a)) != MP_OKAY) {
+      goto ERR;
+   }
+   
+   /* q = q * d */
+   if ((res = mp_mul(&q, d, &q)) != MP_OKAY) { 
+      goto ERR;
+   }
+   
+   /* a = a + q */
+   if ((res = s_mp_add(a, &q, a)) != MP_OKAY) {
+      goto ERR;
+   }
+   
+   if (mp_cmp_mag(a, n) != MP_LT) {
+      s_mp_sub(a, n, a);
+      goto top;
+   }
+   
+ERR:
+   mp_clear(&q);
+   return res;
+}
+
+
+/* determines the setup value */
+static int mp_reduce_2k_setup_l(mp_int *a, mp_int *d)
+{
+   int    res;
+   mp_int tmp;
+   
+   if ((res = mp_init(&tmp)) != MP_OKAY) {
+      return res;
+   }
+   
+   if ((res = mp_2expt(&tmp, mp_count_bits(a))) != MP_OKAY) {
+      goto ERR;
+   }
+   
+   if ((res = s_mp_sub(&tmp, a, d)) != MP_OKAY) {
+      goto ERR;
+   }
+   
+ERR:
+   mp_clear(&tmp);
+   return res;
+}
+
+
+/* computes a = 2**b 
+ *
+ * Simple algorithm which zeroes the int, grows it then just sets one bit
+ * as required.
+ */
+static int mp_2expt (mp_int * a, int b)
+{
+  int     res;
+
+  /* zero a as per default */
+  mp_zero (a);
+
+  /* grow a to accomodate the single bit */
+  if ((res = mp_grow (a, b / DIGIT_BIT + 1)) != MP_OKAY) {
+    return res;
+  }
+
+  /* set the used count of where the bit will go */
+  a->used = b / DIGIT_BIT + 1;
+
+  /* put the single bit in its place */
+  a->dp[b / DIGIT_BIT] = ((mp_digit)1) << (b % DIGIT_BIT);
+
+  return MP_OKAY;
+}
+
+
+/* pre-calculate the value required for Barrett reduction
+ * For a given modulus "b" it calulates the value required in "a"
+ */
+static int mp_reduce_setup (mp_int * a, mp_int * b)
+{
+  int     res;
+  
+  if ((res = mp_2expt (a, b->used * 2 * DIGIT_BIT)) != MP_OKAY) {
+    return res;
+  }
+  return mp_div (a, b, a, NULL);
+}
+
+
+/* reduces x mod m, assumes 0 < x < m**2, mu is 
+ * precomputed via mp_reduce_setup.
+ * From HAC pp.604 Algorithm 14.42
+ */
+static int mp_reduce (mp_int * x, mp_int * m, mp_int * mu)
+{
+  mp_int  q;
+  int     res, um = m->used;
+
+  /* q = x */
+  if ((res = mp_init_copy (&q, x)) != MP_OKAY) {
+    return res;
+  }
+
+  /* q1 = x / b**(k-1)  */
+  mp_rshd (&q, um - 1);         
+
+  /* according to HAC this optimization is ok */
+  if (((unsigned long) um) > (((mp_digit)1) << (DIGIT_BIT - 1))) {
+    if ((res = mp_mul (&q, mu, &q)) != MP_OKAY) {
+      goto CLEANUP;
+    }
+  } else {
+#ifdef BN_S_MP_MUL_HIGH_DIGS_C
+    if ((res = s_mp_mul_high_digs (&q, mu, &q, um)) != MP_OKAY) {
+      goto CLEANUP;
+    }
+#elif defined(BN_FAST_S_MP_MUL_HIGH_DIGS_C)
+    if ((res = fast_s_mp_mul_high_digs (&q, mu, &q, um)) != MP_OKAY) {
+      goto CLEANUP;
+    }
+#else 
+    { 
+#error mp_reduce would always fail
+      res = MP_VAL;
+      goto CLEANUP;
+    }
+#endif
+  }
+
+  /* q3 = q2 / b**(k+1) */
+  mp_rshd (&q, um + 1);         
+
+  /* x = x mod b**(k+1), quick (no division) */
+  if ((res = mp_mod_2d (x, DIGIT_BIT * (um + 1), x)) != MP_OKAY) {
+    goto CLEANUP;
+  }
+
+  /* q = q * m mod b**(k+1), quick (no division) */
+  if ((res = s_mp_mul_digs (&q, m, &q, um + 1)) != MP_OKAY) {
+    goto CLEANUP;
+  }
+
+  /* x = x - q */
+  if ((res = mp_sub (x, &q, x)) != MP_OKAY) {
+    goto CLEANUP;
+  }
+
+  /* If x < 0, add b**(k+1) to it */
+  if (mp_cmp_d (x, 0) == MP_LT) {
+    mp_set (&q, 1);
+    if ((res = mp_lshd (&q, um + 1)) != MP_OKAY) {
+      goto CLEANUP;
+    }
+    if ((res = mp_add (x, &q, x)) != MP_OKAY) {
+      goto CLEANUP;
+    }
+  }
+
+  /* Back off if it's too big */
+  while (mp_cmp (x, m) != MP_LT) {
+    if ((res = s_mp_sub (x, m, x)) != MP_OKAY) {
+      goto CLEANUP;
+    }
+  }
+  
+CLEANUP:
+  mp_clear (&q);
+
+  return res;
+}
+
+
+/* multiplies |a| * |b| and only computes upto digs digits of result
+ * HAC pp. 595, Algorithm 14.12  Modified so you can control how 
+ * many digits of output are created.
+ */
+static int s_mp_mul_digs (mp_int * a, mp_int * b, mp_int * c, int digs)
+{
+  mp_int  t;
+  int     res, pa, pb, ix, iy;
+  mp_digit u;
+  mp_word r;
+  mp_digit tmpx, *tmpt, *tmpy;
+
+  /* can we use the fast multiplier? */
+  if (((digs) < MP_WARRAY) &&
+      MIN (a->used, b->used) < 
+          (1 << ((CHAR_BIT * sizeof (mp_word)) - (2 * DIGIT_BIT)))) {
+    return fast_s_mp_mul_digs (a, b, c, digs);
+  }
+
+  if ((res = mp_init_size (&t, digs)) != MP_OKAY) {
+    return res;
+  }
+  t.used = digs;
+
+  /* compute the digits of the product directly */
+  pa = a->used;
+  for (ix = 0; ix < pa; ix++) {
+    /* set the carry to zero */
+    u = 0;
+
+    /* limit ourselves to making digs digits of output */
+    pb = MIN (b->used, digs - ix);
+
+    /* setup some aliases */
+    /* copy of the digit from a used within the nested loop */
+    tmpx = a->dp[ix];
+    
+    /* an alias for the destination shifted ix places */
+    tmpt = t.dp + ix;
+    
+    /* an alias for the digits of b */
+    tmpy = b->dp;
+
+    /* compute the columns of the output and propagate the carry */
+    for (iy = 0; iy < pb; iy++) {
+      /* compute the column as a mp_word */
+      r       = ((mp_word)*tmpt) +
+                ((mp_word)tmpx) * ((mp_word)*tmpy++) +
+                ((mp_word) u);
+
+      /* the new column is the lower part of the result */
+      *tmpt++ = (mp_digit) (r & ((mp_word) MP_MASK));
+
+      /* get the carry word from the result */
+      u       = (mp_digit) (r >> ((mp_word) DIGIT_BIT));
+    }
+    /* set carry if it is placed below digs */
+    if (ix + iy < digs) {
+      *tmpt = u;
+    }
+  }
+
+  mp_clamp (&t);
+  mp_exch (&t, c);
+
+  mp_clear (&t);
+  return MP_OKAY;
+}
+
+
+/* Fast (comba) multiplier
+ *
+ * This is the fast column-array [comba] multiplier.  It is 
+ * designed to compute the columns of the product first 
+ * then handle the carries afterwards.  This has the effect 
+ * of making the nested loops that compute the columns very
+ * simple and schedulable on super-scalar processors.
+ *
+ * This has been modified to produce a variable number of 
+ * digits of output so if say only a half-product is required 
+ * you don't have to compute the upper half (a feature 
+ * required for fast Barrett reduction).
+ *
+ * Based on Algorithm 14.12 on pp.595 of HAC.
+ *
+ */
+static int fast_s_mp_mul_digs (mp_int * a, mp_int * b, mp_int * c, int digs)
+{
+  int     olduse, res, pa, ix, iz;
+  mp_digit W[MP_WARRAY];
+  register mp_word  _W;
+
+  /* grow the destination as required */
+  if (c->alloc < digs) {
+    if ((res = mp_grow (c, digs)) != MP_OKAY) {
+      return res;
+    }
+  }
+
+  /* number of output digits to produce */
+  pa = MIN(digs, a->used + b->used);
+
+  /* clear the carry */
+  _W = 0;
+  for (ix = 0; ix < pa; ix++) { 
+      int      tx, ty;
+      int      iy;
+      mp_digit *tmpx, *tmpy;
+
+      /* get offsets into the two bignums */
+      ty = MIN(b->used-1, ix);
+      tx = ix - ty;
+
+      /* setup temp aliases */
+      tmpx = a->dp + tx;
+      tmpy = b->dp + ty;
+
+      /* this is the number of times the loop will iterrate, essentially 
+         while (tx++ < a->used && ty-- >= 0) { ... }
+       */
+      iy = MIN(a->used-tx, ty+1);
+
+      /* execute loop */
+      for (iz = 0; iz < iy; ++iz) {
+         _W += ((mp_word)*tmpx++)*((mp_word)*tmpy--);
+
+      }
+
+      /* store term */
+      W[ix] = ((mp_digit)_W) & MP_MASK;
+
+      /* make next carry */
+      _W = _W >> ((mp_word)DIGIT_BIT);
+ }
+
+  /* setup dest */
+  olduse  = c->used;
+  c->used = pa;
+
+  {
+    register mp_digit *tmpc;
+    tmpc = c->dp;
+    for (ix = 0; ix < pa+1; ix++) {
+      /* now extract the previous digit [below the carry] */
+      *tmpc++ = W[ix];
+    }
+
+    /* clear unused digits [that existed in the old copy of c] */
+    for (; ix < olduse; ix++) {
+      *tmpc++ = 0;
+    }
+  }
+  mp_clamp (c);
+  return MP_OKAY;
+}
+
+
+/* init an mp_init for a given size */
+static int mp_init_size (mp_int * a, int size)
+{
+  int x;
+
+  /* pad size so there are always extra digits */
+  size += (MP_PREC * 2) - (size % MP_PREC);	
+  
+  /* alloc mem */
+  a->dp = OPT_CAST(mp_digit) XMALLOC (sizeof (mp_digit) * size);
+  if (a->dp == NULL) {
+    return MP_MEM;
+  }
+
+  /* set the members */
+  a->used  = 0;
+  a->alloc = size;
+  a->sign  = MP_ZPOS;
+
+  /* zero the digits */
+  for (x = 0; x < size; x++) {
+      a->dp[x] = 0;
+  }
+
+  return MP_OKAY;
+}
+
+
+/* low level squaring, b = a*a, HAC pp.596-597, Algorithm 14.16 */
+static int s_mp_sqr (mp_int * a, mp_int * b)
+{
+  mp_int  t;
+  int     res, ix, iy, pa;
+  mp_word r;
+  mp_digit u, tmpx, *tmpt;
+
+  pa = a->used;
+  if ((res = mp_init_size (&t, 2*pa + 1)) != MP_OKAY) {
+    return res;
+  }
+
+  /* default used is maximum possible size */
+  t.used = 2*pa + 1;
+
+  for (ix = 0; ix < pa; ix++) {
+    /* first calculate the digit at 2*ix */
+    /* calculate double precision result */
+    r = ((mp_word) t.dp[2*ix]) +
+        ((mp_word)a->dp[ix])*((mp_word)a->dp[ix]);
+
+    /* store lower part in result */
+    t.dp[ix+ix] = (mp_digit) (r & ((mp_word) MP_MASK));
+
+    /* get the carry */
+    u           = (mp_digit)(r >> ((mp_word) DIGIT_BIT));
+
+    /* left hand side of A[ix] * A[iy] */
+    tmpx        = a->dp[ix];
+
+    /* alias for where to store the results */
+    tmpt        = t.dp + (2*ix + 1);
+    
+    for (iy = ix + 1; iy < pa; iy++) {
+      /* first calculate the product */
+      r       = ((mp_word)tmpx) * ((mp_word)a->dp[iy]);
+
+      /* now calculate the double precision result, note we use
+       * addition instead of *2 since it's easier to optimize
+       */
+      r       = ((mp_word) *tmpt) + r + r + ((mp_word) u);
+
+      /* store lower part */
+      *tmpt++ = (mp_digit) (r & ((mp_word) MP_MASK));
+
+      /* get carry */
+      u       = (mp_digit)(r >> ((mp_word) DIGIT_BIT));
+    }
+    /* propagate upwards */
+    while (u != ((mp_digit) 0)) {
+      r       = ((mp_word) *tmpt) + ((mp_word) u);
+      *tmpt++ = (mp_digit) (r & ((mp_word) MP_MASK));
+      u       = (mp_digit)(r >> ((mp_word) DIGIT_BIT));
+    }
+  }
+
+  mp_clamp (&t);
+  mp_exch (&t, b);
+  mp_clear (&t);
+  return MP_OKAY;
+}
+
+
+/* multiplies |a| * |b| and does not compute the lower digs digits
+ * [meant to get the higher part of the product]
+ */
+static int s_mp_mul_high_digs (mp_int * a, mp_int * b, mp_int * c, int digs)
+{
+  mp_int  t;
+  int     res, pa, pb, ix, iy;
+  mp_digit u;
+  mp_word r;
+  mp_digit tmpx, *tmpt, *tmpy;
+
+  /* can we use the fast multiplier? */
+#ifdef BN_FAST_S_MP_MUL_HIGH_DIGS_C
+  if (((a->used + b->used + 1) < MP_WARRAY)
+      && MIN (a->used, b->used) < (1 << ((CHAR_BIT * sizeof (mp_word)) - (2 * DIGIT_BIT)))) {
+    return fast_s_mp_mul_high_digs (a, b, c, digs);
+  }
+#endif
+
+  if ((res = mp_init_size (&t, a->used + b->used + 1)) != MP_OKAY) {
+    return res;
+  }
+  t.used = a->used + b->used + 1;
+
+  pa = a->used;
+  pb = b->used;
+  for (ix = 0; ix < pa; ix++) {
+    /* clear the carry */
+    u = 0;
+
+    /* left hand side of A[ix] * B[iy] */
+    tmpx = a->dp[ix];
+
+    /* alias to the address of where the digits will be stored */
+    tmpt = &(t.dp[digs]);
+
+    /* alias for where to read the right hand side from */
+    tmpy = b->dp + (digs - ix);
+
+    for (iy = digs - ix; iy < pb; iy++) {
+      /* calculate the double precision result */
+      r       = ((mp_word)*tmpt) +
+                ((mp_word)tmpx) * ((mp_word)*tmpy++) +
+                ((mp_word) u);
+
+      /* get the lower part */
+      *tmpt++ = (mp_digit) (r & ((mp_word) MP_MASK));
+
+      /* carry the carry */
+      u       = (mp_digit) (r >> ((mp_word) DIGIT_BIT));
+    }
+    *tmpt = u;
+  }
+  mp_clamp (&t);
+  mp_exch (&t, c);
+  mp_clear (&t);
+  return MP_OKAY;
+}
+
+
+#ifdef BN_MP_MONTGOMERY_SETUP_C
+/* setups the montgomery reduction stuff */
+static int
+mp_montgomery_setup (mp_int * n, mp_digit * rho)
+{
+  mp_digit x, b;
+
+/* fast inversion mod 2**k
+ *
+ * Based on the fact that
+ *
+ * XA = 1 (mod 2**n)  =>  (X(2-XA)) A = 1 (mod 2**2n)
+ *                    =>  2*X*A - X*X*A*A = 1
+ *                    =>  2*(1) - (1)     = 1
+ */
+  b = n->dp[0];
+
+  if ((b & 1) == 0) {
+    return MP_VAL;
+  }
+
+  x = (((b + 2) & 4) << 1) + b; /* here x*a==1 mod 2**4 */
+  x *= 2 - b * x;               /* here x*a==1 mod 2**8 */
+#if !defined(MP_8BIT)
+  x *= 2 - b * x;               /* here x*a==1 mod 2**16 */
+#endif
+#if defined(MP_64BIT) || !(defined(MP_8BIT) || defined(MP_16BIT))
+  x *= 2 - b * x;               /* here x*a==1 mod 2**32 */
+#endif
+#ifdef MP_64BIT
+  x *= 2 - b * x;               /* here x*a==1 mod 2**64 */
+#endif
+
+  /* rho = -1/m mod b */
+  *rho = (unsigned long)(((mp_word)1 << ((mp_word) DIGIT_BIT)) - x) & MP_MASK;
+
+  return MP_OKAY;
+}
+#endif
+
+
+#ifdef BN_FAST_MP_MONTGOMERY_REDUCE_C
+/* computes xR**-1 == x (mod N) via Montgomery Reduction
+ *
+ * This is an optimized implementation of montgomery_reduce
+ * which uses the comba method to quickly calculate the columns of the
+ * reduction.
+ *
+ * Based on Algorithm 14.32 on pp.601 of HAC.
+*/
+int fast_mp_montgomery_reduce (mp_int * x, mp_int * n, mp_digit rho)
+{
+  int     ix, res, olduse;
+  mp_word W[MP_WARRAY];
+
+  /* get old used count */
+  olduse = x->used;
+
+  /* grow a as required */
+  if (x->alloc < n->used + 1) {
+    if ((res = mp_grow (x, n->used + 1)) != MP_OKAY) {
+      return res;
+    }
+  }
+
+  /* first we have to get the digits of the input into
+   * an array of double precision words W[...]
+   */
+  {
+    register mp_word *_W;
+    register mp_digit *tmpx;
+
+    /* alias for the W[] array */
+    _W   = W;
+
+    /* alias for the digits of  x*/
+    tmpx = x->dp;
+
+    /* copy the digits of a into W[0..a->used-1] */
+    for (ix = 0; ix < x->used; ix++) {
+      *_W++ = *tmpx++;
+    }
+
+    /* zero the high words of W[a->used..m->used*2] */
+    for (; ix < n->used * 2 + 1; ix++) {
+      *_W++ = 0;
+    }
+  }
+
+  /* now we proceed to zero successive digits
+   * from the least significant upwards
+   */
+  for (ix = 0; ix < n->used; ix++) {
+    /* mu = ai * m' mod b
+     *
+     * We avoid a double precision multiplication (which isn't required)
+     * by casting the value down to a mp_digit.  Note this requires
+     * that W[ix-1] have  the carry cleared (see after the inner loop)
+     */
+    register mp_digit mu;
+    mu = (mp_digit) (((W[ix] & MP_MASK) * rho) & MP_MASK);
+
+    /* a = a + mu * m * b**i
+     *
+     * This is computed in place and on the fly.  The multiplication
+     * by b**i is handled by offseting which columns the results
+     * are added to.
+     *
+     * Note the comba method normally doesn't handle carries in the
+     * inner loop In this case we fix the carry from the previous
+     * column since the Montgomery reduction requires digits of the
+     * result (so far) [see above] to work.  This is
+     * handled by fixing up one carry after the inner loop.  The
+     * carry fixups are done in order so after these loops the
+     * first m->used words of W[] have the carries fixed
+     */
+    {
+      register int iy;
+      register mp_digit *tmpn;
+      register mp_word *_W;
+
+      /* alias for the digits of the modulus */
+      tmpn = n->dp;
+
+      /* Alias for the columns set by an offset of ix */
+      _W = W + ix;
+
+      /* inner loop */
+      for (iy = 0; iy < n->used; iy++) {
+          *_W++ += ((mp_word)mu) * ((mp_word)*tmpn++);
+      }
+    }
+
+    /* now fix carry for next digit, W[ix+1] */
+    W[ix + 1] += W[ix] >> ((mp_word) DIGIT_BIT);
+  }
+
+  /* now we have to propagate the carries and
+   * shift the words downward [all those least
+   * significant digits we zeroed].
+   */
+  {
+    register mp_digit *tmpx;
+    register mp_word *_W, *_W1;
+
+    /* nox fix rest of carries */
+
+    /* alias for current word */
+    _W1 = W + ix;
+
+    /* alias for next word, where the carry goes */
+    _W = W + ++ix;
+
+    for (; ix <= n->used * 2 + 1; ix++) {
+      *_W++ += *_W1++ >> ((mp_word) DIGIT_BIT);
+    }
+
+    /* copy out, A = A/b**n
+     *
+     * The result is A/b**n but instead of converting from an
+     * array of mp_word to mp_digit than calling mp_rshd
+     * we just copy them in the right order
+     */
+
+    /* alias for destination word */
+    tmpx = x->dp;
+
+    /* alias for shifted double precision result */
+    _W = W + n->used;
+
+    for (ix = 0; ix < n->used + 1; ix++) {
+      *tmpx++ = (mp_digit)(*_W++ & ((mp_word) MP_MASK));
+    }
+
+    /* zero oldused digits, if the input a was larger than
+     * m->used+1 we'll have to clear the digits
+     */
+    for (; ix < olduse; ix++) {
+      *tmpx++ = 0;
+    }
+  }
+
+  /* set the max used and clamp */
+  x->used = n->used + 1;
+  mp_clamp (x);
+
+  /* if A >= m then A = A - m */
+  if (mp_cmp_mag (x, n) != MP_LT) {
+    return s_mp_sub (x, n, x);
+  }
+  return MP_OKAY;
+}
+#endif
+
+
+#ifdef BN_MP_MUL_2_C
+/* b = a*2 */
+static int mp_mul_2(mp_int * a, mp_int * b)
+{
+  int     x, res, oldused;
+
+  /* grow to accomodate result */
+  if (b->alloc < a->used + 1) {
+    if ((res = mp_grow (b, a->used + 1)) != MP_OKAY) {
+      return res;
+    }
+  }
+
+  oldused = b->used;
+  b->used = a->used;
+
+  {
+    register mp_digit r, rr, *tmpa, *tmpb;
+
+    /* alias for source */
+    tmpa = a->dp;
+    
+    /* alias for dest */
+    tmpb = b->dp;
+
+    /* carry */
+    r = 0;
+    for (x = 0; x < a->used; x++) {
+    
+      /* get what will be the *next* carry bit from the 
+       * MSB of the current digit 
+       */
+      rr = *tmpa >> ((mp_digit)(DIGIT_BIT - 1));
+      
+      /* now shift up this digit, add in the carry [from the previous] */
+      *tmpb++ = ((*tmpa++ << ((mp_digit)1)) | r) & MP_MASK;
+      
+      /* copy the carry that would be from the source 
+       * digit into the next iteration 
+       */
+      r = rr;
+    }
+
+    /* new leading digit? */
+    if (r != 0) {
+      /* add a MSB which is always 1 at this point */
+      *tmpb = 1;
+      ++(b->used);
+    }
+
+    /* now zero any excess digits on the destination 
+     * that we didn't write to 
+     */
+    tmpb = b->dp + b->used;
+    for (x = b->used; x < oldused; x++) {
+      *tmpb++ = 0;
+    }
+  }
+  b->sign = a->sign;
+  return MP_OKAY;
+}
+#endif
+
+
+#ifdef BN_MP_MONTGOMERY_CALC_NORMALIZATION_C
+/*
+ * shifts with subtractions when the result is greater than b.
+ *
+ * The method is slightly modified to shift B unconditionally upto just under
+ * the leading bit of b.  This saves alot of multiple precision shifting.
+ */
+static int mp_montgomery_calc_normalization (mp_int * a, mp_int * b)
+{
+  int     x, bits, res;
+
+  /* how many bits of last digit does b use */
+  bits = mp_count_bits (b) % DIGIT_BIT;
+
+  if (b->used > 1) {
+     if ((res = mp_2expt (a, (b->used - 1) * DIGIT_BIT + bits - 1)) != MP_OKAY) {
+        return res;
+     }
+  } else {
+     mp_set(a, 1);
+     bits = 1;
+  }
+
+
+  /* now compute C = A * B mod b */
+  for (x = bits - 1; x < (int)DIGIT_BIT; x++) {
+    if ((res = mp_mul_2 (a, a)) != MP_OKAY) {
+      return res;
+    }
+    if (mp_cmp_mag (a, b) != MP_LT) {
+      if ((res = s_mp_sub (a, b, a)) != MP_OKAY) {
+        return res;
+      }
+    }
+  }
+
+  return MP_OKAY;
+}
+#endif
+
+
+#ifdef BN_MP_EXPTMOD_FAST_C
+/* computes Y == G**X mod P, HAC pp.616, Algorithm 14.85
+ *
+ * Uses a left-to-right k-ary sliding window to compute the modular exponentiation.
+ * The value of k changes based on the size of the exponent.
+ *
+ * Uses Montgomery or Diminished Radix reduction [whichever appropriate]
+ */
+
+static int mp_exptmod_fast (mp_int * G, mp_int * X, mp_int * P, mp_int * Y, int redmode)
+{
+  mp_int  M[TAB_SIZE], res;
+  mp_digit buf, mp;
+  int     err, bitbuf, bitcpy, bitcnt, mode, digidx, x, y, winsize;
+
+  /* use a pointer to the reduction algorithm.  This allows us to use
+   * one of many reduction algorithms without modding the guts of
+   * the code with if statements everywhere.
+   */
+  int     (*redux)(mp_int*,mp_int*,mp_digit);
+
+  /* find window size */
+  x = mp_count_bits (X);
+  if (x <= 7) {
+    winsize = 2;
+  } else if (x <= 36) {
+    winsize = 3;
+  } else if (x <= 140) {
+    winsize = 4;
+  } else if (x <= 450) {
+    winsize = 5;
+  } else if (x <= 1303) {
+    winsize = 6;
+  } else if (x <= 3529) {
+    winsize = 7;
+  } else {
+    winsize = 8;
+  }
+
+#ifdef MP_LOW_MEM
+  if (winsize > 5) {
+     winsize = 5;
+  }
+#endif
+
+  /* init M array */
+  /* init first cell */
+  if ((err = mp_init(&M[1])) != MP_OKAY) {
+     return err;
+  }
+
+  /* now init the second half of the array */
+  for (x = 1<<(winsize-1); x < (1 << winsize); x++) {
+    if ((err = mp_init(&M[x])) != MP_OKAY) {
+      for (y = 1<<(winsize-1); y < x; y++) {
+        mp_clear (&M[y]);
+      }
+      mp_clear(&M[1]);
+      return err;
+    }
+  }
+
+  /* determine and setup reduction code */
+  if (redmode == 0) {
+#ifdef BN_MP_MONTGOMERY_SETUP_C     
+     /* now setup montgomery  */
+     if ((err = mp_montgomery_setup (P, &mp)) != MP_OKAY) {
+        goto LBL_M;
+     }
+#else
+     err = MP_VAL;
+     goto LBL_M;
+#endif
+
+     /* automatically pick the comba one if available (saves quite a few calls/ifs) */
+#ifdef BN_FAST_MP_MONTGOMERY_REDUCE_C
+     if (((P->used * 2 + 1) < MP_WARRAY) &&
+          P->used < (1 << ((CHAR_BIT * sizeof (mp_word)) - (2 * DIGIT_BIT)))) {
+        redux = fast_mp_montgomery_reduce;
+     } else 
+#endif
+     {
+#ifdef BN_MP_MONTGOMERY_REDUCE_C
+        /* use slower baseline Montgomery method */
+        redux = mp_montgomery_reduce;
+#else
+        err = MP_VAL;
+        goto LBL_M;
+#endif
+     }
+  } else if (redmode == 1) {
+#if defined(BN_MP_DR_SETUP_C) && defined(BN_MP_DR_REDUCE_C)
+     /* setup DR reduction for moduli of the form B**k - b */
+     mp_dr_setup(P, &mp);
+     redux = mp_dr_reduce;
+#else
+     err = MP_VAL;
+     goto LBL_M;
+#endif
+  } else {
+#if defined(BN_MP_REDUCE_2K_SETUP_C) && defined(BN_MP_REDUCE_2K_C)
+     /* setup DR reduction for moduli of the form 2**k - b */
+     if ((err = mp_reduce_2k_setup(P, &mp)) != MP_OKAY) {
+        goto LBL_M;
+     }
+     redux = mp_reduce_2k;
+#else
+     err = MP_VAL;
+     goto LBL_M;
+#endif
+  }
+
+  /* setup result */
+  if ((err = mp_init (&res)) != MP_OKAY) {
+    goto LBL_M;
+  }
+
+  /* create M table
+   *
+
+   *
+   * The first half of the table is not computed though accept for M[0] and M[1]
+   */
+
+  if (redmode == 0) {
+#ifdef BN_MP_MONTGOMERY_CALC_NORMALIZATION_C
+     /* now we need R mod m */
+     if ((err = mp_montgomery_calc_normalization (&res, P)) != MP_OKAY) {
+       goto LBL_RES;
+     }
+#else 
+     err = MP_VAL;
+     goto LBL_RES;
+#endif
+
+     /* now set M[1] to G * R mod m */
+     if ((err = mp_mulmod (G, &res, P, &M[1])) != MP_OKAY) {
+       goto LBL_RES;
+     }
+  } else {
+     mp_set(&res, 1);
+     if ((err = mp_mod(G, P, &M[1])) != MP_OKAY) {
+        goto LBL_RES;
+     }
+  }
+
+  /* compute the value at M[1<<(winsize-1)] by squaring M[1] (winsize-1) times */
+  if ((err = mp_copy (&M[1], &M[1 << (winsize - 1)])) != MP_OKAY) {
+    goto LBL_RES;
+  }
+
+  for (x = 0; x < (winsize - 1); x++) {
+    if ((err = mp_sqr (&M[1 << (winsize - 1)], &M[1 << (winsize - 1)])) != MP_OKAY) {
+      goto LBL_RES;
+    }
+    if ((err = redux (&M[1 << (winsize - 1)], P, mp)) != MP_OKAY) {
+      goto LBL_RES;
+    }
+  }
+
+  /* create upper table */
+  for (x = (1 << (winsize - 1)) + 1; x < (1 << winsize); x++) {
+    if ((err = mp_mul (&M[x - 1], &M[1], &M[x])) != MP_OKAY) {
+      goto LBL_RES;
+    }
+    if ((err = redux (&M[x], P, mp)) != MP_OKAY) {
+      goto LBL_RES;
+    }
+  }
+
+  /* set initial mode and bit cnt */
+  mode   = 0;
+  bitcnt = 1;
+  buf    = 0;
+  digidx = X->used - 1;
+  bitcpy = 0;
+  bitbuf = 0;
+
+  for (;;) {
+    /* grab next digit as required */
+    if (--bitcnt == 0) {
+      /* if digidx == -1 we are out of digits so break */
+      if (digidx == -1) {
+        break;
+      }
+      /* read next digit and reset bitcnt */
+      buf    = X->dp[digidx--];
+      bitcnt = (int)DIGIT_BIT;
+    }
+
+    /* grab the next msb from the exponent */
+    y     = (mp_digit)(buf >> (DIGIT_BIT - 1)) & 1;
+    buf <<= (mp_digit)1;
+
+    /* if the bit is zero and mode == 0 then we ignore it
+     * These represent the leading zero bits before the first 1 bit
+     * in the exponent.  Technically this opt is not required but it
+     * does lower the # of trivial squaring/reductions used
+     */
+    if (mode == 0 && y == 0) {
+      continue;
+    }
+
+    /* if the bit is zero and mode == 1 then we square */
+    if (mode == 1 && y == 0) {
+      if ((err = mp_sqr (&res, &res)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+      if ((err = redux (&res, P, mp)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+      continue;
+    }
+
+    /* else we add it to the window */
+    bitbuf |= (y << (winsize - ++bitcpy));
+    mode    = 2;
+
+    if (bitcpy == winsize) {
+      /* ok window is filled so square as required and multiply  */
+      /* square first */
+      for (x = 0; x < winsize; x++) {
+        if ((err = mp_sqr (&res, &res)) != MP_OKAY) {
+          goto LBL_RES;
+        }
+        if ((err = redux (&res, P, mp)) != MP_OKAY) {
+          goto LBL_RES;
+        }
+      }
+
+      /* then multiply */
+      if ((err = mp_mul (&res, &M[bitbuf], &res)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+      if ((err = redux (&res, P, mp)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+
+      /* empty window and reset */
+      bitcpy = 0;
+      bitbuf = 0;
+      mode   = 1;
+    }
+  }
+
+  /* if bits remain then square/multiply */
+  if (mode == 2 && bitcpy > 0) {
+    /* square then multiply if the bit is set */
+    for (x = 0; x < bitcpy; x++) {
+      if ((err = mp_sqr (&res, &res)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+      if ((err = redux (&res, P, mp)) != MP_OKAY) {
+        goto LBL_RES;
+      }
+
+      /* get next bit of the window */
+      bitbuf <<= 1;
+      if ((bitbuf & (1 << winsize)) != 0) {
+        /* then multiply */
+        if ((err = mp_mul (&res, &M[1], &res)) != MP_OKAY) {
+          goto LBL_RES;
+        }
+        if ((err = redux (&res, P, mp)) != MP_OKAY) {
+          goto LBL_RES;
+        }
+      }
+    }
+  }
+
+  if (redmode == 0) {
+     /* fixup result if Montgomery reduction is used
+      * recall that any value in a Montgomery system is
+      * actually multiplied by R mod n.  So we have
+      * to reduce one more time to cancel out the factor
+      * of R.
+      */
+     if ((err = redux(&res, P, mp)) != MP_OKAY) {
+       goto LBL_RES;
+     }
+  }
+
+  /* swap res with Y */
+  mp_exch (&res, Y);
+  err = MP_OKAY;
+LBL_RES:mp_clear (&res);
+LBL_M:
+  mp_clear(&M[1]);
+  for (x = 1<<(winsize-1); x < (1 << winsize); x++) {
+    mp_clear (&M[x]);
+  }
+  return err;
+}
+#endif
+
+
+#ifdef BN_FAST_S_MP_SQR_C
+/* the jist of squaring...
+ * you do like mult except the offset of the tmpx [one that 
+ * starts closer to zero] can't equal the offset of tmpy.  
+ * So basically you set up iy like before then you min it with
+ * (ty-tx) so that it never happens.  You double all those 
+ * you add in the inner loop
+
+After that loop you do the squares and add them in.
+*/
+
+static int fast_s_mp_sqr (mp_int * a, mp_int * b)
+{
+  int       olduse, res, pa, ix, iz;
+  mp_digit   W[MP_WARRAY], *tmpx;
+  mp_word   W1;
+
+  /* grow the destination as required */
+  pa = a->used + a->used;
+  if (b->alloc < pa) {
+    if ((res = mp_grow (b, pa)) != MP_OKAY) {
+      return res;
+    }
+  }
+
+  /* number of output digits to produce */
+  W1 = 0;
+  for (ix = 0; ix < pa; ix++) { 
+      int      tx, ty, iy;
+      mp_word  _W;
+      mp_digit *tmpy;
+
+      /* clear counter */
+      _W = 0;
+
+      /* get offsets into the two bignums */
+      ty = MIN(a->used-1, ix);
+      tx = ix - ty;
+
+      /* setup temp aliases */
+      tmpx = a->dp + tx;
+      tmpy = a->dp + ty;
+
+      /* this is the number of times the loop will iterrate, essentially
+         while (tx++ < a->used && ty-- >= 0) { ... }
+       */
+      iy = MIN(a->used-tx, ty+1);
+
+      /* now for squaring tx can never equal ty 
+       * we halve the distance since they approach at a rate of 2x
+       * and we have to round because odd cases need to be executed
+       */
+      iy = MIN(iy, (ty-tx+1)>>1);
+
+      /* execute loop */
+      for (iz = 0; iz < iy; iz++) {
+         _W += ((mp_word)*tmpx++)*((mp_word)*tmpy--);
+      }
+
+      /* double the inner product and add carry */
+      _W = _W + _W + W1;
+
+      /* even columns have the square term in them */
+      if ((ix&1) == 0) {
+         _W += ((mp_word)a->dp[ix>>1])*((mp_word)a->dp[ix>>1]);
+      }
+
+      /* store it */
+      W[ix] = (mp_digit)(_W & MP_MASK);
+
+      /* make next carry */
+      W1 = _W >> ((mp_word)DIGIT_BIT);
+  }
+
+  /* setup dest */
+  olduse  = b->used;
+  b->used = a->used+a->used;
+
+  {
+    mp_digit *tmpb;
+    tmpb = b->dp;
+    for (ix = 0; ix < pa; ix++) {
+      *tmpb++ = W[ix] & MP_MASK;
+    }
+
+    /* clear unused digits [that existed in the old copy of c] */
+    for (; ix < olduse; ix++) {
+      *tmpb++ = 0;
+    }
+  }
+  mp_clamp (b);
+  return MP_OKAY;
+}
+#endif
+
+
+#ifdef BN_MP_MUL_D_C
+/* multiply by a digit */
+static int
+mp_mul_d (mp_int * a, mp_digit b, mp_int * c)
+{
+  mp_digit u, *tmpa, *tmpc;
+  mp_word  r;
+  int      ix, res, olduse;
+
+  /* make sure c is big enough to hold a*b */
+  if (c->alloc < a->used + 1) {
+    if ((res = mp_grow (c, a->used + 1)) != MP_OKAY) {
+      return res;
+    }
+  }
+
+  /* get the original destinations used count */
+  olduse = c->used;
+
+  /* set the sign */
+  c->sign = a->sign;
+
+  /* alias for a->dp [source] */
+  tmpa = a->dp;
+
+  /* alias for c->dp [dest] */
+  tmpc = c->dp;
+
+  /* zero carry */
+  u = 0;
+
+  /* compute columns */
+  for (ix = 0; ix < a->used; ix++) {
+    /* compute product and carry sum for this term */
+    r       = ((mp_word) u) + ((mp_word)*tmpa++) * ((mp_word)b);
+
+    /* mask off higher bits to get a single digit */
+    *tmpc++ = (mp_digit) (r & ((mp_word) MP_MASK));
+
+    /* send carry into next iteration */
+    u       = (mp_digit) (r >> ((mp_word) DIGIT_BIT));
+  }
+
+  /* store final carry [if any] and increment ix offset  */
+  *tmpc++ = u;
+  ++ix;
+
+  /* now zero digits above the top */
+  while (ix++ < olduse) {
+     *tmpc++ = 0;
+  }
+
+  /* set used count */
+  c->used = a->used + 1;
+  mp_clamp(c);
+
+  return MP_OKAY;
+}
+#endif
diff --git a/src/tls/pkcs1.c b/src/tls/pkcs1.c
new file mode 100644
index 0000000..72ebd87
--- /dev/null
+++ b/src/tls/pkcs1.c
@@ -0,0 +1,201 @@
+/*
+ * PKCS #1 (RSA Encryption)
+ * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "rsa.h"
+#include "pkcs1.h"
+
+
+static int pkcs1_generate_encryption_block(u8 block_type, size_t modlen,
+					   const u8 *in, size_t inlen,
+					   u8 *out, size_t *outlen)
+{
+	size_t ps_len;
+	u8 *pos;
+
+	/*
+	 * PKCS #1 v1.5, 8.1:
+	 *
+	 * EB = 00 || BT || PS || 00 || D
+	 * BT = 00 or 01 for private-key operation; 02 for public-key operation
+	 * PS = k-3-||D||; at least eight octets
+	 * (BT=0: PS=0x00, BT=1: PS=0xff, BT=2: PS=pseudorandom non-zero)
+	 * k = length of modulus in octets (modlen)
+	 */
+
+	if (modlen < 12 || modlen > *outlen || inlen > modlen - 11) {
+		wpa_printf(MSG_DEBUG, "PKCS #1: %s - Invalid buffer "
+			   "lengths (modlen=%lu outlen=%lu inlen=%lu)",
+			   __func__, (unsigned long) modlen,
+			   (unsigned long) *outlen,
+			   (unsigned long) inlen);
+		return -1;
+	}
+
+	pos = out;
+	*pos++ = 0x00;
+	*pos++ = block_type; /* BT */
+	ps_len = modlen - inlen - 3;
+	switch (block_type) {
+	case 0:
+		os_memset(pos, 0x00, ps_len);
+		pos += ps_len;
+		break;
+	case 1:
+		os_memset(pos, 0xff, ps_len);
+		pos += ps_len;
+		break;
+	case 2:
+		if (os_get_random(pos, ps_len) < 0) {
+			wpa_printf(MSG_DEBUG, "PKCS #1: %s - Failed to get "
+				   "random data for PS", __func__);
+			return -1;
+		}
+		while (ps_len--) {
+			if (*pos == 0x00)
+				*pos = 0x01;
+			pos++;
+		}
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "PKCS #1: %s - Unsupported block type "
+			   "%d", __func__, block_type);
+		return -1;
+	}
+	*pos++ = 0x00;
+	os_memcpy(pos, in, inlen); /* D */
+
+	return 0;
+}
+
+
+int pkcs1_encrypt(int block_type, struct crypto_rsa_key *key,
+		  int use_private, const u8 *in, size_t inlen,
+		  u8 *out, size_t *outlen)
+{
+	size_t modlen;
+
+	modlen = crypto_rsa_get_modulus_len(key);
+
+	if (pkcs1_generate_encryption_block(block_type, modlen, in, inlen,
+					    out, outlen) < 0)
+		return -1;
+
+	return crypto_rsa_exptmod(out, modlen, out, outlen, key, use_private);
+}
+
+
+int pkcs1_v15_private_key_decrypt(struct crypto_rsa_key *key,
+				  const u8 *in, size_t inlen,
+				  u8 *out, size_t *outlen)
+{
+	int res;
+	u8 *pos, *end;
+
+	res = crypto_rsa_exptmod(in, inlen, out, outlen, key, 1);
+	if (res)
+		return res;
+
+	if (*outlen < 2 || out[0] != 0 || out[1] != 2)
+		return -1;
+
+	/* Skip PS (pseudorandom non-zero octets) */
+	pos = out + 2;
+	end = out + *outlen;
+	while (*pos && pos < end)
+		pos++;
+	if (pos == end)
+		return -1;
+	pos++;
+
+	*outlen -= pos - out;
+
+	/* Strip PKCS #1 header */
+	os_memmove(out, pos, *outlen);
+
+	return 0;
+}
+
+
+int pkcs1_decrypt_public_key(struct crypto_rsa_key *key,
+			     const u8 *crypt, size_t crypt_len,
+			     u8 *plain, size_t *plain_len)
+{
+	size_t len;
+	u8 *pos;
+
+	len = *plain_len;
+	if (crypto_rsa_exptmod(crypt, crypt_len, plain, &len, key, 0) < 0)
+		return -1;
+
+	/*
+	 * PKCS #1 v1.5, 8.1:
+	 *
+	 * EB = 00 || BT || PS || 00 || D
+	 * BT = 00 or 01
+	 * PS = k-3-||D|| times (00 if BT=00) or (FF if BT=01)
+	 * k = length of modulus in octets
+	 */
+
+	if (len < 3 + 8 + 16 /* min hash len */ ||
+	    plain[0] != 0x00 || (plain[1] != 0x00 && plain[1] != 0x01)) {
+		wpa_printf(MSG_INFO, "LibTomCrypt: Invalid signature EB "
+			   "structure");
+		return -1;
+	}
+
+	pos = plain + 3;
+	if (plain[1] == 0x00) {
+		/* BT = 00 */
+		if (plain[2] != 0x00) {
+			wpa_printf(MSG_INFO, "LibTomCrypt: Invalid signature "
+				   "PS (BT=00)");
+			return -1;
+		}
+		while (pos + 1 < plain + len && *pos == 0x00 && pos[1] == 0x00)
+			pos++;
+	} else {
+		/* BT = 01 */
+		if (plain[2] != 0xff) {
+			wpa_printf(MSG_INFO, "LibTomCrypt: Invalid signature "
+				   "PS (BT=01)");
+			return -1;
+		}
+		while (pos < plain + len && *pos == 0xff)
+			pos++;
+	}
+
+	if (pos - plain - 2 < 8) {
+		/* PKCS #1 v1.5, 8.1: At least eight octets long PS */
+		wpa_printf(MSG_INFO, "LibTomCrypt: Too short signature "
+			   "padding");
+		return -1;
+	}
+
+	if (pos + 16 /* min hash len */ >= plain + len || *pos != 0x00) {
+		wpa_printf(MSG_INFO, "LibTomCrypt: Invalid signature EB "
+			   "structure (2)");
+		return -1;
+	}
+	pos++;
+	len -= pos - plain;
+
+	/* Strip PKCS #1 header */
+	os_memmove(plain, pos, len);
+	*plain_len = len;
+
+	return 0;
+}
diff --git a/src/tls/pkcs1.h b/src/tls/pkcs1.h
new file mode 100644
index 0000000..68872b1
--- /dev/null
+++ b/src/tls/pkcs1.h
@@ -0,0 +1,28 @@
+/*
+ * PKCS #1 (RSA Encryption)
+ * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef PKCS1_H
+#define PKCS1_H
+
+int pkcs1_encrypt(int block_type, struct crypto_rsa_key *key,
+		  int use_private, const u8 *in, size_t inlen,
+		  u8 *out, size_t *outlen);
+int pkcs1_v15_private_key_decrypt(struct crypto_rsa_key *key,
+				  const u8 *in, size_t inlen,
+				  u8 *out, size_t *outlen);
+int pkcs1_decrypt_public_key(struct crypto_rsa_key *key,
+			     const u8 *crypt, size_t crypt_len,
+			     u8 *plain, size_t *plain_len);
+
+#endif /* PKCS1_H */
diff --git a/src/tls/pkcs5.c b/src/tls/pkcs5.c
new file mode 100644
index 0000000..4291b84
--- /dev/null
+++ b/src/tls/pkcs5.c
@@ -0,0 +1,238 @@
+/*
+ * PKCS #5 (Password-based Encryption)
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/crypto.h"
+#include "crypto/md5.h"
+#include "asn1.h"
+#include "pkcs5.h"
+
+
+struct pkcs5_params {
+	enum pkcs5_alg {
+		PKCS5_ALG_UNKNOWN,
+		PKCS5_ALG_MD5_DES_CBC
+	} alg;
+	u8 salt[8];
+	size_t salt_len;
+	unsigned int iter_count;
+};
+
+
+enum pkcs5_alg pkcs5_get_alg(struct asn1_oid *oid)
+{
+	if (oid->len == 7 &&
+	    oid->oid[0] == 1 /* iso */ &&
+	    oid->oid[1] == 2 /* member-body */ &&
+	    oid->oid[2] == 840 /* us */ &&
+	    oid->oid[3] == 113549 /* rsadsi */ &&
+	    oid->oid[4] == 1 /* pkcs */ &&
+	    oid->oid[5] == 5 /* pkcs-5 */ &&
+	    oid->oid[6] == 3 /* pbeWithMD5AndDES-CBC */)
+		return PKCS5_ALG_MD5_DES_CBC;
+
+	return PKCS5_ALG_UNKNOWN;
+}
+
+
+static int pkcs5_get_params(const u8 *enc_alg, size_t enc_alg_len,
+			    struct pkcs5_params *params)
+{
+	struct asn1_hdr hdr;
+	const u8 *enc_alg_end, *pos, *end;
+	struct asn1_oid oid;
+	char obuf[80];
+
+	/* AlgorithmIdentifier */
+
+	enc_alg_end = enc_alg + enc_alg_len;
+
+	os_memset(params, 0, sizeof(*params));
+
+	if (asn1_get_oid(enc_alg, enc_alg_end - enc_alg, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG, "PKCS #5: Failed to parse OID "
+			   "(algorithm)");
+		return -1;
+	}
+
+	asn1_oid_to_str(&oid, obuf, sizeof(obuf));
+	wpa_printf(MSG_DEBUG, "PKCS #5: encryption algorithm %s", obuf);
+	params->alg = pkcs5_get_alg(&oid);
+	if (params->alg == PKCS5_ALG_UNKNOWN) {
+		wpa_printf(MSG_INFO, "PKCS #5: unsupported encryption "
+			   "algorithm %s", obuf);
+		return -1;
+	}
+
+	/*
+	 * PKCS#5, Section 8
+	 * PBEParameter ::= SEQUENCE {
+	 *   salt OCTET STRING SIZE(8),
+	 *   iterationCount INTEGER }
+	 */
+
+	if (asn1_get_next(pos, enc_alg_end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "PKCS #5: Expected SEQUENCE "
+			   "(PBEParameter) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	/* salt OCTET STRING SIZE(8) */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING ||
+	    hdr.length != 8) {
+		wpa_printf(MSG_DEBUG, "PKCS #5: Expected OCTETSTRING SIZE(8) "
+			   "(salt) - found class %d tag 0x%x size %d",
+			   hdr.class, hdr.tag, hdr.length);
+		return -1;
+	}
+	pos = hdr.payload + hdr.length;
+	os_memcpy(params->salt, hdr.payload, hdr.length);
+	params->salt_len = hdr.length;
+	wpa_hexdump(MSG_DEBUG, "PKCS #5: salt",
+		    params->salt, params->salt_len);
+
+	/* iterationCount INTEGER */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL || hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG, "PKCS #5: Expected INTEGER - found "
+			   "class %d tag 0x%x", hdr.class, hdr.tag);
+		return -1;
+	}
+	if (hdr.length == 1)
+		params->iter_count = *hdr.payload;
+	else if (hdr.length == 2)
+		params->iter_count = WPA_GET_BE16(hdr.payload);
+	else if (hdr.length == 4)
+		params->iter_count = WPA_GET_BE32(hdr.payload);
+	else {
+		wpa_hexdump(MSG_DEBUG, "PKCS #5: Unsupported INTEGER value "
+			    " (iterationCount)",
+			    hdr.payload, hdr.length);
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG, "PKCS #5: iterationCount=0x%x",
+		   params->iter_count);
+	if (params->iter_count == 0 || params->iter_count > 0xffff) {
+		wpa_printf(MSG_INFO, "PKCS #5: Unsupported "
+			   "iterationCount=0x%x", params->iter_count);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static struct crypto_cipher * pkcs5_crypto_init(struct pkcs5_params *params,
+						const char *passwd)
+{
+	unsigned int i;
+	u8 hash[MD5_MAC_LEN];
+	const u8 *addr[2];
+	size_t len[2];
+
+	if (params->alg != PKCS5_ALG_MD5_DES_CBC)
+		return NULL;
+
+	addr[0] = (const u8 *) passwd;
+	len[0] = os_strlen(passwd);
+	addr[1] = params->salt;
+	len[1] = params->salt_len;
+	if (md5_vector(2, addr, len, hash) < 0)
+		return NULL;
+	addr[0] = hash;
+	len[0] = MD5_MAC_LEN;
+	for (i = 1; i < params->iter_count; i++) {
+		if (md5_vector(1, addr, len, hash) < 0)
+			return NULL;
+	}
+	/* TODO: DES key parity bits(?) */
+	wpa_hexdump_key(MSG_DEBUG, "PKCS #5: DES key", hash, 8);
+	wpa_hexdump_key(MSG_DEBUG, "PKCS #5: DES IV", hash + 8, 8);
+
+	return crypto_cipher_init(CRYPTO_CIPHER_ALG_DES, hash + 8, hash, 8);
+}
+
+
+u8 * pkcs5_decrypt(const u8 *enc_alg, size_t enc_alg_len,
+		   const u8 *enc_data, size_t enc_data_len,
+		   const char *passwd, size_t *data_len)
+{
+	struct crypto_cipher *ctx;
+	u8 *eb, pad;
+	struct pkcs5_params params;
+	unsigned int i;
+
+	if (pkcs5_get_params(enc_alg, enc_alg_len, &params) < 0) {
+		wpa_printf(MSG_DEBUG, "PKCS #5: Unsupported parameters");
+		return NULL;
+	}
+
+	ctx = pkcs5_crypto_init(&params, passwd);
+	if (ctx == NULL) {
+		wpa_printf(MSG_DEBUG, "PKCS #5: Failed to initialize crypto");
+		return NULL;
+	}
+
+	/* PKCS #5, Section 7 - Decryption process */
+	if (enc_data_len < 16 || enc_data_len % 8) {
+		wpa_printf(MSG_INFO, "PKCS #5: invalid length of ciphertext "
+			   "%d", (int) enc_data_len);
+		crypto_cipher_deinit(ctx);
+		return NULL;
+	}
+
+	eb = os_malloc(enc_data_len);
+	if (eb == NULL) {
+		crypto_cipher_deinit(ctx);
+		return NULL;
+	}
+
+	if (crypto_cipher_decrypt(ctx, enc_data, eb, enc_data_len) < 0) {
+		wpa_printf(MSG_DEBUG, "PKCS #5: Failed to decrypt EB");
+		crypto_cipher_deinit(ctx);
+		os_free(eb);
+		return NULL;
+	}
+	crypto_cipher_deinit(ctx);
+
+	pad = eb[enc_data_len - 1];
+	if (pad > 8) {
+		wpa_printf(MSG_INFO, "PKCS #5: Invalid PS octet 0x%x", pad);
+		os_free(eb);
+		return NULL;
+	}
+	for (i = enc_data_len - pad; i < enc_data_len; i++) {
+		if (eb[i] != pad) {
+			wpa_hexdump(MSG_INFO, "PKCS #5: Invalid PS",
+				    eb + enc_data_len - pad, pad);
+			os_free(eb);
+			return NULL;
+		}
+	}
+
+	wpa_hexdump_key(MSG_MSGDUMP, "PKCS #5: message M (encrypted key)",
+			eb, enc_data_len - pad);
+
+	*data_len = enc_data_len - pad;
+	return eb;
+}
diff --git a/src/tls/pkcs5.h b/src/tls/pkcs5.h
new file mode 100644
index 0000000..6ed3923
--- /dev/null
+++ b/src/tls/pkcs5.h
@@ -0,0 +1,22 @@
+/*
+ * PKCS #5 (Password-based Encryption)
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef PKCS5_H
+#define PKCS5_H
+
+u8 * pkcs5_decrypt(const u8 *enc_alg, size_t enc_alg_len,
+		   const u8 *enc_data, size_t enc_data_len,
+		   const char *passwd, size_t *data_len);
+
+#endif /* PKCS5_H */
diff --git a/src/tls/pkcs8.c b/src/tls/pkcs8.c
new file mode 100644
index 0000000..69ab262
--- /dev/null
+++ b/src/tls/pkcs8.c
@@ -0,0 +1,193 @@
+/*
+ * PKCS #8 (Private-key information syntax)
+ * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "asn1.h"
+#include "bignum.h"
+#include "rsa.h"
+#include "pkcs5.h"
+#include "pkcs8.h"
+
+
+struct crypto_private_key * pkcs8_key_import(const u8 *buf, size_t len)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+	struct bignum *zero;
+	struct asn1_oid oid;
+	char obuf[80];
+
+	/* PKCS #8, Chapter 6 */
+
+	/* PrivateKeyInfo ::= SEQUENCE */
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Does not start with PKCS #8 "
+			   "header (SEQUENCE); assume PKCS #8 not used");
+		return NULL;
+	}
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	/* version Version (Version ::= INTEGER) */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL || hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Expected INTEGER - found "
+			   "class %d tag 0x%x; assume PKCS #8 not used",
+			   hdr.class, hdr.tag);
+		return NULL;
+	}
+
+	zero = bignum_init();
+	if (zero == NULL)
+		return NULL;
+
+	if (bignum_set_unsigned_bin(zero, hdr.payload, hdr.length) < 0) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Failed to parse INTEGER");
+		bignum_deinit(zero);
+		return NULL;
+	}
+	pos = hdr.payload + hdr.length;
+
+	if (bignum_cmp_d(zero, 0) != 0) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Expected zero INTEGER in the "
+			   "beginning of private key; not found; assume "
+			   "PKCS #8 not used");
+		bignum_deinit(zero);
+		return NULL;
+	}
+	bignum_deinit(zero);
+
+	/* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier
+	 * (PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier) */
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Expected SEQUENCE "
+			   "(AlgorithmIdentifier) - found class %d tag 0x%x; "
+			   "assume PKCS #8 not used",
+			   hdr.class, hdr.tag);
+		return NULL;
+	}
+
+	if (asn1_get_oid(hdr.payload, hdr.length, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Failed to parse OID "
+			   "(algorithm); assume PKCS #8 not used");
+		return NULL;
+	}
+
+	asn1_oid_to_str(&oid, obuf, sizeof(obuf));
+	wpa_printf(MSG_DEBUG, "PKCS #8: algorithm=%s", obuf);
+
+	if (oid.len != 7 ||
+	    oid.oid[0] != 1 /* iso */ ||
+	    oid.oid[1] != 2 /* member-body */ ||
+	    oid.oid[2] != 840 /* us */ ||
+	    oid.oid[3] != 113549 /* rsadsi */ ||
+	    oid.oid[4] != 1 /* pkcs */ ||
+	    oid.oid[5] != 1 /* pkcs-1 */ ||
+	    oid.oid[6] != 1 /* rsaEncryption */) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Unsupported private key "
+			   "algorithm %s", obuf);
+		return NULL;
+	}
+
+	pos = hdr.payload + hdr.length;
+
+	/* privateKey PrivateKey (PrivateKey ::= OCTET STRING) */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Expected OCTETSTRING "
+			   "(privateKey) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return NULL;
+	}
+	wpa_printf(MSG_DEBUG, "PKCS #8: Try to parse RSAPrivateKey");
+
+	return (struct crypto_private_key *)
+		crypto_rsa_import_private_key(hdr.payload, hdr.length);
+}
+
+
+struct crypto_private_key *
+pkcs8_enc_key_import(const u8 *buf, size_t len, const char *passwd)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end, *enc_alg;
+	size_t enc_alg_len;
+	u8 *data;
+	size_t data_len;
+
+	if (passwd == NULL)
+		return NULL;
+
+	/*
+	 * PKCS #8, Chapter 7
+	 * EncryptedPrivateKeyInfo ::= SEQUENCE {
+	 *   encryptionAlgorithm EncryptionAlgorithmIdentifier,
+	 *   encryptedData EncryptedData }
+	 * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+	 * EncryptedData ::= OCTET STRING
+	 */
+
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Does not start with PKCS #8 "
+			   "header (SEQUENCE); assume encrypted PKCS #8 not "
+			   "used");
+		return NULL;
+	}
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	/* encryptionAlgorithm EncryptionAlgorithmIdentifier */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Expected SEQUENCE "
+			   "(AlgorithmIdentifier) - found class %d tag 0x%x; "
+			   "assume encrypted PKCS #8 not used",
+			   hdr.class, hdr.tag);
+		return NULL;
+	}
+	enc_alg = hdr.payload;
+	enc_alg_len = hdr.length;
+	pos = hdr.payload + hdr.length;
+
+	/* encryptedData EncryptedData */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING) {
+		wpa_printf(MSG_DEBUG, "PKCS #8: Expected OCTETSTRING "
+			   "(encryptedData) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return NULL;
+	}
+
+	data = pkcs5_decrypt(enc_alg, enc_alg_len, hdr.payload, hdr.length,
+			     passwd, &data_len);
+	if (data) {
+		struct crypto_private_key *key;
+		key = pkcs8_key_import(data, data_len);
+		os_free(data);
+		return key;
+	}
+
+	return NULL;
+}
diff --git a/src/tls/pkcs8.h b/src/tls/pkcs8.h
new file mode 100644
index 0000000..dac517c
--- /dev/null
+++ b/src/tls/pkcs8.h
@@ -0,0 +1,22 @@
+/*
+ * PKCS #8 (Private-key information syntax)
+ * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef PKCS8_H
+#define PKCS8_H
+
+struct crypto_private_key * pkcs8_key_import(const u8 *buf, size_t len);
+struct crypto_private_key *
+pkcs8_enc_key_import(const u8 *buf, size_t len, const char *passwd);
+
+#endif /* PKCS8_H */
diff --git a/src/tls/rsa.c b/src/tls/rsa.c
new file mode 100644
index 0000000..3084adc
--- /dev/null
+++ b/src/tls/rsa.c
@@ -0,0 +1,358 @@
+/*
+ * RSA
+ * Copyright (c) 2006, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "asn1.h"
+#include "bignum.h"
+#include "rsa.h"
+
+
+struct crypto_rsa_key {
+	int private_key; /* whether private key is set */
+	struct bignum *n; /* modulus (p * q) */
+	struct bignum *e; /* public exponent */
+	/* The following parameters are available only if private_key is set */
+	struct bignum *d; /* private exponent */
+	struct bignum *p; /* prime p (factor of n) */
+	struct bignum *q; /* prime q (factor of n) */
+	struct bignum *dmp1; /* d mod (p - 1); CRT exponent */
+	struct bignum *dmq1; /* d mod (q - 1); CRT exponent */
+	struct bignum *iqmp; /* 1 / q mod p; CRT coefficient */
+};
+
+
+static const u8 * crypto_rsa_parse_integer(const u8 *pos, const u8 *end,
+					   struct bignum *num)
+{
+	struct asn1_hdr hdr;
+
+	if (pos == NULL)
+		return NULL;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL || hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG, "RSA: Expected INTEGER - found class %d "
+			   "tag 0x%x", hdr.class, hdr.tag);
+		return NULL;
+	}
+
+	if (bignum_set_unsigned_bin(num, hdr.payload, hdr.length) < 0) {
+		wpa_printf(MSG_DEBUG, "RSA: Failed to parse INTEGER");
+		return NULL;
+	}
+
+	return hdr.payload + hdr.length;
+}
+
+
+/**
+ * crypto_rsa_import_public_key - Import an RSA public key
+ * @buf: Key buffer (DER encoded RSA public key)
+ * @len: Key buffer length in bytes
+ * Returns: Pointer to the public key or %NULL on failure
+ */
+struct crypto_rsa_key *
+crypto_rsa_import_public_key(const u8 *buf, size_t len)
+{
+	struct crypto_rsa_key *key;
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+
+	key = os_zalloc(sizeof(*key));
+	if (key == NULL)
+		return NULL;
+
+	key->n = bignum_init();
+	key->e = bignum_init();
+	if (key->n == NULL || key->e == NULL) {
+		crypto_rsa_free(key);
+		return NULL;
+	}
+
+	/*
+	 * PKCS #1, 7.1:
+	 * RSAPublicKey ::= SEQUENCE {
+	 *     modulus INTEGER, -- n
+	 *     publicExponent INTEGER -- e 
+	 * }
+	 */
+
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "RSA: Expected SEQUENCE "
+			   "(public key) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		goto error;
+	}
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	pos = crypto_rsa_parse_integer(pos, end, key->n);
+	pos = crypto_rsa_parse_integer(pos, end, key->e);
+
+	if (pos == NULL)
+		goto error;
+
+	if (pos != end) {
+		wpa_hexdump(MSG_DEBUG,
+			    "RSA: Extra data in public key SEQUENCE",
+			    pos, end - pos);
+		goto error;
+	}
+
+	return key;
+
+error:
+	crypto_rsa_free(key);
+	return NULL;
+}
+
+
+/**
+ * crypto_rsa_import_private_key - Import an RSA private key
+ * @buf: Key buffer (DER encoded RSA private key)
+ * @len: Key buffer length in bytes
+ * Returns: Pointer to the private key or %NULL on failure
+ */
+struct crypto_rsa_key *
+crypto_rsa_import_private_key(const u8 *buf, size_t len)
+{
+	struct crypto_rsa_key *key;
+	struct bignum *zero;
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+
+	key = os_zalloc(sizeof(*key));
+	if (key == NULL)
+		return NULL;
+
+	key->private_key = 1;
+
+	key->n = bignum_init();
+	key->e = bignum_init();
+	key->d = bignum_init();
+	key->p = bignum_init();
+	key->q = bignum_init();
+	key->dmp1 = bignum_init();
+	key->dmq1 = bignum_init();
+	key->iqmp = bignum_init();
+
+	if (key->n == NULL || key->e == NULL || key->d == NULL ||
+	    key->p == NULL || key->q == NULL || key->dmp1 == NULL ||
+	    key->dmq1 == NULL || key->iqmp == NULL) {
+		crypto_rsa_free(key);
+		return NULL;
+	}
+
+	/*
+	 * PKCS #1, 7.2:
+	 * RSAPrivateKey ::= SEQUENCE {
+	 *    version Version,
+	 *    modulus INTEGER, -- n
+	 *    publicExponent INTEGER, -- e
+	 *    privateExponent INTEGER, -- d
+	 *    prime1 INTEGER, -- p
+	 *    prime2 INTEGER, -- q
+	 *    exponent1 INTEGER, -- d mod (p-1)
+	 *    exponent2 INTEGER, -- d mod (q-1)
+	 *    coefficient INTEGER -- (inverse of q) mod p
+	 * }
+	 *
+	 * Version ::= INTEGER -- shall be 0 for this version of the standard
+	 */
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "RSA: Expected SEQUENCE "
+			   "(public key) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		goto error;
+	}
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	zero = bignum_init();
+	if (zero == NULL)
+		goto error;
+	pos = crypto_rsa_parse_integer(pos, end, zero);
+	if (pos == NULL || bignum_cmp_d(zero, 0) != 0) {
+		wpa_printf(MSG_DEBUG, "RSA: Expected zero INTEGER in the "
+			   "beginning of private key; not found");
+		bignum_deinit(zero);
+		goto error;
+	}
+	bignum_deinit(zero);
+
+	pos = crypto_rsa_parse_integer(pos, end, key->n);
+	pos = crypto_rsa_parse_integer(pos, end, key->e);
+	pos = crypto_rsa_parse_integer(pos, end, key->d);
+	pos = crypto_rsa_parse_integer(pos, end, key->p);
+	pos = crypto_rsa_parse_integer(pos, end, key->q);
+	pos = crypto_rsa_parse_integer(pos, end, key->dmp1);
+	pos = crypto_rsa_parse_integer(pos, end, key->dmq1);
+	pos = crypto_rsa_parse_integer(pos, end, key->iqmp);
+
+	if (pos == NULL)
+		goto error;
+
+	if (pos != end) {
+		wpa_hexdump(MSG_DEBUG,
+			    "RSA: Extra data in public key SEQUENCE",
+			    pos, end - pos);
+		goto error;
+	}
+
+	return key;
+
+error:
+	crypto_rsa_free(key);
+	return NULL;
+}
+
+
+/**
+ * crypto_rsa_get_modulus_len - Get the modulus length of the RSA key
+ * @key: RSA key
+ * Returns: Modulus length of the key
+ */
+size_t crypto_rsa_get_modulus_len(struct crypto_rsa_key *key)
+{
+	return bignum_get_unsigned_bin_len(key->n);
+}
+
+
+/**
+ * crypto_rsa_exptmod - RSA modular exponentiation
+ * @in: Input data
+ * @inlen: Input data length
+ * @out: Buffer for output data
+ * @outlen: Maximum size of the output buffer and used size on success
+ * @key: RSA key
+ * @use_private: 1 = Use RSA private key, 0 = Use RSA public key
+ * Returns: 0 on success, -1 on failure
+ */
+int crypto_rsa_exptmod(const u8 *in, size_t inlen, u8 *out, size_t *outlen,
+		       struct crypto_rsa_key *key, int use_private)
+{
+	struct bignum *tmp, *a = NULL, *b = NULL;
+	int ret = -1;
+	size_t modlen;
+
+	if (use_private && !key->private_key)
+		return -1;
+
+	tmp = bignum_init();
+	if (tmp == NULL)
+		return -1;
+
+	if (bignum_set_unsigned_bin(tmp, in, inlen) < 0)
+		goto error;
+	if (bignum_cmp(key->n, tmp) < 0) {
+		/* Too large input value for the RSA key modulus */
+		goto error;
+	}
+
+	if (use_private) {
+		/*
+		 * Decrypt (or sign) using Chinese remainer theorem to speed
+		 * up calculation. This is equivalent to tmp = tmp^d mod n
+		 * (which would require more CPU to calculate directly).
+		 *
+		 * dmp1 = (1/e) mod (p-1)
+		 * dmq1 = (1/e) mod (q-1)
+		 * iqmp = (1/q) mod p, where p > q
+		 * m1 = c^dmp1 mod p
+		 * m2 = c^dmq1 mod q
+		 * h = q^-1 (m1 - m2) mod p
+		 * m = m2 + hq
+		 */
+		a = bignum_init();
+		b = bignum_init();
+		if (a == NULL || b == NULL)
+			goto error;
+
+		/* a = tmp^dmp1 mod p */
+		if (bignum_exptmod(tmp, key->dmp1, key->p, a) < 0)
+			goto error;
+
+		/* b = tmp^dmq1 mod q */
+		if (bignum_exptmod(tmp, key->dmq1, key->q, b) < 0)
+			goto error;
+
+		/* tmp = (a - b) * (1/q mod p) (mod p) */
+		if (bignum_sub(a, b, tmp) < 0 ||
+		    bignum_mulmod(tmp, key->iqmp, key->p, tmp) < 0)
+			goto error;
+
+		/* tmp = b + q * tmp */
+		if (bignum_mul(tmp, key->q, tmp) < 0 ||
+		    bignum_add(tmp, b, tmp) < 0)
+			goto error;
+	} else {
+		/* Encrypt (or verify signature) */
+		/* tmp = tmp^e mod N */
+		if (bignum_exptmod(tmp, key->e, key->n, tmp) < 0)
+			goto error;
+	}
+
+	modlen = crypto_rsa_get_modulus_len(key);
+	if (modlen > *outlen) {
+		*outlen = modlen;
+		goto error;
+	}
+
+	if (bignum_get_unsigned_bin_len(tmp) > modlen)
+		goto error; /* should never happen */
+
+	*outlen = modlen;
+	os_memset(out, 0, modlen);
+	if (bignum_get_unsigned_bin(
+		    tmp, out +
+		    (modlen - bignum_get_unsigned_bin_len(tmp)), NULL) < 0)
+		goto error;
+
+	ret = 0;
+
+error:
+	bignum_deinit(tmp);
+	bignum_deinit(a);
+	bignum_deinit(b);
+	return ret;
+}
+
+
+/**
+ * crypto_rsa_free - Free RSA key
+ * @key: RSA key to be freed
+ *
+ * This function frees an RSA key imported with either
+ * crypto_rsa_import_public_key() or crypto_rsa_import_private_key().
+ */
+void crypto_rsa_free(struct crypto_rsa_key *key)
+{
+	if (key) {
+		bignum_deinit(key->n);
+		bignum_deinit(key->e);
+		bignum_deinit(key->d);
+		bignum_deinit(key->p);
+		bignum_deinit(key->q);
+		bignum_deinit(key->dmp1);
+		bignum_deinit(key->dmq1);
+		bignum_deinit(key->iqmp);
+		os_free(key);
+	}
+}
diff --git a/src/tls/rsa.h b/src/tls/rsa.h
new file mode 100644
index 0000000..ac50dfd
--- /dev/null
+++ b/src/tls/rsa.h
@@ -0,0 +1,29 @@
+/*
+ * RSA
+ * Copyright (c) 2006, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef RSA_H
+#define RSA_H
+
+struct crypto_rsa_key;
+
+struct crypto_rsa_key *
+crypto_rsa_import_public_key(const u8 *buf, size_t len);
+struct crypto_rsa_key *
+crypto_rsa_import_private_key(const u8 *buf, size_t len);
+size_t crypto_rsa_get_modulus_len(struct crypto_rsa_key *key);
+int crypto_rsa_exptmod(const u8 *in, size_t inlen, u8 *out, size_t *outlen,
+		       struct crypto_rsa_key *key, int use_private);
+void crypto_rsa_free(struct crypto_rsa_key *key);
+
+#endif /* RSA_H */
diff --git a/src/tls/tlsv1_client.c b/src/tls/tlsv1_client.c
new file mode 100644
index 0000000..afb6031
--- /dev/null
+++ b/src/tls/tlsv1_client.c
@@ -0,0 +1,667 @@
+/*
+ * TLSv1 client (RFC 2246)
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/sha1.h"
+#include "crypto/tls.h"
+#include "tlsv1_common.h"
+#include "tlsv1_record.h"
+#include "tlsv1_client.h"
+#include "tlsv1_client_i.h"
+
+/* TODO:
+ * Support for a message fragmented across several records (RFC 2246, 6.2.1)
+ */
+
+
+void tls_alert(struct tlsv1_client *conn, u8 level, u8 description)
+{
+	conn->alert_level = level;
+	conn->alert_description = description;
+}
+
+
+void tlsv1_client_free_dh(struct tlsv1_client *conn)
+{
+	os_free(conn->dh_p);
+	os_free(conn->dh_g);
+	os_free(conn->dh_ys);
+	conn->dh_p = conn->dh_g = conn->dh_ys = NULL;
+}
+
+
+int tls_derive_pre_master_secret(u8 *pre_master_secret)
+{
+	WPA_PUT_BE16(pre_master_secret, TLS_VERSION);
+	if (os_get_random(pre_master_secret + 2,
+			  TLS_PRE_MASTER_SECRET_LEN - 2))
+		return -1;
+	return 0;
+}
+
+
+int tls_derive_keys(struct tlsv1_client *conn,
+		    const u8 *pre_master_secret, size_t pre_master_secret_len)
+{
+	u8 seed[2 * TLS_RANDOM_LEN];
+	u8 key_block[TLS_MAX_KEY_BLOCK_LEN];
+	u8 *pos;
+	size_t key_block_len;
+
+	if (pre_master_secret) {
+		wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: pre_master_secret",
+				pre_master_secret, pre_master_secret_len);
+		os_memcpy(seed, conn->client_random, TLS_RANDOM_LEN);
+		os_memcpy(seed + TLS_RANDOM_LEN, conn->server_random,
+			  TLS_RANDOM_LEN);
+		if (tls_prf(pre_master_secret, pre_master_secret_len,
+			    "master secret", seed, 2 * TLS_RANDOM_LEN,
+			    conn->master_secret, TLS_MASTER_SECRET_LEN)) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive "
+				   "master_secret");
+			return -1;
+		}
+		wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: master_secret",
+				conn->master_secret, TLS_MASTER_SECRET_LEN);
+	}
+
+	os_memcpy(seed, conn->server_random, TLS_RANDOM_LEN);
+	os_memcpy(seed + TLS_RANDOM_LEN, conn->client_random, TLS_RANDOM_LEN);
+	key_block_len = 2 * (conn->rl.hash_size + conn->rl.key_material_len +
+			     conn->rl.iv_size);
+	if (tls_prf(conn->master_secret, TLS_MASTER_SECRET_LEN,
+		    "key expansion", seed, 2 * TLS_RANDOM_LEN,
+		    key_block, key_block_len)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive key_block");
+		return -1;
+	}
+	wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: key_block",
+			key_block, key_block_len);
+
+	pos = key_block;
+
+	/* client_write_MAC_secret */
+	os_memcpy(conn->rl.write_mac_secret, pos, conn->rl.hash_size);
+	pos += conn->rl.hash_size;
+	/* server_write_MAC_secret */
+	os_memcpy(conn->rl.read_mac_secret, pos, conn->rl.hash_size);
+	pos += conn->rl.hash_size;
+
+	/* client_write_key */
+	os_memcpy(conn->rl.write_key, pos, conn->rl.key_material_len);
+	pos += conn->rl.key_material_len;
+	/* server_write_key */
+	os_memcpy(conn->rl.read_key, pos, conn->rl.key_material_len);
+	pos += conn->rl.key_material_len;
+
+	/* client_write_IV */
+	os_memcpy(conn->rl.write_iv, pos, conn->rl.iv_size);
+	pos += conn->rl.iv_size;
+	/* server_write_IV */
+	os_memcpy(conn->rl.read_iv, pos, conn->rl.iv_size);
+	pos += conn->rl.iv_size;
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_client_handshake - Process TLS handshake
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @in_data: Input data from TLS peer
+ * @in_len: Input data length
+ * @out_len: Length of the output buffer.
+ * @appl_data: Pointer to application data pointer, or %NULL if dropped
+ * @appl_data_len: Pointer to variable that is set to appl_data length
+ * Returns: Pointer to output data, %NULL on failure
+ */
+u8 * tlsv1_client_handshake(struct tlsv1_client *conn,
+			    const u8 *in_data, size_t in_len,
+			    size_t *out_len, u8 **appl_data,
+			    size_t *appl_data_len)
+{
+	const u8 *pos, *end;
+	u8 *msg = NULL, *in_msg, *in_pos, *in_end, alert, ct;
+	size_t in_msg_len;
+	int no_appl_data;
+
+	if (conn->state == CLIENT_HELLO) {
+		if (in_len)
+			return NULL;
+		return tls_send_client_hello(conn, out_len);
+	}
+
+	if (in_data == NULL || in_len == 0)
+		return NULL;
+
+	pos = in_data;
+	end = in_data + in_len;
+	in_msg = os_malloc(in_len);
+	if (in_msg == NULL)
+		return NULL;
+
+	/* Each received packet may include multiple records */
+	while (pos < end) {
+		in_msg_len = in_len;
+		if (tlsv1_record_receive(&conn->rl, pos, end - pos,
+					 in_msg, &in_msg_len, &alert)) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Processing received "
+				   "record failed");
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL, alert);
+			goto failed;
+		}
+		ct = pos[0];
+
+		in_pos = in_msg;
+		in_end = in_msg + in_msg_len;
+
+		/* Each received record may include multiple messages of the
+		 * same ContentType. */
+		while (in_pos < in_end) {
+			in_msg_len = in_end - in_pos;
+			if (tlsv1_client_process_handshake(conn, ct, in_pos,
+							   &in_msg_len,
+							   appl_data,
+							   appl_data_len) < 0)
+				goto failed;
+			in_pos += in_msg_len;
+		}
+
+		pos += TLS_RECORD_HEADER_LEN + WPA_GET_BE16(pos + 3);
+	}
+
+	os_free(in_msg);
+	in_msg = NULL;
+
+	no_appl_data = appl_data == NULL || *appl_data == NULL;
+	msg = tlsv1_client_handshake_write(conn, out_len, no_appl_data);
+
+failed:
+	os_free(in_msg);
+	if (conn->alert_level) {
+		conn->state = FAILED;
+		os_free(msg);
+		msg = tlsv1_client_send_alert(conn, conn->alert_level,
+					      conn->alert_description,
+					      out_len);
+	} else if (msg == NULL) {
+		msg = os_zalloc(1);
+		*out_len = 0;
+	}
+
+	return msg;
+}
+
+
+/**
+ * tlsv1_client_encrypt - Encrypt data into TLS tunnel
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @in_data: Pointer to plaintext data to be encrypted
+ * @in_len: Input buffer length
+ * @out_data: Pointer to output buffer (encrypted TLS data)
+ * @out_len: Maximum out_data length 
+ * Returns: Number of bytes written to out_data, -1 on failure
+ *
+ * This function is used after TLS handshake has been completed successfully to
+ * send data in the encrypted tunnel.
+ */
+int tlsv1_client_encrypt(struct tlsv1_client *conn,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t out_len)
+{
+	size_t rlen;
+
+	wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: Plaintext AppData",
+			in_data, in_len);
+
+	os_memcpy(out_data + TLS_RECORD_HEADER_LEN, in_data, in_len);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_APPLICATION_DATA,
+			      out_data, out_len, in_len, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create a record");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	return rlen;
+}
+
+
+/**
+ * tlsv1_client_decrypt - Decrypt data from TLS tunnel
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @in_data: Pointer to input buffer (encrypted TLS data)
+ * @in_len: Input buffer length
+ * @out_data: Pointer to output buffer (decrypted data from TLS tunnel)
+ * @out_len: Maximum out_data length
+ * Returns: Number of bytes written to out_data, -1 on failure
+ *
+ * This function is used after TLS handshake has been completed successfully to
+ * receive data from the encrypted tunnel.
+ */
+int tlsv1_client_decrypt(struct tlsv1_client *conn,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t out_len)
+{
+	const u8 *in_end, *pos;
+	int res;
+	u8 alert, *out_end, *out_pos;
+	size_t olen;
+
+	pos = in_data;
+	in_end = in_data + in_len;
+	out_pos = out_data;
+	out_end = out_data + out_len;
+
+	while (pos < in_end) {
+		if (pos[0] != TLS_CONTENT_TYPE_APPLICATION_DATA) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Unexpected content type "
+				   "0x%x", pos[0]);
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_UNEXPECTED_MESSAGE);
+			return -1;
+		}
+
+		olen = out_end - out_pos;
+		res = tlsv1_record_receive(&conn->rl, pos, in_end - pos,
+					   out_pos, &olen, &alert);
+		if (res < 0) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Record layer processing "
+				   "failed");
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL, alert);
+			return -1;
+		}
+		out_pos += olen;
+		if (out_pos > out_end) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Buffer not large enough "
+				   "for processing the received record");
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_INTERNAL_ERROR);
+			return -1;
+		}
+
+		pos += TLS_RECORD_HEADER_LEN + WPA_GET_BE16(pos + 3);
+	}
+
+	return out_pos - out_data;
+}
+
+
+/**
+ * tlsv1_client_global_init - Initialize TLSv1 client
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function must be called before using any other TLSv1 client functions.
+ */
+int tlsv1_client_global_init(void)
+{
+	return crypto_global_init();
+}
+
+
+/**
+ * tlsv1_client_global_deinit - Deinitialize TLSv1 client
+ *
+ * This function can be used to deinitialize the TLSv1 client that was
+ * initialized by calling tlsv1_client_global_init(). No TLSv1 client functions
+ * can be called after this before calling tlsv1_client_global_init() again.
+ */
+void tlsv1_client_global_deinit(void)
+{
+	crypto_global_deinit();
+}
+
+
+/**
+ * tlsv1_client_init - Initialize TLSv1 client connection
+ * Returns: Pointer to TLSv1 client connection data or %NULL on failure
+ */
+struct tlsv1_client * tlsv1_client_init(void)
+{
+	struct tlsv1_client *conn;
+	size_t count;
+	u16 *suites;
+
+	conn = os_zalloc(sizeof(*conn));
+	if (conn == NULL)
+		return NULL;
+
+	conn->state = CLIENT_HELLO;
+
+	if (tls_verify_hash_init(&conn->verify) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to initialize verify "
+			   "hash");
+		os_free(conn);
+		return NULL;
+	}
+
+	count = 0;
+	suites = conn->cipher_suites;
+#ifndef CONFIG_CRYPTO_INTERNAL
+	suites[count++] = TLS_RSA_WITH_AES_256_CBC_SHA;
+#endif /* CONFIG_CRYPTO_INTERNAL */
+	suites[count++] = TLS_RSA_WITH_AES_128_CBC_SHA;
+	suites[count++] = TLS_RSA_WITH_3DES_EDE_CBC_SHA;
+	suites[count++] = TLS_RSA_WITH_RC4_128_SHA;
+	suites[count++] = TLS_RSA_WITH_RC4_128_MD5;
+	conn->num_cipher_suites = count;
+
+	return conn;
+}
+
+
+/**
+ * tlsv1_client_deinit - Deinitialize TLSv1 client connection
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ */
+void tlsv1_client_deinit(struct tlsv1_client *conn)
+{
+	crypto_public_key_free(conn->server_rsa_key);
+	tlsv1_record_set_cipher_suite(&conn->rl, TLS_NULL_WITH_NULL_NULL);
+	tlsv1_record_change_write_cipher(&conn->rl);
+	tlsv1_record_change_read_cipher(&conn->rl);
+	tls_verify_hash_free(&conn->verify);
+	os_free(conn->client_hello_ext);
+	tlsv1_client_free_dh(conn);
+	tlsv1_cred_free(conn->cred);
+	os_free(conn);
+}
+
+
+/**
+ * tlsv1_client_established - Check whether connection has been established
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * Returns: 1 if connection is established, 0 if not
+ */
+int tlsv1_client_established(struct tlsv1_client *conn)
+{
+	return conn->state == ESTABLISHED;
+}
+
+
+/**
+ * tlsv1_client_prf - Use TLS-PRF to derive keying material
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @label: Label (e.g., description of the key) for PRF
+ * @server_random_first: seed is 0 = client_random|server_random,
+ * 1 = server_random|client_random
+ * @out: Buffer for output data from TLS-PRF
+ * @out_len: Length of the output buffer
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_client_prf(struct tlsv1_client *conn, const char *label,
+		     int server_random_first, u8 *out, size_t out_len)
+{
+	u8 seed[2 * TLS_RANDOM_LEN];
+
+	if (conn->state != ESTABLISHED)
+		return -1;
+
+	if (server_random_first) {
+		os_memcpy(seed, conn->server_random, TLS_RANDOM_LEN);
+		os_memcpy(seed + TLS_RANDOM_LEN, conn->client_random,
+			  TLS_RANDOM_LEN);
+	} else {
+		os_memcpy(seed, conn->client_random, TLS_RANDOM_LEN);
+		os_memcpy(seed + TLS_RANDOM_LEN, conn->server_random,
+			  TLS_RANDOM_LEN);
+	}
+
+	return tls_prf(conn->master_secret, TLS_MASTER_SECRET_LEN,
+		       label, seed, 2 * TLS_RANDOM_LEN, out, out_len);
+}
+
+
+/**
+ * tlsv1_client_get_cipher - Get current cipher name
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @buf: Buffer for the cipher name
+ * @buflen: buf size
+ * Returns: 0 on success, -1 on failure
+ *
+ * Get the name of the currently used cipher.
+ */
+int tlsv1_client_get_cipher(struct tlsv1_client *conn, char *buf,
+			    size_t buflen)
+{
+	char *cipher;
+
+	switch (conn->rl.cipher_suite) {
+	case TLS_RSA_WITH_RC4_128_MD5:
+		cipher = "RC4-MD5";
+		break;
+	case TLS_RSA_WITH_RC4_128_SHA:
+		cipher = "RC4-SHA";
+		break;
+	case TLS_RSA_WITH_DES_CBC_SHA:
+		cipher = "DES-CBC-SHA";
+		break;
+	case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+		cipher = "DES-CBC3-SHA";
+		break;
+	case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+		cipher = "ADH-AES-128-SHA";
+		break;
+	case TLS_RSA_WITH_AES_256_CBC_SHA:
+		cipher = "AES-256-SHA";
+		break;
+	case TLS_RSA_WITH_AES_128_CBC_SHA:
+		cipher = "AES-128-SHA";
+		break;
+	default:
+		return -1;
+	}
+
+	if (os_strlcpy(buf, cipher, buflen) >= buflen)
+		return -1;
+	return 0;
+}
+
+
+/**
+ * tlsv1_client_shutdown - Shutdown TLS connection
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_client_shutdown(struct tlsv1_client *conn)
+{
+	conn->state = CLIENT_HELLO;
+
+	if (tls_verify_hash_init(&conn->verify) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to re-initialize verify "
+			   "hash");
+		return -1;
+	}
+
+	tlsv1_record_set_cipher_suite(&conn->rl, TLS_NULL_WITH_NULL_NULL);
+	tlsv1_record_change_write_cipher(&conn->rl);
+	tlsv1_record_change_read_cipher(&conn->rl);
+
+	conn->certificate_requested = 0;
+	crypto_public_key_free(conn->server_rsa_key);
+	conn->server_rsa_key = NULL;
+	conn->session_resumed = 0;
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_client_resumed - Was session resumption used
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * Returns: 1 if current session used session resumption, 0 if not
+ */
+int tlsv1_client_resumed(struct tlsv1_client *conn)
+{
+	return !!conn->session_resumed;
+}
+
+
+/**
+ * tlsv1_client_hello_ext - Set TLS extension for ClientHello
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @ext_type: Extension type
+ * @data: Extension payload (%NULL to remove extension)
+ * @data_len: Extension payload length
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_client_hello_ext(struct tlsv1_client *conn, int ext_type,
+			   const u8 *data, size_t data_len)
+{
+	u8 *pos;
+
+	conn->session_ticket_included = 0;
+	os_free(conn->client_hello_ext);
+	conn->client_hello_ext = NULL;
+	conn->client_hello_ext_len = 0;
+
+	if (data == NULL || data_len == 0)
+		return 0;
+
+	pos = conn->client_hello_ext = os_malloc(6 + data_len);
+	if (pos == NULL)
+		return -1;
+
+	WPA_PUT_BE16(pos, 4 + data_len);
+	pos += 2;
+	WPA_PUT_BE16(pos, ext_type);
+	pos += 2;
+	WPA_PUT_BE16(pos, data_len);
+	pos += 2;
+	os_memcpy(pos, data, data_len);
+	conn->client_hello_ext_len = 6 + data_len;
+
+	if (ext_type == TLS_EXT_PAC_OPAQUE) {
+		conn->session_ticket_included = 1;
+		wpa_printf(MSG_DEBUG, "TLSv1: Using session ticket");
+	}
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_client_get_keys - Get master key and random data from TLS connection
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @keys: Structure of key/random data (filled on success)
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_client_get_keys(struct tlsv1_client *conn, struct tls_keys *keys)
+{
+	os_memset(keys, 0, sizeof(*keys));
+	if (conn->state == CLIENT_HELLO)
+		return -1;
+
+	keys->client_random = conn->client_random;
+	keys->client_random_len = TLS_RANDOM_LEN;
+
+	if (conn->state != SERVER_HELLO) {
+		keys->server_random = conn->server_random;
+		keys->server_random_len = TLS_RANDOM_LEN;
+		keys->master_key = conn->master_secret;
+		keys->master_key_len = TLS_MASTER_SECRET_LEN;
+	}
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_client_get_keyblock_size - Get TLS key_block size
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * Returns: Size of the key_block for the negotiated cipher suite or -1 on
+ * failure
+ */
+int tlsv1_client_get_keyblock_size(struct tlsv1_client *conn)
+{
+	if (conn->state == CLIENT_HELLO || conn->state == SERVER_HELLO)
+		return -1;
+
+	return 2 * (conn->rl.hash_size + conn->rl.key_material_len +
+		    conn->rl.iv_size);
+}
+
+
+/**
+ * tlsv1_client_set_cipher_list - Configure acceptable cipher suites
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @ciphers: Zero (TLS_CIPHER_NONE) terminated list of allowed ciphers
+ * (TLS_CIPHER_*).
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_client_set_cipher_list(struct tlsv1_client *conn, u8 *ciphers)
+{
+	size_t count;
+	u16 *suites;
+
+	/* TODO: implement proper configuration of cipher suites */
+	if (ciphers[0] == TLS_CIPHER_ANON_DH_AES128_SHA) {
+		count = 0;
+		suites = conn->cipher_suites;
+#ifndef CONFIG_CRYPTO_INTERNAL
+		suites[count++] = TLS_DH_anon_WITH_AES_256_CBC_SHA;
+#endif /* CONFIG_CRYPTO_INTERNAL */
+		suites[count++] = TLS_DH_anon_WITH_AES_128_CBC_SHA;
+		suites[count++] = TLS_DH_anon_WITH_3DES_EDE_CBC_SHA;
+		suites[count++] = TLS_DH_anon_WITH_RC4_128_MD5;
+		suites[count++] = TLS_DH_anon_WITH_DES_CBC_SHA;
+
+		/*
+		 * Cisco AP (at least 350 and 1200 series) local authentication
+		 * server does not know how to search cipher suites from the
+		 * list and seem to require that the last entry in the list is
+		 * the one that it wants to use. However, TLS specification
+		 * requires the list to be in the client preference order. As a
+		 * workaround, add anon-DH AES-128-SHA1 again at the end of the
+		 * list to allow the Cisco code to find it.
+		 */
+		suites[count++] = TLS_DH_anon_WITH_AES_128_CBC_SHA;
+		conn->num_cipher_suites = count;
+	}
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_client_set_cred - Set client credentials
+ * @conn: TLSv1 client connection data from tlsv1_client_init()
+ * @cred: Credentials from tlsv1_cred_alloc()
+ * Returns: 0 on success, -1 on failure
+ *
+ * On success, the client takes ownership of the credentials block and caller
+ * must not free it. On failure, caller is responsible for freeing the
+ * credential block.
+ */
+int tlsv1_client_set_cred(struct tlsv1_client *conn,
+			  struct tlsv1_credentials *cred)
+{
+	tlsv1_cred_free(conn->cred);
+	conn->cred = cred;
+	return 0;
+}
+
+
+void tlsv1_client_set_session_ticket_cb(struct tlsv1_client *conn,
+					tlsv1_client_session_ticket_cb cb,
+					void *ctx)
+{
+	wpa_printf(MSG_DEBUG, "TLSv1: SessionTicket callback set %p (ctx %p)",
+		   cb, ctx);
+	conn->session_ticket_cb = cb;
+	conn->session_ticket_cb_ctx = ctx;
+}
diff --git a/src/tls/tlsv1_client.h b/src/tls/tlsv1_client.h
new file mode 100644
index 0000000..16ad57d
--- /dev/null
+++ b/src/tls/tlsv1_client.h
@@ -0,0 +1,59 @@
+/*
+ * TLSv1 client (RFC 2246)
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef TLSV1_CLIENT_H
+#define TLSV1_CLIENT_H
+
+#include "tlsv1_cred.h"
+
+struct tlsv1_client;
+
+int tlsv1_client_global_init(void);
+void tlsv1_client_global_deinit(void);
+struct tlsv1_client * tlsv1_client_init(void);
+void tlsv1_client_deinit(struct tlsv1_client *conn);
+int tlsv1_client_established(struct tlsv1_client *conn);
+int tlsv1_client_prf(struct tlsv1_client *conn, const char *label,
+		     int server_random_first, u8 *out, size_t out_len);
+u8 * tlsv1_client_handshake(struct tlsv1_client *conn,
+			    const u8 *in_data, size_t in_len,
+			    size_t *out_len, u8 **appl_data,
+			    size_t *appl_data_len);
+int tlsv1_client_encrypt(struct tlsv1_client *conn,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t out_len);
+int tlsv1_client_decrypt(struct tlsv1_client *conn,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t out_len);
+int tlsv1_client_get_cipher(struct tlsv1_client *conn, char *buf,
+			    size_t buflen);
+int tlsv1_client_shutdown(struct tlsv1_client *conn);
+int tlsv1_client_resumed(struct tlsv1_client *conn);
+int tlsv1_client_hello_ext(struct tlsv1_client *conn, int ext_type,
+			   const u8 *data, size_t data_len);
+int tlsv1_client_get_keys(struct tlsv1_client *conn, struct tls_keys *keys);
+int tlsv1_client_get_keyblock_size(struct tlsv1_client *conn);
+int tlsv1_client_set_cipher_list(struct tlsv1_client *conn, u8 *ciphers);
+int tlsv1_client_set_cred(struct tlsv1_client *conn,
+			  struct tlsv1_credentials *cred);
+
+typedef int (*tlsv1_client_session_ticket_cb)
+(void *ctx, const u8 *ticket, size_t len, const u8 *client_random,
+ const u8 *server_random, u8 *master_secret);
+
+void tlsv1_client_set_session_ticket_cb(struct tlsv1_client *conn,
+					tlsv1_client_session_ticket_cb cb,
+					void *ctx);
+
+#endif /* TLSV1_CLIENT_H */
diff --git a/src/tls/tlsv1_client_i.h b/src/tls/tlsv1_client_i.h
new file mode 100644
index 0000000..7fe179f
--- /dev/null
+++ b/src/tls/tlsv1_client_i.h
@@ -0,0 +1,87 @@
+/*
+ * TLSv1 client - internal structures
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef TLSV1_CLIENT_I_H
+#define TLSV1_CLIENT_I_H
+
+struct tlsv1_client {
+	enum {
+		CLIENT_HELLO, SERVER_HELLO, SERVER_CERTIFICATE,
+		SERVER_KEY_EXCHANGE, SERVER_CERTIFICATE_REQUEST,
+		SERVER_HELLO_DONE, CLIENT_KEY_EXCHANGE, CHANGE_CIPHER_SPEC,
+		SERVER_CHANGE_CIPHER_SPEC, SERVER_FINISHED, ACK_FINISHED,
+		ESTABLISHED, FAILED
+	} state;
+
+	struct tlsv1_record_layer rl;
+
+	u8 session_id[TLS_SESSION_ID_MAX_LEN];
+	size_t session_id_len;
+	u8 client_random[TLS_RANDOM_LEN];
+	u8 server_random[TLS_RANDOM_LEN];
+	u8 master_secret[TLS_MASTER_SECRET_LEN];
+
+	u8 alert_level;
+	u8 alert_description;
+
+	unsigned int certificate_requested:1;
+	unsigned int session_resumed:1;
+	unsigned int session_ticket_included:1;
+	unsigned int use_session_ticket:1;
+
+	struct crypto_public_key *server_rsa_key;
+
+	struct tls_verify_hash verify;
+
+#define MAX_CIPHER_COUNT 30
+	u16 cipher_suites[MAX_CIPHER_COUNT];
+	size_t num_cipher_suites;
+
+	u16 prev_cipher_suite;
+
+	u8 *client_hello_ext;
+	size_t client_hello_ext_len;
+
+	/* The prime modulus used for Diffie-Hellman */
+	u8 *dh_p;
+	size_t dh_p_len;
+	/* The generator used for Diffie-Hellman */
+	u8 *dh_g;
+	size_t dh_g_len;
+	/* The server's Diffie-Hellman public value */
+	u8 *dh_ys;
+	size_t dh_ys_len;
+
+	struct tlsv1_credentials *cred;
+
+	tlsv1_client_session_ticket_cb session_ticket_cb;
+	void *session_ticket_cb_ctx;
+};
+
+
+void tls_alert(struct tlsv1_client *conn, u8 level, u8 description);
+void tlsv1_client_free_dh(struct tlsv1_client *conn);
+int tls_derive_pre_master_secret(u8 *pre_master_secret);
+int tls_derive_keys(struct tlsv1_client *conn,
+		    const u8 *pre_master_secret, size_t pre_master_secret_len);
+u8 * tls_send_client_hello(struct tlsv1_client *conn, size_t *out_len);
+u8 * tlsv1_client_send_alert(struct tlsv1_client *conn, u8 level,
+			     u8 description, size_t *out_len);
+u8 * tlsv1_client_handshake_write(struct tlsv1_client *conn, size_t *out_len,
+				  int no_appl_data);
+int tlsv1_client_process_handshake(struct tlsv1_client *conn, u8 ct,
+				   const u8 *buf, size_t *len,
+				   u8 **out_data, size_t *out_len);
+
+#endif /* TLSV1_CLIENT_I_H */
diff --git a/src/tls/tlsv1_client_read.c b/src/tls/tlsv1_client_read.c
new file mode 100644
index 0000000..ed3f260
--- /dev/null
+++ b/src/tls/tlsv1_client_read.c
@@ -0,0 +1,976 @@
+/*
+ * TLSv1 client - read handshake message
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/md5.h"
+#include "crypto/sha1.h"
+#include "crypto/tls.h"
+#include "x509v3.h"
+#include "tlsv1_common.h"
+#include "tlsv1_record.h"
+#include "tlsv1_client.h"
+#include "tlsv1_client_i.h"
+
+static int tls_process_server_key_exchange(struct tlsv1_client *conn, u8 ct,
+					   const u8 *in_data, size_t *in_len);
+static int tls_process_certificate_request(struct tlsv1_client *conn, u8 ct,
+					   const u8 *in_data, size_t *in_len);
+static int tls_process_server_hello_done(struct tlsv1_client *conn, u8 ct,
+					 const u8 *in_data, size_t *in_len);
+
+
+static int tls_process_server_hello(struct tlsv1_client *conn, u8 ct,
+				    const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len, i;
+	u16 cipher_suite;
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4)
+		goto decode_error;
+
+	/* HandshakeType msg_type */
+	if (*pos != TLS_HANDSHAKE_TYPE_SERVER_HELLO) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected ServerHello)", *pos);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG, "TLSv1: Received ServerHello");
+	pos++;
+	/* uint24 length */
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left)
+		goto decode_error;
+
+	/* body - ServerHello */
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: ServerHello", pos, len);
+	end = pos + len;
+
+	/* ProtocolVersion server_version */
+	if (end - pos < 2)
+		goto decode_error;
+	if (WPA_GET_BE16(pos) != TLS_VERSION) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected protocol version in "
+			   "ServerHello");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_PROTOCOL_VERSION);
+		return -1;
+	}
+	pos += 2;
+
+	/* Random random */
+	if (end - pos < TLS_RANDOM_LEN)
+		goto decode_error;
+
+	os_memcpy(conn->server_random, pos, TLS_RANDOM_LEN);
+	pos += TLS_RANDOM_LEN;
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: server_random",
+		    conn->server_random, TLS_RANDOM_LEN);
+
+	/* SessionID session_id */
+	if (end - pos < 1)
+		goto decode_error;
+	if (end - pos < 1 + *pos || *pos > TLS_SESSION_ID_MAX_LEN)
+		goto decode_error;
+	if (conn->session_id_len && conn->session_id_len == *pos &&
+	    os_memcmp(conn->session_id, pos + 1, conn->session_id_len) == 0) {
+		pos += 1 + conn->session_id_len;
+		wpa_printf(MSG_DEBUG, "TLSv1: Resuming old session");
+		conn->session_resumed = 1;
+	} else {
+		conn->session_id_len = *pos;
+		pos++;
+		os_memcpy(conn->session_id, pos, conn->session_id_len);
+		pos += conn->session_id_len;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: session_id",
+		    conn->session_id, conn->session_id_len);
+
+	/* CipherSuite cipher_suite */
+	if (end - pos < 2)
+		goto decode_error;
+	cipher_suite = WPA_GET_BE16(pos);
+	pos += 2;
+	for (i = 0; i < conn->num_cipher_suites; i++) {
+		if (cipher_suite == conn->cipher_suites[i])
+			break;
+	}
+	if (i == conn->num_cipher_suites) {
+		wpa_printf(MSG_INFO, "TLSv1: Server selected unexpected "
+			   "cipher suite 0x%04x", cipher_suite);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_ILLEGAL_PARAMETER);
+		return -1;
+	}
+
+	if (conn->session_resumed && cipher_suite != conn->prev_cipher_suite) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Server selected a different "
+			   "cipher suite for a resumed connection (0x%04x != "
+			   "0x%04x)", cipher_suite, conn->prev_cipher_suite);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_ILLEGAL_PARAMETER);
+		return -1;
+	}
+
+	if (tlsv1_record_set_cipher_suite(&conn->rl, cipher_suite) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to set CipherSuite for "
+			   "record layer");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	conn->prev_cipher_suite = cipher_suite;
+
+	/* CompressionMethod compression_method */
+	if (end - pos < 1)
+		goto decode_error;
+	if (*pos != TLS_COMPRESSION_NULL) {
+		wpa_printf(MSG_INFO, "TLSv1: Server selected unexpected "
+			   "compression 0x%02x", *pos);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_ILLEGAL_PARAMETER);
+		return -1;
+	}
+	pos++;
+
+	if (end != pos) {
+		/* TODO: ServerHello extensions */
+		wpa_hexdump(MSG_DEBUG, "TLSv1: Unexpected extra data in the "
+			    "end of ServerHello", pos, end - pos);
+		goto decode_error;
+	}
+
+	if (conn->session_ticket_included && conn->session_ticket_cb) {
+		/* TODO: include SessionTicket extension if one was included in
+		 * ServerHello */
+		int res = conn->session_ticket_cb(
+			conn->session_ticket_cb_ctx, NULL, 0,
+			conn->client_random, conn->server_random,
+			conn->master_secret);
+		if (res < 0) {
+			wpa_printf(MSG_DEBUG, "TLSv1: SessionTicket callback "
+				   "indicated failure");
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_HANDSHAKE_FAILURE);
+			return -1;
+		}
+		conn->use_session_ticket = !!res;
+	}
+
+	if ((conn->session_resumed || conn->use_session_ticket) &&
+	    tls_derive_keys(conn, NULL, 0)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive keys");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	*in_len = end - in_data;
+
+	conn->state = (conn->session_resumed || conn->use_session_ticket) ?
+		SERVER_CHANGE_CIPHER_SPEC : SERVER_CERTIFICATE;
+
+	return 0;
+
+decode_error:
+	wpa_printf(MSG_DEBUG, "TLSv1: Failed to decode ServerHello");
+	tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+	return -1;
+}
+
+
+static int tls_process_certificate(struct tlsv1_client *conn, u8 ct,
+				   const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len, list_len, cert_len, idx;
+	u8 type;
+	struct x509_certificate *chain = NULL, *last = NULL, *cert;
+	int reason;
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short Certificate message "
+			   "(len=%lu)", (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	type = *pos++;
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected Certificate message "
+			   "length (len=%lu != left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	if (type == TLS_HANDSHAKE_TYPE_SERVER_KEY_EXCHANGE)
+		return tls_process_server_key_exchange(conn, ct, in_data,
+						       in_len);
+	if (type == TLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST)
+		return tls_process_certificate_request(conn, ct, in_data,
+						       in_len);
+	if (type == TLS_HANDSHAKE_TYPE_SERVER_HELLO_DONE)
+		return tls_process_server_hello_done(conn, ct, in_data,
+						     in_len);
+	if (type != TLS_HANDSHAKE_TYPE_CERTIFICATE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected Certificate/"
+			   "ServerKeyExchange/CertificateRequest/"
+			   "ServerHelloDone)", type);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "TLSv1: Received Certificate (certificate_list len %lu)",
+		   (unsigned long) len);
+
+	/*
+	 * opaque ASN.1Cert<2^24-1>;
+	 *
+	 * struct {
+	 *     ASN.1Cert certificate_list<1..2^24-1>;
+	 * } Certificate;
+	 */
+
+	end = pos + len;
+
+	if (end - pos < 3) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short Certificate "
+			   "(left=%lu)", (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	list_len = WPA_GET_BE24(pos);
+	pos += 3;
+
+	if ((size_t) (end - pos) != list_len) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected certificate_list "
+			   "length (len=%lu left=%lu)",
+			   (unsigned long) list_len,
+			   (unsigned long) (end - pos));
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	idx = 0;
+	while (pos < end) {
+		if (end - pos < 3) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse "
+				   "certificate_list");
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_DECODE_ERROR);
+			x509_certificate_chain_free(chain);
+			return -1;
+		}
+
+		cert_len = WPA_GET_BE24(pos);
+		pos += 3;
+
+		if ((size_t) (end - pos) < cert_len) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Unexpected certificate "
+				   "length (len=%lu left=%lu)",
+				   (unsigned long) cert_len,
+				   (unsigned long) (end - pos));
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_DECODE_ERROR);
+			x509_certificate_chain_free(chain);
+			return -1;
+		}
+
+		wpa_printf(MSG_DEBUG, "TLSv1: Certificate %lu (len %lu)",
+			   (unsigned long) idx, (unsigned long) cert_len);
+
+		if (idx == 0) {
+			crypto_public_key_free(conn->server_rsa_key);
+			if (tls_parse_cert(pos, cert_len,
+					   &conn->server_rsa_key)) {
+				wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse "
+					   "the certificate");
+				tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					  TLS_ALERT_BAD_CERTIFICATE);
+				x509_certificate_chain_free(chain);
+				return -1;
+			}
+		}
+
+		cert = x509_certificate_parse(pos, cert_len);
+		if (cert == NULL) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse "
+				   "the certificate");
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_BAD_CERTIFICATE);
+			x509_certificate_chain_free(chain);
+			return -1;
+		}
+
+		if (last == NULL)
+			chain = cert;
+		else
+			last->next = cert;
+		last = cert;
+
+		idx++;
+		pos += cert_len;
+	}
+
+	if (conn->cred &&
+	    x509_certificate_chain_validate(conn->cred->trusted_certs, chain,
+					    &reason) < 0) {
+		int tls_reason;
+		wpa_printf(MSG_DEBUG, "TLSv1: Server certificate chain "
+			   "validation failed (reason=%d)", reason);
+		switch (reason) {
+		case X509_VALIDATE_BAD_CERTIFICATE:
+			tls_reason = TLS_ALERT_BAD_CERTIFICATE;
+			break;
+		case X509_VALIDATE_UNSUPPORTED_CERTIFICATE:
+			tls_reason = TLS_ALERT_UNSUPPORTED_CERTIFICATE;
+			break;
+		case X509_VALIDATE_CERTIFICATE_REVOKED:
+			tls_reason = TLS_ALERT_CERTIFICATE_REVOKED;
+			break;
+		case X509_VALIDATE_CERTIFICATE_EXPIRED:
+			tls_reason = TLS_ALERT_CERTIFICATE_EXPIRED;
+			break;
+		case X509_VALIDATE_CERTIFICATE_UNKNOWN:
+			tls_reason = TLS_ALERT_CERTIFICATE_UNKNOWN;
+			break;
+		case X509_VALIDATE_UNKNOWN_CA:
+			tls_reason = TLS_ALERT_UNKNOWN_CA;
+			break;
+		default:
+			tls_reason = TLS_ALERT_BAD_CERTIFICATE;
+			break;
+		}
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, tls_reason);
+		x509_certificate_chain_free(chain);
+		return -1;
+	}
+
+	x509_certificate_chain_free(chain);
+
+	*in_len = end - in_data;
+
+	conn->state = SERVER_KEY_EXCHANGE;
+
+	return 0;
+}
+
+
+static int tlsv1_process_diffie_hellman(struct tlsv1_client *conn,
+					const u8 *buf, size_t len)
+{
+	const u8 *pos, *end;
+
+	tlsv1_client_free_dh(conn);
+
+	pos = buf;
+	end = buf + len;
+
+	if (end - pos < 3)
+		goto fail;
+	conn->dh_p_len = WPA_GET_BE16(pos);
+	pos += 2;
+	if (conn->dh_p_len == 0 || end - pos < (int) conn->dh_p_len) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Invalid dh_p length %lu",
+			   (unsigned long) conn->dh_p_len);
+		goto fail;
+	}
+	conn->dh_p = os_malloc(conn->dh_p_len);
+	if (conn->dh_p == NULL)
+		goto fail;
+	os_memcpy(conn->dh_p, pos, conn->dh_p_len);
+	pos += conn->dh_p_len;
+	wpa_hexdump(MSG_DEBUG, "TLSv1: DH p (prime)",
+		    conn->dh_p, conn->dh_p_len);
+
+	if (end - pos < 3)
+		goto fail;
+	conn->dh_g_len = WPA_GET_BE16(pos);
+	pos += 2;
+	if (conn->dh_g_len == 0 || end - pos < (int) conn->dh_g_len)
+		goto fail;
+	conn->dh_g = os_malloc(conn->dh_g_len);
+	if (conn->dh_g == NULL)
+		goto fail;
+	os_memcpy(conn->dh_g, pos, conn->dh_g_len);
+	pos += conn->dh_g_len;
+	wpa_hexdump(MSG_DEBUG, "TLSv1: DH g (generator)",
+		    conn->dh_g, conn->dh_g_len);
+	if (conn->dh_g_len == 1 && conn->dh_g[0] < 2)
+		goto fail;
+
+	if (end - pos < 3)
+		goto fail;
+	conn->dh_ys_len = WPA_GET_BE16(pos);
+	pos += 2;
+	if (conn->dh_ys_len == 0 || end - pos < (int) conn->dh_ys_len)
+		goto fail;
+	conn->dh_ys = os_malloc(conn->dh_ys_len);
+	if (conn->dh_ys == NULL)
+		goto fail;
+	os_memcpy(conn->dh_ys, pos, conn->dh_ys_len);
+	pos += conn->dh_ys_len;
+	wpa_hexdump(MSG_DEBUG, "TLSv1: DH Ys (server's public value)",
+		    conn->dh_ys, conn->dh_ys_len);
+
+	return 0;
+
+fail:
+	wpa_printf(MSG_DEBUG, "TLSv1: Processing DH params failed");
+	tlsv1_client_free_dh(conn);
+	return -1;
+}
+
+
+static int tls_process_server_key_exchange(struct tlsv1_client *conn, u8 ct,
+					   const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len;
+	u8 type;
+	const struct tls_cipher_suite *suite;
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short ServerKeyExchange "
+			   "(Left=%lu)", (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	type = *pos++;
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Mismatch in ServerKeyExchange "
+			   "length (len=%lu != left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	end = pos + len;
+
+	if (type == TLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST)
+		return tls_process_certificate_request(conn, ct, in_data,
+						       in_len);
+	if (type == TLS_HANDSHAKE_TYPE_SERVER_HELLO_DONE)
+		return tls_process_server_hello_done(conn, ct, in_data,
+						     in_len);
+	if (type != TLS_HANDSHAKE_TYPE_SERVER_KEY_EXCHANGE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected ServerKeyExchange/"
+			   "CertificateRequest/ServerHelloDone)", type);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received ServerKeyExchange");
+
+	if (!tls_server_key_exchange_allowed(conn->rl.cipher_suite)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: ServerKeyExchange not allowed "
+			   "with the selected cipher suite");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "TLSv1: ServerKeyExchange", pos, len);
+	suite = tls_get_cipher_suite(conn->rl.cipher_suite);
+	if (suite && suite->key_exchange == TLS_KEY_X_DH_anon) {
+		if (tlsv1_process_diffie_hellman(conn, pos, len) < 0) {
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_DECODE_ERROR);
+			return -1;
+		}
+	} else {
+		wpa_printf(MSG_DEBUG, "TLSv1: UnexpectedServerKeyExchange");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	*in_len = end - in_data;
+
+	conn->state = SERVER_CERTIFICATE_REQUEST;
+
+	return 0;
+}
+
+
+static int tls_process_certificate_request(struct tlsv1_client *conn, u8 ct,
+					   const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len;
+	u8 type;
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short CertificateRequest "
+			   "(left=%lu)", (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	type = *pos++;
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Mismatch in CertificateRequest "
+			   "length (len=%lu != left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	end = pos + len;
+
+	if (type == TLS_HANDSHAKE_TYPE_SERVER_HELLO_DONE)
+		return tls_process_server_hello_done(conn, ct, in_data,
+						     in_len);
+	if (type != TLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected CertificateRequest/"
+			   "ServerHelloDone)", type);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received CertificateRequest");
+
+	conn->certificate_requested = 1;
+
+	*in_len = end - in_data;
+
+	conn->state = SERVER_HELLO_DONE;
+
+	return 0;
+}
+
+
+static int tls_process_server_hello_done(struct tlsv1_client *conn, u8 ct,
+					 const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len;
+	u8 type;
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short ServerHelloDone "
+			   "(left=%lu)", (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	type = *pos++;
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Mismatch in ServerHelloDone "
+			   "length (len=%lu != left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+	end = pos + len;
+
+	if (type != TLS_HANDSHAKE_TYPE_SERVER_HELLO_DONE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected ServerHelloDone)", type);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received ServerHelloDone");
+
+	*in_len = end - in_data;
+
+	conn->state = CLIENT_KEY_EXCHANGE;
+
+	return 0;
+}
+
+
+static int tls_process_server_change_cipher_spec(struct tlsv1_client *conn,
+						 u8 ct, const u8 *in_data,
+						 size_t *in_len)
+{
+	const u8 *pos;
+	size_t left;
+
+	if (ct != TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected ChangeCipherSpec; "
+			   "received content type 0x%x", ct);
+		if (conn->use_session_ticket) {
+			int res;
+			wpa_printf(MSG_DEBUG, "TLSv1: Server may have "
+				   "rejected SessionTicket");
+			conn->use_session_ticket = 0;
+
+			/* Notify upper layers that SessionTicket failed */
+			res = conn->session_ticket_cb(
+				conn->session_ticket_cb_ctx, NULL, 0, NULL,
+				NULL, NULL);
+			if (res < 0) {
+				wpa_printf(MSG_DEBUG, "TLSv1: SessionTicket "
+					   "callback indicated failure");
+				tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					  TLS_ALERT_HANDSHAKE_FAILURE);
+				return -1;
+			}
+
+			conn->state = SERVER_CERTIFICATE;
+			return tls_process_certificate(conn, ct, in_data,
+						       in_len);
+		}
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 1) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short ChangeCipherSpec");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	if (*pos != TLS_CHANGE_CIPHER_SPEC) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected ChangeCipherSpec; "
+			   "received data 0x%x", *pos);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received ChangeCipherSpec");
+	if (tlsv1_record_change_read_cipher(&conn->rl) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to change read cipher "
+			   "for record layer");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	*in_len = pos + 1 - in_data;
+
+	conn->state = SERVER_FINISHED;
+
+	return 0;
+}
+
+
+static int tls_process_server_finished(struct tlsv1_client *conn, u8 ct,
+				       const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len, hlen;
+	u8 verify_data[TLS_VERIFY_DATA_LEN];
+	u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN];
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Finished; "
+			   "received content type 0x%x", ct);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short record (left=%lu) for "
+			   "Finished",
+			   (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	if (pos[0] != TLS_HANDSHAKE_TYPE_FINISHED) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Finished; received "
+			   "type 0x%x", pos[0]);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	len = WPA_GET_BE24(pos + 1);
+
+	pos += 4;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short buffer for Finished "
+			   "(len=%lu > left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+	end = pos + len;
+	if (len != TLS_VERIFY_DATA_LEN) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected verify_data length "
+			   "in Finished: %lu (expected %d)",
+			   (unsigned long) len, TLS_VERIFY_DATA_LEN);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: verify_data in Finished",
+		    pos, TLS_VERIFY_DATA_LEN);
+
+	hlen = MD5_MAC_LEN;
+	if (conn->verify.md5_server == NULL ||
+	    crypto_hash_finish(conn->verify.md5_server, hash, &hlen) < 0) {
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		conn->verify.md5_server = NULL;
+		crypto_hash_finish(conn->verify.sha1_server, NULL, NULL);
+		conn->verify.sha1_server = NULL;
+		return -1;
+	}
+	conn->verify.md5_server = NULL;
+	hlen = SHA1_MAC_LEN;
+	if (conn->verify.sha1_server == NULL ||
+	    crypto_hash_finish(conn->verify.sha1_server, hash + MD5_MAC_LEN,
+			       &hlen) < 0) {
+		conn->verify.sha1_server = NULL;
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	conn->verify.sha1_server = NULL;
+
+	if (tls_prf(conn->master_secret, TLS_MASTER_SECRET_LEN,
+		    "server finished", hash, MD5_MAC_LEN + SHA1_MAC_LEN,
+		    verify_data, TLS_VERIFY_DATA_LEN)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive verify_data");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_DECRYPT_ERROR);
+		return -1;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "TLSv1: verify_data (server)",
+			verify_data, TLS_VERIFY_DATA_LEN);
+
+	if (os_memcmp(pos, verify_data, TLS_VERIFY_DATA_LEN) != 0) {
+		wpa_printf(MSG_INFO, "TLSv1: Mismatch in verify_data");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received Finished");
+
+	*in_len = end - in_data;
+
+	conn->state = (conn->session_resumed || conn->use_session_ticket) ?
+		CHANGE_CIPHER_SPEC : ACK_FINISHED;
+
+	return 0;
+}
+
+
+static int tls_process_application_data(struct tlsv1_client *conn, u8 ct,
+					const u8 *in_data, size_t *in_len,
+					u8 **out_data, size_t *out_len)
+{
+	const u8 *pos;
+	size_t left;
+
+	if (ct != TLS_CONTENT_TYPE_APPLICATION_DATA) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Application Data; "
+			   "received content type 0x%x", ct);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	wpa_hexdump(MSG_DEBUG, "TLSv1: Application Data included in Handshake",
+		    pos, left);
+
+	*out_data = os_malloc(left);
+	if (*out_data) {
+		os_memcpy(*out_data, pos, left);
+		*out_len = left;
+	}
+
+	return 0;
+}
+
+
+int tlsv1_client_process_handshake(struct tlsv1_client *conn, u8 ct,
+				   const u8 *buf, size_t *len,
+				   u8 **out_data, size_t *out_len)
+{
+	if (ct == TLS_CONTENT_TYPE_ALERT) {
+		if (*len < 2) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Alert underflow");
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_DECODE_ERROR);
+			return -1;
+		}
+		wpa_printf(MSG_DEBUG, "TLSv1: Received alert %d:%d",
+			   buf[0], buf[1]);
+		*len = 2;
+		conn->state = FAILED;
+		return -1;
+	}
+
+	if (ct == TLS_CONTENT_TYPE_HANDSHAKE && *len >= 4 &&
+	    buf[0] == TLS_HANDSHAKE_TYPE_HELLO_REQUEST) {
+		size_t hr_len = WPA_GET_BE24(buf + 1);
+		if (hr_len > *len - 4) {
+			wpa_printf(MSG_DEBUG, "TLSv1: HelloRequest underflow");
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_DECODE_ERROR);
+			return -1;
+		}
+		wpa_printf(MSG_DEBUG, "TLSv1: Ignored HelloRequest");
+		*len = 4 + hr_len;
+		return 0;
+	}
+
+	switch (conn->state) {
+	case SERVER_HELLO:
+		if (tls_process_server_hello(conn, ct, buf, len))
+			return -1;
+		break;
+	case SERVER_CERTIFICATE:
+		if (tls_process_certificate(conn, ct, buf, len))
+			return -1;
+		break;
+	case SERVER_KEY_EXCHANGE:
+		if (tls_process_server_key_exchange(conn, ct, buf, len))
+			return -1;
+		break;
+	case SERVER_CERTIFICATE_REQUEST:
+		if (tls_process_certificate_request(conn, ct, buf, len))
+			return -1;
+		break;
+	case SERVER_HELLO_DONE:
+		if (tls_process_server_hello_done(conn, ct, buf, len))
+			return -1;
+		break;
+	case SERVER_CHANGE_CIPHER_SPEC:
+		if (tls_process_server_change_cipher_spec(conn, ct, buf, len))
+			return -1;
+		break;
+	case SERVER_FINISHED:
+		if (tls_process_server_finished(conn, ct, buf, len))
+			return -1;
+		break;
+	case ACK_FINISHED:
+		if (out_data &&
+		    tls_process_application_data(conn, ct, buf, len, out_data,
+						 out_len))
+			return -1;
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected state %d "
+			   "while processing received message",
+			   conn->state);
+		return -1;
+	}
+
+	if (ct == TLS_CONTENT_TYPE_HANDSHAKE)
+		tls_verify_hash_add(&conn->verify, buf, *len);
+
+	return 0;
+}
diff --git a/src/tls/tlsv1_client_write.c b/src/tls/tlsv1_client_write.c
new file mode 100644
index 0000000..0898df9
--- /dev/null
+++ b/src/tls/tlsv1_client_write.c
@@ -0,0 +1,798 @@
+/*
+ * TLSv1 client - write handshake message
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/md5.h"
+#include "crypto/sha1.h"
+#include "crypto/tls.h"
+#include "crypto/random.h"
+#include "x509v3.h"
+#include "tlsv1_common.h"
+#include "tlsv1_record.h"
+#include "tlsv1_client.h"
+#include "tlsv1_client_i.h"
+
+
+static size_t tls_client_cert_chain_der_len(struct tlsv1_client *conn)
+{
+	size_t len = 0;
+	struct x509_certificate *cert;
+
+	if (conn->cred == NULL)
+		return 0;
+
+	cert = conn->cred->cert;
+	while (cert) {
+		len += 3 + cert->cert_len;
+		if (x509_certificate_self_signed(cert))
+			break;
+		cert = x509_certificate_get_subject(conn->cred->trusted_certs,
+						    &cert->issuer);
+	}
+
+	return len;
+}
+
+
+u8 * tls_send_client_hello(struct tlsv1_client *conn, size_t *out_len)
+{
+	u8 *hello, *end, *pos, *hs_length, *hs_start, *rhdr;
+	struct os_time now;
+	size_t len, i;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send ClientHello");
+	*out_len = 0;
+
+	os_get_time(&now);
+	WPA_PUT_BE32(conn->client_random, now.sec);
+	if (random_get_bytes(conn->client_random + 4, TLS_RANDOM_LEN - 4)) {
+		wpa_printf(MSG_ERROR, "TLSv1: Could not generate "
+			   "client_random");
+		return NULL;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: client_random",
+		    conn->client_random, TLS_RANDOM_LEN);
+
+	len = 100 + conn->num_cipher_suites * 2 + conn->client_hello_ext_len;
+	hello = os_malloc(len);
+	if (hello == NULL)
+		return NULL;
+	end = hello + len;
+
+	rhdr = hello;
+	pos = rhdr + TLS_RECORD_HEADER_LEN;
+
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_CLIENT_HELLO;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	/* body - ClientHello */
+	/* ProtocolVersion client_version */
+	WPA_PUT_BE16(pos, TLS_VERSION);
+	pos += 2;
+	/* Random random: uint32 gmt_unix_time, opaque random_bytes */
+	os_memcpy(pos, conn->client_random, TLS_RANDOM_LEN);
+	pos += TLS_RANDOM_LEN;
+	/* SessionID session_id */
+	*pos++ = conn->session_id_len;
+	os_memcpy(pos, conn->session_id, conn->session_id_len);
+	pos += conn->session_id_len;
+	/* CipherSuite cipher_suites<2..2^16-1> */
+	WPA_PUT_BE16(pos, 2 * conn->num_cipher_suites);
+	pos += 2;
+	for (i = 0; i < conn->num_cipher_suites; i++) {
+		WPA_PUT_BE16(pos, conn->cipher_suites[i]);
+		pos += 2;
+	}
+	/* CompressionMethod compression_methods<1..2^8-1> */
+	*pos++ = 1;
+	*pos++ = TLS_COMPRESSION_NULL;
+
+	if (conn->client_hello_ext) {
+		os_memcpy(pos, conn->client_hello_ext,
+			  conn->client_hello_ext_len);
+		pos += conn->client_hello_ext_len;
+	}
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, out_len) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create TLS record");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		os_free(hello);
+		return NULL;
+	}
+
+	conn->state = SERVER_HELLO;
+
+	return hello;
+}
+
+
+static int tls_write_client_certificate(struct tlsv1_client *conn,
+					u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length, *cert_start;
+	size_t rlen;
+	struct x509_certificate *cert;
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send Certificate");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_CERTIFICATE;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	/* body - Certificate */
+	/* uint24 length (to be filled) */
+	cert_start = pos;
+	pos += 3;
+	cert = conn->cred ? conn->cred->cert : NULL;
+	while (cert) {
+		if (pos + 3 + cert->cert_len > end) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Not enough buffer space "
+				   "for Certificate (cert_len=%lu left=%lu)",
+				   (unsigned long) cert->cert_len,
+				   (unsigned long) (end - pos));
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_INTERNAL_ERROR);
+			return -1;
+		}
+		WPA_PUT_BE24(pos, cert->cert_len);
+		pos += 3;
+		os_memcpy(pos, cert->cert_start, cert->cert_len);
+		pos += cert->cert_len;
+
+		if (x509_certificate_self_signed(cert))
+			break;
+		cert = x509_certificate_get_subject(conn->cred->trusted_certs,
+						    &cert->issuer);
+	}
+	if (conn->cred == NULL || cert == conn->cred->cert || cert == NULL) {
+		/*
+		 * Client was not configured with all the needed certificates
+		 * to form a full certificate chain. The server may fail to
+		 * validate the chain unless it is configured with all the
+		 * missing CA certificates.
+		 */
+		wpa_printf(MSG_DEBUG, "TLSv1: Full client certificate chain "
+			   "not configured - validation may fail");
+	}
+	WPA_PUT_BE24(cert_start, pos - cert_start - 3);
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to generate a record");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	pos = rhdr + rlen;
+
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static int tlsv1_key_x_anon_dh(struct tlsv1_client *conn, u8 **pos, u8 *end)
+{
+	/* ClientDiffieHellmanPublic */
+	u8 *csecret, *csecret_start, *dh_yc, *shared;
+	size_t csecret_len, dh_yc_len, shared_len;
+
+	csecret_len = conn->dh_p_len;
+	csecret = os_malloc(csecret_len);
+	if (csecret == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to allocate "
+			   "memory for Yc (Diffie-Hellman)");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	if (random_get_bytes(csecret, csecret_len)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to get random "
+			   "data for Diffie-Hellman");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		os_free(csecret);
+		return -1;
+	}
+
+	if (os_memcmp(csecret, conn->dh_p, csecret_len) > 0)
+		csecret[0] = 0; /* make sure Yc < p */
+
+	csecret_start = csecret;
+	while (csecret_len > 1 && *csecret_start == 0) {
+		csecret_start++;
+		csecret_len--;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "TLSv1: DH client's secret value",
+			csecret_start, csecret_len);
+
+	/* Yc = g^csecret mod p */
+	dh_yc_len = conn->dh_p_len;
+	dh_yc = os_malloc(dh_yc_len);
+	if (dh_yc == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to allocate "
+			   "memory for Diffie-Hellman");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		os_free(csecret);
+		return -1;
+	}
+	if (crypto_mod_exp(conn->dh_g, conn->dh_g_len,
+			   csecret_start, csecret_len,
+			   conn->dh_p, conn->dh_p_len,
+			   dh_yc, &dh_yc_len)) {
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		os_free(csecret);
+		os_free(dh_yc);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "TLSv1: DH Yc (client's public value)",
+		    dh_yc, dh_yc_len);
+
+	WPA_PUT_BE16(*pos, dh_yc_len);
+	*pos += 2;
+	if (*pos + dh_yc_len > end) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Not enough room in the "
+			   "message buffer for Yc");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		os_free(csecret);
+		os_free(dh_yc);
+		return -1;
+	}
+	os_memcpy(*pos, dh_yc, dh_yc_len);
+	*pos += dh_yc_len;
+	os_free(dh_yc);
+
+	shared_len = conn->dh_p_len;
+	shared = os_malloc(shared_len);
+	if (shared == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Could not allocate memory for "
+			   "DH");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		os_free(csecret);
+		return -1;
+	}
+
+	/* shared = Ys^csecret mod p */
+	if (crypto_mod_exp(conn->dh_ys, conn->dh_ys_len,
+			   csecret_start, csecret_len,
+			   conn->dh_p, conn->dh_p_len,
+			   shared, &shared_len)) {
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		os_free(csecret);
+		os_free(shared);
+		return -1;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "TLSv1: Shared secret from DH key exchange",
+			shared, shared_len);
+
+	os_memset(csecret_start, 0, csecret_len);
+	os_free(csecret);
+	if (tls_derive_keys(conn, shared, shared_len)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive keys");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		os_free(shared);
+		return -1;
+	}
+	os_memset(shared, 0, shared_len);
+	os_free(shared);
+	tlsv1_client_free_dh(conn);
+	return 0;
+}
+
+
+static int tlsv1_key_x_rsa(struct tlsv1_client *conn, u8 **pos, u8 *end)
+{
+	u8 pre_master_secret[TLS_PRE_MASTER_SECRET_LEN];
+	size_t clen;
+	int res;
+
+	if (tls_derive_pre_master_secret(pre_master_secret) < 0 ||
+	    tls_derive_keys(conn, pre_master_secret,
+			    TLS_PRE_MASTER_SECRET_LEN)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive keys");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	/* EncryptedPreMasterSecret */
+	if (conn->server_rsa_key == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No server RSA key to "
+			   "use for encrypting pre-master secret");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	/* RSA encrypted value is encoded with PKCS #1 v1.5 block type 2. */
+	*pos += 2;
+	clen = end - *pos;
+	res = crypto_public_key_encrypt_pkcs1_v15(
+		conn->server_rsa_key,
+		pre_master_secret, TLS_PRE_MASTER_SECRET_LEN,
+		*pos, &clen);
+	os_memset(pre_master_secret, 0, TLS_PRE_MASTER_SECRET_LEN);
+	if (res < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: RSA encryption failed");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	WPA_PUT_BE16(*pos - 2, clen);
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: Encrypted pre_master_secret",
+		    *pos, clen);
+	*pos += clen;
+
+	return 0;
+}
+
+
+static int tls_write_client_key_exchange(struct tlsv1_client *conn,
+					 u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length;
+	size_t rlen;
+	tls_key_exchange keyx;
+	const struct tls_cipher_suite *suite;
+
+	suite = tls_get_cipher_suite(conn->rl.cipher_suite);
+	if (suite == NULL)
+		keyx = TLS_KEY_X_NULL;
+	else
+		keyx = suite->key_exchange;
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send ClientKeyExchange");
+
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_CLIENT_KEY_EXCHANGE;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	/* body - ClientKeyExchange */
+	if (keyx == TLS_KEY_X_DH_anon) {
+		if (tlsv1_key_x_anon_dh(conn, &pos, end) < 0)
+			return -1;
+	} else {
+		if (tlsv1_key_x_rsa(conn, &pos, end) < 0)
+			return -1;
+	}
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create a record");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	pos = rhdr + rlen;
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static int tls_write_client_certificate_verify(struct tlsv1_client *conn,
+					       u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length, *signed_start;
+	size_t rlen, hlen, clen;
+	u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN], *hpos;
+	enum { SIGN_ALG_RSA, SIGN_ALG_DSA } alg = SIGN_ALG_RSA;
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send CertificateVerify");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+
+	/*
+	 * RFC 2246: 7.4.3 and 7.4.8:
+	 * Signature signature
+	 *
+	 * RSA:
+	 * digitally-signed struct {
+	 *     opaque md5_hash[16];
+	 *     opaque sha_hash[20];
+	 * };
+	 *
+	 * DSA:
+	 * digitally-signed struct {
+	 *     opaque sha_hash[20];
+	 * };
+	 *
+	 * The hash values are calculated over all handshake messages sent or
+	 * received starting at ClientHello up to, but not including, this
+	 * CertificateVerify message, including the type and length fields of
+	 * the handshake messages.
+	 */
+
+	hpos = hash;
+
+	if (alg == SIGN_ALG_RSA) {
+		hlen = MD5_MAC_LEN;
+		if (conn->verify.md5_cert == NULL ||
+		    crypto_hash_finish(conn->verify.md5_cert, hpos, &hlen) < 0)
+		{
+			tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				  TLS_ALERT_INTERNAL_ERROR);
+			conn->verify.md5_cert = NULL;
+			crypto_hash_finish(conn->verify.sha1_cert, NULL, NULL);
+			conn->verify.sha1_cert = NULL;
+			return -1;
+		}
+		hpos += MD5_MAC_LEN;
+	} else
+		crypto_hash_finish(conn->verify.md5_cert, NULL, NULL);
+
+	conn->verify.md5_cert = NULL;
+	hlen = SHA1_MAC_LEN;
+	if (conn->verify.sha1_cert == NULL ||
+	    crypto_hash_finish(conn->verify.sha1_cert, hpos, &hlen) < 0) {
+		conn->verify.sha1_cert = NULL;
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	conn->verify.sha1_cert = NULL;
+
+	if (alg == SIGN_ALG_RSA)
+		hlen += MD5_MAC_LEN;
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: CertificateVerify hash", hash, hlen);
+
+	/*
+	 * RFC 2246, 4.7:
+	 * In digital signing, one-way hash functions are used as input for a
+	 * signing algorithm. A digitally-signed element is encoded as an
+	 * opaque vector <0..2^16-1>, where the length is specified by the
+	 * signing algorithm and key.
+	 *
+	 * In RSA signing, a 36-byte structure of two hashes (one SHA and one
+	 * MD5) is signed (encrypted with the private key). It is encoded with
+	 * PKCS #1 block type 0 or type 1 as described in [PKCS1].
+	 */
+	signed_start = pos; /* length to be filled */
+	pos += 2;
+	clen = end - pos;
+	if (conn->cred == NULL ||
+	    crypto_private_key_sign_pkcs1(conn->cred->key, hash, hlen,
+					  pos, &clen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to sign hash (PKCS #1)");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	WPA_PUT_BE16(signed_start, clen);
+
+	pos += clen;
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to generate a record");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	pos = rhdr + rlen;
+
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static int tls_write_client_change_cipher_spec(struct tlsv1_client *conn,
+					       u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr;
+	size_t rlen;
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send ChangeCipherSpec");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+	*pos = TLS_CHANGE_CIPHER_SPEC;
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC,
+			      rhdr, end - rhdr, 1, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create a record");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	if (tlsv1_record_change_write_cipher(&conn->rl) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to set write cipher for "
+			   "record layer");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	*msgpos = rhdr + rlen;
+
+	return 0;
+}
+
+
+static int tls_write_client_finished(struct tlsv1_client *conn,
+				     u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length;
+	size_t rlen, hlen;
+	u8 verify_data[TLS_VERIFY_DATA_LEN];
+	u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN];
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send Finished");
+
+	/* Encrypted Handshake Message: Finished */
+
+	hlen = MD5_MAC_LEN;
+	if (conn->verify.md5_client == NULL ||
+	    crypto_hash_finish(conn->verify.md5_client, hash, &hlen) < 0) {
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		conn->verify.md5_client = NULL;
+		crypto_hash_finish(conn->verify.sha1_client, NULL, NULL);
+		conn->verify.sha1_client = NULL;
+		return -1;
+	}
+	conn->verify.md5_client = NULL;
+	hlen = SHA1_MAC_LEN;
+	if (conn->verify.sha1_client == NULL ||
+	    crypto_hash_finish(conn->verify.sha1_client, hash + MD5_MAC_LEN,
+			       &hlen) < 0) {
+		conn->verify.sha1_client = NULL;
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	conn->verify.sha1_client = NULL;
+
+	if (tls_prf(conn->master_secret, TLS_MASTER_SECRET_LEN,
+		    "client finished", hash, MD5_MAC_LEN + SHA1_MAC_LEN,
+		    verify_data, TLS_VERIFY_DATA_LEN)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to generate verify_data");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "TLSv1: verify_data (client)",
+			verify_data, TLS_VERIFY_DATA_LEN);
+
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_FINISHED;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	os_memcpy(pos, verify_data, TLS_VERIFY_DATA_LEN);
+	pos += TLS_VERIFY_DATA_LEN;
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create a record");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	pos = rhdr + rlen;
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static u8 * tls_send_client_key_exchange(struct tlsv1_client *conn,
+					 size_t *out_len)
+{
+	u8 *msg, *end, *pos;
+	size_t msglen;
+
+	*out_len = 0;
+
+	msglen = 2000;
+	if (conn->certificate_requested)
+		msglen += tls_client_cert_chain_der_len(conn);
+
+	msg = os_malloc(msglen);
+	if (msg == NULL)
+		return NULL;
+
+	pos = msg;
+	end = msg + msglen;
+
+	if (conn->certificate_requested) {
+		if (tls_write_client_certificate(conn, &pos, end) < 0) {
+			os_free(msg);
+			return NULL;
+		}
+	}
+
+	if (tls_write_client_key_exchange(conn, &pos, end) < 0 ||
+	    (conn->certificate_requested && conn->cred && conn->cred->key &&
+	     tls_write_client_certificate_verify(conn, &pos, end) < 0) ||
+	    tls_write_client_change_cipher_spec(conn, &pos, end) < 0 ||
+	    tls_write_client_finished(conn, &pos, end) < 0) {
+		os_free(msg);
+		return NULL;
+	}
+
+	*out_len = pos - msg;
+
+	conn->state = SERVER_CHANGE_CIPHER_SPEC;
+
+	return msg;
+}
+
+
+static u8 * tls_send_change_cipher_spec(struct tlsv1_client *conn,
+					size_t *out_len)
+{
+	u8 *msg, *end, *pos;
+
+	*out_len = 0;
+
+	msg = os_malloc(1000);
+	if (msg == NULL)
+		return NULL;
+
+	pos = msg;
+	end = msg + 1000;
+
+	if (tls_write_client_change_cipher_spec(conn, &pos, end) < 0 ||
+	    tls_write_client_finished(conn, &pos, end) < 0) {
+		os_free(msg);
+		return NULL;
+	}
+
+	*out_len = pos - msg;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Session resumption completed "
+		   "successfully");
+	conn->state = ESTABLISHED;
+
+	return msg;
+}
+
+
+u8 * tlsv1_client_handshake_write(struct tlsv1_client *conn, size_t *out_len,
+				  int no_appl_data)
+{
+	switch (conn->state) {
+	case CLIENT_KEY_EXCHANGE:
+		return tls_send_client_key_exchange(conn, out_len);
+	case CHANGE_CIPHER_SPEC:
+		return tls_send_change_cipher_spec(conn, out_len);
+	case ACK_FINISHED:
+		wpa_printf(MSG_DEBUG, "TLSv1: Handshake completed "
+			   "successfully");
+		conn->state = ESTABLISHED;
+		*out_len = 0;
+		if (no_appl_data) {
+			/* Need to return something to get final TLS ACK. */
+			return os_malloc(1);
+		}
+		return NULL;
+	default:
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected state %d while "
+			   "generating reply", conn->state);
+		return NULL;
+	}
+}
+
+
+u8 * tlsv1_client_send_alert(struct tlsv1_client *conn, u8 level,
+			     u8 description, size_t *out_len)
+{
+	u8 *alert, *pos, *length;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send Alert(%d:%d)", level, description);
+	*out_len = 0;
+
+	alert = os_malloc(10);
+	if (alert == NULL)
+		return NULL;
+
+	pos = alert;
+
+	/* TLSPlaintext */
+	/* ContentType type */
+	*pos++ = TLS_CONTENT_TYPE_ALERT;
+	/* ProtocolVersion version */
+	WPA_PUT_BE16(pos, TLS_VERSION);
+	pos += 2;
+	/* uint16 length (to be filled) */
+	length = pos;
+	pos += 2;
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Alert */
+	/* AlertLevel level */
+	*pos++ = level;
+	/* AlertDescription description */
+	*pos++ = description;
+
+	WPA_PUT_BE16(length, pos - length - 2);
+	*out_len = pos - alert;
+
+	return alert;
+}
diff --git a/src/tls/tlsv1_common.c b/src/tls/tlsv1_common.c
new file mode 100644
index 0000000..2f9dd0f
--- /dev/null
+++ b/src/tls/tlsv1_common.c
@@ -0,0 +1,241 @@
+/*
+ * TLSv1 common routines
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "x509v3.h"
+#include "tlsv1_common.h"
+
+
+/*
+ * TODO:
+ * RFC 2246 Section 9: Mandatory to implement TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+ * Add support for commonly used cipher suites; don't bother with exportable
+ * suites.
+ */ 
+
+static const struct tls_cipher_suite tls_cipher_suites[] = {
+	{ TLS_NULL_WITH_NULL_NULL, TLS_KEY_X_NULL, TLS_CIPHER_NULL,
+	  TLS_HASH_NULL },
+	{ TLS_RSA_WITH_RC4_128_MD5, TLS_KEY_X_RSA, TLS_CIPHER_RC4_128,
+	  TLS_HASH_MD5 },
+	{ TLS_RSA_WITH_RC4_128_SHA, TLS_KEY_X_RSA, TLS_CIPHER_RC4_128,
+	  TLS_HASH_SHA },
+	{ TLS_RSA_WITH_DES_CBC_SHA, TLS_KEY_X_RSA, TLS_CIPHER_DES_CBC,
+	  TLS_HASH_SHA },
+	{ TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_KEY_X_RSA,
+	  TLS_CIPHER_3DES_EDE_CBC, TLS_HASH_SHA },
+ 	{ TLS_DH_anon_WITH_RC4_128_MD5, TLS_KEY_X_DH_anon,
+	  TLS_CIPHER_RC4_128, TLS_HASH_MD5 },
+ 	{ TLS_DH_anon_WITH_DES_CBC_SHA, TLS_KEY_X_DH_anon,
+	  TLS_CIPHER_DES_CBC, TLS_HASH_SHA },
+ 	{ TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, TLS_KEY_X_DH_anon,
+	  TLS_CIPHER_3DES_EDE_CBC, TLS_HASH_SHA },
+	{ TLS_RSA_WITH_AES_128_CBC_SHA, TLS_KEY_X_RSA, TLS_CIPHER_AES_128_CBC,
+	  TLS_HASH_SHA },
+	{ TLS_DH_anon_WITH_AES_128_CBC_SHA, TLS_KEY_X_DH_anon,
+	  TLS_CIPHER_AES_128_CBC, TLS_HASH_SHA },
+	{ TLS_RSA_WITH_AES_256_CBC_SHA, TLS_KEY_X_RSA, TLS_CIPHER_AES_256_CBC,
+	  TLS_HASH_SHA },
+	{ TLS_DH_anon_WITH_AES_256_CBC_SHA, TLS_KEY_X_DH_anon,
+	  TLS_CIPHER_AES_256_CBC, TLS_HASH_SHA }
+};
+
+#define NUM_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+#define NUM_TLS_CIPHER_SUITES NUM_ELEMS(tls_cipher_suites)
+
+
+static const struct tls_cipher_data tls_ciphers[] = {
+	{ TLS_CIPHER_NULL,         TLS_CIPHER_STREAM,  0,  0,  0,
+	  CRYPTO_CIPHER_NULL },
+	{ TLS_CIPHER_IDEA_CBC,     TLS_CIPHER_BLOCK,  16, 16,  8,
+	  CRYPTO_CIPHER_NULL },
+	{ TLS_CIPHER_RC2_CBC_40,   TLS_CIPHER_BLOCK,   5, 16,  0,
+	  CRYPTO_CIPHER_ALG_RC2 },
+	{ TLS_CIPHER_RC4_40,       TLS_CIPHER_STREAM,  5, 16,  0,
+	  CRYPTO_CIPHER_ALG_RC4 },
+	{ TLS_CIPHER_RC4_128,      TLS_CIPHER_STREAM, 16, 16,  0,
+	  CRYPTO_CIPHER_ALG_RC4 },
+	{ TLS_CIPHER_DES40_CBC,    TLS_CIPHER_BLOCK,   5,  8,  8,
+	  CRYPTO_CIPHER_ALG_DES },
+	{ TLS_CIPHER_DES_CBC,      TLS_CIPHER_BLOCK,   8,  8,  8,
+	  CRYPTO_CIPHER_ALG_DES },
+	{ TLS_CIPHER_3DES_EDE_CBC, TLS_CIPHER_BLOCK,  24, 24,  8,
+	  CRYPTO_CIPHER_ALG_3DES },
+	{ TLS_CIPHER_AES_128_CBC,  TLS_CIPHER_BLOCK,  16, 16, 16,
+	  CRYPTO_CIPHER_ALG_AES },
+	{ TLS_CIPHER_AES_256_CBC,  TLS_CIPHER_BLOCK,  32, 32, 16,
+	  CRYPTO_CIPHER_ALG_AES }
+};
+
+#define NUM_TLS_CIPHER_DATA NUM_ELEMS(tls_ciphers)
+
+
+/**
+ * tls_get_cipher_suite - Get TLS cipher suite
+ * @suite: Cipher suite identifier
+ * Returns: Pointer to the cipher data or %NULL if not found
+ */
+const struct tls_cipher_suite * tls_get_cipher_suite(u16 suite)
+{
+	size_t i;
+	for (i = 0; i < NUM_TLS_CIPHER_SUITES; i++)
+		if (tls_cipher_suites[i].suite == suite)
+			return &tls_cipher_suites[i];
+	return NULL;
+}
+
+
+const struct tls_cipher_data * tls_get_cipher_data(tls_cipher cipher)
+{
+	size_t i;
+	for (i = 0; i < NUM_TLS_CIPHER_DATA; i++)
+		if (tls_ciphers[i].cipher == cipher)
+			return &tls_ciphers[i];
+	return NULL;
+}
+
+
+int tls_server_key_exchange_allowed(tls_cipher cipher)
+{
+	const struct tls_cipher_suite *suite;
+
+	/* RFC 2246, Section 7.4.3 */
+	suite = tls_get_cipher_suite(cipher);
+	if (suite == NULL)
+		return 0;
+
+	switch (suite->key_exchange) {
+	case TLS_KEY_X_DHE_DSS:
+	case TLS_KEY_X_DHE_DSS_EXPORT:
+	case TLS_KEY_X_DHE_RSA:
+	case TLS_KEY_X_DHE_RSA_EXPORT:
+	case TLS_KEY_X_DH_anon_EXPORT:
+	case TLS_KEY_X_DH_anon:
+		return 1;
+	case TLS_KEY_X_RSA_EXPORT:
+		return 1 /* FIX: public key len > 512 bits */;
+	default:
+		return 0;
+	}
+}
+
+
+/**
+ * tls_parse_cert - Parse DER encoded X.509 certificate and get public key
+ * @buf: ASN.1 DER encoded certificate
+ * @len: Length of the buffer
+ * @pk: Buffer for returning the allocated public key
+ * Returns: 0 on success, -1 on failure
+ *
+ * This functions parses an ASN.1 DER encoded X.509 certificate and retrieves
+ * the public key from it. The caller is responsible for freeing the public key
+ * by calling crypto_public_key_free().
+ */
+int tls_parse_cert(const u8 *buf, size_t len, struct crypto_public_key **pk)
+{
+	struct x509_certificate *cert;
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: Parse ASN.1 DER certificate",
+		    buf, len);
+
+	*pk = crypto_public_key_from_cert(buf, len);
+	if (*pk)
+		return 0;
+
+	cert = x509_certificate_parse(buf, len);
+	if (cert == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse X.509 "
+			   "certificate");
+		return -1;
+	}
+
+	/* TODO
+	 * verify key usage (must allow encryption)
+	 *
+	 * All certificate profiles, key and cryptographic formats are
+	 * defined by the IETF PKIX working group [PKIX]. When a key
+	 * usage extension is present, the digitalSignature bit must be
+	 * set for the key to be eligible for signing, as described
+	 * above, and the keyEncipherment bit must be present to allow
+	 * encryption, as described above. The keyAgreement bit must be
+	 * set on Diffie-Hellman certificates. (PKIX: RFC 3280)
+	 */
+
+	*pk = crypto_public_key_import(cert->public_key, cert->public_key_len);
+	x509_certificate_free(cert);
+
+	if (*pk == NULL) {
+		wpa_printf(MSG_ERROR, "TLSv1: Failed to import "
+			   "server public key");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+int tls_verify_hash_init(struct tls_verify_hash *verify)
+{
+	tls_verify_hash_free(verify);
+	verify->md5_client = crypto_hash_init(CRYPTO_HASH_ALG_MD5, NULL, 0);
+	verify->md5_server = crypto_hash_init(CRYPTO_HASH_ALG_MD5, NULL, 0);
+	verify->md5_cert = crypto_hash_init(CRYPTO_HASH_ALG_MD5, NULL, 0);
+	verify->sha1_client = crypto_hash_init(CRYPTO_HASH_ALG_SHA1, NULL, 0);
+	verify->sha1_server = crypto_hash_init(CRYPTO_HASH_ALG_SHA1, NULL, 0);
+	verify->sha1_cert = crypto_hash_init(CRYPTO_HASH_ALG_SHA1, NULL, 0);
+	if (verify->md5_client == NULL || verify->md5_server == NULL ||
+	    verify->md5_cert == NULL || verify->sha1_client == NULL ||
+	    verify->sha1_server == NULL || verify->sha1_cert == NULL) {
+		tls_verify_hash_free(verify);
+		return -1;
+	}
+	return 0;
+}
+
+
+void tls_verify_hash_add(struct tls_verify_hash *verify, const u8 *buf,
+			 size_t len)
+{
+	if (verify->md5_client && verify->sha1_client) {
+		crypto_hash_update(verify->md5_client, buf, len);
+		crypto_hash_update(verify->sha1_client, buf, len);
+	}
+	if (verify->md5_server && verify->sha1_server) {
+		crypto_hash_update(verify->md5_server, buf, len);
+		crypto_hash_update(verify->sha1_server, buf, len);
+	}
+	if (verify->md5_cert && verify->sha1_cert) {
+		crypto_hash_update(verify->md5_cert, buf, len);
+		crypto_hash_update(verify->sha1_cert, buf, len);
+	}
+}
+
+
+void tls_verify_hash_free(struct tls_verify_hash *verify)
+{
+	crypto_hash_finish(verify->md5_client, NULL, NULL);
+	crypto_hash_finish(verify->md5_server, NULL, NULL);
+	crypto_hash_finish(verify->md5_cert, NULL, NULL);
+	crypto_hash_finish(verify->sha1_client, NULL, NULL);
+	crypto_hash_finish(verify->sha1_server, NULL, NULL);
+	crypto_hash_finish(verify->sha1_cert, NULL, NULL);
+	verify->md5_client = NULL;
+	verify->md5_server = NULL;
+	verify->md5_cert = NULL;
+	verify->sha1_client = NULL;
+	verify->sha1_server = NULL;
+	verify->sha1_cert = NULL;
+}
diff --git a/src/tls/tlsv1_common.h b/src/tls/tlsv1_common.h
new file mode 100644
index 0000000..763a4af
--- /dev/null
+++ b/src/tls/tlsv1_common.h
@@ -0,0 +1,216 @@
+/*
+ * TLSv1 common definitions
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef TLSV1_COMMON_H
+#define TLSV1_COMMON_H
+
+#include "crypto/crypto.h"
+
+#define TLS_VERSION 0x0301 /* TLSv1 */
+#define TLS_RANDOM_LEN 32
+#define TLS_PRE_MASTER_SECRET_LEN 48
+#define TLS_MASTER_SECRET_LEN 48
+#define TLS_SESSION_ID_MAX_LEN 32
+#define TLS_VERIFY_DATA_LEN 12
+
+/* HandshakeType */
+enum {
+	TLS_HANDSHAKE_TYPE_HELLO_REQUEST = 0,
+	TLS_HANDSHAKE_TYPE_CLIENT_HELLO = 1,
+	TLS_HANDSHAKE_TYPE_SERVER_HELLO = 2,
+	TLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET = 4 /* RFC 4507 */,
+	TLS_HANDSHAKE_TYPE_CERTIFICATE = 11,
+	TLS_HANDSHAKE_TYPE_SERVER_KEY_EXCHANGE = 12,
+	TLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST = 13,
+	TLS_HANDSHAKE_TYPE_SERVER_HELLO_DONE = 14,
+	TLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY = 15,
+	TLS_HANDSHAKE_TYPE_CLIENT_KEY_EXCHANGE = 16,
+	TLS_HANDSHAKE_TYPE_FINISHED = 20,
+	TLS_HANDSHAKE_TYPE_CERTIFICATE_URL = 21 /* RFC 4366 */,
+	TLS_HANDSHAKE_TYPE_CERTIFICATE_STATUS = 22 /* RFC 4366 */
+};
+
+/* CipherSuite */
+#define TLS_NULL_WITH_NULL_NULL			0x0000 /* RFC 2246 */
+#define TLS_RSA_WITH_NULL_MD5			0x0001 /* RFC 2246 */
+#define TLS_RSA_WITH_NULL_SHA			0x0002 /* RFC 2246 */
+#define TLS_RSA_EXPORT_WITH_RC4_40_MD5		0x0003 /* RFC 2246 */
+#define TLS_RSA_WITH_RC4_128_MD5		0x0004 /* RFC 2246 */
+#define TLS_RSA_WITH_RC4_128_SHA		0x0005 /* RFC 2246 */
+#define TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5	0x0006 /* RFC 2246 */
+#define TLS_RSA_WITH_IDEA_CBC_SHA		0x0007 /* RFC 2246 */
+#define TLS_RSA_EXPORT_WITH_DES40_CBC_SHA	0x0008 /* RFC 2246 */
+#define TLS_RSA_WITH_DES_CBC_SHA		0x0009 /* RFC 2246 */
+#define TLS_RSA_WITH_3DES_EDE_CBC_SHA		0x000A /* RFC 2246 */
+#define TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA	0x000B /* RFC 2246 */
+#define TLS_DH_DSS_WITH_DES_CBC_SHA		0x000C /* RFC 2246 */
+#define TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA	0x000D /* RFC 2246 */
+#define TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA	0x000E /* RFC 2246 */
+#define TLS_DH_RSA_WITH_DES_CBC_SHA		0x000F /* RFC 2246 */
+#define TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA	0x0010 /* RFC 2246 */
+#define TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA	0x0011 /* RFC 2246 */
+#define TLS_DHE_DSS_WITH_DES_CBC_SHA		0x0012 /* RFC 2246 */
+#define TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA	0x0013 /* RFC 2246 */
+#define TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA	0x0014 /* RFC 2246 */
+#define TLS_DHE_RSA_WITH_DES_CBC_SHA		0x0015 /* RFC 2246 */
+#define TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA	0x0016 /* RFC 2246 */
+#define TLS_DH_anon_EXPORT_WITH_RC4_40_MD5	0x0017 /* RFC 2246 */
+#define TLS_DH_anon_WITH_RC4_128_MD5		0x0018 /* RFC 2246 */
+#define TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA	0x0019 /* RFC 2246 */
+#define TLS_DH_anon_WITH_DES_CBC_SHA		0x001A /* RFC 2246 */
+#define TLS_DH_anon_WITH_3DES_EDE_CBC_SHA	0x001B /* RFC 2246 */
+#define TLS_RSA_WITH_AES_128_CBC_SHA		0x002F /* RFC 3268 */
+#define TLS_DH_DSS_WITH_AES_128_CBC_SHA		0x0030 /* RFC 3268 */
+#define TLS_DH_RSA_WITH_AES_128_CBC_SHA		0x0031 /* RFC 3268 */
+#define TLS_DHE_DSS_WITH_AES_128_CBC_SHA	0x0032 /* RFC 3268 */
+#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA	0x0033 /* RFC 3268 */
+#define TLS_DH_anon_WITH_AES_128_CBC_SHA	0x0034 /* RFC 3268 */
+#define TLS_RSA_WITH_AES_256_CBC_SHA		0x0035 /* RFC 3268 */
+#define TLS_DH_DSS_WITH_AES_256_CBC_SHA		0x0036 /* RFC 3268 */
+#define TLS_DH_RSA_WITH_AES_256_CBC_SHA		0x0037 /* RFC 3268 */
+#define TLS_DHE_DSS_WITH_AES_256_CBC_SHA	0x0038 /* RFC 3268 */
+#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA	0x0039 /* RFC 3268 */
+#define TLS_DH_anon_WITH_AES_256_CBC_SHA	0x003A /* RFC 3268 */
+
+/* CompressionMethod */
+#define TLS_COMPRESSION_NULL 0
+
+/* AlertLevel */
+#define TLS_ALERT_LEVEL_WARNING 1
+#define TLS_ALERT_LEVEL_FATAL 2
+
+/* AlertDescription */
+#define TLS_ALERT_CLOSE_NOTIFY			0
+#define TLS_ALERT_UNEXPECTED_MESSAGE		10
+#define TLS_ALERT_BAD_RECORD_MAC		20
+#define TLS_ALERT_DECRYPTION_FAILED		21
+#define TLS_ALERT_RECORD_OVERFLOW		22
+#define TLS_ALERT_DECOMPRESSION_FAILURE		30
+#define TLS_ALERT_HANDSHAKE_FAILURE		40
+#define TLS_ALERT_BAD_CERTIFICATE		42
+#define TLS_ALERT_UNSUPPORTED_CERTIFICATE	43
+#define TLS_ALERT_CERTIFICATE_REVOKED		44
+#define TLS_ALERT_CERTIFICATE_EXPIRED		45
+#define TLS_ALERT_CERTIFICATE_UNKNOWN		46
+#define TLS_ALERT_ILLEGAL_PARAMETER		47
+#define TLS_ALERT_UNKNOWN_CA			48
+#define TLS_ALERT_ACCESS_DENIED			49
+#define TLS_ALERT_DECODE_ERROR			50
+#define TLS_ALERT_DECRYPT_ERROR			51
+#define TLS_ALERT_EXPORT_RESTRICTION		60
+#define TLS_ALERT_PROTOCOL_VERSION		70
+#define TLS_ALERT_INSUFFICIENT_SECURITY		71
+#define TLS_ALERT_INTERNAL_ERROR		80
+#define TLS_ALERT_USER_CANCELED			90
+#define TLS_ALERT_NO_RENEGOTIATION		100
+#define TLS_ALERT_UNSUPPORTED_EXTENSION		110 /* RFC 4366 */
+#define TLS_ALERT_CERTIFICATE_UNOBTAINABLE	111 /* RFC 4366 */
+#define TLS_ALERT_UNRECOGNIZED_NAME		112 /* RFC 4366 */
+#define TLS_ALERT_BAD_CERTIFICATE_STATUS_RESPONSE	113 /* RFC 4366 */
+#define TLS_ALERT_BAD_CERTIFICATE_HASH_VALUE	114 /* RFC 4366 */
+
+/* ChangeCipherSpec */
+enum {
+	TLS_CHANGE_CIPHER_SPEC = 1
+};
+
+/* TLS Extensions */
+#define TLS_EXT_SERVER_NAME			0 /* RFC 4366 */
+#define TLS_EXT_MAX_FRAGMENT_LENGTH		1 /* RFC 4366 */
+#define TLS_EXT_CLIENT_CERTIFICATE_URL		2 /* RFC 4366 */
+#define TLS_EXT_TRUSTED_CA_KEYS			3 /* RFC 4366 */
+#define TLS_EXT_TRUNCATED_HMAC			4 /* RFC 4366 */
+#define TLS_EXT_STATUS_REQUEST			5 /* RFC 4366 */
+#define TLS_EXT_SESSION_TICKET			35 /* RFC 4507 */
+
+#define TLS_EXT_PAC_OPAQUE TLS_EXT_SESSION_TICKET /* EAP-FAST terminology */
+
+
+typedef enum {
+	TLS_KEY_X_NULL,
+	TLS_KEY_X_RSA,
+	TLS_KEY_X_RSA_EXPORT,
+	TLS_KEY_X_DH_DSS_EXPORT,
+	TLS_KEY_X_DH_DSS,
+	TLS_KEY_X_DH_RSA_EXPORT,
+	TLS_KEY_X_DH_RSA,
+	TLS_KEY_X_DHE_DSS_EXPORT,
+	TLS_KEY_X_DHE_DSS,
+	TLS_KEY_X_DHE_RSA_EXPORT,
+	TLS_KEY_X_DHE_RSA,
+	TLS_KEY_X_DH_anon_EXPORT,
+	TLS_KEY_X_DH_anon
+} tls_key_exchange;
+
+typedef enum {
+	TLS_CIPHER_NULL,
+	TLS_CIPHER_RC4_40,
+	TLS_CIPHER_RC4_128,
+	TLS_CIPHER_RC2_CBC_40,
+	TLS_CIPHER_IDEA_CBC,
+	TLS_CIPHER_DES40_CBC,
+	TLS_CIPHER_DES_CBC,
+	TLS_CIPHER_3DES_EDE_CBC,
+	TLS_CIPHER_AES_128_CBC,
+	TLS_CIPHER_AES_256_CBC
+} tls_cipher;
+
+typedef enum {
+	TLS_HASH_NULL,
+	TLS_HASH_MD5,
+	TLS_HASH_SHA
+} tls_hash;
+
+struct tls_cipher_suite {
+	u16 suite;
+	tls_key_exchange key_exchange;
+	tls_cipher cipher;
+	tls_hash hash;
+};
+
+typedef enum {
+	TLS_CIPHER_STREAM,
+	TLS_CIPHER_BLOCK
+} tls_cipher_type;
+
+struct tls_cipher_data {
+	tls_cipher cipher;
+	tls_cipher_type type;
+	size_t key_material;
+	size_t expanded_key_material;
+	size_t block_size; /* also iv_size */
+	enum crypto_cipher_alg alg;
+};
+
+
+struct tls_verify_hash {
+	struct crypto_hash *md5_client;
+	struct crypto_hash *sha1_client;
+	struct crypto_hash *md5_server;
+	struct crypto_hash *sha1_server;
+	struct crypto_hash *md5_cert;
+	struct crypto_hash *sha1_cert;
+};
+
+
+const struct tls_cipher_suite * tls_get_cipher_suite(u16 suite);
+const struct tls_cipher_data * tls_get_cipher_data(tls_cipher cipher);
+int tls_server_key_exchange_allowed(tls_cipher cipher);
+int tls_parse_cert(const u8 *buf, size_t len, struct crypto_public_key **pk);
+int tls_verify_hash_init(struct tls_verify_hash *verify);
+void tls_verify_hash_add(struct tls_verify_hash *verify, const u8 *buf,
+			 size_t len);
+void tls_verify_hash_free(struct tls_verify_hash *verify);
+
+#endif /* TLSV1_COMMON_H */
diff --git a/src/tls/tlsv1_cred.c b/src/tls/tlsv1_cred.c
new file mode 100644
index 0000000..aa467ef
--- /dev/null
+++ b/src/tls/tlsv1_cred.c
@@ -0,0 +1,493 @@
+/*
+ * TLSv1 credentials
+ * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "base64.h"
+#include "crypto/crypto.h"
+#include "x509v3.h"
+#include "tlsv1_cred.h"
+
+
+struct tlsv1_credentials * tlsv1_cred_alloc(void)
+{
+	struct tlsv1_credentials *cred;
+	cred = os_zalloc(sizeof(*cred));
+	return cred;
+}
+
+
+void tlsv1_cred_free(struct tlsv1_credentials *cred)
+{
+	if (cred == NULL)
+		return;
+
+	x509_certificate_chain_free(cred->trusted_certs);
+	x509_certificate_chain_free(cred->cert);
+	crypto_private_key_free(cred->key);
+	os_free(cred->dh_p);
+	os_free(cred->dh_g);
+	os_free(cred);
+}
+
+
+static int tlsv1_add_cert_der(struct x509_certificate **chain,
+			      const u8 *buf, size_t len)
+{
+	struct x509_certificate *cert;
+	char name[128];
+
+	cert = x509_certificate_parse(buf, len);
+	if (cert == NULL) {
+		wpa_printf(MSG_INFO, "TLSv1: %s - failed to parse certificate",
+			   __func__);
+		return -1;
+	}
+
+	cert->next = *chain;
+	*chain = cert;
+
+	x509_name_string(&cert->subject, name, sizeof(name));
+	wpa_printf(MSG_DEBUG, "TLSv1: Added certificate: %s", name);
+
+	return 0;
+}
+
+
+static const char *pem_cert_begin = "-----BEGIN CERTIFICATE-----";
+static const char *pem_cert_end = "-----END CERTIFICATE-----";
+static const char *pem_key_begin = "-----BEGIN RSA PRIVATE KEY-----";
+static const char *pem_key_end = "-----END RSA PRIVATE KEY-----";
+static const char *pem_key2_begin = "-----BEGIN PRIVATE KEY-----";
+static const char *pem_key2_end = "-----END PRIVATE KEY-----";
+static const char *pem_key_enc_begin = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
+static const char *pem_key_enc_end = "-----END ENCRYPTED PRIVATE KEY-----";
+
+
+static const u8 * search_tag(const char *tag, const u8 *buf, size_t len)
+{
+	size_t i, plen;
+
+	plen = os_strlen(tag);
+	if (len < plen)
+		return NULL;
+
+	for (i = 0; i < len - plen; i++) {
+		if (os_memcmp(buf + i, tag, plen) == 0)
+			return buf + i;
+	}
+
+	return NULL;
+}
+
+
+static int tlsv1_add_cert(struct x509_certificate **chain,
+			  const u8 *buf, size_t len)
+{
+	const u8 *pos, *end;
+	unsigned char *der;
+	size_t der_len;
+
+	pos = search_tag(pem_cert_begin, buf, len);
+	if (!pos) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No PEM certificate tag found - "
+			   "assume DER format");
+		return tlsv1_add_cert_der(chain, buf, len);
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Converting PEM format certificate into "
+		   "DER format");
+
+	while (pos) {
+		pos += os_strlen(pem_cert_begin);
+		end = search_tag(pem_cert_end, pos, buf + len - pos);
+		if (end == NULL) {
+			wpa_printf(MSG_INFO, "TLSv1: Could not find PEM "
+				   "certificate end tag (%s)", pem_cert_end);
+			return -1;
+		}
+
+		der = base64_decode(pos, end - pos, &der_len);
+		if (der == NULL) {
+			wpa_printf(MSG_INFO, "TLSv1: Could not decode PEM "
+				   "certificate");
+			return -1;
+		}
+
+		if (tlsv1_add_cert_der(chain, der, der_len) < 0) {
+			wpa_printf(MSG_INFO, "TLSv1: Failed to parse PEM "
+				   "certificate after DER conversion");
+			os_free(der);
+			return -1;
+		}
+
+		os_free(der);
+
+		end += os_strlen(pem_cert_end);
+		pos = search_tag(pem_cert_begin, end, buf + len - end);
+	}
+
+	return 0;
+}
+
+
+static int tlsv1_set_cert_chain(struct x509_certificate **chain,
+				const char *cert, const u8 *cert_blob,
+				size_t cert_blob_len)
+{
+	if (cert_blob)
+		return tlsv1_add_cert(chain, cert_blob, cert_blob_len);
+
+	if (cert) {
+		u8 *buf;
+		size_t len;
+		int ret;
+
+		buf = (u8 *) os_readfile(cert, &len);
+		if (buf == NULL) {
+			wpa_printf(MSG_INFO, "TLSv1: Failed to read '%s'",
+				   cert);
+			return -1;
+		}
+
+		ret = tlsv1_add_cert(chain, buf, len);
+		os_free(buf);
+		return ret;
+	}
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_set_ca_cert - Set trusted CA certificate(s)
+ * @cred: TLSv1 credentials from tlsv1_cred_alloc()
+ * @cert: File or reference name for X.509 certificate in PEM or DER format
+ * @cert_blob: cert as inlined data or %NULL if not used
+ * @cert_blob_len: ca_cert_blob length
+ * @path: Path to CA certificates (not yet supported)
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_set_ca_cert(struct tlsv1_credentials *cred, const char *cert,
+		      const u8 *cert_blob, size_t cert_blob_len,
+		      const char *path)
+{
+	if (tlsv1_set_cert_chain(&cred->trusted_certs, cert,
+				 cert_blob, cert_blob_len) < 0)
+		return -1;
+
+	if (path) {
+		/* TODO: add support for reading number of certificate files */
+		wpa_printf(MSG_INFO, "TLSv1: Use of CA certificate directory "
+			   "not yet supported");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_set_cert - Set certificate
+ * @cred: TLSv1 credentials from tlsv1_cred_alloc()
+ * @cert: File or reference name for X.509 certificate in PEM or DER format
+ * @cert_blob: cert as inlined data or %NULL if not used
+ * @cert_blob_len: cert_blob length
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_set_cert(struct tlsv1_credentials *cred, const char *cert,
+		   const u8 *cert_blob, size_t cert_blob_len)
+{
+	return tlsv1_set_cert_chain(&cred->cert, cert,
+				    cert_blob, cert_blob_len);
+}
+
+
+static struct crypto_private_key * tlsv1_set_key_pem(const u8 *key, size_t len)
+{
+	const u8 *pos, *end;
+	unsigned char *der;
+	size_t der_len;
+	struct crypto_private_key *pkey;
+
+	pos = search_tag(pem_key_begin, key, len);
+	if (!pos) {
+		pos = search_tag(pem_key2_begin, key, len);
+		if (!pos)
+			return NULL;
+		pos += os_strlen(pem_key2_begin);
+		end = search_tag(pem_key2_end, pos, key + len - pos);
+		if (!end)
+			return NULL;
+	} else {
+		pos += os_strlen(pem_key_begin);
+		end = search_tag(pem_key_end, pos, key + len - pos);
+		if (!end)
+			return NULL;
+	}
+
+	der = base64_decode(pos, end - pos, &der_len);
+	if (!der)
+		return NULL;
+	pkey = crypto_private_key_import(der, der_len, NULL);
+	os_free(der);
+	return pkey;
+}
+
+
+static struct crypto_private_key * tlsv1_set_key_enc_pem(const u8 *key,
+							 size_t len,
+							 const char *passwd)
+{
+	const u8 *pos, *end;
+	unsigned char *der;
+	size_t der_len;
+	struct crypto_private_key *pkey;
+
+	if (passwd == NULL)
+		return NULL;
+	pos = search_tag(pem_key_enc_begin, key, len);
+	if (!pos)
+		return NULL;
+	pos += os_strlen(pem_key_enc_begin);
+	end = search_tag(pem_key_enc_end, pos, key + len - pos);
+	if (!end)
+		return NULL;
+
+	der = base64_decode(pos, end - pos, &der_len);
+	if (!der)
+		return NULL;
+	pkey = crypto_private_key_import(der, der_len, passwd);
+	os_free(der);
+	return pkey;
+}
+
+
+static int tlsv1_set_key(struct tlsv1_credentials *cred,
+			 const u8 *key, size_t len, const char *passwd)
+{
+	cred->key = crypto_private_key_import(key, len, passwd);
+	if (cred->key == NULL)
+		cred->key = tlsv1_set_key_pem(key, len);
+	if (cred->key == NULL)
+		cred->key = tlsv1_set_key_enc_pem(key, len, passwd);
+	if (cred->key == NULL) {
+		wpa_printf(MSG_INFO, "TLSv1: Failed to parse private key");
+		return -1;
+	}
+	return 0;
+}
+
+
+/**
+ * tlsv1_set_private_key - Set private key
+ * @cred: TLSv1 credentials from tlsv1_cred_alloc()
+ * @private_key: File or reference name for the key in PEM or DER format
+ * @private_key_passwd: Passphrase for decrypted private key, %NULL if no
+ * passphrase is used.
+ * @private_key_blob: private_key as inlined data or %NULL if not used
+ * @private_key_blob_len: private_key_blob length
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_set_private_key(struct tlsv1_credentials *cred,
+			  const char *private_key,
+			  const char *private_key_passwd,
+			  const u8 *private_key_blob,
+			  size_t private_key_blob_len)
+{
+	crypto_private_key_free(cred->key);
+	cred->key = NULL;
+
+	if (private_key_blob)
+		return tlsv1_set_key(cred, private_key_blob,
+				     private_key_blob_len,
+				     private_key_passwd);
+
+	if (private_key) {
+		u8 *buf;
+		size_t len;
+		int ret;
+
+		buf = (u8 *) os_readfile(private_key, &len);
+		if (buf == NULL) {
+			wpa_printf(MSG_INFO, "TLSv1: Failed to read '%s'",
+				   private_key);
+			return -1;
+		}
+
+		ret = tlsv1_set_key(cred, buf, len, private_key_passwd);
+		os_free(buf);
+		return ret;
+	}
+
+	return 0;
+}
+
+
+static int tlsv1_set_dhparams_der(struct tlsv1_credentials *cred,
+				  const u8 *dh, size_t len)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+
+	pos = dh;
+	end = dh + len;
+
+	/*
+	 * DHParameter ::= SEQUENCE {
+	 *   prime INTEGER, -- p
+	 *   base INTEGER, -- g
+	 *   privateValueLength INTEGER OPTIONAL }
+	 */
+
+	/* DHParamer ::= SEQUENCE */
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "DH: DH parameters did not start with a "
+			   "valid SEQUENCE - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+
+	/* prime INTEGER */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0)
+		return -1;
+
+	if (hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG, "DH: No INTEGER tag found for p; "
+			   "class=%d tag=0x%x", hdr.class, hdr.tag);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_MSGDUMP, "DH: prime (p)", hdr.payload, hdr.length);
+	if (hdr.length == 0)
+		return -1;
+	os_free(cred->dh_p);
+	cred->dh_p = os_malloc(hdr.length);
+	if (cred->dh_p == NULL)
+		return -1;
+	os_memcpy(cred->dh_p, hdr.payload, hdr.length);
+	cred->dh_p_len = hdr.length;
+	pos = hdr.payload + hdr.length;
+
+	/* base INTEGER */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0)
+		return -1;
+
+	if (hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG, "DH: No INTEGER tag found for g; "
+			   "class=%d tag=0x%x", hdr.class, hdr.tag);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_MSGDUMP, "DH: base (g)", hdr.payload, hdr.length);
+	if (hdr.length == 0)
+		return -1;
+	os_free(cred->dh_g);
+	cred->dh_g = os_malloc(hdr.length);
+	if (cred->dh_g == NULL)
+		return -1;
+	os_memcpy(cred->dh_g, hdr.payload, hdr.length);
+	cred->dh_g_len = hdr.length;
+
+	return 0;
+}
+
+
+static const char *pem_dhparams_begin = "-----BEGIN DH PARAMETERS-----";
+static const char *pem_dhparams_end = "-----END DH PARAMETERS-----";
+
+
+static int tlsv1_set_dhparams_blob(struct tlsv1_credentials *cred,
+				   const u8 *buf, size_t len)
+{
+	const u8 *pos, *end;
+	unsigned char *der;
+	size_t der_len;
+
+	pos = search_tag(pem_dhparams_begin, buf, len);
+	if (!pos) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No PEM dhparams tag found - "
+			   "assume DER format");
+		return tlsv1_set_dhparams_der(cred, buf, len);
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Converting PEM format dhparams into DER "
+		   "format");
+
+	pos += os_strlen(pem_dhparams_begin);
+	end = search_tag(pem_dhparams_end, pos, buf + len - pos);
+	if (end == NULL) {
+		wpa_printf(MSG_INFO, "TLSv1: Could not find PEM dhparams end "
+			   "tag (%s)", pem_dhparams_end);
+		return -1;
+	}
+
+	der = base64_decode(pos, end - pos, &der_len);
+	if (der == NULL) {
+		wpa_printf(MSG_INFO, "TLSv1: Could not decode PEM dhparams");
+		return -1;
+	}
+
+	if (tlsv1_set_dhparams_der(cred, der, der_len) < 0) {
+		wpa_printf(MSG_INFO, "TLSv1: Failed to parse PEM dhparams "
+			   "DER conversion");
+		os_free(der);
+		return -1;
+	}
+
+	os_free(der);
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_set_dhparams - Set Diffie-Hellman parameters
+ * @cred: TLSv1 credentials from tlsv1_cred_alloc()
+ * @dh_file: File or reference name for the DH params in PEM or DER format
+ * @dh_blob: DH params as inlined data or %NULL if not used
+ * @dh_blob_len: dh_blob length
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_set_dhparams(struct tlsv1_credentials *cred, const char *dh_file,
+		       const u8 *dh_blob, size_t dh_blob_len)
+{
+	if (dh_blob)
+		return tlsv1_set_dhparams_blob(cred, dh_blob, dh_blob_len);
+
+	if (dh_file) {
+		u8 *buf;
+		size_t len;
+		int ret;
+
+		buf = (u8 *) os_readfile(dh_file, &len);
+		if (buf == NULL) {
+			wpa_printf(MSG_INFO, "TLSv1: Failed to read '%s'",
+				   dh_file);
+			return -1;
+		}
+
+		ret = tlsv1_set_dhparams_blob(cred, buf, len);
+		os_free(buf);
+		return ret;
+	}
+
+	return 0;
+}
diff --git a/src/tls/tlsv1_cred.h b/src/tls/tlsv1_cred.h
new file mode 100644
index 0000000..8425fe4
--- /dev/null
+++ b/src/tls/tlsv1_cred.h
@@ -0,0 +1,46 @@
+/*
+ * TLSv1 credentials
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef TLSV1_CRED_H
+#define TLSV1_CRED_H
+
+struct tlsv1_credentials {
+	struct x509_certificate *trusted_certs;
+	struct x509_certificate *cert;
+	struct crypto_private_key *key;
+
+	/* Diffie-Hellman parameters */
+	u8 *dh_p; /* prime */
+	size_t dh_p_len;
+	u8 *dh_g; /* generator */
+	size_t dh_g_len;
+};
+
+
+struct tlsv1_credentials * tlsv1_cred_alloc(void);
+void tlsv1_cred_free(struct tlsv1_credentials *cred);
+int tlsv1_set_ca_cert(struct tlsv1_credentials *cred, const char *cert,
+		      const u8 *cert_blob, size_t cert_blob_len,
+		      const char *path);
+int tlsv1_set_cert(struct tlsv1_credentials *cred, const char *cert,
+		   const u8 *cert_blob, size_t cert_blob_len);
+int tlsv1_set_private_key(struct tlsv1_credentials *cred,
+			  const char *private_key,
+			  const char *private_key_passwd,
+			  const u8 *private_key_blob,
+			  size_t private_key_blob_len);
+int tlsv1_set_dhparams(struct tlsv1_credentials *cred, const char *dh_file,
+		       const u8 *dh_blob, size_t dh_blob_len);
+
+#endif /* TLSV1_CRED_H */
diff --git a/src/tls/tlsv1_record.c b/src/tls/tlsv1_record.c
new file mode 100644
index 0000000..e811f0e
--- /dev/null
+++ b/src/tls/tlsv1_record.c
@@ -0,0 +1,409 @@
+/*
+ * TLSv1 Record Protocol
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/md5.h"
+#include "crypto/sha1.h"
+#include "tlsv1_common.h"
+#include "tlsv1_record.h"
+
+
+/**
+ * tlsv1_record_set_cipher_suite - TLS record layer: Set cipher suite
+ * @rl: Pointer to TLS record layer data
+ * @cipher_suite: New cipher suite
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is used to prepare TLS record layer for cipher suite change.
+ * tlsv1_record_change_write_cipher() and
+ * tlsv1_record_change_read_cipher() functions can then be used to change the
+ * currently used ciphers.
+ */
+int tlsv1_record_set_cipher_suite(struct tlsv1_record_layer *rl,
+				  u16 cipher_suite)
+{
+	const struct tls_cipher_suite *suite;
+	const struct tls_cipher_data *data;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Selected cipher suite: 0x%04x",
+		   cipher_suite);
+	rl->cipher_suite = cipher_suite;
+
+	suite = tls_get_cipher_suite(cipher_suite);
+	if (suite == NULL)
+		return -1;
+
+	if (suite->hash == TLS_HASH_MD5) {
+		rl->hash_alg = CRYPTO_HASH_ALG_HMAC_MD5;
+		rl->hash_size = MD5_MAC_LEN;
+	} else if (suite->hash == TLS_HASH_SHA) {
+		rl->hash_alg = CRYPTO_HASH_ALG_HMAC_SHA1;
+		rl->hash_size = SHA1_MAC_LEN;
+	}
+
+	data = tls_get_cipher_data(suite->cipher);
+	if (data == NULL)
+		return -1;
+
+	rl->key_material_len = data->key_material;
+	rl->iv_size = data->block_size;
+	rl->cipher_alg = data->alg;
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_record_change_write_cipher - TLS record layer: Change write cipher
+ * @rl: Pointer to TLS record layer data
+ * Returns: 0 on success (cipher changed), -1 on failure
+ *
+ * This function changes TLS record layer to use the new cipher suite
+ * configured with tlsv1_record_set_cipher_suite() for writing.
+ */
+int tlsv1_record_change_write_cipher(struct tlsv1_record_layer *rl)
+{
+	wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - New write cipher suite "
+		   "0x%04x", rl->cipher_suite);
+	rl->write_cipher_suite = rl->cipher_suite;
+	os_memset(rl->write_seq_num, 0, TLS_SEQ_NUM_LEN);
+
+	if (rl->write_cbc) {
+		crypto_cipher_deinit(rl->write_cbc);
+		rl->write_cbc = NULL;
+	}
+	if (rl->cipher_alg != CRYPTO_CIPHER_NULL) {
+		rl->write_cbc = crypto_cipher_init(rl->cipher_alg,
+						   rl->write_iv, rl->write_key,
+						   rl->key_material_len);
+		if (rl->write_cbc == NULL) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to initialize "
+				   "cipher");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_record_change_read_cipher - TLS record layer: Change read cipher
+ * @rl: Pointer to TLS record layer data
+ * Returns: 0 on success (cipher changed), -1 on failure
+ *
+ * This function changes TLS record layer to use the new cipher suite
+ * configured with tlsv1_record_set_cipher_suite() for reading.
+ */
+int tlsv1_record_change_read_cipher(struct tlsv1_record_layer *rl)
+{
+	wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - New read cipher suite "
+		   "0x%04x", rl->cipher_suite);
+	rl->read_cipher_suite = rl->cipher_suite;
+	os_memset(rl->read_seq_num, 0, TLS_SEQ_NUM_LEN);
+
+	if (rl->read_cbc) {
+		crypto_cipher_deinit(rl->read_cbc);
+		rl->read_cbc = NULL;
+	}
+	if (rl->cipher_alg != CRYPTO_CIPHER_NULL) {
+		rl->read_cbc = crypto_cipher_init(rl->cipher_alg,
+						  rl->read_iv, rl->read_key,
+						  rl->key_material_len);
+		if (rl->read_cbc == NULL) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to initialize "
+				   "cipher");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_record_send - TLS record layer: Send a message
+ * @rl: Pointer to TLS record layer data
+ * @content_type: Content type (TLS_CONTENT_TYPE_*)
+ * @buf: Buffer to send (with TLS_RECORD_HEADER_LEN octets reserved in the
+ * beginning for record layer to fill in; payload filled in after this and
+ * extra space in the end for HMAC).
+ * @buf_size: Maximum buf size
+ * @payload_len: Length of the payload
+ * @out_len: Buffer for returning the used buf length
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function fills in the TLS record layer header, adds HMAC, and encrypts
+ * the data using the current write cipher.
+ */
+int tlsv1_record_send(struct tlsv1_record_layer *rl, u8 content_type, u8 *buf,
+		      size_t buf_size, size_t payload_len, size_t *out_len)
+{
+	u8 *pos, *ct_start, *length, *payload;
+	struct crypto_hash *hmac;
+	size_t clen;
+
+	pos = buf;
+	/* ContentType type */
+	ct_start = pos;
+	*pos++ = content_type;
+	/* ProtocolVersion version */
+	WPA_PUT_BE16(pos, TLS_VERSION);
+	pos += 2;
+	/* uint16 length */
+	length = pos;
+	WPA_PUT_BE16(length, payload_len);
+	pos += 2;
+
+	/* opaque fragment[TLSPlaintext.length] */
+	payload = pos;
+	pos += payload_len;
+
+	if (rl->write_cipher_suite != TLS_NULL_WITH_NULL_NULL) {
+		hmac = crypto_hash_init(rl->hash_alg, rl->write_mac_secret,
+					rl->hash_size);
+		if (hmac == NULL) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Failed "
+				   "to initialize HMAC");
+			return -1;
+		}
+		crypto_hash_update(hmac, rl->write_seq_num, TLS_SEQ_NUM_LEN);
+		/* type + version + length + fragment */
+		crypto_hash_update(hmac, ct_start, pos - ct_start);
+		clen = buf + buf_size - pos;
+		if (clen < rl->hash_size) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Not "
+				   "enough room for MAC");
+			crypto_hash_finish(hmac, NULL, NULL);
+			return -1;
+		}
+
+		if (crypto_hash_finish(hmac, pos, &clen) < 0) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Failed "
+				   "to calculate HMAC");
+			return -1;
+		}
+		wpa_hexdump(MSG_MSGDUMP, "TLSv1: Record Layer - Write HMAC",
+			    pos, clen);
+		pos += clen;
+		if (rl->iv_size) {
+			size_t len = pos - payload;
+			size_t pad;
+			pad = (len + 1) % rl->iv_size;
+			if (pad)
+				pad = rl->iv_size - pad;
+			if (pos + pad + 1 > buf + buf_size) {
+				wpa_printf(MSG_DEBUG, "TLSv1: No room for "
+					   "block cipher padding");
+				return -1;
+			}
+			os_memset(pos, pad, pad + 1);
+			pos += pad + 1;
+		}
+
+		if (crypto_cipher_encrypt(rl->write_cbc, payload,
+					  payload, pos - payload) < 0)
+			return -1;
+	}
+
+	WPA_PUT_BE16(length, pos - length - 2);
+	inc_byte_array(rl->write_seq_num, TLS_SEQ_NUM_LEN);
+
+	*out_len = pos - buf;
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_record_receive - TLS record layer: Process a received message
+ * @rl: Pointer to TLS record layer data
+ * @in_data: Received data
+ * @in_len: Length of the received data
+ * @out_data: Buffer for output data (must be at least as long as in_data)
+ * @out_len: Set to maximum out_data length by caller; used to return the
+ * length of the used data
+ * @alert: Buffer for returning an alert value on failure
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function decrypts the received message, verifies HMAC and TLS record
+ * layer header.
+ */
+int tlsv1_record_receive(struct tlsv1_record_layer *rl,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t *out_len, u8 *alert)
+{
+	size_t i, rlen, hlen;
+	u8 padlen;
+	struct crypto_hash *hmac;
+	u8 len[2], hash[100];
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: Record Layer - Received",
+		    in_data, in_len);
+
+	if (in_len < TLS_RECORD_HEADER_LEN) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short record (in_len=%lu)",
+			   (unsigned long) in_len);
+		*alert = TLS_ALERT_DECODE_ERROR;
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received content type %d version %d.%d "
+		   "length %d", in_data[0], in_data[1], in_data[2],
+		   WPA_GET_BE16(in_data + 3));
+
+	if (in_data[0] != TLS_CONTENT_TYPE_HANDSHAKE &&
+	    in_data[0] != TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC &&
+	    in_data[0] != TLS_CONTENT_TYPE_ALERT &&
+	    in_data[0] != TLS_CONTENT_TYPE_APPLICATION_DATA) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected content type 0x%x",
+			   in_data[0]);
+		*alert = TLS_ALERT_UNEXPECTED_MESSAGE;
+		return -1;
+	}
+
+	if (WPA_GET_BE16(in_data + 1) != TLS_VERSION) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected protocol version "
+			   "%d.%d", in_data[1], in_data[2]);
+		*alert = TLS_ALERT_PROTOCOL_VERSION;
+		return -1;
+	}
+
+	rlen = WPA_GET_BE16(in_data + 3);
+
+	/* TLSCiphertext must not be more than 2^14+2048 bytes */
+	if (TLS_RECORD_HEADER_LEN + rlen > 18432) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Record overflow (len=%lu)",
+			   (unsigned long) (TLS_RECORD_HEADER_LEN + rlen));
+		*alert = TLS_ALERT_RECORD_OVERFLOW;
+		return -1;
+	}
+
+	in_data += TLS_RECORD_HEADER_LEN;
+	in_len -= TLS_RECORD_HEADER_LEN;
+
+	if (rlen > in_len) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Not all record data included "
+			   "(rlen=%lu > in_len=%lu)",
+			   (unsigned long) rlen, (unsigned long) in_len);
+		*alert = TLS_ALERT_DECODE_ERROR;
+		return -1;
+	}
+
+	in_len = rlen;
+
+	if (*out_len < in_len) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Not enough output buffer for "
+			   "processing received record");
+		*alert = TLS_ALERT_INTERNAL_ERROR;
+		return -1;
+	}
+
+	os_memcpy(out_data, in_data, in_len);
+	*out_len = in_len;
+
+	if (rl->read_cipher_suite != TLS_NULL_WITH_NULL_NULL) {
+		if (crypto_cipher_decrypt(rl->read_cbc, out_data,
+					  out_data, in_len) < 0) {
+			*alert = TLS_ALERT_DECRYPTION_FAILED;
+			return -1;
+		}
+		if (rl->iv_size) {
+			if (in_len == 0) {
+				wpa_printf(MSG_DEBUG, "TLSv1: Too short record"
+					   " (no pad)");
+				*alert = TLS_ALERT_DECODE_ERROR;
+				return -1;
+			}
+			padlen = out_data[in_len - 1];
+			if (padlen >= in_len) {
+				wpa_printf(MSG_DEBUG, "TLSv1: Incorrect pad "
+					   "length (%u, in_len=%lu) in "
+					   "received record",
+					   padlen, (unsigned long) in_len);
+				*alert = TLS_ALERT_DECRYPTION_FAILED;
+				return -1;
+			}
+			for (i = in_len - padlen; i < in_len; i++) {
+				if (out_data[i] != padlen) {
+					wpa_hexdump(MSG_DEBUG,
+						    "TLSv1: Invalid pad in "
+						    "received record",
+						    out_data + in_len - padlen,
+						    padlen);
+					*alert = TLS_ALERT_DECRYPTION_FAILED;
+					return -1;
+				}
+			}
+
+			*out_len -= padlen + 1;
+		}
+
+		wpa_hexdump(MSG_MSGDUMP,
+			    "TLSv1: Record Layer - Decrypted data",
+			    out_data, in_len);
+
+		if (*out_len < rl->hash_size) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Too short record; no "
+				   "hash value");
+			*alert = TLS_ALERT_INTERNAL_ERROR;
+			return -1;
+		}
+
+		*out_len -= rl->hash_size;
+
+		hmac = crypto_hash_init(rl->hash_alg, rl->read_mac_secret,
+					rl->hash_size);
+		if (hmac == NULL) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Failed "
+				   "to initialize HMAC");
+			*alert = TLS_ALERT_INTERNAL_ERROR;
+			return -1;
+		}
+
+		crypto_hash_update(hmac, rl->read_seq_num, TLS_SEQ_NUM_LEN);
+		/* type + version + length + fragment */
+		crypto_hash_update(hmac, in_data - TLS_RECORD_HEADER_LEN, 3);
+		WPA_PUT_BE16(len, *out_len);
+		crypto_hash_update(hmac, len, 2);
+		crypto_hash_update(hmac, out_data, *out_len);
+		hlen = sizeof(hash);
+		if (crypto_hash_finish(hmac, hash, &hlen) < 0) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Record Layer - Failed "
+				   "to calculate HMAC");
+			return -1;
+		}
+		if (hlen != rl->hash_size ||
+		    os_memcmp(hash, out_data + *out_len, hlen) != 0) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Invalid HMAC value in "
+				   "received message");
+			*alert = TLS_ALERT_BAD_RECORD_MAC;
+			return -1;
+		}
+	}
+
+	/* TLSCompressed must not be more than 2^14+1024 bytes */
+	if (TLS_RECORD_HEADER_LEN + *out_len > 17408) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Record overflow (len=%lu)",
+			   (unsigned long) (TLS_RECORD_HEADER_LEN + *out_len));
+		*alert = TLS_ALERT_RECORD_OVERFLOW;
+		return -1;
+	}
+
+	inc_byte_array(rl->read_seq_num, TLS_SEQ_NUM_LEN);
+
+	return 0;
+}
diff --git a/src/tls/tlsv1_record.h b/src/tls/tlsv1_record.h
new file mode 100644
index 0000000..9c7c0a4
--- /dev/null
+++ b/src/tls/tlsv1_record.h
@@ -0,0 +1,74 @@
+/*
+ * TLSv1 Record Protocol
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef TLSV1_RECORD_H
+#define TLSV1_RECORD_H
+
+#include "crypto/crypto.h"
+
+#define TLS_MAX_WRITE_MAC_SECRET_LEN 20
+#define TLS_MAX_WRITE_KEY_LEN 32
+#define TLS_MAX_IV_LEN 16
+#define TLS_MAX_KEY_BLOCK_LEN (2 * (TLS_MAX_WRITE_MAC_SECRET_LEN + \
+				    TLS_MAX_WRITE_KEY_LEN + TLS_MAX_IV_LEN))
+
+#define TLS_SEQ_NUM_LEN 8
+#define TLS_RECORD_HEADER_LEN 5
+
+/* ContentType */
+enum {
+	TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC = 20,
+	TLS_CONTENT_TYPE_ALERT = 21,
+	TLS_CONTENT_TYPE_HANDSHAKE = 22,
+	TLS_CONTENT_TYPE_APPLICATION_DATA = 23
+};
+
+struct tlsv1_record_layer {
+	u8 write_mac_secret[TLS_MAX_WRITE_MAC_SECRET_LEN];
+	u8 read_mac_secret[TLS_MAX_WRITE_MAC_SECRET_LEN];
+	u8 write_key[TLS_MAX_WRITE_KEY_LEN];
+	u8 read_key[TLS_MAX_WRITE_KEY_LEN];
+	u8 write_iv[TLS_MAX_IV_LEN];
+	u8 read_iv[TLS_MAX_IV_LEN];
+
+	size_t hash_size;
+	size_t key_material_len;
+	size_t iv_size; /* also block_size */
+
+	enum crypto_hash_alg hash_alg;
+	enum crypto_cipher_alg cipher_alg;
+
+	u8 write_seq_num[TLS_SEQ_NUM_LEN];
+	u8 read_seq_num[TLS_SEQ_NUM_LEN];
+
+	u16 cipher_suite;
+	u16 write_cipher_suite;
+	u16 read_cipher_suite;
+
+	struct crypto_cipher *write_cbc;
+	struct crypto_cipher *read_cbc;
+};
+
+
+int tlsv1_record_set_cipher_suite(struct tlsv1_record_layer *rl,
+				  u16 cipher_suite);
+int tlsv1_record_change_write_cipher(struct tlsv1_record_layer *rl);
+int tlsv1_record_change_read_cipher(struct tlsv1_record_layer *rl);
+int tlsv1_record_send(struct tlsv1_record_layer *rl, u8 content_type, u8 *buf,
+		      size_t buf_size, size_t payload_len, size_t *out_len);
+int tlsv1_record_receive(struct tlsv1_record_layer *rl,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t *out_len, u8 *alert);
+
+#endif /* TLSV1_RECORD_H */
diff --git a/src/tls/tlsv1_server.c b/src/tls/tlsv1_server.c
new file mode 100644
index 0000000..6a61235
--- /dev/null
+++ b/src/tls/tlsv1_server.c
@@ -0,0 +1,592 @@
+/*
+ * TLSv1 server (RFC 2246)
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/sha1.h"
+#include "crypto/tls.h"
+#include "tlsv1_common.h"
+#include "tlsv1_record.h"
+#include "tlsv1_server.h"
+#include "tlsv1_server_i.h"
+
+/* TODO:
+ * Support for a message fragmented across several records (RFC 2246, 6.2.1)
+ */
+
+
+void tlsv1_server_alert(struct tlsv1_server *conn, u8 level, u8 description)
+{
+	conn->alert_level = level;
+	conn->alert_description = description;
+}
+
+
+int tlsv1_server_derive_keys(struct tlsv1_server *conn,
+			     const u8 *pre_master_secret,
+			     size_t pre_master_secret_len)
+{
+	u8 seed[2 * TLS_RANDOM_LEN];
+	u8 key_block[TLS_MAX_KEY_BLOCK_LEN];
+	u8 *pos;
+	size_t key_block_len;
+
+	if (pre_master_secret) {
+		wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: pre_master_secret",
+				pre_master_secret, pre_master_secret_len);
+		os_memcpy(seed, conn->client_random, TLS_RANDOM_LEN);
+		os_memcpy(seed + TLS_RANDOM_LEN, conn->server_random,
+			  TLS_RANDOM_LEN);
+		if (tls_prf(pre_master_secret, pre_master_secret_len,
+			    "master secret", seed, 2 * TLS_RANDOM_LEN,
+			    conn->master_secret, TLS_MASTER_SECRET_LEN)) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive "
+				   "master_secret");
+			return -1;
+		}
+		wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: master_secret",
+				conn->master_secret, TLS_MASTER_SECRET_LEN);
+	}
+
+	os_memcpy(seed, conn->server_random, TLS_RANDOM_LEN);
+	os_memcpy(seed + TLS_RANDOM_LEN, conn->client_random, TLS_RANDOM_LEN);
+	key_block_len = 2 * (conn->rl.hash_size + conn->rl.key_material_len +
+			     conn->rl.iv_size);
+	if (tls_prf(conn->master_secret, TLS_MASTER_SECRET_LEN,
+		    "key expansion", seed, 2 * TLS_RANDOM_LEN,
+		    key_block, key_block_len)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive key_block");
+		return -1;
+	}
+	wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: key_block",
+			key_block, key_block_len);
+
+	pos = key_block;
+
+	/* client_write_MAC_secret */
+	os_memcpy(conn->rl.read_mac_secret, pos, conn->rl.hash_size);
+	pos += conn->rl.hash_size;
+	/* server_write_MAC_secret */
+	os_memcpy(conn->rl.write_mac_secret, pos, conn->rl.hash_size);
+	pos += conn->rl.hash_size;
+
+	/* client_write_key */
+	os_memcpy(conn->rl.read_key, pos, conn->rl.key_material_len);
+	pos += conn->rl.key_material_len;
+	/* server_write_key */
+	os_memcpy(conn->rl.write_key, pos, conn->rl.key_material_len);
+	pos += conn->rl.key_material_len;
+
+	/* client_write_IV */
+	os_memcpy(conn->rl.read_iv, pos, conn->rl.iv_size);
+	pos += conn->rl.iv_size;
+	/* server_write_IV */
+	os_memcpy(conn->rl.write_iv, pos, conn->rl.iv_size);
+	pos += conn->rl.iv_size;
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_server_handshake - Process TLS handshake
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * @in_data: Input data from TLS peer
+ * @in_len: Input data length
+ * @out_len: Length of the output buffer.
+ * Returns: Pointer to output data, %NULL on failure
+ */
+u8 * tlsv1_server_handshake(struct tlsv1_server *conn,
+			    const u8 *in_data, size_t in_len,
+			    size_t *out_len)
+{
+	const u8 *pos, *end;
+	u8 *msg = NULL, *in_msg, *in_pos, *in_end, alert, ct;
+	size_t in_msg_len;
+
+	if (in_data == NULL || in_len == 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No input data to server");
+		return NULL;
+	}
+
+	pos = in_data;
+	end = in_data + in_len;
+	in_msg = os_malloc(in_len);
+	if (in_msg == NULL)
+		return NULL;
+
+	/* Each received packet may include multiple records */
+	while (pos < end) {
+		in_msg_len = in_len;
+		if (tlsv1_record_receive(&conn->rl, pos, end - pos,
+					 in_msg, &in_msg_len, &alert)) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Processing received "
+				   "record failed");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, alert);
+			goto failed;
+		}
+		ct = pos[0];
+
+		in_pos = in_msg;
+		in_end = in_msg + in_msg_len;
+
+		/* Each received record may include multiple messages of the
+		 * same ContentType. */
+		while (in_pos < in_end) {
+			in_msg_len = in_end - in_pos;
+			if (tlsv1_server_process_handshake(conn, ct, in_pos,
+							   &in_msg_len) < 0)
+				goto failed;
+			in_pos += in_msg_len;
+		}
+
+		pos += TLS_RECORD_HEADER_LEN + WPA_GET_BE16(pos + 3);
+	}
+
+	os_free(in_msg);
+	in_msg = NULL;
+
+	msg = tlsv1_server_handshake_write(conn, out_len);
+
+failed:
+	os_free(in_msg);
+	if (conn->alert_level) {
+		if (conn->state == FAILED) {
+			/* Avoid alert loops */
+			wpa_printf(MSG_DEBUG, "TLSv1: Drop alert loop");
+			os_free(msg);
+			return NULL;
+		}
+		conn->state = FAILED;
+		os_free(msg);
+		msg = tlsv1_server_send_alert(conn, conn->alert_level,
+					      conn->alert_description,
+					      out_len);
+	}
+
+	return msg;
+}
+
+
+/**
+ * tlsv1_server_encrypt - Encrypt data into TLS tunnel
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * @in_data: Pointer to plaintext data to be encrypted
+ * @in_len: Input buffer length
+ * @out_data: Pointer to output buffer (encrypted TLS data)
+ * @out_len: Maximum out_data length 
+ * Returns: Number of bytes written to out_data, -1 on failure
+ *
+ * This function is used after TLS handshake has been completed successfully to
+ * send data in the encrypted tunnel.
+ */
+int tlsv1_server_encrypt(struct tlsv1_server *conn,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t out_len)
+{
+	size_t rlen;
+
+	wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: Plaintext AppData",
+			in_data, in_len);
+
+	os_memcpy(out_data + TLS_RECORD_HEADER_LEN, in_data, in_len);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_APPLICATION_DATA,
+			      out_data, out_len, in_len, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create a record");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	return rlen;
+}
+
+
+/**
+ * tlsv1_server_decrypt - Decrypt data from TLS tunnel
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * @in_data: Pointer to input buffer (encrypted TLS data)
+ * @in_len: Input buffer length
+ * @out_data: Pointer to output buffer (decrypted data from TLS tunnel)
+ * @out_len: Maximum out_data length
+ * Returns: Number of bytes written to out_data, -1 on failure
+ *
+ * This function is used after TLS handshake has been completed successfully to
+ * receive data from the encrypted tunnel.
+ */
+int tlsv1_server_decrypt(struct tlsv1_server *conn,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t out_len)
+{
+	const u8 *in_end, *pos;
+	int res;
+	u8 alert, *out_end, *out_pos;
+	size_t olen;
+
+	pos = in_data;
+	in_end = in_data + in_len;
+	out_pos = out_data;
+	out_end = out_data + out_len;
+
+	while (pos < in_end) {
+		if (pos[0] != TLS_CONTENT_TYPE_APPLICATION_DATA) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Unexpected content type "
+				   "0x%x", pos[0]);
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_UNEXPECTED_MESSAGE);
+			return -1;
+		}
+
+		olen = out_end - out_pos;
+		res = tlsv1_record_receive(&conn->rl, pos, in_end - pos,
+					   out_pos, &olen, &alert);
+		if (res < 0) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Record layer processing "
+				   "failed");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, alert);
+			return -1;
+		}
+		out_pos += olen;
+		if (out_pos > out_end) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Buffer not large enough "
+				   "for processing the received record");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_INTERNAL_ERROR);
+			return -1;
+		}
+
+		pos += TLS_RECORD_HEADER_LEN + WPA_GET_BE16(pos + 3);
+	}
+
+	return out_pos - out_data;
+}
+
+
+/**
+ * tlsv1_server_global_init - Initialize TLSv1 server
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function must be called before using any other TLSv1 server functions.
+ */
+int tlsv1_server_global_init(void)
+{
+	return crypto_global_init();
+}
+
+
+/**
+ * tlsv1_server_global_deinit - Deinitialize TLSv1 server
+ *
+ * This function can be used to deinitialize the TLSv1 server that was
+ * initialized by calling tlsv1_server_global_init(). No TLSv1 server functions
+ * can be called after this before calling tlsv1_server_global_init() again.
+ */
+void tlsv1_server_global_deinit(void)
+{
+	crypto_global_deinit();
+}
+
+
+/**
+ * tlsv1_server_init - Initialize TLSv1 server connection
+ * @cred: Pointer to server credentials from tlsv1_server_cred_alloc()
+ * Returns: Pointer to TLSv1 server connection data or %NULL on failure
+ */
+struct tlsv1_server * tlsv1_server_init(struct tlsv1_credentials *cred)
+{
+	struct tlsv1_server *conn;
+	size_t count;
+	u16 *suites;
+
+	conn = os_zalloc(sizeof(*conn));
+	if (conn == NULL)
+		return NULL;
+
+	conn->cred = cred;
+
+	conn->state = CLIENT_HELLO;
+
+	if (tls_verify_hash_init(&conn->verify) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to initialize verify "
+			   "hash");
+		os_free(conn);
+		return NULL;
+	}
+
+	count = 0;
+	suites = conn->cipher_suites;
+#ifndef CONFIG_CRYPTO_INTERNAL
+	suites[count++] = TLS_RSA_WITH_AES_256_CBC_SHA;
+#endif /* CONFIG_CRYPTO_INTERNAL */
+	suites[count++] = TLS_RSA_WITH_AES_128_CBC_SHA;
+	suites[count++] = TLS_RSA_WITH_3DES_EDE_CBC_SHA;
+	suites[count++] = TLS_RSA_WITH_RC4_128_SHA;
+	suites[count++] = TLS_RSA_WITH_RC4_128_MD5;
+	conn->num_cipher_suites = count;
+
+	return conn;
+}
+
+
+static void tlsv1_server_clear_data(struct tlsv1_server *conn)
+{
+	tlsv1_record_set_cipher_suite(&conn->rl, TLS_NULL_WITH_NULL_NULL);
+	tlsv1_record_change_write_cipher(&conn->rl);
+	tlsv1_record_change_read_cipher(&conn->rl);
+	tls_verify_hash_free(&conn->verify);
+
+	crypto_public_key_free(conn->client_rsa_key);
+	conn->client_rsa_key = NULL;
+
+	os_free(conn->session_ticket);
+	conn->session_ticket = NULL;
+	conn->session_ticket_len = 0;
+	conn->use_session_ticket = 0;
+
+	os_free(conn->dh_secret);
+	conn->dh_secret = NULL;
+	conn->dh_secret_len = 0;
+}
+
+
+/**
+ * tlsv1_server_deinit - Deinitialize TLSv1 server connection
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ */
+void tlsv1_server_deinit(struct tlsv1_server *conn)
+{
+	tlsv1_server_clear_data(conn);
+	os_free(conn);
+}
+
+
+/**
+ * tlsv1_server_established - Check whether connection has been established
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * Returns: 1 if connection is established, 0 if not
+ */
+int tlsv1_server_established(struct tlsv1_server *conn)
+{
+	return conn->state == ESTABLISHED;
+}
+
+
+/**
+ * tlsv1_server_prf - Use TLS-PRF to derive keying material
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * @label: Label (e.g., description of the key) for PRF
+ * @server_random_first: seed is 0 = client_random|server_random,
+ * 1 = server_random|client_random
+ * @out: Buffer for output data from TLS-PRF
+ * @out_len: Length of the output buffer
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_server_prf(struct tlsv1_server *conn, const char *label,
+		     int server_random_first, u8 *out, size_t out_len)
+{
+	u8 seed[2 * TLS_RANDOM_LEN];
+
+	if (conn->state != ESTABLISHED)
+		return -1;
+
+	if (server_random_first) {
+		os_memcpy(seed, conn->server_random, TLS_RANDOM_LEN);
+		os_memcpy(seed + TLS_RANDOM_LEN, conn->client_random,
+			  TLS_RANDOM_LEN);
+	} else {
+		os_memcpy(seed, conn->client_random, TLS_RANDOM_LEN);
+		os_memcpy(seed + TLS_RANDOM_LEN, conn->server_random,
+			  TLS_RANDOM_LEN);
+	}
+
+	return tls_prf(conn->master_secret, TLS_MASTER_SECRET_LEN,
+		       label, seed, 2 * TLS_RANDOM_LEN, out, out_len);
+}
+
+
+/**
+ * tlsv1_server_get_cipher - Get current cipher name
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * @buf: Buffer for the cipher name
+ * @buflen: buf size
+ * Returns: 0 on success, -1 on failure
+ *
+ * Get the name of the currently used cipher.
+ */
+int tlsv1_server_get_cipher(struct tlsv1_server *conn, char *buf,
+			    size_t buflen)
+{
+	char *cipher;
+
+	switch (conn->rl.cipher_suite) {
+	case TLS_RSA_WITH_RC4_128_MD5:
+		cipher = "RC4-MD5";
+		break;
+	case TLS_RSA_WITH_RC4_128_SHA:
+		cipher = "RC4-SHA";
+		break;
+	case TLS_RSA_WITH_DES_CBC_SHA:
+		cipher = "DES-CBC-SHA";
+		break;
+	case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+		cipher = "DES-CBC3-SHA";
+		break;
+	case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+		cipher = "ADH-AES-128-SHA";
+		break;
+	case TLS_RSA_WITH_AES_256_CBC_SHA:
+		cipher = "AES-256-SHA";
+		break;
+	case TLS_RSA_WITH_AES_128_CBC_SHA:
+		cipher = "AES-128-SHA";
+		break;
+	default:
+		return -1;
+	}
+
+	if (os_strlcpy(buf, cipher, buflen) >= buflen)
+		return -1;
+	return 0;
+}
+
+
+/**
+ * tlsv1_server_shutdown - Shutdown TLS connection
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_server_shutdown(struct tlsv1_server *conn)
+{
+	conn->state = CLIENT_HELLO;
+
+	if (tls_verify_hash_init(&conn->verify) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to re-initialize verify "
+			   "hash");
+		return -1;
+	}
+
+	tlsv1_server_clear_data(conn);
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_server_resumed - Was session resumption used
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * Returns: 1 if current session used session resumption, 0 if not
+ */
+int tlsv1_server_resumed(struct tlsv1_server *conn)
+{
+	return 0;
+}
+
+
+/**
+ * tlsv1_server_get_keys - Get master key and random data from TLS connection
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * @keys: Structure of key/random data (filled on success)
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_server_get_keys(struct tlsv1_server *conn, struct tls_keys *keys)
+{
+	os_memset(keys, 0, sizeof(*keys));
+	if (conn->state == CLIENT_HELLO)
+		return -1;
+
+	keys->client_random = conn->client_random;
+	keys->client_random_len = TLS_RANDOM_LEN;
+
+	if (conn->state != SERVER_HELLO) {
+		keys->server_random = conn->server_random;
+		keys->server_random_len = TLS_RANDOM_LEN;
+		keys->master_key = conn->master_secret;
+		keys->master_key_len = TLS_MASTER_SECRET_LEN;
+	}
+
+	return 0;
+}
+
+
+/**
+ * tlsv1_server_get_keyblock_size - Get TLS key_block size
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * Returns: Size of the key_block for the negotiated cipher suite or -1 on
+ * failure
+ */
+int tlsv1_server_get_keyblock_size(struct tlsv1_server *conn)
+{
+	if (conn->state == CLIENT_HELLO || conn->state == SERVER_HELLO)
+		return -1;
+
+	return 2 * (conn->rl.hash_size + conn->rl.key_material_len +
+		    conn->rl.iv_size);
+}
+
+
+/**
+ * tlsv1_server_set_cipher_list - Configure acceptable cipher suites
+ * @conn: TLSv1 server connection data from tlsv1_server_init()
+ * @ciphers: Zero (TLS_CIPHER_NONE) terminated list of allowed ciphers
+ * (TLS_CIPHER_*).
+ * Returns: 0 on success, -1 on failure
+ */
+int tlsv1_server_set_cipher_list(struct tlsv1_server *conn, u8 *ciphers)
+{
+	size_t count;
+	u16 *suites;
+
+	/* TODO: implement proper configuration of cipher suites */
+	if (ciphers[0] == TLS_CIPHER_ANON_DH_AES128_SHA) {
+		count = 0;
+		suites = conn->cipher_suites;
+#ifndef CONFIG_CRYPTO_INTERNAL
+		suites[count++] = TLS_RSA_WITH_AES_256_CBC_SHA;
+#endif /* CONFIG_CRYPTO_INTERNAL */
+		suites[count++] = TLS_RSA_WITH_AES_128_CBC_SHA;
+		suites[count++] = TLS_RSA_WITH_3DES_EDE_CBC_SHA;
+		suites[count++] = TLS_RSA_WITH_RC4_128_SHA;
+		suites[count++] = TLS_RSA_WITH_RC4_128_MD5;
+#ifndef CONFIG_CRYPTO_INTERNAL
+		suites[count++] = TLS_DH_anon_WITH_AES_256_CBC_SHA;
+#endif /* CONFIG_CRYPTO_INTERNAL */
+		suites[count++] = TLS_DH_anon_WITH_AES_128_CBC_SHA;
+		suites[count++] = TLS_DH_anon_WITH_3DES_EDE_CBC_SHA;
+		suites[count++] = TLS_DH_anon_WITH_RC4_128_MD5;
+		suites[count++] = TLS_DH_anon_WITH_DES_CBC_SHA;
+		conn->num_cipher_suites = count;
+	}
+
+	return 0;
+}
+
+
+int tlsv1_server_set_verify(struct tlsv1_server *conn, int verify_peer)
+{
+	conn->verify_peer = verify_peer;
+	return 0;
+}
+
+
+void tlsv1_server_set_session_ticket_cb(struct tlsv1_server *conn,
+					tlsv1_server_session_ticket_cb cb,
+					void *ctx)
+{
+	wpa_printf(MSG_DEBUG, "TLSv1: SessionTicket callback set %p (ctx %p)",
+		   cb, ctx);
+	conn->session_ticket_cb = cb;
+	conn->session_ticket_cb_ctx = ctx;
+}
diff --git a/src/tls/tlsv1_server.h b/src/tls/tlsv1_server.h
new file mode 100644
index 0000000..00c536c
--- /dev/null
+++ b/src/tls/tlsv1_server.h
@@ -0,0 +1,54 @@
+/*
+ * TLSv1 server (RFC 2246)
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef TLSV1_SERVER_H
+#define TLSV1_SERVER_H
+
+#include "tlsv1_cred.h"
+
+struct tlsv1_server;
+
+int tlsv1_server_global_init(void);
+void tlsv1_server_global_deinit(void);
+struct tlsv1_server * tlsv1_server_init(struct tlsv1_credentials *cred);
+void tlsv1_server_deinit(struct tlsv1_server *conn);
+int tlsv1_server_established(struct tlsv1_server *conn);
+int tlsv1_server_prf(struct tlsv1_server *conn, const char *label,
+		     int server_random_first, u8 *out, size_t out_len);
+u8 * tlsv1_server_handshake(struct tlsv1_server *conn,
+			    const u8 *in_data, size_t in_len, size_t *out_len);
+int tlsv1_server_encrypt(struct tlsv1_server *conn,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t out_len);
+int tlsv1_server_decrypt(struct tlsv1_server *conn,
+			 const u8 *in_data, size_t in_len,
+			 u8 *out_data, size_t out_len);
+int tlsv1_server_get_cipher(struct tlsv1_server *conn, char *buf,
+			    size_t buflen);
+int tlsv1_server_shutdown(struct tlsv1_server *conn);
+int tlsv1_server_resumed(struct tlsv1_server *conn);
+int tlsv1_server_get_keys(struct tlsv1_server *conn, struct tls_keys *keys);
+int tlsv1_server_get_keyblock_size(struct tlsv1_server *conn);
+int tlsv1_server_set_cipher_list(struct tlsv1_server *conn, u8 *ciphers);
+int tlsv1_server_set_verify(struct tlsv1_server *conn, int verify_peer);
+
+typedef int (*tlsv1_server_session_ticket_cb)
+(void *ctx, const u8 *ticket, size_t len, const u8 *client_random,
+ const u8 *server_random, u8 *master_secret);
+
+void tlsv1_server_set_session_ticket_cb(struct tlsv1_server *conn,
+					tlsv1_server_session_ticket_cb cb,
+					void *ctx);
+
+#endif /* TLSV1_SERVER_H */
diff --git a/src/tls/tlsv1_server_i.h b/src/tls/tlsv1_server_i.h
new file mode 100644
index 0000000..d11ea75
--- /dev/null
+++ b/src/tls/tlsv1_server_i.h
@@ -0,0 +1,77 @@
+/*
+ * TLSv1 server - internal structures
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef TLSV1_SERVER_I_H
+#define TLSV1_SERVER_I_H
+
+struct tlsv1_server {
+	enum {
+		CLIENT_HELLO, SERVER_HELLO, SERVER_CERTIFICATE,
+		SERVER_KEY_EXCHANGE, SERVER_CERTIFICATE_REQUEST,
+		SERVER_HELLO_DONE, CLIENT_CERTIFICATE, CLIENT_KEY_EXCHANGE,
+		CERTIFICATE_VERIFY, CHANGE_CIPHER_SPEC, CLIENT_FINISHED,
+		SERVER_CHANGE_CIPHER_SPEC, SERVER_FINISHED,
+		ESTABLISHED, FAILED
+	} state;
+
+	struct tlsv1_record_layer rl;
+
+	u8 session_id[TLS_SESSION_ID_MAX_LEN];
+	size_t session_id_len;
+	u8 client_random[TLS_RANDOM_LEN];
+	u8 server_random[TLS_RANDOM_LEN];
+	u8 master_secret[TLS_MASTER_SECRET_LEN];
+
+	u8 alert_level;
+	u8 alert_description;
+
+	struct crypto_public_key *client_rsa_key;
+
+	struct tls_verify_hash verify;
+
+#define MAX_CIPHER_COUNT 30
+	u16 cipher_suites[MAX_CIPHER_COUNT];
+	size_t num_cipher_suites;
+
+	u16 cipher_suite;
+
+	struct tlsv1_credentials *cred;
+
+	int verify_peer;
+	u16 client_version;
+
+	u8 *session_ticket;
+	size_t session_ticket_len;
+
+	tlsv1_server_session_ticket_cb session_ticket_cb;
+	void *session_ticket_cb_ctx;
+
+	int use_session_ticket;
+
+	u8 *dh_secret;
+	size_t dh_secret_len;
+};
+
+
+void tlsv1_server_alert(struct tlsv1_server *conn, u8 level, u8 description);
+int tlsv1_server_derive_keys(struct tlsv1_server *conn,
+			     const u8 *pre_master_secret,
+			     size_t pre_master_secret_len);
+u8 * tlsv1_server_handshake_write(struct tlsv1_server *conn, size_t *out_len);
+u8 * tlsv1_server_send_alert(struct tlsv1_server *conn, u8 level,
+			     u8 description, size_t *out_len);
+int tlsv1_server_process_handshake(struct tlsv1_server *conn, u8 ct,
+				   const u8 *buf, size_t *len);
+
+#endif /* TLSV1_SERVER_I_H */
diff --git a/src/tls/tlsv1_server_read.c b/src/tls/tlsv1_server_read.c
new file mode 100644
index 0000000..49e811f
--- /dev/null
+++ b/src/tls/tlsv1_server_read.c
@@ -0,0 +1,1134 @@
+/*
+ * TLSv1 server - read handshake message
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/md5.h"
+#include "crypto/sha1.h"
+#include "crypto/tls.h"
+#include "x509v3.h"
+#include "tlsv1_common.h"
+#include "tlsv1_record.h"
+#include "tlsv1_server.h"
+#include "tlsv1_server_i.h"
+
+
+static int tls_process_client_key_exchange(struct tlsv1_server *conn, u8 ct,
+					   const u8 *in_data, size_t *in_len);
+static int tls_process_change_cipher_spec(struct tlsv1_server *conn,
+					  u8 ct, const u8 *in_data,
+					  size_t *in_len);
+
+
+static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct,
+				    const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end, *c;
+	size_t left, len, i, j;
+	u16 cipher_suite;
+	u16 num_suites;
+	int compr_null_found;
+	u16 ext_type, ext_len;
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4)
+		goto decode_error;
+
+	/* HandshakeType msg_type */
+	if (*pos != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected ClientHello)", *pos);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG, "TLSv1: Received ClientHello");
+	pos++;
+	/* uint24 length */
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left)
+		goto decode_error;
+
+	/* body - ClientHello */
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: ClientHello", pos, len);
+	end = pos + len;
+
+	/* ProtocolVersion client_version */
+	if (end - pos < 2)
+		goto decode_error;
+	conn->client_version = WPA_GET_BE16(pos);
+	wpa_printf(MSG_DEBUG, "TLSv1: Client version %d.%d",
+		   conn->client_version >> 8, conn->client_version & 0xff);
+	if (conn->client_version < TLS_VERSION) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected protocol version in "
+			   "ClientHello");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_PROTOCOL_VERSION);
+		return -1;
+	}
+	pos += 2;
+
+	/* Random random */
+	if (end - pos < TLS_RANDOM_LEN)
+		goto decode_error;
+
+	os_memcpy(conn->client_random, pos, TLS_RANDOM_LEN);
+	pos += TLS_RANDOM_LEN;
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: client_random",
+		    conn->client_random, TLS_RANDOM_LEN);
+
+	/* SessionID session_id */
+	if (end - pos < 1)
+		goto decode_error;
+	if (end - pos < 1 + *pos || *pos > TLS_SESSION_ID_MAX_LEN)
+		goto decode_error;
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: client session_id", pos + 1, *pos);
+	pos += 1 + *pos;
+	/* TODO: add support for session resumption */
+
+	/* CipherSuite cipher_suites<2..2^16-1> */
+	if (end - pos < 2)
+		goto decode_error;
+	num_suites = WPA_GET_BE16(pos);
+	pos += 2;
+	if (end - pos < num_suites)
+		goto decode_error;
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: client cipher suites",
+		    pos, num_suites);
+	if (num_suites & 1)
+		goto decode_error;
+	num_suites /= 2;
+
+	cipher_suite = 0;
+	for (i = 0; !cipher_suite && i < conn->num_cipher_suites; i++) {
+		c = pos;
+		for (j = 0; j < num_suites; j++) {
+			u16 tmp = WPA_GET_BE16(c);
+			c += 2;
+			if (!cipher_suite && tmp == conn->cipher_suites[i]) {
+				cipher_suite = tmp;
+				break;
+			}
+		}
+	}
+	pos += num_suites * 2;
+	if (!cipher_suite) {
+		wpa_printf(MSG_INFO, "TLSv1: No supported cipher suite "
+			   "available");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_ILLEGAL_PARAMETER);
+		return -1;
+	}
+
+	if (tlsv1_record_set_cipher_suite(&conn->rl, cipher_suite) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to set CipherSuite for "
+			   "record layer");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	conn->cipher_suite = cipher_suite;
+
+	/* CompressionMethod compression_methods<1..2^8-1> */
+	if (end - pos < 1)
+		goto decode_error;
+	num_suites = *pos++;
+	if (end - pos < num_suites)
+		goto decode_error;
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: client compression_methods",
+		    pos, num_suites);
+	compr_null_found = 0;
+	for (i = 0; i < num_suites; i++) {
+		if (*pos++ == TLS_COMPRESSION_NULL)
+			compr_null_found = 1;
+	}
+	if (!compr_null_found) {
+		wpa_printf(MSG_INFO, "TLSv1: Client does not accept NULL "
+			   "compression");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_ILLEGAL_PARAMETER);
+		return -1;
+	}
+
+	if (end - pos == 1) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected extra octet in the "
+			    "end of ClientHello: 0x%02x", *pos);
+		goto decode_error;
+	}
+
+	if (end - pos >= 2) {
+		/* Extension client_hello_extension_list<0..2^16-1> */
+		ext_len = WPA_GET_BE16(pos);
+		pos += 2;
+
+		wpa_printf(MSG_DEBUG, "TLSv1: %u bytes of ClientHello "
+			   "extensions", ext_len);
+		if (end - pos != ext_len) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Invalid ClientHello "
+				   "extension list length %u (expected %u)",
+				   ext_len, (unsigned int) (end - pos));
+			goto decode_error;
+		}
+
+		/*
+		 * struct {
+		 *   ExtensionType extension_type (0..65535)
+		 *   opaque extension_data<0..2^16-1>
+		 * } Extension;
+		 */
+
+		while (pos < end) {
+			if (end - pos < 2) {
+				wpa_printf(MSG_DEBUG, "TLSv1: Invalid "
+					   "extension_type field");
+				goto decode_error;
+			}
+
+			ext_type = WPA_GET_BE16(pos);
+			pos += 2;
+
+			if (end - pos < 2) {
+				wpa_printf(MSG_DEBUG, "TLSv1: Invalid "
+					   "extension_data length field");
+				goto decode_error;
+			}
+
+			ext_len = WPA_GET_BE16(pos);
+			pos += 2;
+
+			if (end - pos < ext_len) {
+				wpa_printf(MSG_DEBUG, "TLSv1: Invalid "
+					   "extension_data field");
+				goto decode_error;
+			}
+
+			wpa_printf(MSG_DEBUG, "TLSv1: ClientHello Extension "
+				   "type %u", ext_type);
+			wpa_hexdump(MSG_MSGDUMP, "TLSv1: ClientHello "
+				    "Extension data", pos, ext_len);
+
+			if (ext_type == TLS_EXT_SESSION_TICKET) {
+				os_free(conn->session_ticket);
+				conn->session_ticket = os_malloc(ext_len);
+				if (conn->session_ticket) {
+					os_memcpy(conn->session_ticket, pos,
+						  ext_len);
+					conn->session_ticket_len = ext_len;
+				}
+			}
+
+			pos += ext_len;
+		}
+	}
+
+	*in_len = end - in_data;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: ClientHello OK - proceed to "
+		   "ServerHello");
+	conn->state = SERVER_HELLO;
+
+	return 0;
+
+decode_error:
+	wpa_printf(MSG_DEBUG, "TLSv1: Failed to decode ClientHello");
+	tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			   TLS_ALERT_DECODE_ERROR);
+	return -1;
+}
+
+
+static int tls_process_certificate(struct tlsv1_server *conn, u8 ct,
+				   const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len, list_len, cert_len, idx;
+	u8 type;
+	struct x509_certificate *chain = NULL, *last = NULL, *cert;
+	int reason;
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short Certificate message "
+			   "(len=%lu)", (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	type = *pos++;
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected Certificate message "
+			   "length (len=%lu != left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	if (type == TLS_HANDSHAKE_TYPE_CLIENT_KEY_EXCHANGE) {
+		if (conn->verify_peer) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Client did not include "
+				   "Certificate");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_UNEXPECTED_MESSAGE);
+			return -1;
+		}
+
+		return tls_process_client_key_exchange(conn, ct, in_data,
+						       in_len);
+	}
+	if (type != TLS_HANDSHAKE_TYPE_CERTIFICATE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected Certificate/"
+			   "ClientKeyExchange)", type);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "TLSv1: Received Certificate (certificate_list len %lu)",
+		   (unsigned long) len);
+
+	/*
+	 * opaque ASN.1Cert<2^24-1>;
+	 *
+	 * struct {
+	 *     ASN.1Cert certificate_list<1..2^24-1>;
+	 * } Certificate;
+	 */
+
+	end = pos + len;
+
+	if (end - pos < 3) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short Certificate "
+			   "(left=%lu)", (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	list_len = WPA_GET_BE24(pos);
+	pos += 3;
+
+	if ((size_t) (end - pos) != list_len) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected certificate_list "
+			   "length (len=%lu left=%lu)",
+			   (unsigned long) list_len,
+			   (unsigned long) (end - pos));
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	idx = 0;
+	while (pos < end) {
+		if (end - pos < 3) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse "
+				   "certificate_list");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_DECODE_ERROR);
+			x509_certificate_chain_free(chain);
+			return -1;
+		}
+
+		cert_len = WPA_GET_BE24(pos);
+		pos += 3;
+
+		if ((size_t) (end - pos) < cert_len) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Unexpected certificate "
+				   "length (len=%lu left=%lu)",
+				   (unsigned long) cert_len,
+				   (unsigned long) (end - pos));
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_DECODE_ERROR);
+			x509_certificate_chain_free(chain);
+			return -1;
+		}
+
+		wpa_printf(MSG_DEBUG, "TLSv1: Certificate %lu (len %lu)",
+			   (unsigned long) idx, (unsigned long) cert_len);
+
+		if (idx == 0) {
+			crypto_public_key_free(conn->client_rsa_key);
+			if (tls_parse_cert(pos, cert_len,
+					   &conn->client_rsa_key)) {
+				wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse "
+					   "the certificate");
+				tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+						   TLS_ALERT_BAD_CERTIFICATE);
+				x509_certificate_chain_free(chain);
+				return -1;
+			}
+		}
+
+		cert = x509_certificate_parse(pos, cert_len);
+		if (cert == NULL) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse "
+				   "the certificate");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_BAD_CERTIFICATE);
+			x509_certificate_chain_free(chain);
+			return -1;
+		}
+
+		if (last == NULL)
+			chain = cert;
+		else
+			last->next = cert;
+		last = cert;
+
+		idx++;
+		pos += cert_len;
+	}
+
+	if (x509_certificate_chain_validate(conn->cred->trusted_certs, chain,
+					    &reason) < 0) {
+		int tls_reason;
+		wpa_printf(MSG_DEBUG, "TLSv1: Server certificate chain "
+			   "validation failed (reason=%d)", reason);
+		switch (reason) {
+		case X509_VALIDATE_BAD_CERTIFICATE:
+			tls_reason = TLS_ALERT_BAD_CERTIFICATE;
+			break;
+		case X509_VALIDATE_UNSUPPORTED_CERTIFICATE:
+			tls_reason = TLS_ALERT_UNSUPPORTED_CERTIFICATE;
+			break;
+		case X509_VALIDATE_CERTIFICATE_REVOKED:
+			tls_reason = TLS_ALERT_CERTIFICATE_REVOKED;
+			break;
+		case X509_VALIDATE_CERTIFICATE_EXPIRED:
+			tls_reason = TLS_ALERT_CERTIFICATE_EXPIRED;
+			break;
+		case X509_VALIDATE_CERTIFICATE_UNKNOWN:
+			tls_reason = TLS_ALERT_CERTIFICATE_UNKNOWN;
+			break;
+		case X509_VALIDATE_UNKNOWN_CA:
+			tls_reason = TLS_ALERT_UNKNOWN_CA;
+			break;
+		default:
+			tls_reason = TLS_ALERT_BAD_CERTIFICATE;
+			break;
+		}
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, tls_reason);
+		x509_certificate_chain_free(chain);
+		return -1;
+	}
+
+	x509_certificate_chain_free(chain);
+
+	*in_len = end - in_data;
+
+	conn->state = CLIENT_KEY_EXCHANGE;
+
+	return 0;
+}
+
+
+static int tls_process_client_key_exchange_rsa(
+	struct tlsv1_server *conn, const u8 *pos, const u8 *end)
+{
+	u8 *out;
+	size_t outlen, outbuflen;
+	u16 encr_len;
+	int res;
+	int use_random = 0;
+
+	if (end - pos < 2) {
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	encr_len = WPA_GET_BE16(pos);
+	pos += 2;
+
+	outbuflen = outlen = end - pos;
+	out = os_malloc(outlen >= TLS_PRE_MASTER_SECRET_LEN ?
+			outlen : TLS_PRE_MASTER_SECRET_LEN);
+	if (out == NULL) {
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	/*
+	 * struct {
+	 *   ProtocolVersion client_version;
+	 *   opaque random[46];
+	 * } PreMasterSecret;
+	 *
+	 * struct {
+	 *   public-key-encrypted PreMasterSecret pre_master_secret;
+	 * } EncryptedPreMasterSecret;
+	 */
+
+	/*
+	 * Note: To avoid Bleichenbacher attack, we do not report decryption or
+	 * parsing errors from EncryptedPreMasterSecret processing to the
+	 * client. Instead, a random pre-master secret is used to force the
+	 * handshake to fail.
+	 */
+
+	if (crypto_private_key_decrypt_pkcs1_v15(conn->cred->key,
+						 pos, end - pos,
+						 out, &outlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to decrypt "
+			   "PreMasterSecret (encr_len=%d outlen=%lu)",
+			   (int) (end - pos), (unsigned long) outlen);
+		use_random = 1;
+	}
+
+	if (outlen != TLS_PRE_MASTER_SECRET_LEN) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected PreMasterSecret "
+			   "length %lu", (unsigned long) outlen);
+		use_random = 1;
+	}
+
+	if (WPA_GET_BE16(out) != conn->client_version) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Client version in "
+			   "ClientKeyExchange does not match with version in "
+			   "ClientHello");
+		use_random = 1;
+	}
+
+	if (use_random) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Using random premaster secret "
+			   "to avoid revealing information about private key");
+		outlen = TLS_PRE_MASTER_SECRET_LEN;
+		if (os_get_random(out, outlen)) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Failed to get random "
+				   "data");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_INTERNAL_ERROR);
+			os_free(out);
+			return -1;
+		}
+	}
+
+	res = tlsv1_server_derive_keys(conn, out, outlen);
+
+	/* Clear the pre-master secret since it is not needed anymore */
+	os_memset(out, 0, outbuflen);
+	os_free(out);
+
+	if (res) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive keys");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static int tls_process_client_key_exchange_dh_anon(
+	struct tlsv1_server *conn, const u8 *pos, const u8 *end)
+{
+	const u8 *dh_yc;
+	u16 dh_yc_len;
+	u8 *shared;
+	size_t shared_len;
+	int res;
+
+	/*
+	 * struct {
+	 *   select (PublicValueEncoding) {
+	 *     case implicit: struct { };
+	 *     case explicit: opaque dh_Yc<1..2^16-1>;
+	 *   } dh_public;
+	 * } ClientDiffieHellmanPublic;
+	 */
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: ClientDiffieHellmanPublic",
+		    pos, end - pos);
+
+	if (end == pos) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Implicit public value encoding "
+			   "not supported");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	if (end - pos < 3) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Invalid client public value "
+			   "length");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	dh_yc_len = WPA_GET_BE16(pos);
+	dh_yc = pos + 2;
+
+	if (dh_yc + dh_yc_len > end) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Client public value overflow "
+			   "(length %d)", dh_yc_len);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "TLSv1: DH Yc (client's public value)",
+		    dh_yc, dh_yc_len);
+
+	if (conn->cred == NULL || conn->cred->dh_p == NULL ||
+	    conn->dh_secret == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No DH parameters available");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	shared_len = conn->cred->dh_p_len;
+	shared = os_malloc(shared_len);
+	if (shared == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Could not allocate memory for "
+			   "DH");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	/* shared = Yc^secret mod p */
+	if (crypto_mod_exp(dh_yc, dh_yc_len, conn->dh_secret,
+			   conn->dh_secret_len,
+			   conn->cred->dh_p, conn->cred->dh_p_len,
+			   shared, &shared_len)) {
+		os_free(shared);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "TLSv1: Shared secret from DH key exchange",
+			shared, shared_len);
+
+	os_memset(conn->dh_secret, 0, conn->dh_secret_len);
+	os_free(conn->dh_secret);
+	conn->dh_secret = NULL;
+
+	res = tlsv1_server_derive_keys(conn, shared, shared_len);
+
+	/* Clear the pre-master secret since it is not needed anymore */
+	os_memset(shared, 0, shared_len);
+	os_free(shared);
+
+	if (res) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive keys");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static int tls_process_client_key_exchange(struct tlsv1_server *conn, u8 ct,
+					   const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len;
+	u8 type;
+	tls_key_exchange keyx;
+	const struct tls_cipher_suite *suite;
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short ClientKeyExchange "
+			   "(Left=%lu)", (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	type = *pos++;
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Mismatch in ClientKeyExchange "
+			   "length (len=%lu != left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	end = pos + len;
+
+	if (type != TLS_HANDSHAKE_TYPE_CLIENT_KEY_EXCHANGE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected ClientKeyExchange)", type);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received ClientKeyExchange");
+
+	wpa_hexdump(MSG_DEBUG, "TLSv1: ClientKeyExchange", pos, len);
+
+	suite = tls_get_cipher_suite(conn->rl.cipher_suite);
+	if (suite == NULL)
+		keyx = TLS_KEY_X_NULL;
+	else
+		keyx = suite->key_exchange;
+
+	if (keyx == TLS_KEY_X_DH_anon &&
+	    tls_process_client_key_exchange_dh_anon(conn, pos, end) < 0)
+		return -1;
+
+	if (keyx != TLS_KEY_X_DH_anon &&
+	    tls_process_client_key_exchange_rsa(conn, pos, end) < 0)
+		return -1;
+
+	*in_len = end - in_data;
+
+	conn->state = CERTIFICATE_VERIFY;
+
+	return 0;
+}
+
+
+static int tls_process_certificate_verify(struct tlsv1_server *conn, u8 ct,
+					  const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len;
+	u8 type;
+	size_t hlen, buflen;
+	u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN], *hpos, *buf;
+	enum { SIGN_ALG_RSA, SIGN_ALG_DSA } alg = SIGN_ALG_RSA;
+	u16 slen;
+
+	if (ct == TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC) {
+		if (conn->verify_peer) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Client did not include "
+				   "CertificateVerify");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_UNEXPECTED_MESSAGE);
+			return -1;
+		}
+
+		return tls_process_change_cipher_spec(conn, ct, in_data,
+						      in_len);
+	}
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; "
+			   "received content type 0x%x", ct);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short CertificateVerify "
+			   "message (len=%lu)", (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	type = *pos++;
+	len = WPA_GET_BE24(pos);
+	pos += 3;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected CertificateVerify "
+			   "message length (len=%lu != left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	end = pos + len;
+
+	if (type != TLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
+			   "message %d (expected CertificateVerify)", type);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received CertificateVerify");
+
+	/*
+	 * struct {
+	 *   Signature signature;
+	 * } CertificateVerify;
+	 */
+
+	hpos = hash;
+
+	if (alg == SIGN_ALG_RSA) {
+		hlen = MD5_MAC_LEN;
+		if (conn->verify.md5_cert == NULL ||
+		    crypto_hash_finish(conn->verify.md5_cert, hpos, &hlen) < 0)
+		{
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_INTERNAL_ERROR);
+			conn->verify.md5_cert = NULL;
+			crypto_hash_finish(conn->verify.sha1_cert, NULL, NULL);
+			conn->verify.sha1_cert = NULL;
+			return -1;
+		}
+		hpos += MD5_MAC_LEN;
+	} else
+		crypto_hash_finish(conn->verify.md5_cert, NULL, NULL);
+
+	conn->verify.md5_cert = NULL;
+	hlen = SHA1_MAC_LEN;
+	if (conn->verify.sha1_cert == NULL ||
+	    crypto_hash_finish(conn->verify.sha1_cert, hpos, &hlen) < 0) {
+		conn->verify.sha1_cert = NULL;
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	conn->verify.sha1_cert = NULL;
+
+	if (alg == SIGN_ALG_RSA)
+		hlen += MD5_MAC_LEN;
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: CertificateVerify hash", hash, hlen);
+
+	if (end - pos < 2) {
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+	slen = WPA_GET_BE16(pos);
+	pos += 2;
+	if (end - pos < slen) {
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: Signature", pos, end - pos);
+	if (conn->client_rsa_key == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No client public key to verify "
+			   "signature");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	buflen = end - pos;
+	buf = os_malloc(end - pos);
+	if (crypto_public_key_decrypt_pkcs1(conn->client_rsa_key,
+					    pos, end - pos, buf, &buflen) < 0)
+	{
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to decrypt signature");
+		os_free(buf);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECRYPT_ERROR);
+		return -1;
+	}
+
+	wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: Decrypted Signature",
+			buf, buflen);
+
+	if (buflen != hlen || os_memcmp(buf, hash, buflen) != 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Invalid Signature in "
+			   "CertificateVerify - did not match with calculated "
+			   "hash");
+		os_free(buf);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECRYPT_ERROR);
+		return -1;
+	}
+
+	os_free(buf);
+
+	*in_len = end - in_data;
+
+	conn->state = CHANGE_CIPHER_SPEC;
+
+	return 0;
+}
+
+
+static int tls_process_change_cipher_spec(struct tlsv1_server *conn,
+					  u8 ct, const u8 *in_data,
+					  size_t *in_len)
+{
+	const u8 *pos;
+	size_t left;
+
+	if (ct != TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected ChangeCipherSpec; "
+			   "received content type 0x%x", ct);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 1) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short ChangeCipherSpec");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	if (*pos != TLS_CHANGE_CIPHER_SPEC) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected ChangeCipherSpec; "
+			   "received data 0x%x", *pos);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received ChangeCipherSpec");
+	if (tlsv1_record_change_read_cipher(&conn->rl) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to change read cipher "
+			   "for record layer");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	*in_len = pos + 1 - in_data;
+
+	conn->state = CLIENT_FINISHED;
+
+	return 0;
+}
+
+
+static int tls_process_client_finished(struct tlsv1_server *conn, u8 ct,
+				       const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len, hlen;
+	u8 verify_data[TLS_VERIFY_DATA_LEN];
+	u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN];
+
+	if (ct != TLS_CONTENT_TYPE_HANDSHAKE) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Finished; "
+			   "received content type 0x%x", ct);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	pos = in_data;
+	left = *in_len;
+
+	if (left < 4) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short record (left=%lu) for "
+			   "Finished",
+			   (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	if (pos[0] != TLS_HANDSHAKE_TYPE_FINISHED) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Expected Finished; received "
+			   "type 0x%x", pos[0]);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	len = WPA_GET_BE24(pos + 1);
+
+	pos += 4;
+	left -= 4;
+
+	if (len > left) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Too short buffer for Finished "
+			   "(len=%lu > left=%lu)",
+			   (unsigned long) len, (unsigned long) left);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+	end = pos + len;
+	if (len != TLS_VERIFY_DATA_LEN) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected verify_data length "
+			   "in Finished: %lu (expected %d)",
+			   (unsigned long) len, TLS_VERIFY_DATA_LEN);
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: verify_data in Finished",
+		    pos, TLS_VERIFY_DATA_LEN);
+
+	hlen = MD5_MAC_LEN;
+	if (conn->verify.md5_client == NULL ||
+	    crypto_hash_finish(conn->verify.md5_client, hash, &hlen) < 0) {
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		conn->verify.md5_client = NULL;
+		crypto_hash_finish(conn->verify.sha1_client, NULL, NULL);
+		conn->verify.sha1_client = NULL;
+		return -1;
+	}
+	conn->verify.md5_client = NULL;
+	hlen = SHA1_MAC_LEN;
+	if (conn->verify.sha1_client == NULL ||
+	    crypto_hash_finish(conn->verify.sha1_client, hash + MD5_MAC_LEN,
+			       &hlen) < 0) {
+		conn->verify.sha1_client = NULL;
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	conn->verify.sha1_client = NULL;
+
+	if (tls_prf(conn->master_secret, TLS_MASTER_SECRET_LEN,
+		    "client finished", hash, MD5_MAC_LEN + SHA1_MAC_LEN,
+		    verify_data, TLS_VERIFY_DATA_LEN)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to derive verify_data");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_DECRYPT_ERROR);
+		return -1;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "TLSv1: verify_data (client)",
+			verify_data, TLS_VERIFY_DATA_LEN);
+
+	if (os_memcmp(pos, verify_data, TLS_VERIFY_DATA_LEN) != 0) {
+		wpa_printf(MSG_INFO, "TLSv1: Mismatch in verify_data");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received Finished");
+
+	*in_len = end - in_data;
+
+	if (conn->use_session_ticket) {
+		/* Abbreviated handshake using session ticket; RFC 4507 */
+		wpa_printf(MSG_DEBUG, "TLSv1: Abbreviated handshake completed "
+			   "successfully");
+		conn->state = ESTABLISHED;
+	} else {
+		/* Full handshake */
+		conn->state = SERVER_CHANGE_CIPHER_SPEC;
+	}
+
+	return 0;
+}
+
+
+int tlsv1_server_process_handshake(struct tlsv1_server *conn, u8 ct,
+				   const u8 *buf, size_t *len)
+{
+	if (ct == TLS_CONTENT_TYPE_ALERT) {
+		if (*len < 2) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Alert underflow");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_DECODE_ERROR);
+			return -1;
+		}
+		wpa_printf(MSG_DEBUG, "TLSv1: Received alert %d:%d",
+			   buf[0], buf[1]);
+		*len = 2;
+		conn->state = FAILED;
+		return -1;
+	}
+
+	switch (conn->state) {
+	case CLIENT_HELLO:
+		if (tls_process_client_hello(conn, ct, buf, len))
+			return -1;
+		break;
+	case CLIENT_CERTIFICATE:
+		if (tls_process_certificate(conn, ct, buf, len))
+			return -1;
+		break;
+	case CLIENT_KEY_EXCHANGE:
+		if (tls_process_client_key_exchange(conn, ct, buf, len))
+			return -1;
+		break;
+	case CERTIFICATE_VERIFY:
+		if (tls_process_certificate_verify(conn, ct, buf, len))
+			return -1;
+		break;
+	case CHANGE_CIPHER_SPEC:
+		if (tls_process_change_cipher_spec(conn, ct, buf, len))
+			return -1;
+		break;
+	case CLIENT_FINISHED:
+		if (tls_process_client_finished(conn, ct, buf, len))
+			return -1;
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected state %d "
+			   "while processing received message",
+			   conn->state);
+		return -1;
+	}
+
+	if (ct == TLS_CONTENT_TYPE_HANDSHAKE)
+		tls_verify_hash_add(&conn->verify, buf, *len);
+
+	return 0;
+}
diff --git a/src/tls/tlsv1_server_write.c b/src/tls/tlsv1_server_write.c
new file mode 100644
index 0000000..e89e52e
--- /dev/null
+++ b/src/tls/tlsv1_server_write.c
@@ -0,0 +1,791 @@
+/*
+ * TLSv1 server - write handshake message
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/md5.h"
+#include "crypto/sha1.h"
+#include "crypto/tls.h"
+#include "crypto/random.h"
+#include "x509v3.h"
+#include "tlsv1_common.h"
+#include "tlsv1_record.h"
+#include "tlsv1_server.h"
+#include "tlsv1_server_i.h"
+
+
+static size_t tls_server_cert_chain_der_len(struct tlsv1_server *conn)
+{
+	size_t len = 0;
+	struct x509_certificate *cert;
+
+	cert = conn->cred->cert;
+	while (cert) {
+		len += 3 + cert->cert_len;
+		if (x509_certificate_self_signed(cert))
+			break;
+		cert = x509_certificate_get_subject(conn->cred->trusted_certs,
+						    &cert->issuer);
+	}
+
+	return len;
+}
+
+
+static int tls_write_server_hello(struct tlsv1_server *conn,
+				  u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length;
+	struct os_time now;
+	size_t rlen;
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send ServerHello");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+
+	os_get_time(&now);
+	WPA_PUT_BE32(conn->server_random, now.sec);
+	if (random_get_bytes(conn->server_random + 4, TLS_RANDOM_LEN - 4)) {
+		wpa_printf(MSG_ERROR, "TLSv1: Could not generate "
+			   "server_random");
+		return -1;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: server_random",
+		    conn->server_random, TLS_RANDOM_LEN);
+
+	conn->session_id_len = TLS_SESSION_ID_MAX_LEN;
+	if (random_get_bytes(conn->session_id, conn->session_id_len)) {
+		wpa_printf(MSG_ERROR, "TLSv1: Could not generate "
+			   "session_id");
+		return -1;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: session_id",
+		    conn->session_id, conn->session_id_len);
+
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_SERVER_HELLO;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	/* body - ServerHello */
+	/* ProtocolVersion server_version */
+	WPA_PUT_BE16(pos, TLS_VERSION);
+	pos += 2;
+	/* Random random: uint32 gmt_unix_time, opaque random_bytes */
+	os_memcpy(pos, conn->server_random, TLS_RANDOM_LEN);
+	pos += TLS_RANDOM_LEN;
+	/* SessionID session_id */
+	*pos++ = conn->session_id_len;
+	os_memcpy(pos, conn->session_id, conn->session_id_len);
+	pos += conn->session_id_len;
+	/* CipherSuite cipher_suite */
+	WPA_PUT_BE16(pos, conn->cipher_suite);
+	pos += 2;
+	/* CompressionMethod compression_method */
+	*pos++ = TLS_COMPRESSION_NULL;
+
+	if (conn->session_ticket && conn->session_ticket_cb) {
+		int res = conn->session_ticket_cb(
+			conn->session_ticket_cb_ctx,
+			conn->session_ticket, conn->session_ticket_len,
+			conn->client_random, conn->server_random,
+			conn->master_secret);
+		if (res < 0) {
+			wpa_printf(MSG_DEBUG, "TLSv1: SessionTicket callback "
+				   "indicated failure");
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_HANDSHAKE_FAILURE);
+			return -1;
+		}
+		conn->use_session_ticket = res;
+
+		if (conn->use_session_ticket) {
+			if (tlsv1_server_derive_keys(conn, NULL, 0) < 0) {
+				wpa_printf(MSG_DEBUG, "TLSv1: Failed to "
+					   "derive keys");
+				tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+						   TLS_ALERT_INTERNAL_ERROR);
+				return -1;
+			}
+		}
+
+		/*
+		 * RFC 4507 specifies that server would include an empty
+		 * SessionTicket extension in ServerHello and a
+		 * NewSessionTicket message after the ServerHello. However,
+		 * EAP-FAST (RFC 4851), i.e., the only user of SessionTicket
+		 * extension at the moment, does not use such extensions.
+		 *
+		 * TODO: Add support for configuring RFC 4507 behavior and make
+		 * EAP-FAST disable it.
+		 */
+	}
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create TLS record");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	pos = rhdr + rlen;
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static int tls_write_server_certificate(struct tlsv1_server *conn,
+					u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length, *cert_start;
+	size_t rlen;
+	struct x509_certificate *cert;
+	const struct tls_cipher_suite *suite;
+
+	suite = tls_get_cipher_suite(conn->rl.cipher_suite);
+	if (suite && suite->key_exchange == TLS_KEY_X_DH_anon) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Do not send Certificate when "
+			   "using anonymous DH");
+		return 0;
+	}
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send Certificate");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_CERTIFICATE;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	/* body - Certificate */
+	/* uint24 length (to be filled) */
+	cert_start = pos;
+	pos += 3;
+	cert = conn->cred->cert;
+	while (cert) {
+		if (pos + 3 + cert->cert_len > end) {
+			wpa_printf(MSG_DEBUG, "TLSv1: Not enough buffer space "
+				   "for Certificate (cert_len=%lu left=%lu)",
+				   (unsigned long) cert->cert_len,
+				   (unsigned long) (end - pos));
+			tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+					   TLS_ALERT_INTERNAL_ERROR);
+			return -1;
+		}
+		WPA_PUT_BE24(pos, cert->cert_len);
+		pos += 3;
+		os_memcpy(pos, cert->cert_start, cert->cert_len);
+		pos += cert->cert_len;
+
+		if (x509_certificate_self_signed(cert))
+			break;
+		cert = x509_certificate_get_subject(conn->cred->trusted_certs,
+						    &cert->issuer);
+	}
+	if (cert == conn->cred->cert || cert == NULL) {
+		/*
+		 * Server was not configured with all the needed certificates
+		 * to form a full certificate chain. The client may fail to
+		 * validate the chain unless it is configured with all the
+		 * missing CA certificates.
+		 */
+		wpa_printf(MSG_DEBUG, "TLSv1: Full server certificate chain "
+			   "not configured - validation may fail");
+	}
+	WPA_PUT_BE24(cert_start, pos - cert_start - 3);
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to generate a record");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	pos = rhdr + rlen;
+
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static int tls_write_server_key_exchange(struct tlsv1_server *conn,
+					 u8 **msgpos, u8 *end)
+{
+	tls_key_exchange keyx;
+	const struct tls_cipher_suite *suite;
+	u8 *pos, *rhdr, *hs_start, *hs_length;
+	size_t rlen;
+	u8 *dh_ys;
+	size_t dh_ys_len;
+
+	suite = tls_get_cipher_suite(conn->rl.cipher_suite);
+	if (suite == NULL)
+		keyx = TLS_KEY_X_NULL;
+	else
+		keyx = suite->key_exchange;
+
+	if (!tls_server_key_exchange_allowed(conn->rl.cipher_suite)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No ServerKeyExchange needed");
+		return 0;
+	}
+
+	if (keyx != TLS_KEY_X_DH_anon) {
+		/* TODO? */
+		wpa_printf(MSG_DEBUG, "TLSv1: ServerKeyExchange not yet "
+			   "supported with key exchange type %d", keyx);
+		return -1;
+	}
+
+	if (conn->cred == NULL || conn->cred->dh_p == NULL ||
+	    conn->cred->dh_g == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No DH parameters available for "
+			   "ServerKeyExhcange");
+		return -1;
+	}
+
+	os_free(conn->dh_secret);
+	conn->dh_secret_len = conn->cred->dh_p_len;
+	conn->dh_secret = os_malloc(conn->dh_secret_len);
+	if (conn->dh_secret == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to allocate "
+			   "memory for secret (Diffie-Hellman)");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	if (random_get_bytes(conn->dh_secret, conn->dh_secret_len)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to get random "
+			   "data for Diffie-Hellman");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		os_free(conn->dh_secret);
+		conn->dh_secret = NULL;
+		return -1;
+	}
+
+	if (os_memcmp(conn->dh_secret, conn->cred->dh_p, conn->dh_secret_len) >
+	    0)
+		conn->dh_secret[0] = 0; /* make sure secret < p */
+
+	pos = conn->dh_secret;
+	while (pos + 1 < conn->dh_secret + conn->dh_secret_len && *pos == 0)
+		pos++;
+	if (pos != conn->dh_secret) {
+		os_memmove(conn->dh_secret, pos,
+			   conn->dh_secret_len - (pos - conn->dh_secret));
+		conn->dh_secret_len -= pos - conn->dh_secret;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "TLSv1: DH server's secret value",
+			conn->dh_secret, conn->dh_secret_len);
+
+	/* Ys = g^secret mod p */
+	dh_ys_len = conn->cred->dh_p_len;
+	dh_ys = os_malloc(dh_ys_len);
+	if (dh_ys == NULL) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to allocate memory for "
+			   "Diffie-Hellman");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	if (crypto_mod_exp(conn->cred->dh_g, conn->cred->dh_g_len,
+			   conn->dh_secret, conn->dh_secret_len,
+			   conn->cred->dh_p, conn->cred->dh_p_len,
+			   dh_ys, &dh_ys_len)) {
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		os_free(dh_ys);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "TLSv1: DH Ys (server's public value)",
+		    dh_ys, dh_ys_len);
+
+	/*
+	 * struct {
+	 *    select (KeyExchangeAlgorithm) {
+	 *       case diffie_hellman:
+	 *          ServerDHParams params;
+	 *          Signature signed_params;
+	 *       case rsa:
+	 *          ServerRSAParams params;
+	 *          Signature signed_params;
+	 *    };
+	 * } ServerKeyExchange;
+	 *
+	 * struct {
+	 *    opaque dh_p<1..2^16-1>;
+	 *    opaque dh_g<1..2^16-1>;
+	 *    opaque dh_Ys<1..2^16-1>;
+	 * } ServerDHParams;
+	 */
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send ServerKeyExchange");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_SERVER_KEY_EXCHANGE;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+
+	/* body - ServerDHParams */
+	/* dh_p */
+	if (pos + 2 + conn->cred->dh_p_len > end) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Not enough buffer space for "
+			   "dh_p");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		os_free(dh_ys);
+		return -1;
+	}
+	WPA_PUT_BE16(pos, conn->cred->dh_p_len);
+	pos += 2;
+	os_memcpy(pos, conn->cred->dh_p, conn->cred->dh_p_len);
+	pos += conn->cred->dh_p_len;
+
+	/* dh_g */
+	if (pos + 2 + conn->cred->dh_g_len > end) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Not enough buffer space for "
+			   "dh_g");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		os_free(dh_ys);
+		return -1;
+	}
+	WPA_PUT_BE16(pos, conn->cred->dh_g_len);
+	pos += 2;
+	os_memcpy(pos, conn->cred->dh_g, conn->cred->dh_g_len);
+	pos += conn->cred->dh_g_len;
+
+	/* dh_Ys */
+	if (pos + 2 + dh_ys_len > end) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Not enough buffer space for "
+			   "dh_Ys");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		os_free(dh_ys);
+		return -1;
+	}
+	WPA_PUT_BE16(pos, dh_ys_len);
+	pos += 2;
+	os_memcpy(pos, dh_ys, dh_ys_len);
+	pos += dh_ys_len;
+	os_free(dh_ys);
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to generate a record");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	pos = rhdr + rlen;
+
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static int tls_write_server_certificate_request(struct tlsv1_server *conn,
+						u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length;
+	size_t rlen;
+
+	if (!conn->verify_peer) {
+		wpa_printf(MSG_DEBUG, "TLSv1: No CertificateRequest needed");
+		return 0;
+	}
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send CertificateRequest");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	/* body - CertificateRequest */
+
+	/*
+	 * enum {
+	 *   rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
+	 *   (255)
+	 * } ClientCertificateType;
+	 * ClientCertificateType certificate_types<1..2^8-1>
+	 */
+	*pos++ = 1;
+	*pos++ = 1; /* rsa_sign */
+
+	/*
+	 * opaque DistinguishedName<1..2^16-1>
+	 * DistinguishedName certificate_authorities<3..2^16-1>
+	 */
+	/* TODO: add support for listing DNs for trusted CAs */
+	WPA_PUT_BE16(pos, 0);
+	pos += 2;
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to generate a record");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	pos = rhdr + rlen;
+
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static int tls_write_server_hello_done(struct tlsv1_server *conn,
+				       u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length;
+	size_t rlen;
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send ServerHelloDone");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_SERVER_HELLO_DONE;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	/* body - ServerHelloDone (empty) */
+
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to generate a record");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	pos = rhdr + rlen;
+
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static int tls_write_server_change_cipher_spec(struct tlsv1_server *conn,
+					       u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr;
+	size_t rlen;
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send ChangeCipherSpec");
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+	*pos = TLS_CHANGE_CIPHER_SPEC;
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC,
+			      rhdr, end - rhdr, 1, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create a record");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	if (tlsv1_record_change_write_cipher(&conn->rl) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to set write cipher for "
+			   "record layer");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	*msgpos = rhdr + rlen;
+
+	return 0;
+}
+
+
+static int tls_write_server_finished(struct tlsv1_server *conn,
+				     u8 **msgpos, u8 *end)
+{
+	u8 *pos, *rhdr, *hs_start, *hs_length;
+	size_t rlen, hlen;
+	u8 verify_data[TLS_VERIFY_DATA_LEN];
+	u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN];
+
+	pos = *msgpos;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send Finished");
+
+	/* Encrypted Handshake Message: Finished */
+
+	hlen = MD5_MAC_LEN;
+	if (conn->verify.md5_server == NULL ||
+	    crypto_hash_finish(conn->verify.md5_server, hash, &hlen) < 0) {
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		conn->verify.md5_server = NULL;
+		crypto_hash_finish(conn->verify.sha1_server, NULL, NULL);
+		conn->verify.sha1_server = NULL;
+		return -1;
+	}
+	conn->verify.md5_server = NULL;
+	hlen = SHA1_MAC_LEN;
+	if (conn->verify.sha1_server == NULL ||
+	    crypto_hash_finish(conn->verify.sha1_server, hash + MD5_MAC_LEN,
+			       &hlen) < 0) {
+		conn->verify.sha1_server = NULL;
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	conn->verify.sha1_server = NULL;
+
+	if (tls_prf(conn->master_secret, TLS_MASTER_SECRET_LEN,
+		    "server finished", hash, MD5_MAC_LEN + SHA1_MAC_LEN,
+		    verify_data, TLS_VERIFY_DATA_LEN)) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to generate verify_data");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "TLSv1: verify_data (server)",
+			verify_data, TLS_VERIFY_DATA_LEN);
+
+	rhdr = pos;
+	pos += TLS_RECORD_HEADER_LEN;
+	/* Handshake */
+	hs_start = pos;
+	/* HandshakeType msg_type */
+	*pos++ = TLS_HANDSHAKE_TYPE_FINISHED;
+	/* uint24 length (to be filled) */
+	hs_length = pos;
+	pos += 3;
+	os_memcpy(pos, verify_data, TLS_VERIFY_DATA_LEN);
+	pos += TLS_VERIFY_DATA_LEN;
+	WPA_PUT_BE24(hs_length, pos - hs_length - 3);
+	tls_verify_hash_add(&conn->verify, hs_start, pos - hs_start);
+
+	if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE,
+			      rhdr, end - rhdr, pos - hs_start, &rlen) < 0) {
+		wpa_printf(MSG_DEBUG, "TLSv1: Failed to create a record");
+		tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL,
+				   TLS_ALERT_INTERNAL_ERROR);
+		return -1;
+	}
+
+	pos = rhdr + rlen;
+
+	*msgpos = pos;
+
+	return 0;
+}
+
+
+static u8 * tls_send_server_hello(struct tlsv1_server *conn, size_t *out_len)
+{
+	u8 *msg, *end, *pos;
+	size_t msglen;
+
+	*out_len = 0;
+
+	msglen = 1000 + tls_server_cert_chain_der_len(conn);
+
+	msg = os_malloc(msglen);
+	if (msg == NULL)
+		return NULL;
+
+	pos = msg;
+	end = msg + msglen;
+
+	if (tls_write_server_hello(conn, &pos, end) < 0) {
+		os_free(msg);
+		return NULL;
+	}
+
+	if (conn->use_session_ticket) {
+		/* Abbreviated handshake using session ticket; RFC 4507 */
+		if (tls_write_server_change_cipher_spec(conn, &pos, end) < 0 ||
+		    tls_write_server_finished(conn, &pos, end) < 0) {
+			os_free(msg);
+			return NULL;
+		}
+
+		*out_len = pos - msg;
+
+		conn->state = CHANGE_CIPHER_SPEC;
+
+		return msg;
+	}
+
+	/* Full handshake */
+	if (tls_write_server_certificate(conn, &pos, end) < 0 ||
+	    tls_write_server_key_exchange(conn, &pos, end) < 0 ||
+	    tls_write_server_certificate_request(conn, &pos, end) < 0 ||
+	    tls_write_server_hello_done(conn, &pos, end) < 0) {
+		os_free(msg);
+		return NULL;
+	}
+
+	*out_len = pos - msg;
+
+	conn->state = CLIENT_CERTIFICATE;
+
+	return msg;
+}
+
+
+static u8 * tls_send_change_cipher_spec(struct tlsv1_server *conn,
+					size_t *out_len)
+{
+	u8 *msg, *end, *pos;
+
+	*out_len = 0;
+
+	msg = os_malloc(1000);
+	if (msg == NULL)
+		return NULL;
+
+	pos = msg;
+	end = msg + 1000;
+
+	if (tls_write_server_change_cipher_spec(conn, &pos, end) < 0 ||
+	    tls_write_server_finished(conn, &pos, end) < 0) {
+		os_free(msg);
+		return NULL;
+	}
+
+	*out_len = pos - msg;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Handshake completed successfully");
+	conn->state = ESTABLISHED;
+
+	return msg;
+}
+
+
+u8 * tlsv1_server_handshake_write(struct tlsv1_server *conn, size_t *out_len)
+{
+	switch (conn->state) {
+	case SERVER_HELLO:
+		return tls_send_server_hello(conn, out_len);
+	case SERVER_CHANGE_CIPHER_SPEC:
+		return tls_send_change_cipher_spec(conn, out_len);
+	default:
+		if (conn->state == ESTABLISHED && conn->use_session_ticket) {
+			/* Abbreviated handshake was already completed. */
+			return NULL;
+		}
+		wpa_printf(MSG_DEBUG, "TLSv1: Unexpected state %d while "
+			   "generating reply", conn->state);
+		return NULL;
+	}
+}
+
+
+u8 * tlsv1_server_send_alert(struct tlsv1_server *conn, u8 level,
+			     u8 description, size_t *out_len)
+{
+	u8 *alert, *pos, *length;
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Send Alert(%d:%d)", level, description);
+	*out_len = 0;
+
+	alert = os_malloc(10);
+	if (alert == NULL)
+		return NULL;
+
+	pos = alert;
+
+	/* TLSPlaintext */
+	/* ContentType type */
+	*pos++ = TLS_CONTENT_TYPE_ALERT;
+	/* ProtocolVersion version */
+	WPA_PUT_BE16(pos, TLS_VERSION);
+	pos += 2;
+	/* uint16 length (to be filled) */
+	length = pos;
+	pos += 2;
+	/* opaque fragment[TLSPlaintext.length] */
+
+	/* Alert */
+	/* AlertLevel level */
+	*pos++ = level;
+	/* AlertDescription description */
+	*pos++ = description;
+
+	WPA_PUT_BE16(length, pos - length - 2);
+	*out_len = pos - alert;
+
+	return alert;
+}
diff --git a/src/tls/x509v3.c b/src/tls/x509v3.c
new file mode 100644
index 0000000..bc93df6
--- /dev/null
+++ b/src/tls/x509v3.c
@@ -0,0 +1,1985 @@
+/*
+ * X.509v3 certificate parsing and processing (RFC 3280 profile)
+ * Copyright (c) 2006-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/crypto.h"
+#include "asn1.h"
+#include "x509v3.h"
+
+
+static void x509_free_name(struct x509_name *name)
+{
+	size_t i;
+
+	for (i = 0; i < name->num_attr; i++) {
+		os_free(name->attr[i].value);
+		name->attr[i].value = NULL;
+		name->attr[i].type = X509_NAME_ATTR_NOT_USED;
+	}
+	name->num_attr = 0;
+	os_free(name->email);
+	name->email = NULL;
+
+	os_free(name->alt_email);
+	os_free(name->dns);
+	os_free(name->uri);
+	os_free(name->ip);
+	name->alt_email = name->dns = name->uri = NULL;
+	name->ip = NULL;
+	name->ip_len = 0;
+	os_memset(&name->rid, 0, sizeof(name->rid));
+}
+
+
+/**
+ * x509_certificate_free - Free an X.509 certificate
+ * @cert: Certificate to be freed
+ */
+void x509_certificate_free(struct x509_certificate *cert)
+{
+	if (cert == NULL)
+		return;
+	if (cert->next) {
+		wpa_printf(MSG_DEBUG, "X509: x509_certificate_free: cer=%p "
+			   "was still on a list (next=%p)\n",
+			   cert, cert->next);
+	}
+	x509_free_name(&cert->issuer);
+	x509_free_name(&cert->subject);
+	os_free(cert->public_key);
+	os_free(cert->sign_value);
+	os_free(cert);
+}
+
+
+/**
+ * x509_certificate_free - Free an X.509 certificate chain
+ * @cert: Pointer to the first certificate in the chain
+ */
+void x509_certificate_chain_free(struct x509_certificate *cert)
+{
+	struct x509_certificate *next;
+
+	while (cert) {
+		next = cert->next;
+		cert->next = NULL;
+		x509_certificate_free(cert);
+		cert = next;
+	}
+}
+
+
+static int x509_whitespace(char c)
+{
+	return c == ' ' || c == '\t';
+}
+
+
+static void x509_str_strip_whitespace(char *a)
+{
+	char *ipos, *opos;
+	int remove_whitespace = 1;
+
+	ipos = opos = a;
+
+	while (*ipos) {
+		if (remove_whitespace && x509_whitespace(*ipos))
+			ipos++;
+		else {
+			remove_whitespace = x509_whitespace(*ipos);
+			*opos++ = *ipos++;
+		}
+	}
+
+	*opos-- = '\0';
+	if (opos > a && x509_whitespace(*opos))
+		*opos = '\0';
+}
+
+
+static int x509_str_compare(const char *a, const char *b)
+{
+	char *aa, *bb;
+	int ret;
+
+	if (!a && b)
+		return -1;
+	if (a && !b)
+		return 1;
+	if (!a && !b)
+		return 0;
+
+	aa = os_strdup(a);
+	bb = os_strdup(b);
+
+	if (aa == NULL || bb == NULL) {
+		os_free(aa);
+		os_free(bb);
+		return os_strcasecmp(a, b);
+	}
+
+	x509_str_strip_whitespace(aa);
+	x509_str_strip_whitespace(bb);
+
+	ret = os_strcasecmp(aa, bb);
+
+	os_free(aa);
+	os_free(bb);
+
+	return ret;
+}
+
+
+/**
+ * x509_name_compare - Compare X.509 certificate names
+ * @a: Certificate name
+ * @b: Certificate name
+ * Returns: <0, 0, or >0 based on whether a is less than, equal to, or
+ * greater than b
+ */
+int x509_name_compare(struct x509_name *a, struct x509_name *b)
+{
+	int res;
+	size_t i;
+
+	if (!a && b)
+		return -1;
+	if (a && !b)
+		return 1;
+	if (!a && !b)
+		return 0;
+	if (a->num_attr < b->num_attr)
+		return -1;
+	if (a->num_attr > b->num_attr)
+		return 1;
+
+	for (i = 0; i < a->num_attr; i++) {
+		if (a->attr[i].type < b->attr[i].type)
+			return -1;
+		if (a->attr[i].type > b->attr[i].type)
+			return -1;
+		res = x509_str_compare(a->attr[i].value, b->attr[i].value);
+		if (res)
+			return res;
+	}
+	res = x509_str_compare(a->email, b->email);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+
+static int x509_parse_algorithm_identifier(
+	const u8 *buf, size_t len,
+	struct x509_algorithm_identifier *id, const u8 **next)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+
+	/*
+	 * AlgorithmIdentifier ::= SEQUENCE {
+	 *     algorithm            OBJECT IDENTIFIER,
+	 *     parameters           ANY DEFINED BY algorithm OPTIONAL
+	 * }
+	 */
+
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE "
+			   "(AlgorithmIdentifier) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	if (end > buf + len)
+		return -1;
+
+	*next = end;
+
+	if (asn1_get_oid(pos, end - pos, &id->oid, &pos))
+		return -1;
+
+	/* TODO: optional parameters */
+
+	return 0;
+}
+
+
+static int x509_parse_public_key(const u8 *buf, size_t len,
+				 struct x509_certificate *cert,
+				 const u8 **next)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+
+	/*
+	 * SubjectPublicKeyInfo ::= SEQUENCE {
+	 *     algorithm            AlgorithmIdentifier,
+	 *     subjectPublicKey     BIT STRING
+	 * }
+	 */
+
+	pos = buf;
+	end = buf + len;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE "
+			   "(SubjectPublicKeyInfo) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+
+	if (pos + hdr.length > end)
+		return -1;
+	end = pos + hdr.length;
+	*next = end;
+
+	if (x509_parse_algorithm_identifier(pos, end - pos,
+					    &cert->public_key_alg, &pos))
+		return -1;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_BITSTRING) {
+		wpa_printf(MSG_DEBUG, "X509: Expected BITSTRING "
+			   "(subjectPublicKey) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	if (hdr.length < 1)
+		return -1;
+	pos = hdr.payload;
+	if (*pos) {
+		wpa_printf(MSG_DEBUG, "X509: BITSTRING - %d unused bits",
+			   *pos);
+		/*
+		 * TODO: should this be rejected? X.509 certificates are
+		 * unlikely to use such a construction. Now we would end up
+		 * including the extra bits in the buffer which may also be
+		 * ok.
+		 */
+	}
+	os_free(cert->public_key);
+	cert->public_key = os_malloc(hdr.length - 1);
+	if (cert->public_key == NULL) {
+		wpa_printf(MSG_DEBUG, "X509: Failed to allocate memory for "
+			   "public key");
+		return -1;
+	}
+	os_memcpy(cert->public_key, pos + 1, hdr.length - 1);
+	cert->public_key_len = hdr.length - 1;
+	wpa_hexdump(MSG_MSGDUMP, "X509: subjectPublicKey",
+		    cert->public_key, cert->public_key_len);
+
+	return 0;
+}
+
+
+static int x509_parse_name(const u8 *buf, size_t len, struct x509_name *name,
+			   const u8 **next)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end, *set_pos, *set_end, *seq_pos, *seq_end;
+	struct asn1_oid oid;
+	char *val;
+
+	/*
+	 * Name ::= CHOICE { RDNSequence }
+	 * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+	 * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
+	 * AttributeTypeAndValue ::= SEQUENCE {
+	 *     type     AttributeType,
+	 *     value    AttributeValue
+	 * }
+	 * AttributeType ::= OBJECT IDENTIFIER
+	 * AttributeValue ::= ANY DEFINED BY AttributeType
+	 */
+
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE "
+			   "(Name / RDNSequencer) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+
+	if (pos + hdr.length > buf + len)
+		return -1;
+
+	end = *next = pos + hdr.length;
+
+	while (pos < end) {
+		enum x509_name_attr_type type;
+
+		if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_UNIVERSAL ||
+		    hdr.tag != ASN1_TAG_SET) {
+			wpa_printf(MSG_DEBUG, "X509: Expected SET "
+				   "(RelativeDistinguishedName) - found class "
+				   "%d tag 0x%x", hdr.class, hdr.tag);
+			x509_free_name(name);
+			return -1;
+		}
+
+		set_pos = hdr.payload;
+		pos = set_end = hdr.payload + hdr.length;
+
+		if (asn1_get_next(set_pos, set_end - set_pos, &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_UNIVERSAL ||
+		    hdr.tag != ASN1_TAG_SEQUENCE) {
+			wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE "
+				   "(AttributeTypeAndValue) - found class %d "
+				   "tag 0x%x", hdr.class, hdr.tag);
+			x509_free_name(name);
+			return -1;
+		}
+
+		seq_pos = hdr.payload;
+		seq_end = hdr.payload + hdr.length;
+
+		if (asn1_get_oid(seq_pos, seq_end - seq_pos, &oid, &seq_pos)) {
+			x509_free_name(name);
+			return -1;
+		}
+
+		if (asn1_get_next(seq_pos, seq_end - seq_pos, &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_UNIVERSAL) {
+			wpa_printf(MSG_DEBUG, "X509: Failed to parse "
+				   "AttributeValue");
+			x509_free_name(name);
+			return -1;
+		}
+
+		/* RFC 3280:
+		 * MUST: country, organization, organizational-unit,
+		 * distinguished name qualifier, state or province name,
+		 * common name, serial number.
+		 * SHOULD: locality, title, surname, given name, initials,
+		 * pseudonym, generation qualifier.
+		 * MUST: domainComponent (RFC 2247).
+		 */
+		type = X509_NAME_ATTR_NOT_USED;
+		if (oid.len == 4 &&
+		    oid.oid[0] == 2 && oid.oid[1] == 5 && oid.oid[2] == 4) {
+			/* id-at ::= 2.5.4 */
+			switch (oid.oid[3]) {
+			case 3:
+				/* commonName */
+				type = X509_NAME_ATTR_CN;
+				break;
+			case 6:
+				/*  countryName */
+				type = X509_NAME_ATTR_C;
+				break;
+			case 7:
+				/* localityName */
+				type = X509_NAME_ATTR_L;
+				break;
+			case 8:
+				/* stateOrProvinceName */
+				type = X509_NAME_ATTR_ST;
+				break;
+			case 10:
+				/* organizationName */
+				type = X509_NAME_ATTR_O;
+				break;
+			case 11:
+				/* organizationalUnitName */
+				type = X509_NAME_ATTR_OU;
+				break;
+			}
+		} else if (oid.len == 7 &&
+			   oid.oid[0] == 1 && oid.oid[1] == 2 &&
+			   oid.oid[2] == 840 && oid.oid[3] == 113549 &&
+			   oid.oid[4] == 1 && oid.oid[5] == 9 &&
+			   oid.oid[6] == 1) {
+			/* 1.2.840.113549.1.9.1 - e-mailAddress */
+			os_free(name->email);
+			name->email = os_malloc(hdr.length + 1);
+			if (name->email == NULL) {
+				x509_free_name(name);
+				return -1;
+			}
+			os_memcpy(name->email, hdr.payload, hdr.length);
+			name->email[hdr.length] = '\0';
+			continue;
+		} else if (oid.len == 7 &&
+			   oid.oid[0] == 0 && oid.oid[1] == 9 &&
+			   oid.oid[2] == 2342 && oid.oid[3] == 19200300 &&
+			   oid.oid[4] == 100 && oid.oid[5] == 1 &&
+			   oid.oid[6] == 25) {
+			/* 0.9.2342.19200300.100.1.25 - domainComponent */
+			type = X509_NAME_ATTR_DC;
+		}
+
+		if (type == X509_NAME_ATTR_NOT_USED) {
+			wpa_hexdump(MSG_DEBUG, "X509: Unrecognized OID",
+				    (u8 *) oid.oid,
+				    oid.len * sizeof(oid.oid[0]));
+			wpa_hexdump_ascii(MSG_MSGDUMP, "X509: Attribute Data",
+					  hdr.payload, hdr.length);
+			continue;
+		}
+
+		if (name->num_attr == X509_MAX_NAME_ATTRIBUTES) {
+			wpa_printf(MSG_INFO, "X509: Too many Name attributes");
+			x509_free_name(name);
+			return -1;
+		}
+
+		val = os_malloc(hdr.length + 1);
+		if (val == NULL) {
+			x509_free_name(name);
+			return -1;
+		}
+		os_memcpy(val, hdr.payload, hdr.length);
+		val[hdr.length] = '\0';
+		if (os_strlen(val) != hdr.length) {
+			wpa_printf(MSG_INFO, "X509: Reject certificate with "
+				   "embedded NUL byte in a string (%s[NUL])",
+				   val);
+			x509_free_name(name);
+			return -1;
+		}
+
+		name->attr[name->num_attr].type = type;
+		name->attr[name->num_attr].value = val;
+		name->num_attr++;
+	}
+
+	return 0;
+}
+
+
+static char * x509_name_attr_str(enum x509_name_attr_type type)
+{
+	switch (type) {
+	case X509_NAME_ATTR_NOT_USED:
+		return "[N/A]";
+	case X509_NAME_ATTR_DC:
+		return "DC";
+	case X509_NAME_ATTR_CN:
+		return "CN";
+	case X509_NAME_ATTR_C:
+		return "C";
+	case X509_NAME_ATTR_L:
+		return "L";
+	case X509_NAME_ATTR_ST:
+		return "ST";
+	case X509_NAME_ATTR_O:
+		return "O";
+	case X509_NAME_ATTR_OU:
+		return "OU";
+	}
+	return "?";
+}
+
+
+/**
+ * x509_name_string - Convert an X.509 certificate name into a string
+ * @name: Name to convert
+ * @buf: Buffer for the string
+ * @len: Maximum buffer length
+ */
+void x509_name_string(struct x509_name *name, char *buf, size_t len)
+{
+	char *pos, *end;
+	int ret;
+	size_t i;
+
+	if (len == 0)
+		return;
+
+	pos = buf;
+	end = buf + len;
+
+	for (i = 0; i < name->num_attr; i++) {
+		ret = os_snprintf(pos, end - pos, "%s=%s, ",
+				  x509_name_attr_str(name->attr[i].type),
+				  name->attr[i].value);
+		if (ret < 0 || ret >= end - pos)
+			goto done;
+		pos += ret;
+	}
+
+	if (pos > buf + 1 && pos[-1] == ' ' && pos[-2] == ',') {
+		pos--;
+		*pos = '\0';
+		pos--;
+		*pos = '\0';
+	}
+
+	if (name->email) {
+		ret = os_snprintf(pos, end - pos, "/emailAddress=%s",
+				  name->email);
+		if (ret < 0 || ret >= end - pos)
+			goto done;
+		pos += ret;
+	}
+
+done:
+	end[-1] = '\0';
+}
+
+
+static int x509_parse_time(const u8 *buf, size_t len, u8 asn1_tag,
+			   os_time_t *val)
+{
+	const char *pos;
+	int year, month, day, hour, min, sec;
+
+	/*
+	 * Time ::= CHOICE {
+	 *     utcTime        UTCTime,
+	 *     generalTime    GeneralizedTime
+	 * }
+	 *
+	 * UTCTime: YYMMDDHHMMSSZ
+	 * GeneralizedTime: YYYYMMDDHHMMSSZ
+	 */
+
+	pos = (const char *) buf;
+
+	switch (asn1_tag) {
+	case ASN1_TAG_UTCTIME:
+		if (len != 13 || buf[12] != 'Z') {
+			wpa_hexdump_ascii(MSG_DEBUG, "X509: Unrecognized "
+					  "UTCTime format", buf, len);
+			return -1;
+		}
+		if (sscanf(pos, "%02d", &year) != 1) {
+			wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse "
+					  "UTCTime year", buf, len);
+			return -1;
+		}
+		if (year < 50)
+			year += 2000;
+		else
+			year += 1900;
+		pos += 2;
+		break;
+	case ASN1_TAG_GENERALIZEDTIME:
+		if (len != 15 || buf[14] != 'Z') {
+			wpa_hexdump_ascii(MSG_DEBUG, "X509: Unrecognized "
+					  "GeneralizedTime format", buf, len);
+			return -1;
+		}
+		if (sscanf(pos, "%04d", &year) != 1) {
+			wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse "
+					  "GeneralizedTime year", buf, len);
+			return -1;
+		}
+		pos += 4;
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "X509: Expected UTCTime or "
+			   "GeneralizedTime - found tag 0x%x", asn1_tag);
+		return -1;
+	}
+
+	if (sscanf(pos, "%02d", &month) != 1) {
+		wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
+				  "(month)", buf, len);
+		return -1;
+	}
+	pos += 2;
+
+	if (sscanf(pos, "%02d", &day) != 1) {
+		wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
+				  "(day)", buf, len);
+		return -1;
+	}
+	pos += 2;
+
+	if (sscanf(pos, "%02d", &hour) != 1) {
+		wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
+				  "(hour)", buf, len);
+		return -1;
+	}
+	pos += 2;
+
+	if (sscanf(pos, "%02d", &min) != 1) {
+		wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
+				  "(min)", buf, len);
+		return -1;
+	}
+	pos += 2;
+
+	if (sscanf(pos, "%02d", &sec) != 1) {
+		wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
+				  "(sec)", buf, len);
+		return -1;
+	}
+
+	if (os_mktime(year, month, day, hour, min, sec, val) < 0) {
+		wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to convert Time",
+				  buf, len);
+		if (year < 1970) {
+			/*
+			 * At least some test certificates have been configured
+			 * to use dates prior to 1970. Set the date to
+			 * beginning of 1970 to handle these case.
+			 */
+			wpa_printf(MSG_DEBUG, "X509: Year=%d before epoch - "
+				   "assume epoch as the time", year);
+			*val = 0;
+			return 0;
+		}
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static int x509_parse_validity(const u8 *buf, size_t len,
+			       struct x509_certificate *cert, const u8 **next)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos;
+	size_t plen;
+
+	/*
+	 * Validity ::= SEQUENCE {
+	 *     notBefore      Time,
+	 *     notAfter       Time
+	 * }
+	 *
+	 * RFC 3280, 4.1.2.5:
+	 * CAs conforming to this profile MUST always encode certificate
+	 * validity dates through the year 2049 as UTCTime; certificate
+	 * validity dates in 2050 or later MUST be encoded as GeneralizedTime.
+	 */
+
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE "
+			   "(Validity) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+	plen = hdr.length;
+
+	if (pos + plen > buf + len)
+		return -1;
+
+	*next = pos + plen;
+
+	if (asn1_get_next(pos, plen, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    x509_parse_time(hdr.payload, hdr.length, hdr.tag,
+			    &cert->not_before) < 0) {
+		wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse notBefore "
+				  "Time", hdr.payload, hdr.length);
+		return -1;
+	}
+
+	pos = hdr.payload + hdr.length;
+	plen = *next - pos;
+
+	if (asn1_get_next(pos, plen, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    x509_parse_time(hdr.payload, hdr.length, hdr.tag,
+			    &cert->not_after) < 0) {
+		wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse notAfter "
+				  "Time", hdr.payload, hdr.length);
+		return -1;
+	}
+
+	wpa_printf(MSG_MSGDUMP, "X509: Validity: notBefore: %lu notAfter: %lu",
+		   (unsigned long) cert->not_before,
+		   (unsigned long) cert->not_after);
+
+	return 0;
+}
+
+
+static int x509_id_ce_oid(struct asn1_oid *oid)
+{
+	/* id-ce arc from X.509 for standard X.509v3 extensions */
+	return oid->len >= 4 &&
+		oid->oid[0] == 2 /* joint-iso-ccitt */ &&
+		oid->oid[1] == 5 /* ds */ &&
+		oid->oid[2] == 29 /* id-ce */;
+}
+
+
+static int x509_parse_ext_key_usage(struct x509_certificate *cert,
+				    const u8 *pos, size_t len)
+{
+	struct asn1_hdr hdr;
+
+	/*
+	 * KeyUsage ::= BIT STRING {
+	 *     digitalSignature        (0),
+	 *     nonRepudiation          (1),
+	 *     keyEncipherment         (2),
+	 *     dataEncipherment        (3),
+	 *     keyAgreement            (4),
+	 *     keyCertSign             (5),
+	 *     cRLSign                 (6),
+	 *     encipherOnly            (7),
+	 *     decipherOnly            (8) }
+	 */
+
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_BITSTRING ||
+	    hdr.length < 1) {
+		wpa_printf(MSG_DEBUG, "X509: Expected BIT STRING in "
+			   "KeyUsage; found %d tag 0x%x len %d",
+			   hdr.class, hdr.tag, hdr.length);
+		return -1;
+	}
+
+	cert->extensions_present |= X509_EXT_KEY_USAGE;
+	cert->key_usage = asn1_bit_string_to_long(hdr.payload, hdr.length);
+
+	wpa_printf(MSG_DEBUG, "X509: KeyUsage 0x%lx", cert->key_usage);
+
+	return 0;
+}
+
+
+static int x509_parse_ext_basic_constraints(struct x509_certificate *cert,
+					    const u8 *pos, size_t len)
+{
+	struct asn1_hdr hdr;
+	unsigned long value;
+	size_t left;
+
+	/*
+	 * BasicConstraints ::= SEQUENCE {
+	 * cA                      BOOLEAN DEFAULT FALSE,
+	 * pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
+	 */
+
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE in "
+			   "BasicConstraints; found %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	cert->extensions_present |= X509_EXT_BASIC_CONSTRAINTS;
+
+	if (hdr.length == 0)
+		return 0;
+
+	if (asn1_get_next(hdr.payload, hdr.length, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL) {
+		wpa_printf(MSG_DEBUG, "X509: Failed to parse "
+			   "BasicConstraints");
+		return -1;
+	}
+
+	if (hdr.tag == ASN1_TAG_BOOLEAN) {
+		if (hdr.length != 1) {
+			wpa_printf(MSG_DEBUG, "X509: Unexpected "
+				   "Boolean length (%u) in BasicConstraints",
+				   hdr.length);
+			return -1;
+		}
+		cert->ca = hdr.payload[0];
+
+		if (hdr.payload + hdr.length == pos + len) {
+			wpa_printf(MSG_DEBUG, "X509: BasicConstraints - cA=%d",
+				   cert->ca);
+			return 0;
+		}
+
+		if (asn1_get_next(hdr.payload + hdr.length, len - hdr.length,
+				  &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_UNIVERSAL) {
+			wpa_printf(MSG_DEBUG, "X509: Failed to parse "
+				   "BasicConstraints");
+			return -1;
+		}
+	}
+
+	if (hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG, "X509: Expected INTEGER in "
+			   "BasicConstraints; found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	left = hdr.length;
+	value = 0;
+	while (left) {
+		value <<= 8;
+		value |= *pos++;
+		left--;
+	}
+
+	cert->path_len_constraint = value;
+	cert->extensions_present |= X509_EXT_PATH_LEN_CONSTRAINT;
+
+	wpa_printf(MSG_DEBUG, "X509: BasicConstraints - cA=%d "
+		   "pathLenConstraint=%lu",
+		   cert->ca, cert->path_len_constraint);
+
+	return 0;
+}
+
+
+static int x509_parse_alt_name_rfc8222(struct x509_name *name,
+				       const u8 *pos, size_t len)
+{
+	/* rfc822Name IA5String */
+	wpa_hexdump_ascii(MSG_MSGDUMP, "X509: altName - rfc822Name", pos, len);
+	os_free(name->alt_email);
+	name->alt_email = os_zalloc(len + 1);
+	if (name->alt_email == NULL)
+		return -1;
+	os_memcpy(name->alt_email, pos, len);
+	if (os_strlen(name->alt_email) != len) {
+		wpa_printf(MSG_INFO, "X509: Reject certificate with "
+			   "embedded NUL byte in rfc822Name (%s[NUL])",
+			   name->alt_email);
+		os_free(name->alt_email);
+		name->alt_email = NULL;
+		return -1;
+	}
+	return 0;
+}
+
+
+static int x509_parse_alt_name_dns(struct x509_name *name,
+				   const u8 *pos, size_t len)
+{
+	/* dNSName IA5String */
+	wpa_hexdump_ascii(MSG_MSGDUMP, "X509: altName - dNSName", pos, len);
+	os_free(name->dns);
+	name->dns = os_zalloc(len + 1);
+	if (name->dns == NULL)
+		return -1;
+	os_memcpy(name->dns, pos, len);
+	if (os_strlen(name->dns) != len) {
+		wpa_printf(MSG_INFO, "X509: Reject certificate with "
+			   "embedded NUL byte in dNSName (%s[NUL])",
+			   name->dns);
+		os_free(name->dns);
+		name->dns = NULL;
+		return -1;
+	}
+	return 0;
+}
+
+
+static int x509_parse_alt_name_uri(struct x509_name *name,
+				   const u8 *pos, size_t len)
+{
+	/* uniformResourceIdentifier IA5String */
+	wpa_hexdump_ascii(MSG_MSGDUMP,
+			  "X509: altName - uniformResourceIdentifier",
+			  pos, len);
+	os_free(name->uri);
+	name->uri = os_zalloc(len + 1);
+	if (name->uri == NULL)
+		return -1;
+	os_memcpy(name->uri, pos, len);
+	if (os_strlen(name->uri) != len) {
+		wpa_printf(MSG_INFO, "X509: Reject certificate with "
+			   "embedded NUL byte in uniformResourceIdentifier "
+			   "(%s[NUL])", name->uri);
+		os_free(name->uri);
+		name->uri = NULL;
+		return -1;
+	}
+	return 0;
+}
+
+
+static int x509_parse_alt_name_ip(struct x509_name *name,
+				       const u8 *pos, size_t len)
+{
+	/* iPAddress OCTET STRING */
+	wpa_hexdump(MSG_MSGDUMP, "X509: altName - iPAddress", pos, len);
+	os_free(name->ip);
+	name->ip = os_malloc(len);
+	if (name->ip == NULL)
+		return -1;
+	os_memcpy(name->ip, pos, len);
+	name->ip_len = len;
+	return 0;
+}
+
+
+static int x509_parse_alt_name_rid(struct x509_name *name,
+				   const u8 *pos, size_t len)
+{
+	char buf[80];
+
+	/* registeredID OBJECT IDENTIFIER */
+	if (asn1_parse_oid(pos, len, &name->rid) < 0)
+		return -1;
+
+	asn1_oid_to_str(&name->rid, buf, sizeof(buf));
+	wpa_printf(MSG_MSGDUMP, "X509: altName - registeredID: %s", buf);
+
+	return 0;
+}
+
+
+static int x509_parse_ext_alt_name(struct x509_name *name,
+				   const u8 *pos, size_t len)
+{
+	struct asn1_hdr hdr;
+	const u8 *p, *end;
+
+	/*
+	 * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+	 *
+	 * GeneralName ::= CHOICE {
+	 *     otherName                       [0]     OtherName,
+	 *     rfc822Name                      [1]     IA5String,
+	 *     dNSName                         [2]     IA5String,
+	 *     x400Address                     [3]     ORAddress,
+	 *     directoryName                   [4]     Name,
+	 *     ediPartyName                    [5]     EDIPartyName,
+	 *     uniformResourceIdentifier       [6]     IA5String,
+	 *     iPAddress                       [7]     OCTET STRING,
+	 *     registeredID                    [8]     OBJECT IDENTIFIER }
+	 *
+	 * OtherName ::= SEQUENCE {
+	 *     type-id    OBJECT IDENTIFIER,
+	 *     value      [0] EXPLICIT ANY DEFINED BY type-id }
+	 *
+	 * EDIPartyName ::= SEQUENCE {
+	 *     nameAssigner            [0]     DirectoryString OPTIONAL,
+	 *     partyName               [1]     DirectoryString }
+	 */
+
+	for (p = pos, end = pos + len; p < end; p = hdr.payload + hdr.length) {
+		int res;
+
+		if (asn1_get_next(p, end - p, &hdr) < 0) {
+			wpa_printf(MSG_DEBUG, "X509: Failed to parse "
+				   "SubjectAltName item");
+			return -1;
+		}
+
+		if (hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC)
+			continue;
+
+		switch (hdr.tag) {
+		case 1:
+			res = x509_parse_alt_name_rfc8222(name, hdr.payload,
+							  hdr.length);
+			break;
+		case 2:
+			res = x509_parse_alt_name_dns(name, hdr.payload,
+						      hdr.length);
+			break;
+		case 6:
+			res = x509_parse_alt_name_uri(name, hdr.payload,
+						      hdr.length);
+			break;
+		case 7:
+			res = x509_parse_alt_name_ip(name, hdr.payload,
+						     hdr.length);
+			break;
+		case 8:
+			res = x509_parse_alt_name_rid(name, hdr.payload,
+						      hdr.length);
+			break;
+		case 0: /* TODO: otherName */
+		case 3: /* TODO: x500Address */
+		case 4: /* TODO: directoryName */
+		case 5: /* TODO: ediPartyName */
+		default:
+			res = 0;
+			break;
+		}
+		if (res < 0)
+			return res;
+	}
+
+	return 0;
+}
+
+
+static int x509_parse_ext_subject_alt_name(struct x509_certificate *cert,
+					   const u8 *pos, size_t len)
+{
+	struct asn1_hdr hdr;
+
+	/* SubjectAltName ::= GeneralNames */
+
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE in "
+			   "SubjectAltName; found %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "X509: SubjectAltName");
+	cert->extensions_present |= X509_EXT_SUBJECT_ALT_NAME;
+
+	if (hdr.length == 0)
+		return 0;
+
+	return x509_parse_ext_alt_name(&cert->subject, hdr.payload,
+				       hdr.length);
+}
+
+
+static int x509_parse_ext_issuer_alt_name(struct x509_certificate *cert,
+					  const u8 *pos, size_t len)
+{
+	struct asn1_hdr hdr;
+
+	/* IssuerAltName ::= GeneralNames */
+
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE in "
+			   "IssuerAltName; found %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "X509: IssuerAltName");
+	cert->extensions_present |= X509_EXT_ISSUER_ALT_NAME;
+
+	if (hdr.length == 0)
+		return 0;
+
+	return x509_parse_ext_alt_name(&cert->issuer, hdr.payload,
+				       hdr.length);
+}
+
+
+static int x509_parse_extension_data(struct x509_certificate *cert,
+				     struct asn1_oid *oid,
+				     const u8 *pos, size_t len)
+{
+	if (!x509_id_ce_oid(oid))
+		return 1;
+
+	/* TODO: add other extensions required by RFC 3280, Ch 4.2:
+	 * certificate policies (section 4.2.1.5)
+	 * name constraints (section 4.2.1.11)
+	 * policy constraints (section 4.2.1.12)
+	 * extended key usage (section 4.2.1.13)
+	 * inhibit any-policy (section 4.2.1.15)
+	 */
+	switch (oid->oid[3]) {
+	case 15: /* id-ce-keyUsage */
+		return x509_parse_ext_key_usage(cert, pos, len);
+	case 17: /* id-ce-subjectAltName */
+		return x509_parse_ext_subject_alt_name(cert, pos, len);
+	case 18: /* id-ce-issuerAltName */
+		return x509_parse_ext_issuer_alt_name(cert, pos, len);
+	case 19: /* id-ce-basicConstraints */
+		return x509_parse_ext_basic_constraints(cert, pos, len);
+	default:
+		return 1;
+	}
+}
+
+
+static int x509_parse_extension(struct x509_certificate *cert,
+				const u8 *pos, size_t len, const u8 **next)
+{
+	const u8 *end;
+	struct asn1_hdr hdr;
+	struct asn1_oid oid;
+	int critical_ext = 0, res;
+	char buf[80];
+
+	/*
+	 * Extension  ::=  SEQUENCE  {
+	 *     extnID      OBJECT IDENTIFIER,
+	 *     critical    BOOLEAN DEFAULT FALSE,
+	 *     extnValue   OCTET STRING
+	 * }
+	 */
+
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 header in "
+			   "Extensions: class %d tag 0x%x; expected SEQUENCE",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+	*next = end = pos + hdr.length;
+
+	if (asn1_get_oid(pos, end - pos, &oid, &pos) < 0) {
+		wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 data for "
+			   "Extension (expected OID)");
+		return -1;
+	}
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    (hdr.tag != ASN1_TAG_BOOLEAN &&
+	     hdr.tag != ASN1_TAG_OCTETSTRING)) {
+		wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 header in "
+			   "Extensions: class %d tag 0x%x; expected BOOLEAN "
+			   "or OCTET STRING", hdr.class, hdr.tag);
+		return -1;
+	}
+
+	if (hdr.tag == ASN1_TAG_BOOLEAN) {
+		if (hdr.length != 1) {
+			wpa_printf(MSG_DEBUG, "X509: Unexpected "
+				   "Boolean length (%u)", hdr.length);
+			return -1;
+		}
+		critical_ext = hdr.payload[0];
+		pos = hdr.payload;
+		if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+		    (hdr.class != ASN1_CLASS_UNIVERSAL &&
+		     hdr.class != ASN1_CLASS_PRIVATE) ||
+		    hdr.tag != ASN1_TAG_OCTETSTRING) {
+			wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 header "
+				   "in Extensions: class %d tag 0x%x; "
+				   "expected OCTET STRING",
+				   hdr.class, hdr.tag);
+			return -1;
+		}
+	}
+
+	asn1_oid_to_str(&oid, buf, sizeof(buf));
+	wpa_printf(MSG_DEBUG, "X509: Extension: extnID=%s critical=%d",
+		   buf, critical_ext);
+	wpa_hexdump(MSG_MSGDUMP, "X509: extnValue", hdr.payload, hdr.length);
+
+	res = x509_parse_extension_data(cert, &oid, hdr.payload, hdr.length);
+	if (res < 0)
+		return res;
+	if (res == 1 && critical_ext) {
+		wpa_printf(MSG_INFO, "X509: Unknown critical extension %s",
+			   buf);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static int x509_parse_extensions(struct x509_certificate *cert,
+				 const u8 *pos, size_t len)
+{
+	const u8 *end;
+	struct asn1_hdr hdr;
+
+	/* Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension */
+
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 data "
+			   "for Extensions: class %d tag 0x%x; "
+			   "expected SEQUENCE", hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	while (pos < end) {
+		if (x509_parse_extension(cert, pos, end - pos, &pos)
+		    < 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+
+static int x509_parse_tbs_certificate(const u8 *buf, size_t len,
+				      struct x509_certificate *cert,
+				      const u8 **next)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+	size_t left;
+	char sbuf[128];
+	unsigned long value;
+
+	/* tbsCertificate TBSCertificate ::= SEQUENCE */
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: tbsCertificate did not start "
+			   "with a valid SEQUENCE - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+	end = *next = pos + hdr.length;
+
+	/*
+	 * version [0]  EXPLICIT Version DEFAULT v1
+	 * Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
+	 */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0)
+		return -1;
+	pos = hdr.payload;
+
+	if (hdr.class == ASN1_CLASS_CONTEXT_SPECIFIC) {
+		if (asn1_get_next(pos, end - pos, &hdr) < 0)
+			return -1;
+
+		if (hdr.class != ASN1_CLASS_UNIVERSAL ||
+		    hdr.tag != ASN1_TAG_INTEGER) {
+			wpa_printf(MSG_DEBUG, "X509: No INTEGER tag found for "
+				   "version field - found class %d tag 0x%x",
+				   hdr.class, hdr.tag);
+			return -1;
+		}
+		if (hdr.length != 1) {
+			wpa_printf(MSG_DEBUG, "X509: Unexpected version field "
+				   "length %u (expected 1)", hdr.length);
+			return -1;
+		}
+		pos = hdr.payload;
+		left = hdr.length;
+		value = 0;
+		while (left) {
+			value <<= 8;
+			value |= *pos++;
+			left--;
+		}
+
+		cert->version = value;
+		if (cert->version != X509_CERT_V1 &&
+		    cert->version != X509_CERT_V2 &&
+		    cert->version != X509_CERT_V3) {
+			wpa_printf(MSG_DEBUG, "X509: Unsupported version %d",
+				   cert->version + 1);
+			return -1;
+		}
+
+		if (asn1_get_next(pos, end - pos, &hdr) < 0)
+			return -1;
+	} else
+		cert->version = X509_CERT_V1;
+	wpa_printf(MSG_MSGDUMP, "X509: Version X.509v%d", cert->version + 1);
+
+	/* serialNumber CertificateSerialNumber ::= INTEGER */
+	if (hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG, "X509: No INTEGER tag found for "
+			   "serialNumber; class=%d tag=0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	left = hdr.length;
+	while (left) {
+		cert->serial_number <<= 8;
+		cert->serial_number |= *pos++;
+		left--;
+	}
+	wpa_printf(MSG_MSGDUMP, "X509: serialNumber %lu", cert->serial_number);
+
+	/* signature AlgorithmIdentifier */
+	if (x509_parse_algorithm_identifier(pos, end - pos, &cert->signature,
+					    &pos))
+		return -1;
+
+	/* issuer Name */
+	if (x509_parse_name(pos, end - pos, &cert->issuer, &pos))
+		return -1;
+	x509_name_string(&cert->issuer, sbuf, sizeof(sbuf));
+	wpa_printf(MSG_MSGDUMP, "X509: issuer %s", sbuf);
+
+	/* validity Validity */
+	if (x509_parse_validity(pos, end - pos, cert, &pos))
+		return -1;
+
+	/* subject Name */
+	if (x509_parse_name(pos, end - pos, &cert->subject, &pos))
+		return -1;
+	x509_name_string(&cert->subject, sbuf, sizeof(sbuf));
+	wpa_printf(MSG_MSGDUMP, "X509: subject %s", sbuf);
+
+	/* subjectPublicKeyInfo SubjectPublicKeyInfo */
+	if (x509_parse_public_key(pos, end - pos, cert, &pos))
+		return -1;
+
+	if (pos == end)
+		return 0;
+
+	if (cert->version == X509_CERT_V1)
+		return 0;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC) {
+		wpa_printf(MSG_DEBUG, "X509: Expected Context-Specific"
+			   " tag to parse optional tbsCertificate "
+			   "field(s); parsed class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	if (hdr.tag == 1) {
+		/* issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL */
+		wpa_printf(MSG_DEBUG, "X509: issuerUniqueID");
+		/* TODO: parse UniqueIdentifier ::= BIT STRING */
+
+		if (hdr.payload + hdr.length == end)
+			return 0;
+
+		if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC) {
+			wpa_printf(MSG_DEBUG, "X509: Expected Context-Specific"
+				   " tag to parse optional tbsCertificate "
+				   "field(s); parsed class %d tag 0x%x",
+				   hdr.class, hdr.tag);
+			return -1;
+		}
+	}
+
+	if (hdr.tag == 2) {
+		/* subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL */
+		wpa_printf(MSG_DEBUG, "X509: subjectUniqueID");
+		/* TODO: parse UniqueIdentifier ::= BIT STRING */
+
+		if (hdr.payload + hdr.length == end)
+			return 0;
+
+		if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC) {
+			wpa_printf(MSG_DEBUG, "X509: Expected Context-Specific"
+				   " tag to parse optional tbsCertificate "
+				   "field(s); parsed class %d tag 0x%x",
+				   hdr.class, hdr.tag);
+			return -1;
+		}
+	}
+
+	if (hdr.tag != 3) {
+		wpa_printf(MSG_DEBUG, "X509: Ignored unexpected "
+			   "Context-Specific tag %d in optional "
+			   "tbsCertificate fields", hdr.tag);
+		return 0;
+	}
+
+	/* extensions      [3]  EXPLICIT Extensions OPTIONAL */
+
+	if (cert->version != X509_CERT_V3) {
+		wpa_printf(MSG_DEBUG, "X509: X.509%d certificate and "
+			   "Extensions data which are only allowed for "
+			   "version 3", cert->version + 1);
+		return -1;
+	}
+
+	if (x509_parse_extensions(cert, hdr.payload, hdr.length) < 0)
+		return -1;
+
+	pos = hdr.payload + hdr.length;
+	if (pos < end) {
+		wpa_hexdump(MSG_DEBUG,
+			    "X509: Ignored extra tbsCertificate data",
+			    pos, end - pos);
+	}
+
+	return 0;
+}
+
+
+static int x509_rsadsi_oid(struct asn1_oid *oid)
+{
+	return oid->len >= 4 &&
+		oid->oid[0] == 1 /* iso */ &&
+		oid->oid[1] == 2 /* member-body */ &&
+		oid->oid[2] == 840 /* us */ &&
+		oid->oid[3] == 113549 /* rsadsi */;
+}
+
+
+static int x509_pkcs_oid(struct asn1_oid *oid)
+{
+	return oid->len >= 5 &&
+		x509_rsadsi_oid(oid) &&
+		oid->oid[4] == 1 /* pkcs */;
+}
+
+
+static int x509_digest_oid(struct asn1_oid *oid)
+{
+	return oid->len >= 5 &&
+		x509_rsadsi_oid(oid) &&
+		oid->oid[4] == 2 /* digestAlgorithm */;
+}
+
+
+static int x509_sha1_oid(struct asn1_oid *oid)
+{
+	return oid->len == 6 &&
+		oid->oid[0] == 1 /* iso */ &&
+		oid->oid[1] == 3 /* identified-organization */ &&
+		oid->oid[2] == 14 /* oiw */ &&
+		oid->oid[3] == 3 /* secsig */ &&
+		oid->oid[4] == 2 /* algorithms */ &&
+		oid->oid[5] == 26 /* id-sha1 */;
+}
+
+
+static int x509_sha256_oid(struct asn1_oid *oid)
+{
+	return oid->len == 9 &&
+		oid->oid[0] == 2 /* joint-iso-itu-t */ &&
+		oid->oid[1] == 16 /* country */ &&
+		oid->oid[2] == 840 /* us */ &&
+		oid->oid[3] == 1 /* organization */ &&
+		oid->oid[4] == 101 /* gov */ &&
+		oid->oid[5] == 3 /* csor */ &&
+		oid->oid[6] == 4 /* nistAlgorithm */ &&
+		oid->oid[7] == 2 /* hashAlgs */ &&
+		oid->oid[8] == 1 /* sha256 */;
+}
+
+
+/**
+ * x509_certificate_parse - Parse a X.509 certificate in DER format
+ * @buf: Pointer to the X.509 certificate in DER format
+ * @len: Buffer length
+ * Returns: Pointer to the parsed certificate or %NULL on failure
+ *
+ * Caller is responsible for freeing the returned certificate by calling
+ * x509_certificate_free().
+ */
+struct x509_certificate * x509_certificate_parse(const u8 *buf, size_t len)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end, *hash_start;
+	struct x509_certificate *cert;
+
+	cert = os_zalloc(sizeof(*cert) + len);
+	if (cert == NULL)
+		return NULL;
+	os_memcpy(cert + 1, buf, len);
+	cert->cert_start = (u8 *) (cert + 1);
+	cert->cert_len = len;
+
+	pos = buf;
+	end = buf + len;
+
+	/* RFC 3280 - X.509 v3 certificate / ASN.1 DER */
+
+	/* Certificate ::= SEQUENCE */
+	if (asn1_get_next(pos, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Certificate did not start with "
+			   "a valid SEQUENCE - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		x509_certificate_free(cert);
+		return NULL;
+	}
+	pos = hdr.payload;
+
+	if (pos + hdr.length > end) {
+		x509_certificate_free(cert);
+		return NULL;
+	}
+
+	if (pos + hdr.length < end) {
+		wpa_hexdump(MSG_MSGDUMP, "X509: Ignoring extra data after DER "
+			    "encoded certificate",
+			    pos + hdr.length, end - pos + hdr.length);
+		end = pos + hdr.length;
+	}
+
+	hash_start = pos;
+	cert->tbs_cert_start = cert->cert_start + (hash_start - buf);
+	if (x509_parse_tbs_certificate(pos, end - pos, cert, &pos)) {
+		x509_certificate_free(cert);
+		return NULL;
+	}
+	cert->tbs_cert_len = pos - hash_start;
+
+	/* signatureAlgorithm AlgorithmIdentifier */
+	if (x509_parse_algorithm_identifier(pos, end - pos,
+					    &cert->signature_alg, &pos)) {
+		x509_certificate_free(cert);
+		return NULL;
+	}
+
+	/* signatureValue BIT STRING */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_BITSTRING) {
+		wpa_printf(MSG_DEBUG, "X509: Expected BITSTRING "
+			   "(signatureValue) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		x509_certificate_free(cert);
+		return NULL;
+	}
+	if (hdr.length < 1) {
+		x509_certificate_free(cert);
+		return NULL;
+	}
+	pos = hdr.payload;
+	if (*pos) {
+		wpa_printf(MSG_DEBUG, "X509: BITSTRING - %d unused bits",
+			   *pos);
+		/* PKCS #1 v1.5 10.2.1:
+		 * It is an error if the length in bits of the signature S is
+		 * not a multiple of eight.
+		 */
+		x509_certificate_free(cert);
+		return NULL;
+	}
+	os_free(cert->sign_value);
+	cert->sign_value = os_malloc(hdr.length - 1);
+	if (cert->sign_value == NULL) {
+		wpa_printf(MSG_DEBUG, "X509: Failed to allocate memory for "
+			   "signatureValue");
+		x509_certificate_free(cert);
+		return NULL;
+	}
+	os_memcpy(cert->sign_value, pos + 1, hdr.length - 1);
+	cert->sign_value_len = hdr.length - 1;
+	wpa_hexdump(MSG_MSGDUMP, "X509: signature",
+		    cert->sign_value, cert->sign_value_len);
+
+	return cert;
+}
+
+
+/**
+ * x509_certificate_check_signature - Verify certificate signature
+ * @issuer: Issuer certificate
+ * @cert: Certificate to be verified
+ * Returns: 0 if cert has a valid signature that was signed by the issuer,
+ * -1 if not
+ */
+int x509_certificate_check_signature(struct x509_certificate *issuer,
+				     struct x509_certificate *cert)
+{
+	struct crypto_public_key *pk;
+	u8 *data;
+	const u8 *pos, *end, *next, *da_end;
+	size_t data_len;
+	struct asn1_hdr hdr;
+	struct asn1_oid oid;
+	u8 hash[32];
+	size_t hash_len;
+
+	if (!x509_pkcs_oid(&cert->signature.oid) ||
+	    cert->signature.oid.len != 7 ||
+	    cert->signature.oid.oid[5] != 1 /* pkcs-1 */) {
+		wpa_printf(MSG_DEBUG, "X509: Unrecognized signature "
+			   "algorithm");
+		return -1;
+	}
+
+	pk = crypto_public_key_import(issuer->public_key,
+				      issuer->public_key_len);
+	if (pk == NULL)
+		return -1;
+
+	data_len = cert->sign_value_len;
+	data = os_malloc(data_len);
+	if (data == NULL) {
+		crypto_public_key_free(pk);
+		return -1;
+	}
+
+	if (crypto_public_key_decrypt_pkcs1(pk, cert->sign_value,
+					    cert->sign_value_len, data,
+					    &data_len) < 0) {
+		wpa_printf(MSG_DEBUG, "X509: Failed to decrypt signature");
+		crypto_public_key_free(pk);
+		os_free(data);
+		return -1;
+	}
+	crypto_public_key_free(pk);
+
+	wpa_hexdump(MSG_MSGDUMP, "X509: Signature data D", data, data_len);
+
+	/*
+	 * PKCS #1 v1.5, 10.1.2:
+	 *
+	 * DigestInfo ::= SEQUENCE {
+	 *     digestAlgorithm DigestAlgorithmIdentifier,
+	 *     digest Digest
+	 * }
+	 *
+	 * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+	 *
+	 * Digest ::= OCTET STRING
+	 *
+	 */
+	if (asn1_get_next(data, data_len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE "
+			   "(DigestInfo) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		os_free(data);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	/*
+	 * X.509:
+	 * AlgorithmIdentifier ::= SEQUENCE {
+	 *     algorithm            OBJECT IDENTIFIER,
+	 *     parameters           ANY DEFINED BY algorithm OPTIONAL
+	 * }
+	 */
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE "
+			   "(AlgorithmIdentifier) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		os_free(data);
+		return -1;
+	}
+	da_end = hdr.payload + hdr.length;
+
+	if (asn1_get_oid(hdr.payload, hdr.length, &oid, &next)) {
+		wpa_printf(MSG_DEBUG, "X509: Failed to parse digestAlgorithm");
+		os_free(data);
+		return -1;
+	}
+
+	if (x509_sha1_oid(&oid)) {
+		if (cert->signature.oid.oid[6] !=
+		    5 /* sha-1WithRSAEncryption */) {
+			wpa_printf(MSG_DEBUG, "X509: digestAlgorithm SHA1 "
+				   "does not match with certificate "
+				   "signatureAlgorithm (%lu)",
+				   cert->signature.oid.oid[6]);
+			os_free(data);
+			return -1;
+		}
+		goto skip_digest_oid;
+	}
+
+	if (x509_sha256_oid(&oid)) {
+		if (cert->signature.oid.oid[6] !=
+		    11 /* sha2561WithRSAEncryption */) {
+			wpa_printf(MSG_DEBUG, "X509: digestAlgorithm SHA256 "
+				   "does not match with certificate "
+				   "signatureAlgorithm (%lu)",
+				   cert->signature.oid.oid[6]);
+			os_free(data);
+			return -1;
+		}
+		goto skip_digest_oid;
+	}
+
+	if (!x509_digest_oid(&oid)) {
+		wpa_printf(MSG_DEBUG, "X509: Unrecognized digestAlgorithm");
+		os_free(data);
+		return -1;
+	}
+	switch (oid.oid[5]) {
+	case 5: /* md5 */
+		if (cert->signature.oid.oid[6] != 4 /* md5WithRSAEncryption */)
+		{
+			wpa_printf(MSG_DEBUG, "X509: digestAlgorithm MD5 does "
+				   "not match with certificate "
+				   "signatureAlgorithm (%lu)",
+				   cert->signature.oid.oid[6]);
+			os_free(data);
+			return -1;
+		}
+		break;
+	case 2: /* md2 */
+	case 4: /* md4 */
+	default:
+		wpa_printf(MSG_DEBUG, "X509: Unsupported digestAlgorithm "
+			   "(%lu)", oid.oid[5]);
+		os_free(data);
+		return -1;
+	}
+
+skip_digest_oid:
+	/* Digest ::= OCTET STRING */
+	pos = da_end;
+	end = data + data_len;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING) {
+		wpa_printf(MSG_DEBUG, "X509: Expected OCTETSTRING "
+			   "(Digest) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		os_free(data);
+		return -1;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "X509: Decrypted Digest",
+		    hdr.payload, hdr.length);
+
+	switch (cert->signature.oid.oid[6]) {
+	case 4: /* md5WithRSAEncryption */
+		md5_vector(1, &cert->tbs_cert_start, &cert->tbs_cert_len,
+			   hash);
+		hash_len = 16;
+		wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (MD5)",
+			    hash, hash_len);
+		break;
+	case 5: /* sha-1WithRSAEncryption */
+		sha1_vector(1, &cert->tbs_cert_start, &cert->tbs_cert_len,
+			    hash);
+		hash_len = 20;
+		wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (SHA1)",
+			    hash, hash_len);
+		break;
+	case 11: /* sha256WithRSAEncryption */
+		sha256_vector(1, &cert->tbs_cert_start, &cert->tbs_cert_len,
+			      hash);
+		hash_len = 32;
+		wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (SHA256)",
+			    hash, hash_len);
+		break;
+	case 2: /* md2WithRSAEncryption */
+	case 12: /* sha384WithRSAEncryption */
+	case 13: /* sha512WithRSAEncryption */
+	default:
+		wpa_printf(MSG_INFO, "X509: Unsupported certificate signature "
+			   "algorithm (%lu)", cert->signature.oid.oid[6]);
+		os_free(data);
+		return -1;
+	}
+
+	if (hdr.length != hash_len ||
+	    os_memcmp(hdr.payload, hash, hdr.length) != 0) {
+		wpa_printf(MSG_INFO, "X509: Certificate Digest does not match "
+			   "with calculated tbsCertificate hash");
+		os_free(data);
+		return -1;
+	}
+
+	os_free(data);
+
+	wpa_printf(MSG_DEBUG, "X509: Certificate Digest matches with "
+		   "calculated tbsCertificate hash");
+
+	return 0;
+}
+
+
+static int x509_valid_issuer(const struct x509_certificate *cert)
+{
+	if ((cert->extensions_present & X509_EXT_BASIC_CONSTRAINTS) &&
+	    !cert->ca) {
+		wpa_printf(MSG_DEBUG, "X509: Non-CA certificate used as an "
+			   "issuer");
+		return -1;
+	}
+
+	if (cert->version == X509_CERT_V3 &&
+	    !(cert->extensions_present & X509_EXT_BASIC_CONSTRAINTS)) {
+		wpa_printf(MSG_DEBUG, "X509: v3 CA certificate did not "
+			   "include BasicConstraints extension");
+		return -1;
+	}
+
+	if ((cert->extensions_present & X509_EXT_KEY_USAGE) &&
+	    !(cert->key_usage & X509_KEY_USAGE_KEY_CERT_SIGN)) {
+		wpa_printf(MSG_DEBUG, "X509: Issuer certificate did not have "
+			   "keyCertSign bit in Key Usage");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/**
+ * x509_certificate_chain_validate - Validate X.509 certificate chain
+ * @trusted: List of trusted certificates
+ * @chain: Certificate chain to be validated (first chain must be issued by
+ * signed by the second certificate in the chain and so on)
+ * @reason: Buffer for returning failure reason (X509_VALIDATE_*)
+ * Returns: 0 if chain is valid, -1 if not
+ */
+int x509_certificate_chain_validate(struct x509_certificate *trusted,
+				    struct x509_certificate *chain,
+				    int *reason)
+{
+	long unsigned idx;
+	int chain_trusted = 0;
+	struct x509_certificate *cert, *trust;
+	char buf[128];
+	struct os_time now;
+
+	*reason = X509_VALIDATE_OK;
+
+	wpa_printf(MSG_DEBUG, "X509: Validate certificate chain");
+	os_get_time(&now);
+
+	for (cert = chain, idx = 0; cert; cert = cert->next, idx++) {
+		x509_name_string(&cert->subject, buf, sizeof(buf)); 
+		wpa_printf(MSG_DEBUG, "X509: %lu: %s", idx, buf);
+
+		if (chain_trusted)
+			continue;
+
+		if ((unsigned long) now.sec <
+		    (unsigned long) cert->not_before ||
+		    (unsigned long) now.sec >
+		    (unsigned long) cert->not_after) {
+			wpa_printf(MSG_INFO, "X509: Certificate not valid "
+				   "(now=%lu not_before=%lu not_after=%lu)",
+				   now.sec, cert->not_before, cert->not_after);
+			*reason = X509_VALIDATE_CERTIFICATE_EXPIRED;
+			return -1;
+		}
+
+		if (cert->next) {
+			if (x509_name_compare(&cert->issuer,
+					      &cert->next->subject) != 0) {
+				wpa_printf(MSG_DEBUG, "X509: Certificate "
+					   "chain issuer name mismatch");
+				x509_name_string(&cert->issuer, buf,
+						 sizeof(buf)); 
+				wpa_printf(MSG_DEBUG, "X509: cert issuer: %s",
+					   buf);
+				x509_name_string(&cert->next->subject, buf,
+						 sizeof(buf)); 
+				wpa_printf(MSG_DEBUG, "X509: next cert "
+					   "subject: %s", buf);
+				*reason = X509_VALIDATE_CERTIFICATE_UNKNOWN;
+				return -1;
+			}
+
+			if (x509_valid_issuer(cert->next) < 0) {
+				*reason = X509_VALIDATE_BAD_CERTIFICATE;
+				return -1;
+			}
+
+			if ((cert->next->extensions_present &
+			     X509_EXT_PATH_LEN_CONSTRAINT) &&
+			    idx > cert->next->path_len_constraint) {
+				wpa_printf(MSG_DEBUG, "X509: pathLenConstraint"
+					   " not met (idx=%lu issuer "
+					   "pathLenConstraint=%lu)", idx,
+					   cert->next->path_len_constraint);
+				*reason = X509_VALIDATE_BAD_CERTIFICATE;
+				return -1;
+			}
+
+			if (x509_certificate_check_signature(cert->next, cert)
+			    < 0) {
+				wpa_printf(MSG_DEBUG, "X509: Invalid "
+					   "certificate signature within "
+					   "chain");
+				*reason = X509_VALIDATE_BAD_CERTIFICATE;
+				return -1;
+			}
+		}
+
+		for (trust = trusted; trust; trust = trust->next) {
+			if (x509_name_compare(&cert->issuer, &trust->subject)
+			    == 0)
+				break;
+		}
+
+		if (trust) {
+			wpa_printf(MSG_DEBUG, "X509: Found issuer from the "
+				   "list of trusted certificates");
+			if (x509_valid_issuer(trust) < 0) {
+				*reason = X509_VALIDATE_BAD_CERTIFICATE;
+				return -1;
+			}
+
+			if (x509_certificate_check_signature(trust, cert) < 0)
+			{
+				wpa_printf(MSG_DEBUG, "X509: Invalid "
+					   "certificate signature");
+				*reason = X509_VALIDATE_BAD_CERTIFICATE;
+				return -1;
+			}
+
+			wpa_printf(MSG_DEBUG, "X509: Trusted certificate "
+				   "found to complete the chain");
+			chain_trusted = 1;
+		}
+	}
+
+	if (!chain_trusted) {
+		wpa_printf(MSG_DEBUG, "X509: Did not find any of the issuers "
+			   "from the list of trusted certificates");
+		if (trusted) {
+			*reason = X509_VALIDATE_UNKNOWN_CA;
+			return -1;
+		}
+		wpa_printf(MSG_DEBUG, "X509: Certificate chain validation "
+			   "disabled - ignore unknown CA issue");
+	}
+
+	wpa_printf(MSG_DEBUG, "X509: Certificate chain valid");
+
+	return 0;
+}
+
+
+/**
+ * x509_certificate_get_subject - Get a certificate based on Subject name
+ * @chain: Certificate chain to search through
+ * @name: Subject name to search for
+ * Returns: Pointer to the certificate with the given Subject name or
+ * %NULL on failure
+ */
+struct x509_certificate *
+x509_certificate_get_subject(struct x509_certificate *chain,
+			     struct x509_name *name)
+{
+	struct x509_certificate *cert;
+
+	for (cert = chain; cert; cert = cert->next) {
+		if (x509_name_compare(&cert->subject, name) == 0)
+			return cert;
+	}
+	return NULL;
+}
+
+
+/**
+ * x509_certificate_self_signed - Is the certificate self-signed?
+ * @cert: Certificate
+ * Returns: 1 if certificate is self-signed, 0 if not
+ */
+int x509_certificate_self_signed(struct x509_certificate *cert)
+{
+	return x509_name_compare(&cert->issuer, &cert->subject) == 0;
+}
diff --git a/src/tls/x509v3.h b/src/tls/x509v3.h
new file mode 100644
index 0000000..37292d7
--- /dev/null
+++ b/src/tls/x509v3.h
@@ -0,0 +1,129 @@
+/*
+ * X.509v3 certificate parsing and processing
+ * Copyright (c) 2006, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef X509V3_H
+#define X509V3_H
+
+#include "asn1.h"
+
+struct x509_algorithm_identifier {
+	struct asn1_oid oid;
+};
+
+struct x509_name_attr {
+	enum x509_name_attr_type {
+		X509_NAME_ATTR_NOT_USED,
+		X509_NAME_ATTR_DC,
+		X509_NAME_ATTR_CN,
+		X509_NAME_ATTR_C,
+		X509_NAME_ATTR_L,
+		X509_NAME_ATTR_ST,
+		X509_NAME_ATTR_O,
+		X509_NAME_ATTR_OU
+	} type;
+	char *value;
+};
+
+#define X509_MAX_NAME_ATTRIBUTES 20
+
+struct x509_name {
+	struct x509_name_attr attr[X509_MAX_NAME_ATTRIBUTES];
+	size_t num_attr;
+	char *email; /* emailAddress */
+
+	/* from alternative name extension */
+	char *alt_email; /* rfc822Name */
+	char *dns; /* dNSName */
+	char *uri; /* uniformResourceIdentifier */
+	u8 *ip; /* iPAddress */
+	size_t ip_len; /* IPv4: 4, IPv6: 16 */
+	struct asn1_oid rid; /* registeredID */
+};
+
+struct x509_certificate {
+	struct x509_certificate *next;
+	enum { X509_CERT_V1 = 0, X509_CERT_V2 = 1, X509_CERT_V3 = 2 } version;
+	unsigned long serial_number;
+	struct x509_algorithm_identifier signature;
+	struct x509_name issuer;
+	struct x509_name subject;
+	os_time_t not_before;
+	os_time_t not_after;
+	struct x509_algorithm_identifier public_key_alg;
+	u8 *public_key;
+	size_t public_key_len;
+	struct x509_algorithm_identifier signature_alg;
+	u8 *sign_value;
+	size_t sign_value_len;
+
+	/* Extensions */
+	unsigned int extensions_present;
+#define X509_EXT_BASIC_CONSTRAINTS		(1 << 0)
+#define X509_EXT_PATH_LEN_CONSTRAINT		(1 << 1)
+#define X509_EXT_KEY_USAGE			(1 << 2)
+#define X509_EXT_SUBJECT_ALT_NAME		(1 << 3)
+#define X509_EXT_ISSUER_ALT_NAME		(1 << 4)
+
+	/* BasicConstraints */
+	int ca; /* cA */
+	unsigned long path_len_constraint; /* pathLenConstraint */
+
+	/* KeyUsage */
+	unsigned long key_usage;
+#define X509_KEY_USAGE_DIGITAL_SIGNATURE	(1 << 0)
+#define X509_KEY_USAGE_NON_REPUDIATION		(1 << 1)
+#define X509_KEY_USAGE_KEY_ENCIPHERMENT		(1 << 2)
+#define X509_KEY_USAGE_DATA_ENCIPHERMENT	(1 << 3)
+#define X509_KEY_USAGE_KEY_AGREEMENT		(1 << 4)
+#define X509_KEY_USAGE_KEY_CERT_SIGN		(1 << 5)
+#define X509_KEY_USAGE_CRL_SIGN			(1 << 6)
+#define X509_KEY_USAGE_ENCIPHER_ONLY		(1 << 7)
+#define X509_KEY_USAGE_DECIPHER_ONLY		(1 << 8)
+
+	/*
+	 * The DER format certificate follows struct x509_certificate. These
+	 * pointers point to that buffer.
+	 */
+	const u8 *cert_start;
+	size_t cert_len;
+	const u8 *tbs_cert_start;
+	size_t tbs_cert_len;
+};
+
+enum {
+	X509_VALIDATE_OK,
+	X509_VALIDATE_BAD_CERTIFICATE,
+	X509_VALIDATE_UNSUPPORTED_CERTIFICATE,
+	X509_VALIDATE_CERTIFICATE_REVOKED,
+	X509_VALIDATE_CERTIFICATE_EXPIRED,
+	X509_VALIDATE_CERTIFICATE_UNKNOWN,
+	X509_VALIDATE_UNKNOWN_CA
+};
+
+void x509_certificate_free(struct x509_certificate *cert);
+struct x509_certificate * x509_certificate_parse(const u8 *buf, size_t len);
+void x509_name_string(struct x509_name *name, char *buf, size_t len);
+int x509_name_compare(struct x509_name *a, struct x509_name *b);
+void x509_certificate_chain_free(struct x509_certificate *cert);
+int x509_certificate_check_signature(struct x509_certificate *issuer,
+				     struct x509_certificate *cert);
+int x509_certificate_chain_validate(struct x509_certificate *trusted,
+				    struct x509_certificate *chain,
+				    int *reason);
+struct x509_certificate *
+x509_certificate_get_subject(struct x509_certificate *chain,
+			     struct x509_name *name);
+int x509_certificate_self_signed(struct x509_certificate *cert);
+
+#endif /* X509V3_H */