aboutsummaryrefslogtreecommitdiffstats
path: root/src/tls
diff options
context:
space:
mode:
authorJouni Malinen <j@w1.fi>2008-02-28 01:34:43 (GMT)
committerJouni Malinen <jm@jm.kir.nu>2008-02-28 01:34:43 (GMT)
commit6fc6879bd55a394f807cbbe927df736c190cb8ab (patch)
treecdf50da0c58f21510a808d53502a060d911ff243 /src/tls
downloadhostap-6fc6879bd55a394f807cbbe927df736c190cb8ab.zip
hostap-6fc6879bd55a394f807cbbe927df736c190cb8ab.tar.gz
hostap-6fc6879bd55a394f807cbbe927df736c190cb8ab.tar.bz2
Re-initialize hostapd/wpa_supplicant git repository based on 0.6.3 release
Diffstat (limited to 'src/tls')
-rw-r--r--src/tls/.gitignore1
-rw-r--r--src/tls/Makefile6
-rw-r--r--src/tls/asn1.c209
-rw-r--r--src/tls/asn1.h71
-rw-r--r--src/tls/asn1_test.c210
-rw-r--r--src/tls/bignum.c230
-rw-r--r--src/tls/bignum.h38
-rw-r--r--src/tls/libtommath.c2370
-rw-r--r--src/tls/rsa.c359
-rw-r--r--src/tls/rsa.h29
-rw-r--r--src/tls/tlsv1_client.c658
-rw-r--r--src/tls/tlsv1_client.h59
-rw-r--r--src/tls/tlsv1_client_i.h87
-rw-r--r--src/tls/tlsv1_client_read.c976
-rw-r--r--src/tls/tlsv1_client_write.c802
-rw-r--r--src/tls/tlsv1_common.c241
-rw-r--r--src/tls/tlsv1_common.h216
-rw-r--r--src/tls/tlsv1_cred.c422
-rw-r--r--src/tls/tlsv1_cred.h46
-rw-r--r--src/tls/tlsv1_record.c409
-rw-r--r--src/tls/tlsv1_record.h74
-rw-r--r--src/tls/tlsv1_server.c596
-rw-r--r--src/tls/tlsv1_server.h54
-rw-r--r--src/tls/tlsv1_server_i.h77
-rw-r--r--src/tls/tlsv1_server_read.c1142
-rw-r--r--src/tls/tlsv1_server_write.c796
-rw-r--r--src/tls/x509v3.c1684
-rw-r--r--src/tls/x509v3.h154
28 files changed, 12016 insertions, 0 deletions
diff --git a/src/tls/.gitignore b/src/tls/.gitignore
new file mode 100644
index 0000000..a438335
--- /dev/null
+++ b/src/tls/.gitignore
@@ -0,0 +1 @@
+*.d
diff --git a/src/tls/Makefile b/src/tls/Makefile
new file mode 100644
index 0000000..37d649c
--- /dev/null
+++ b/src/tls/Makefile
@@ -0,0 +1,6 @@
+all:
+ @echo Nothing to be made.
+
+clean:
+ for d in $(SUBDIRS); do make -C $$d clean; done
+ rm -f *~ *.o *.d
diff --git a/src/tls/asn1.c b/src/tls/asn1.c
new file mode 100644
index 0000000..96bc1ac
--- /dev/null
+++ b/src/tls/asn1.c
@@ -0,0 +1,209 @@
+/*
+ * 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"
+
+#ifdef CONFIG_INTERNAL_X509
+
+#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_get_oid(const u8 *buf, size_t len, struct asn1_oid *oid,
+ const u8 **next)
+{
+ struct asn1_hdr hdr;
+ const u8 *pos, *end;
+ unsigned long val;
+ u8 tmp;
+
+ os_memset(oid, 0, sizeof(*oid));
+
+ 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;
+ }
+
+ pos = hdr.payload;
+ end = hdr.payload + hdr.length;
+ *next = end;
+
+ 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;
+}
+
+
+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;
+}
+
+#endif /* CONFIG_INTERNAL_X509 */
diff --git a/src/tls/asn1.h b/src/tls/asn1.h
new file mode 100644
index 0000000..c02ada8
--- /dev/null
+++ b/src/tls/asn1.h
@@ -0,0 +1,71 @@
+/*
+ * 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_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/asn1_test.c b/src/tls/asn1_test.c
new file mode 100644
index 0000000..a5c7753
--- /dev/null
+++ b/src/tls/asn1_test.c
@@ -0,0 +1,210 @@
+/*
+ * Testing tool for ASN.1/X.509v3 routines
+ * 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 "x509v3.h"
+
+extern int wpa_debug_level;
+
+
+static const char * asn1_class_str(int class)
+{
+ switch (class) {
+ case ASN1_CLASS_UNIVERSAL:
+ return "Universal";
+ case ASN1_CLASS_APPLICATION:
+ return "Application";
+ case ASN1_CLASS_CONTEXT_SPECIFIC:
+ return "Context-specific";
+ case ASN1_CLASS_PRIVATE:
+ return "Private";
+ default:
+ return "?";
+ }
+}
+
+
+int asn1_parse(const u8 *buf, size_t len, int level)
+{
+ const u8 *pos, *prev, *end;
+ char prefix[10], str[100];
+ int _level;
+ struct asn1_hdr hdr;
+ struct asn1_oid oid;
+ u8 tmp;
+
+ _level = level;
+ if ((size_t) _level > sizeof(prefix) - 1)
+ _level = sizeof(prefix) - 1;
+ memset(prefix, ' ', _level);
+ prefix[_level] = '\0';
+
+ pos = buf;
+ end = buf + len;
+
+ while (pos < end) {
+ if (asn1_get_next(pos, end - pos, &hdr) < 0)
+ return -1;
+
+ prev = pos;
+ pos = hdr.payload;
+
+ wpa_printf(MSG_MSGDUMP, "ASN.1:%s Class %d(%s) P/C %d(%s) "
+ "Tag %u Length %u",
+ prefix, hdr.class, asn1_class_str(hdr.class),
+ hdr.constructed,
+ hdr.constructed ? "Constructed" : "Primitive",
+ hdr.tag, hdr.length);
+
+ if (hdr.class == ASN1_CLASS_CONTEXT_SPECIFIC &&
+ hdr.constructed) {
+ if (asn1_parse(pos, hdr.length, level + 1) < 0)
+ return -1;
+ pos += hdr.length;
+ }
+
+ if (hdr.class != ASN1_CLASS_UNIVERSAL)
+ continue;
+
+ switch (hdr.tag) {
+ case ASN1_TAG_EOC:
+ if (hdr.length) {
+ wpa_printf(MSG_DEBUG, "ASN.1: Non-zero "
+ "end-of-contents length (%u)",
+ hdr.length);
+ return -1;
+ }
+ wpa_printf(MSG_MSGDUMP, "ASN.1:%s EOC", prefix);
+ break;
+ case ASN1_TAG_BOOLEAN:
+ if (hdr.length != 1) {
+ wpa_printf(MSG_DEBUG, "ASN.1: Unexpected "
+ "Boolean length (%u)", hdr.length);
+ return -1;
+ }
+ tmp = *pos++;
+ wpa_printf(MSG_MSGDUMP, "ASN.1:%s Boolean %s",
+ prefix, tmp ? "TRUE" : "FALSE");
+ break;
+ case ASN1_TAG_INTEGER:
+ wpa_hexdump(MSG_MSGDUMP, "ASN.1: INTEGER",
+ pos, hdr.length);
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_BITSTRING:
+ wpa_hexdump(MSG_MSGDUMP, "ASN.1: BitString",
+ pos, hdr.length);
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_OCTETSTRING:
+ wpa_hexdump(MSG_MSGDUMP, "ASN.1: OctetString",
+ pos, hdr.length);
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_NULL:
+ if (hdr.length) {
+ wpa_printf(MSG_DEBUG, "ASN.1: Non-zero Null "
+ "length (%u)", hdr.length);
+ return -1;
+ }
+ wpa_printf(MSG_MSGDUMP, "ASN.1:%s Null", prefix);
+ break;
+ case ASN1_TAG_OID:
+ if (asn1_get_oid(prev, end - prev, &oid, &prev) < 0) {
+ wpa_printf(MSG_DEBUG, "ASN.1: Invalid OID");
+ return -1;
+ }
+ asn1_oid_to_str(&oid, str, sizeof(str));
+ wpa_printf(MSG_DEBUG, "ASN.1:%s OID %s", prefix, str);
+ pos += hdr.length;
+ break;
+ case ANS1_TAG_RELATIVE_OID:
+ wpa_hexdump(MSG_MSGDUMP, "ASN.1: Relative OID",
+ pos, hdr.length);
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_SEQUENCE:
+ wpa_printf(MSG_MSGDUMP, "ASN.1:%s SEQUENCE", prefix);
+ if (asn1_parse(pos, hdr.length, level + 1) < 0)
+ return -1;
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_SET:
+ wpa_printf(MSG_MSGDUMP, "ASN.1:%s SET", prefix);
+ if (asn1_parse(pos, hdr.length, level + 1) < 0)
+ return -1;
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_PRINTABLESTRING:
+ wpa_hexdump_ascii(MSG_MSGDUMP,
+ "ASN.1: PrintableString",
+ pos, hdr.length);
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_IA5STRING:
+ wpa_hexdump_ascii(MSG_MSGDUMP, "ASN.1: IA5String",
+ pos, hdr.length);
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_UTCTIME:
+ wpa_hexdump_ascii(MSG_MSGDUMP, "ASN.1: UTCTIME",
+ pos, hdr.length);
+ pos += hdr.length;
+ break;
+ case ASN1_TAG_VISIBLESTRING:
+ wpa_hexdump_ascii(MSG_MSGDUMP, "ASN.1: VisibleString",
+ pos, hdr.length);
+ pos += hdr.length;
+ break;
+ default:
+ wpa_printf(MSG_DEBUG, "ASN.1: Unknown tag %d",
+ hdr.tag);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+int main(int argc, char *argv[])
+{
+ FILE *f;
+ u8 buf[3000];
+ size_t len;
+ struct x509_certificate *cert;
+
+ wpa_debug_level = 0;
+
+ f = fopen(argv[1], "rb");
+ if (f == NULL)
+ return -1;
+ len = fread(buf, 1, sizeof(buf), f);
+ fclose(f);
+
+ if (asn1_parse(buf, len, 0) < 0)
+ printf("Failed to parse DER ASN.1\n");
+
+ printf("\n\n");
+
+ cert = x509_certificate_parse(buf, len);
+ if (cert == NULL)
+ printf("Failed to parse X.509 certificate\n");
+ x509_certificate_free(cert);
+
+ return 0;
+}
diff --git a/src/tls/bignum.c b/src/tls/bignum.c
new file mode 100644
index 0000000..6b718f9
--- /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
+ * @a: 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..b637707
--- /dev/null
+++ b/src/tls/libtommath.c
@@ -0,0 +1,2370 @@
+/*
+ * Minimal code for RSA support from LibTomMath 0.3.9
+ * http://math.libtomcrypt.com/
+ * http://math.libtomcrypt.com/files/ltm-0.39.tar.bz2
+ * This library was released in public domain by Tom St Denis.
+ *
+ * The combination in this file is not using many of the optimized algorithms
+ * (e.g., Montgomery reduction) and is considerable slower than the LibTomMath
+ * with its default of SC_RSA_1 settins. 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. However, it is likely worth the
+ * effort to use the full library with SC_RSA_1 instead of this minimized copy.
+ * Including the optimized algorithms may increase the size requirements by
+ * 15 kB or so (measured with x86 build).
+ *
+ * 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 */
+
+
+/* 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);
+
+static int mp_init_multi(mp_int *mp, ...);
+static void mp_clear_multi(mp_int *mp, ...);
+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);
+static int mp_div_2(mp_int * a, mp_int * b);
+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);
+static int mp_invmod(mp_int * a, mp_int * b, mp_int * c);
+static int mp_abs(mp_int * a, mp_int * b);
+static int mp_invmod_slow(mp_int * a, mp_int * b, mp_int * c);
+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);
+
+
+
+/* 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 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
+ }
+
+/* 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;
+ }
+}
+
+
+/* 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;
+}
+
+
+/* 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));
+}
+
+
+/* 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;
+}
+
+
+/* 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;
+}
+
+
+/* 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;
+}
+
+
+/* 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;
+}
+
+
+/* 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;
+}
+
+
+/* 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;
+}
+
+
+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. */
+}
+
+
+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);
+}
+
+
+/* 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;
+}
+
+
+/* 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;
+}
+
+
+#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;
+}
diff --git a/src/tls/rsa.c b/src/tls/rsa.c
new file mode 100644
index 0000000..bfc0d52
--- /dev/null
+++ b/src/tls/rsa.c
@@ -0,0 +1,359 @@
+/*
+ * 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 "crypto.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..97a195f
--- /dev/null
+++ b/src/tls/tlsv1_client.c
@@ -0,0 +1,658 @@
+/*
+ * 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 "sha1.h"
+#include "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.
+ * 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)
+{
+#ifdef EAP_FAST
+ 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;
+ conn->num_cipher_suites = count;
+ }
+
+ return 0;
+#else /* EAP_FAST */
+ return -1;
+#endif /* EAP_FAST */
+}
+
+
+/**
+ * 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..5767fee
--- /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 "md5.h"
+#include "sha1.h"
+#include "x509v3.h"
+#include "tls.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 %d",
+ 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..e0c95cb
--- /dev/null
+++ b/src/tls/tlsv1_client_write.c
@@ -0,0 +1,802 @@
+/*
+ * 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 "md5.h"
+#include "sha1.h"
+#include "x509v3.h"
+#include "tls.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 (os_get_random(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)
+{
+#ifdef EAP_FAST
+ /* 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 (os_get_random(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;
+#else /* EAP_FAST */
+ tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_INTERNAL_ERROR);
+ return -1;
+#endif /* EAP_FAST */
+}
+
+
+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 = 1000;
+ 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..7750564
--- /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.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..d556467
--- /dev/null
+++ b/src/tls/tlsv1_cred.c
@@ -0,0 +1,422 @@
+/*
+ * 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "base64.h"
+#include "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 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 int tlsv1_set_key(struct tlsv1_credentials *cred,
+ const u8 *key, size_t len)
+{
+ cred->key = crypto_private_key_import(key, len);
+ 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);
+
+ 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);
+ 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..f226ac3
--- /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 "md5.h"
+#include "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..9170fb1
--- /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.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..c204a47
--- /dev/null
+++ b/src/tls/tlsv1_server.c
@@ -0,0 +1,596 @@
+/*
+ * 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 "sha1.h"
+#include "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)
+{
+#ifdef EAP_FAST
+ 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;
+#else /* EAP_FAST */
+ return -1;
+#endif /* EAP_FAST */
+}
+
+
+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..0e299d8
--- /dev/null
+++ b/src/tls/tlsv1_server_read.c
@@ -0,0 +1,1142 @@
+/*
+ * 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 "md5.h"
+#include "sha1.h"
+#include "x509v3.h"
+#include "tls.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;
+
+ 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) {
+ u16 ext_len;
+
+ /* 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, end - pos);
+ goto decode_error;
+ }
+
+ /*
+ * struct {
+ * ExtensionType extension_type (0..65535)
+ * opaque extension_data<0..2^16-1>
+ * } Extension;
+ */
+
+ while (pos < end) {
+ u16 ext_type, ext_len;
+
+ 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)",
+ 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)
+{
+#ifdef EAP_FAST
+ 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;
+#else /* EAP_FAST */
+ return -1;
+#endif /* EAP_FAST */
+}
+
+
+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..cf54f42
--- /dev/null
+++ b/src/tls/tlsv1_server_write.c
@@ -0,0 +1,796 @@
+/*
+ * 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 "md5.h"
+#include "sha1.h"
+#include "x509v3.h"
+#include "tls.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 (os_get_random(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 (os_get_random(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;
+#ifdef EAP_FAST
+ u8 *pos, *rhdr, *hs_start, *hs_length;
+ size_t rlen;
+ u8 *dh_ys;
+ size_t dh_ys_len;
+#endif /* EAP_FAST */
+
+ 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;
+ }
+
+#ifdef EAP_FAST
+ 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 (os_get_random(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;
+#else /* EAP_FAST */
+ return -1;
+#endif /* EAP_FAST */
+}
+
+
+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..4da4891
--- /dev/null
+++ b/src/tls/x509v3.c
@@ -0,0 +1,1684 @@
+/*
+ * 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"
+
+#ifdef CONFIG_INTERNAL_X509
+
+#include "asn1.h"
+#include "crypto.h"
+#include "x509v3.h"
+
+
+static void x509_free_name(struct x509_name *name)
+{
+ os_free(name->cn);
+ os_free(name->c);
+ os_free(name->l);
+ os_free(name->st);
+ os_free(name->o);
+ os_free(name->ou);
+ os_free(name->email);
+ name->cn = name->c = name->l = name->st = name->o = name->ou = NULL;
+ name->email = NULL;
+}
+
+
+/**
+ * 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;
+
+ if (!a && b)
+ return -1;
+ if (a && !b)
+ return 1;
+ if (!a && !b)
+ return 0;
+
+ res = x509_str_compare(a->cn, b->cn);
+ if (res)
+ return res;
+ res = x509_str_compare(a->c, b->c);
+ if (res)
+ return res;
+ res = x509_str_compare(a->l, b->l);
+ if (res)
+ return res;
+ res = x509_str_compare(a->st, b->st);
+ if (res)
+ return res;
+ res = x509_str_compare(a->o, b->o);
+ if (res)
+ return res;
+ res = x509_str_compare(a->ou, b->ou);
+ 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 **fieldp;
+
+ /*
+ * 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) {
+ 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).
+ */
+ fieldp = NULL;
+ 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 */
+ fieldp = &name->cn;
+ break;
+ case 6:
+ /* countryName */
+ fieldp = &name->c;
+ break;
+ case 7:
+ /* localityName */
+ fieldp = &name->l;
+ break;
+ case 8:
+ /* stateOrProvinceName */
+ fieldp = &name->st;
+ break;
+ case 10:
+ /* organizationName */
+ fieldp = &name->o;
+ break;
+ case 11:
+ /* organizationalUnitName */
+ fieldp = &name->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 */
+ fieldp = &name->email;
+ }
+
+ if (fieldp == NULL) {
+ 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;
+ }
+
+ os_free(*fieldp);
+ *fieldp = os_malloc(hdr.length + 1);
+ if (*fieldp == NULL) {
+ x509_free_name(name);
+ return -1;
+ }
+ os_memcpy(*fieldp, hdr.payload, hdr.length);
+ (*fieldp)[hdr.length] = '\0';
+ }
+
+ return 0;
+}
+
+
+/**
+ * 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;
+
+ if (len == 0)
+ return;
+
+ pos = buf;
+ end = buf + len;
+
+ if (name->c) {
+ ret = os_snprintf(pos, end - pos, "C=%s, ", name->c);
+ if (ret < 0 || ret >= end - pos)
+ goto done;
+ pos += ret;
+ }
+ if (name->st) {
+ ret = os_snprintf(pos, end - pos, "ST=%s, ", name->st);
+ if (ret < 0 || ret >= end - pos)
+ goto done;
+ pos += ret;
+ }
+ if (name->l) {
+ ret = os_snprintf(pos, end - pos, "L=%s, ", name->l);
+ if (ret < 0 || ret >= end - pos)
+ goto done;
+ pos += ret;
+ }
+ if (name->o) {
+ ret = os_snprintf(pos, end - pos, "O=%s, ", name->o);
+ if (ret < 0 || ret >= end - pos)
+ goto done;
+ pos += ret;
+ }
+ if (name->ou) {
+ ret = os_snprintf(pos, end - pos, "OU=%s, ", name->ou);
+ if (ret < 0 || ret >= end - pos)
+ goto done;
+ pos += ret;
+ }
+ if (name->cn) {
+ ret = os_snprintf(pos, end - pos, "CN=%s, ", name->cn);
+ if (ret < 0 || ret >= end - pos)
+ goto done;
+ pos += ret;
+ }
+
+ if (pos > buf + 1 && pos[-1] == ' ' && pos[-2] == ',') {
+ *pos-- = '\0';
+ *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_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)
+ * the subject alternative name (section 4.2.1.7)
+ * 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 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 */;
+}
+
+
+/**
+ * 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[20];
+ 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_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 2: /* md2WithRSAEncryption */
+ case 11: /* sha256WithRSAEncryption */
+ 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;
+}
+
+#endif /* CONFIG_INTERNAL_X509 */
diff --git a/src/tls/x509v3.h b/src/tls/x509v3.h
new file mode 100644
index 0000000..a52bcf8
--- /dev/null
+++ b/src/tls/x509v3.h
@@ -0,0 +1,154 @@
+/*
+ * 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 {
+ char *cn; /* commonName */
+ char *c; /* countryName */
+ char *l; /* localityName */
+ char *st; /* stateOrProvinceName */
+ char *o; /* organizationName */
+ char *ou; /* organizationalUnitName */
+ char *email; /* emailAddress */
+};
+
+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)
+
+ /* 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
+};
+
+#ifdef CONFIG_INTERNAL_X509
+
+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);
+
+#else /* CONFIG_INTERNAL_X509 */
+
+static inline void x509_certificate_free(struct x509_certificate *cert)
+{
+}
+
+static inline struct x509_certificate *
+x509_certificate_parse(const u8 *buf, size_t len)
+{
+ return NULL;
+}
+
+static inline void x509_name_string(struct x509_name *name, char *buf,
+ size_t len)
+{
+ if (len)
+ buf[0] = '\0';
+}
+
+static inline void x509_certificate_chain_free(struct x509_certificate *cert)
+{
+}
+
+static inline int
+x509_certificate_chain_validate(struct x509_certificate *trusted,
+ struct x509_certificate *chain,
+ int *reason)
+{
+ return -1;
+}
+
+static inline struct x509_certificate *
+x509_certificate_get_subject(struct x509_certificate *chain,
+ struct x509_name *name)
+{
+ return NULL;
+}
+
+static inline int x509_certificate_self_signed(struct x509_certificate *cert)
+{
+ return -1;
+}
+
+#endif /* CONFIG_INTERNAL_X509 */
+
+#endif /* X509V3_H */