Cumulative patch from commit 06f14421ea8644d12a7b0be6b583114869f9c451

06f1442 TLS: Parse OCSPResponse to extract BasicOCSPResponse
d560288 TLS: Parse CertificateStatus message
eeba168 TLS: Add status_request ClientHello extension if OCSP is requested
4303d53 TLS: Parse ServerHello extensions
6b7bb42 TLS: Add minimal support for PKCS #12
5ce2941 TLS: Extend PKCS #5 to support PKCS #12 style key decryption
f6a62df TLS: Fix and complete ASN.1 tag list
3c108b7 EAP peer: External server certificate chain validation
b6e5e14 EAP-FAST peer: Fix PAC parser error messages
5b904b3 EAP-FAST: Check T-PRF result in MSK/EMSK derivation
b1d8c5c EAP-FAST peer: Fix error path handling for Session-Id
36478a1 OpenSSL: Support new API for HMAC/EVP_MD_CTX in OpenSSL 1.1.x-pre1
9257610 FT: Fix FTIE generation for EAPOL-Key msg 3/4
e44bd28 FT: Fix sm->assoc_resp_ftie storing on the AP side
59e78c2 FT: Fix FTIE generation for 4-way handshake after FT protocol run
b0ecbd3 AP: Use more readable version of management group cipher in error cases
651c6a8 Add TEST_ASSOC_IE for WPA/RSN IE testing on AP side
58059e6 FST: Print debug entry on MB IE update based on EVENT_AUTH
af041f9 dbus: Add support for vendor specific elements
5c8acf7 EAP-IKEv2: Check HMAC SHA1/MD5 result
7b991b4 Use proper build config for parsing proxy_arp
4db29e6 TLS: Add support for PKCS #5 v2.0 PBES2

Change-Id: I10b71e4d3573ef60a52ea6ff56afcd3a06a0b7b0
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/src/tls/Makefile b/src/tls/Makefile
index 27cdfca..52a890a 100644
--- a/src/tls/Makefile
+++ b/src/tls/Makefile
@@ -24,6 +24,7 @@
 	tlsv1_client.o \
 	tlsv1_client_read.o \
 	tlsv1_client_write.o \
+	tlsv1_client_ocsp.o \
 	tlsv1_common.o \
 	tlsv1_cred.o \
 	tlsv1_record.o \
diff --git a/src/tls/asn1.h b/src/tls/asn1.h
index 7475007..6bd7df5 100644
--- a/src/tls/asn1.h
+++ b/src/tls/asn1.h
@@ -20,6 +20,7 @@
 #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_EMBEDDED_PDV	0x0B /* 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 */
@@ -35,7 +36,8 @@
 #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_TAG_CHARACTERSTRING	0x1D /* not yet parsed */
+#define ASN1_TAG_BMPSTRING	0x1E /* not yet parsed */
 
 #define ASN1_CLASS_UNIVERSAL		0
 #define ASN1_CLASS_APPLICATION		1
diff --git a/src/tls/pkcs5.c b/src/tls/pkcs5.c
index 8a93483..a2ad83b 100644
--- a/src/tls/pkcs5.c
+++ b/src/tls/pkcs5.c
@@ -1,6 +1,6 @@
 /*
  * PKCS #5 (Password-based Encryption)
- * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2009-2015, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -11,6 +11,7 @@
 #include "common.h"
 #include "crypto/crypto.h"
 #include "crypto/md5.h"
+#include "crypto/sha1.h"
 #include "asn1.h"
 #include "pkcs5.h"
 
@@ -18,30 +19,261 @@
 struct pkcs5_params {
 	enum pkcs5_alg {
 		PKCS5_ALG_UNKNOWN,
-		PKCS5_ALG_MD5_DES_CBC
+		PKCS5_ALG_MD5_DES_CBC,
+		PKCS5_ALG_PBES2,
+		PKCS5_ALG_SHA1_3DES_CBC,
 	} alg;
-	u8 salt[8];
+	u8 salt[64];
 	size_t salt_len;
 	unsigned int iter_count;
+	enum pbes2_enc_alg {
+		PBES2_ENC_ALG_UNKNOWN,
+		PBES2_ENC_ALG_DES_EDE3_CBC,
+	} enc_alg;
+	u8 iv[8];
+	size_t iv_len;
 };
 
 
+static int oid_is_rsadsi(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 pkcs5_is_oid(struct asn1_oid *oid, unsigned long alg)
+{
+	return oid->len == 7 &&
+		oid_is_rsadsi(oid) &&
+		oid->oid[4] == 1 /* pkcs */ &&
+		oid->oid[5] == 5 /* pkcs-5 */ &&
+		oid->oid[6] == alg;
+}
+
+
+static int enc_alg_is_oid(struct asn1_oid *oid, unsigned long alg)
+{
+	return oid->len == 6 &&
+		oid_is_rsadsi(oid) &&
+		oid->oid[4] == 3 /* encryptionAlgorithm */ &&
+		oid->oid[5] == alg;
+}
+
+
+static int pkcs12_is_pbe_oid(struct asn1_oid *oid, unsigned long alg)
+{
+	return oid->len == 8 &&
+		oid_is_rsadsi(oid) &&
+		oid->oid[4] == 1 /* pkcs */ &&
+		oid->oid[5] == 12 /* pkcs-12 */ &&
+		oid->oid[6] == 1 /* pkcs-12PbeIds */ &&
+		oid->oid[7] == alg;
+}
+
+
 static enum pkcs5_alg pkcs5_get_alg(struct asn1_oid *oid)
 {
-	if (oid->len == 7 &&
-	    oid->oid[0] == 1 /* iso */ &&
-	    oid->oid[1] == 2 /* member-body */ &&
-	    oid->oid[2] == 840 /* us */ &&
-	    oid->oid[3] == 113549 /* rsadsi */ &&
-	    oid->oid[4] == 1 /* pkcs */ &&
-	    oid->oid[5] == 5 /* pkcs-5 */ &&
-	    oid->oid[6] == 3 /* pbeWithMD5AndDES-CBC */)
+	if (pkcs5_is_oid(oid, 3)) /* pbeWithMD5AndDES-CBC (PBES1) */
 		return PKCS5_ALG_MD5_DES_CBC;
-
+	if (pkcs12_is_pbe_oid(oid, 3)) /* pbeWithSHAAnd3-KeyTripleDES-CBC */
+		return PKCS5_ALG_SHA1_3DES_CBC;
+	if (pkcs5_is_oid(oid, 13)) /* id-PBES2 (PBES2) */
+		return PKCS5_ALG_PBES2;
 	return PKCS5_ALG_UNKNOWN;
 }
 
 
+static int pkcs5_get_params_pbes2(struct pkcs5_params *params, const u8 *pos,
+				  const u8 *enc_alg_end)
+{
+	struct asn1_hdr hdr;
+	const u8 *end, *kdf_end;
+	struct asn1_oid oid;
+	char obuf[80];
+
+	/*
+	 * RFC 2898, Ch. A.4
+	 *
+	 * PBES2-params ::= SEQUENCE {
+	 *     keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
+	 *     encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} }
+	 *
+	 * PBES2-KDFs ALGORITHM-IDENTIFIER ::=
+	 *     { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+	 */
+
+	if (asn1_get_next(pos, enc_alg_end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Expected SEQUENCE (PBES2-params) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Expected SEQUENCE (keyDerivationFunc) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	kdf_end = end = hdr.payload + hdr.length;
+
+	if (asn1_get_oid(pos, end - pos, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Failed to parse OID (keyDerivationFunc algorithm)");
+		return -1;
+	}
+
+	asn1_oid_to_str(&oid, obuf, sizeof(obuf));
+	wpa_printf(MSG_DEBUG, "PKCS #5: PBES2 keyDerivationFunc algorithm %s",
+		   obuf);
+	if (!pkcs5_is_oid(&oid, 12)) /* id-PBKDF2 */ {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Unsupported PBES2 keyDerivationFunc algorithm %s",
+			   obuf);
+		return -1;
+	}
+
+	/*
+	 * RFC 2898, C.
+	 *
+	 * PBKDF2-params ::= SEQUENCE {
+	 *     salt CHOICE {
+	 *       specified OCTET STRING,
+	 *       otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
+	 *     },
+	 *     iterationCount INTEGER (1..MAX),
+	 *     keyLength INTEGER (1..MAX) OPTIONAL,
+	 *     prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT
+	 *     algid-hmacWithSHA1
+	 * }
+	 */
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Expected SEQUENCE (PBKDF2-params) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	/* For now, only support the salt CHOICE specified (OCTET STRING) */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING ||
+	    hdr.length > sizeof(params->salt)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Expected OCTET STRING (salt.specified) - found class %d tag 0x%x size %d",
+			   hdr.class, hdr.tag, hdr.length);
+		return -1;
+	}
+	pos = hdr.payload + hdr.length;
+	os_memcpy(params->salt, hdr.payload, hdr.length);
+	params->salt_len = hdr.length;
+	wpa_hexdump(MSG_DEBUG, "PKCS #5: salt", params->salt, params->salt_len);
+
+	/* iterationCount INTEGER */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL || hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Expected INTEGER - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	if (hdr.length == 1) {
+		params->iter_count = *hdr.payload;
+	} else if (hdr.length == 2) {
+		params->iter_count = WPA_GET_BE16(hdr.payload);
+	} else if (hdr.length == 4) {
+		params->iter_count = WPA_GET_BE32(hdr.payload);
+	} else {
+		wpa_hexdump(MSG_DEBUG,
+			    "PKCS #5: Unsupported INTEGER value (iterationCount)",
+			    hdr.payload, hdr.length);
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG, "PKCS #5: iterationCount=0x%x",
+		   params->iter_count);
+	if (params->iter_count == 0 || params->iter_count > 0xffff) {
+		wpa_printf(MSG_INFO, "PKCS #5: Unsupported iterationCount=0x%x",
+			   params->iter_count);
+		return -1;
+	}
+
+	/* For now, ignore optional keyLength and prf */
+
+	pos = kdf_end;
+
+	/* encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} */
+
+	if (asn1_get_next(pos, enc_alg_end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Expected SEQUENCE (encryptionScheme) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	if (asn1_get_oid(pos, end - pos, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Failed to parse OID (encryptionScheme algorithm)");
+		return -1;
+	}
+
+	asn1_oid_to_str(&oid, obuf, sizeof(obuf));
+	wpa_printf(MSG_DEBUG, "PKCS #5: PBES2 encryptionScheme algorithm %s",
+		   obuf);
+	if (enc_alg_is_oid(&oid, 7)) {
+		params->enc_alg = PBES2_ENC_ALG_DES_EDE3_CBC;
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Unsupported PBES2 encryptionScheme algorithm %s",
+			   obuf);
+		return -1;
+	}
+
+	/*
+	 * RFC 2898, B.2.2:
+	 * The parameters field associated with this OID in an
+	 * AlgorithmIdentifier shall have type OCTET STRING (SIZE(8)),
+	 * specifying the initialization vector for CBC mode.
+	 */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING ||
+	    hdr.length != 8) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #5: Expected OCTET STRING (SIZE(8)) (IV) - found class %d tag 0x%x size %d",
+			   hdr.class, hdr.tag, hdr.length);
+		return -1;
+	}
+	os_memcpy(params->iv, hdr.payload, hdr.length);
+	params->iv_len = hdr.length;
+	wpa_hexdump(MSG_DEBUG, "PKCS #5: IV", params->iv, params->iv_len);
+
+	return 0;
+}
+
+
 static int pkcs5_get_params(const u8 *enc_alg, size_t enc_alg_len,
 			    struct pkcs5_params *params)
 {
@@ -71,11 +303,23 @@
 		return -1;
 	}
 
+	if (params->alg == PKCS5_ALG_PBES2)
+		return pkcs5_get_params_pbes2(params, pos, enc_alg_end);
+
+	/* PBES1 */
+
 	/*
 	 * PKCS#5, Section 8
 	 * PBEParameter ::= SEQUENCE {
 	 *   salt OCTET STRING SIZE(8),
 	 *   iterationCount INTEGER }
+	 *
+	 * Note: The same implementation can be used to parse the PKCS #12
+	 * version described in RFC 7292, C:
+	 * pkcs-12PbeParams ::= SEQUENCE {
+	 *     salt        OCTET STRING,
+	 *     iterations  INTEGER
+	 * }
 	 */
 
 	if (asn1_get_next(pos, enc_alg_end - pos, &hdr) < 0 ||
@@ -89,11 +333,11 @@
 	pos = hdr.payload;
 	end = hdr.payload + hdr.length;
 
-	/* salt OCTET STRING SIZE(8) */
+	/* salt OCTET STRING SIZE(8) (PKCS #5) or OCTET STRING (PKCS #12) */
 	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
 	    hdr.class != ASN1_CLASS_UNIVERSAL ||
 	    hdr.tag != ASN1_TAG_OCTETSTRING ||
-	    hdr.length != 8) {
+	    hdr.length > sizeof(params->salt)) {
 		wpa_printf(MSG_DEBUG, "PKCS #5: Expected OCTETSTRING SIZE(8) "
 			   "(salt) - found class %d tag 0x%x size %d",
 			   hdr.class, hdr.tag, hdr.length);
@@ -136,6 +380,174 @@
 }
 
 
+static struct crypto_cipher *
+pkcs5_crypto_init_pbes2(struct pkcs5_params *params, const char *passwd)
+{
+	u8 key[24];
+
+	if (params->enc_alg != PBES2_ENC_ALG_DES_EDE3_CBC ||
+	    params->iv_len != 8)
+		return NULL;
+
+	wpa_hexdump_ascii_key(MSG_DEBUG, "PKCS #5: PBES2 password for PBKDF2",
+			      passwd, os_strlen(passwd));
+	wpa_hexdump(MSG_DEBUG, "PKCS #5: PBES2 salt for PBKDF2",
+		    params->salt, params->salt_len);
+	wpa_printf(MSG_DEBUG, "PKCS #5: PBES2 PBKDF2 iterations: %u",
+		   params->iter_count);
+	if (pbkdf2_sha1(passwd, params->salt, params->salt_len,
+			params->iter_count, key, sizeof(key)) < 0)
+		return NULL;
+	wpa_hexdump_key(MSG_DEBUG, "PKCS #5: DES EDE3 key", key, sizeof(key));
+	wpa_hexdump(MSG_DEBUG, "PKCS #5: DES IV", params->iv, params->iv_len);
+
+	return crypto_cipher_init(CRYPTO_CIPHER_ALG_3DES, params->iv,
+				  key, sizeof(key));
+}
+
+
+static void add_byte_array_mod(u8 *a, const u8 *b, size_t len)
+{
+	size_t i;
+	unsigned int carry = 0;
+
+	for (i = len - 1; i < len; i--) {
+		carry = carry + a[i] + b[i];
+		a[i] = carry & 0xff;
+		carry >>= 8;
+	}
+}
+
+
+static int pkcs12_key_gen(const u8 *pw, size_t pw_len, const u8 *salt,
+			  size_t salt_len, u8 id, unsigned int iter,
+			  size_t out_len, u8 *out)
+{
+	unsigned int u, v, S_len, P_len, i;
+	u8 *D = NULL, *I = NULL, *B = NULL, *pos;
+	int res = -1;
+
+	/* RFC 7292, B.2 */
+	u = SHA1_MAC_LEN;
+	v = 64;
+
+	/* D = copies of ID */
+	D = os_malloc(v);
+	if (!D)
+		goto done;
+	os_memset(D, id, v);
+
+	/* S = copies of salt; P = copies of password, I = S || P */
+	S_len = v * ((salt_len + v - 1) / v);
+	P_len = v * ((pw_len + v - 1) / v);
+	I = os_malloc(S_len + P_len);
+	if (!I)
+		goto done;
+	pos = I;
+	if (salt_len) {
+		for (i = 0; i < S_len; i++)
+			*pos++ = salt[i % salt_len];
+	}
+	if (pw_len) {
+		for (i = 0; i < P_len; i++)
+			*pos++ = pw[i % pw_len];
+	}
+
+	B = os_malloc(v);
+	if (!B)
+		goto done;
+
+	for (;;) {
+		u8 hash[SHA1_MAC_LEN];
+		const u8 *addr[2];
+		size_t len[2];
+
+		addr[0] = D;
+		len[0] = v;
+		addr[1] = I;
+		len[1] = S_len + P_len;
+		if (sha1_vector(2, addr, len, hash) < 0)
+			goto done;
+
+		addr[0] = hash;
+		len[0] = SHA1_MAC_LEN;
+		for (i = 1; i < iter; i++) {
+			if (sha1_vector(1, addr, len, hash) < 0)
+				goto done;
+		}
+
+		if (out_len <= u) {
+			os_memcpy(out, hash, out_len);
+			res = 0;
+			goto done;
+		}
+
+		os_memcpy(out, hash, u);
+		out += u;
+		out_len -= u;
+
+		/* I_j = (I_j + B + 1) mod 2^(v*8) */
+		/* B = copies of Ai (final hash value) */
+		for (i = 0; i < v; i++)
+			B[i] = hash[i % u];
+		inc_byte_array(B, v);
+		for (i = 0; i < S_len + P_len; i += v)
+			add_byte_array_mod(&I[i], B, v);
+	}
+
+done:
+	os_free(B);
+	os_free(I);
+	os_free(D);
+	return res;
+}
+
+
+#define PKCS12_ID_ENC 1
+#define PKCS12_ID_IV 2
+#define PKCS12_ID_MAC 3
+
+static struct crypto_cipher *
+pkcs12_crypto_init_sha1(struct pkcs5_params *params, const char *passwd)
+{
+	unsigned int i;
+	u8 *pw;
+	size_t pw_len;
+	u8 key[24];
+	u8 iv[8];
+
+	if (params->alg != PKCS5_ALG_SHA1_3DES_CBC)
+		return NULL;
+
+	pw_len = passwd ? os_strlen(passwd) : 0;
+	pw = os_malloc(2 * (pw_len + 1));
+	if (!pw)
+		return NULL;
+	if (pw_len) {
+		for (i = 0; i <= pw_len; i++)
+			WPA_PUT_BE16(&pw[2 * i], passwd[i]);
+		pw_len = 2 * (pw_len + 1);
+	}
+
+	if (pkcs12_key_gen(pw, pw_len, params->salt, params->salt_len,
+			   PKCS12_ID_ENC, params->iter_count,
+			   sizeof(key), key) < 0 ||
+	    pkcs12_key_gen(pw, pw_len, params->salt, params->salt_len,
+			   PKCS12_ID_IV, params->iter_count,
+			   sizeof(iv), iv) < 0) {
+		os_free(pw);
+		return NULL;
+	}
+
+	os_free(pw);
+
+	wpa_hexdump_key(MSG_DEBUG, "PKCS #12: DES key", key, sizeof(key));
+	wpa_hexdump_key(MSG_DEBUG, "PKCS #12: DES IV", iv, sizeof(iv));
+
+	return crypto_cipher_init(CRYPTO_CIPHER_ALG_3DES, iv, key, sizeof(key));
+}
+
+
 static struct crypto_cipher * pkcs5_crypto_init(struct pkcs5_params *params,
 						const char *passwd)
 {
@@ -144,6 +556,12 @@
 	const u8 *addr[2];
 	size_t len[2];
 
+	if (params->alg == PKCS5_ALG_PBES2)
+		return pkcs5_crypto_init_pbes2(params, passwd);
+
+	if (params->alg == PKCS5_ALG_SHA1_3DES_CBC)
+		return pkcs12_crypto_init_sha1(params, passwd);
+
 	if (params->alg != PKCS5_ALG_MD5_DES_CBC)
 		return NULL;
 
diff --git a/src/tls/tlsv1_client.c b/src/tls/tlsv1_client.c
index 846d293..cc404c1 100644
--- a/src/tls/tlsv1_client.c
+++ b/src/tls/tlsv1_client.c
@@ -1,6 +1,6 @@
 /*
  * TLS v1.0/v1.1/v1.2 client (RFC 2246, RFC 4346, RFC 5246)
- * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2006-2015, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -11,6 +11,7 @@
 #include "common.h"
 #include "crypto/sha1.h"
 #include "crypto/tls.h"
+#include "x509v3.h"
 #include "tlsv1_common.h"
 #include "tlsv1_record.h"
 #include "tlsv1_client.h"
@@ -494,6 +495,7 @@
 	tlsv1_client_free_dh(conn);
 	tlsv1_cred_free(conn->cred);
 	wpabuf_free(conn->partial_input);
+	x509_certificate_chain_free(conn->server_cert);
 	os_free(conn);
 }
 
diff --git a/src/tls/tlsv1_client_i.h b/src/tls/tlsv1_client_i.h
index 6c4dbc7..12ec8df 100644
--- a/src/tls/tlsv1_client_i.h
+++ b/src/tls/tlsv1_client_i.h
@@ -36,6 +36,7 @@
 	unsigned int session_ticket_included:1;
 	unsigned int use_session_ticket:1;
 	unsigned int cert_in_cb:1;
+	unsigned int ocsp_resp_received:1;
 
 	struct crypto_public_key *server_rsa_key;
 
@@ -70,6 +71,8 @@
 	void (*event_cb)(void *ctx, enum tls_event ev,
 			 union tls_event_data *data);
 	void *cb_ctx;
+
+	struct x509_certificate *server_cert;
 };
 
 
@@ -87,4 +90,11 @@
 				   const u8 *buf, size_t *len,
 				   u8 **out_data, size_t *out_len);
 
+enum tls_ocsp_result {
+	TLS_OCSP_NO_RESPONSE, TLS_OCSP_INVALID, TLS_OCSP_GOOD, TLS_OCSP_REVOKED
+};
+
+enum tls_ocsp_result tls_process_ocsp_response(struct tlsv1_client *conn,
+					       const u8 *resp, size_t len);
+
 #endif /* TLSV1_CLIENT_I_H */
diff --git a/src/tls/tlsv1_client_ocsp.c b/src/tls/tlsv1_client_ocsp.c
new file mode 100644
index 0000000..bcc7a86
--- /dev/null
+++ b/src/tls/tlsv1_client_ocsp.c
@@ -0,0 +1,169 @@
+/*
+ * TLSv1 client - OCSP
+ * Copyright (c) 2015, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/tls.h"
+#include "asn1.h"
+#include "tlsv1_common.h"
+#include "tlsv1_record.h"
+#include "tlsv1_client.h"
+#include "tlsv1_client_i.h"
+
+
+/* RFC 6960, 4.2.1: OCSPResponseStatus ::= ENUMERATED */
+enum ocsp_response_status {
+	OCSP_RESP_STATUS_SUCCESSFUL = 0,
+	OCSP_RESP_STATUS_MALFORMED_REQ = 1,
+	OCSP_RESP_STATUS_INT_ERROR = 2,
+	OCSP_RESP_STATUS_TRY_LATER = 3,
+	/* 4 not used */
+	OCSP_RESP_STATUS_SIG_REQUIRED = 5,
+	OCSP_RESP_STATUS_UNAUTHORIZED = 6,
+};
+
+
+static int is_oid_basic_ocsp_resp(struct asn1_oid *oid)
+{
+	return oid->len == 10 &&
+		oid->oid[0] == 1 /* iso */ &&
+		oid->oid[1] == 3 /* identified-organization */ &&
+		oid->oid[2] == 6 /* dod */ &&
+		oid->oid[3] == 1 /* internet */ &&
+		oid->oid[4] == 5 /* security */ &&
+		oid->oid[5] == 5 /* mechanisms */ &&
+		oid->oid[6] == 7 /* id-pkix */ &&
+		oid->oid[7] == 48 /* id-ad */ &&
+		oid->oid[8] == 1 /* id-pkix-ocsp */ &&
+		oid->oid[9] == 1 /* id-pkix-ocsp-basic */;
+}
+
+
+static enum tls_ocsp_result
+tls_process_basic_ocsp_response(struct tlsv1_client *conn, const u8 *resp,
+				size_t len)
+{
+	wpa_hexdump(MSG_MSGDUMP, "OCSP: BasicOCSPResponse", resp, len);
+
+	/*
+	 * RFC 6960, 4.2.1:
+	 * BasicOCSPResponse       ::= SEQUENCE {
+	 *    tbsResponseData      ResponseData,
+	 *    signatureAlgorithm   AlgorithmIdentifier,
+	 *    signature            BIT STRING,
+	 *    certs            [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+	 */
+
+	/* TODO */
+	return TLS_OCSP_NO_RESPONSE;
+}
+
+
+enum tls_ocsp_result tls_process_ocsp_response(struct tlsv1_client *conn,
+					       const u8 *resp, size_t len)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+	u8 resp_status;
+	struct asn1_oid oid;
+	char obuf[80];
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: OCSPResponse", resp, len);
+
+	/*
+	 * RFC 6960, 4.2.1:
+	 * OCSPResponse ::= SEQUENCE {
+	 *    responseStatus  OCSPResponseStatus,
+	 *    responseBytes   [0] EXPLICIT ResponseBytes OPTIONAL }
+	 */
+
+	if (asn1_get_next(resp, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "OCSP: Expected SEQUENCE (OCSPResponse) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return TLS_OCSP_INVALID;
+	}
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	/* OCSPResponseStatus ::= ENUMERATED */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_ENUMERATED ||
+	    hdr.length != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "OCSP: Expected ENUMERATED (responseStatus) - found class %d tag 0x%x length %u",
+			   hdr.class, hdr.tag, hdr.length);
+		return TLS_OCSP_INVALID;
+	}
+	resp_status = hdr.payload[0];
+	wpa_printf(MSG_DEBUG, "OCSP: responseStatus %u", resp_status);
+	pos = hdr.payload + hdr.length;
+	if (resp_status != OCSP_RESP_STATUS_SUCCESSFUL) {
+		wpa_printf(MSG_DEBUG, "OCSP: No stapling result");
+		return TLS_OCSP_NO_RESPONSE;
+	}
+
+	/* responseBytes   [0] EXPLICIT ResponseBytes OPTIONAL */
+	if (pos == end)
+		return TLS_OCSP_NO_RESPONSE;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC ||
+	    hdr.tag != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "OCSP: Expected [0] EXPLICIT (responseBytes) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return TLS_OCSP_INVALID;
+	}
+
+	/*
+	 * ResponseBytes ::= SEQUENCE {
+	 *     responseType   OBJECT IDENTIFIER,
+	 *     response       OCTET STRING }
+	 */
+
+	if (asn1_get_next(hdr.payload, hdr.length, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "OCSP: Expected SEQUENCE (ResponseBytes) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return TLS_OCSP_INVALID;
+	}
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	/* responseType   OBJECT IDENTIFIER */
+	if (asn1_get_oid(pos, end - pos, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG,
+			   "OCSP: Failed to parse OID (responseType)");
+		return TLS_OCSP_INVALID;
+	}
+	asn1_oid_to_str(&oid, obuf, sizeof(obuf));
+	wpa_printf(MSG_DEBUG, "OCSP: responseType %s", obuf);
+	if (!is_oid_basic_ocsp_resp(&oid)) {
+		wpa_printf(MSG_DEBUG, "OCSP: Ignore unsupported response type");
+		return TLS_OCSP_NO_RESPONSE;
+	}
+
+	/* response       OCTET STRING */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING) {
+		wpa_printf(MSG_DEBUG,
+			   "OCSP: Expected OCTET STRING (response) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return TLS_OCSP_INVALID;
+	}
+
+	return tls_process_basic_ocsp_response(conn, hdr.payload, hdr.length);
+}
diff --git a/src/tls/tlsv1_client_read.c b/src/tls/tlsv1_client_read.c
index 40c6a46..b1fa15f 100644
--- a/src/tls/tlsv1_client_read.c
+++ b/src/tls/tlsv1_client_read.c
@@ -1,6 +1,6 @@
 /*
  * TLSv1 client - read handshake message
- * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2006-2015, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -38,6 +38,43 @@
 }
 
 
+static int tls_process_server_hello_extensions(struct tlsv1_client *conn,
+					       const u8 *pos, size_t len)
+{
+	const u8 *end = pos + len;
+
+	wpa_hexdump(MSG_MSGDUMP, "TLSv1: ServerHello extensions",
+		    pos, len);
+	while (pos < end) {
+		u16 ext, elen;
+
+		if (end - pos < 4) {
+			wpa_printf(MSG_INFO, "TLSv1: Truncated ServerHello extension header");
+			return -1;
+		}
+
+		ext = WPA_GET_BE16(pos);
+		pos += 2;
+		elen = WPA_GET_BE16(pos);
+		pos += 2;
+
+		if (elen > end - pos) {
+			wpa_printf(MSG_INFO, "TLSv1: Truncated ServerHello extension");
+			return -1;
+		}
+
+		wpa_printf(MSG_DEBUG, "TLSv1: ServerHello ExtensionType %u",
+			   ext);
+		wpa_hexdump(MSG_DEBUG, "TLSv1: ServerHello extension data",
+			    pos, elen);
+
+		pos += elen;
+	}
+
+	return 0;
+}
+
+
 static int tls_process_server_hello(struct tlsv1_client *conn, u8 ct,
 				    const u8 *in_data, size_t *in_len)
 {
@@ -177,8 +214,24 @@
 	}
 	pos++;
 
+	if (end - pos >= 2) {
+		u16 ext_len;
+
+		ext_len = WPA_GET_BE16(pos);
+		pos += 2;
+		if (end - pos < ext_len) {
+			wpa_printf(MSG_INFO,
+				   "TLSv1: Invalid ServerHello extension length: %u (left: %u)",
+				   ext_len, (unsigned int) (end - pos));
+			goto decode_error;
+		}
+
+		if (tls_process_server_hello_extensions(conn, pos, ext_len))
+			goto decode_error;
+		pos += ext_len;
+	}
+
 	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;
@@ -561,7 +614,12 @@
 		return -1;
 	}
 
-	x509_certificate_chain_free(chain);
+	if (conn->flags & TLS_CONN_REQUEST_OCSP) {
+		x509_certificate_chain_free(conn->server_cert);
+		conn->server_cert = chain;
+	} else {
+		x509_certificate_chain_free(chain);
+	}
 
 	*in_len = end - in_data;
 
@@ -732,6 +790,134 @@
 }
 
 
+static int tls_process_certificate_status(struct tlsv1_client *conn, u8 ct,
+					   const u8 *in_data, size_t *in_len)
+{
+	const u8 *pos, *end;
+	size_t left, len;
+	u8 type, status_type;
+	u32 ocsp_resp_len;
+	enum tls_ocsp_result res;
+
+	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 CertificateStatus (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 CertificateStatus 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_STATUS) {
+		wpa_printf(MSG_DEBUG,
+			   "TLSv1: Received unexpected handshake message %d (expected CertificateStatus)",
+			   type);
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_UNEXPECTED_MESSAGE);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "TLSv1: Received CertificateStatus");
+
+	/*
+	 * struct {
+	 *     CertificateStatusType status_type;
+	 *     select (status_type) {
+	 *         case ocsp: OCSPResponse;
+	 *     } response;
+	 * } CertificateStatus;
+	 */
+	if (end - pos < 1) {
+		wpa_printf(MSG_INFO, "TLSv1: Too short CertificateStatus");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+	status_type = *pos++;
+	wpa_printf(MSG_DEBUG, "TLSv1: CertificateStatus status_type %u",
+		   status_type);
+
+	if (status_type != 1 /* ocsp */) {
+		wpa_printf(MSG_DEBUG,
+			   "TLSv1: Ignore unsupported CertificateStatus");
+		goto skip;
+	}
+
+	/* opaque OCSPResponse<1..2^24-1>; */
+	if (end - pos < 3) {
+		wpa_printf(MSG_INFO, "TLSv1: Too short OCSPResponse");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+	ocsp_resp_len = WPA_GET_BE24(pos);
+	pos += 3;
+	if (end - pos < ocsp_resp_len) {
+		wpa_printf(MSG_INFO, "TLSv1: Truncated OCSPResponse");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	}
+
+	res = tls_process_ocsp_response(conn, pos, ocsp_resp_len);
+	switch (res) {
+	case TLS_OCSP_NO_RESPONSE:
+		if (!(conn->flags & TLS_CONN_REQUIRE_OCSP))
+			goto skip;
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_BAD_CERTIFICATE_STATUS_RESPONSE);
+		return -1;
+	case TLS_OCSP_INVALID:
+		if (!(conn->flags & TLS_CONN_REQUIRE_OCSP))
+			goto skip; /* ignore - process as if no response */
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR);
+		return -1;
+	case TLS_OCSP_GOOD:
+		wpa_printf(MSG_DEBUG, "TLSv1: OCSP response good");
+		break;
+	case TLS_OCSP_REVOKED:
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_CERTIFICATE_REVOKED);
+		if (conn->server_cert)
+			tls_cert_chain_failure_event(
+				conn, 0, conn->server_cert, TLS_FAIL_REVOKED,
+				"certificate revoked");
+		return -1;
+	}
+	conn->ocsp_resp_received = 1;
+
+skip:
+	*in_len = end - in_data;
+
+	conn->state = SERVER_KEY_EXCHANGE;
+
+	return 0;
+}
+
+
 static int tls_process_server_key_exchange(struct tlsv1_client *conn, u8 ct,
 					   const u8 *in_data, size_t *in_len)
 {
@@ -773,6 +959,10 @@
 
 	end = pos + len;
 
+	if ((conn->flags & TLS_CONN_REQUEST_OCSP) &&
+	    type == TLS_HANDSHAKE_TYPE_CERTIFICATE_STATUS)
+		return tls_process_certificate_status(conn, ct, in_data,
+						      in_len);
 	if (type == TLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST)
 		return tls_process_certificate_request(conn, ct, in_data,
 						       in_len);
@@ -782,7 +972,9 @@
 	if (type != TLS_HANDSHAKE_TYPE_SERVER_KEY_EXCHANGE) {
 		wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake "
 			   "message %d (expected ServerKeyExchange/"
-			   "CertificateRequest/ServerHelloDone)", type);
+			   "CertificateRequest/ServerHelloDone%s)", type,
+			   (conn->flags & TLS_CONN_REQUEST_OCSP) ?
+			   "/CertificateStatus" : "");
 		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
 			  TLS_ALERT_UNEXPECTED_MESSAGE);
 		return -1;
@@ -936,6 +1128,15 @@
 
 	wpa_printf(MSG_DEBUG, "TLSv1: Received ServerHelloDone");
 
+	if ((conn->flags & TLS_CONN_REQUIRE_OCSP) &&
+	    !conn->ocsp_resp_received) {
+		wpa_printf(MSG_INFO,
+			   "TLSv1: No OCSP response received - reject handshake");
+		tls_alert(conn, TLS_ALERT_LEVEL_FATAL,
+			  TLS_ALERT_BAD_CERTIFICATE_STATUS_RESPONSE);
+		return -1;
+	}
+
 	*in_len = end - in_data;
 
 	conn->state = CLIENT_KEY_EXCHANGE;
diff --git a/src/tls/tlsv1_client_write.c b/src/tls/tlsv1_client_write.c
index b1906b2..8e8cb5e 100644
--- a/src/tls/tlsv1_client_write.c
+++ b/src/tls/tlsv1_client_write.c
@@ -1,6 +1,6 @@
 /*
  * TLSv1 client - write handshake message
- * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2006-2015, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -156,6 +156,44 @@
 		pos += conn->client_hello_ext_len;
 	}
 
+	if (conn->flags & TLS_CONN_REQUEST_OCSP) {
+		wpa_printf(MSG_DEBUG,
+			   "TLSv1: Add status_request extension for OCSP stapling");
+		/* ExtensionsType extension_type = status_request(5) */
+		WPA_PUT_BE16(pos, TLS_EXT_STATUS_REQUEST);
+		pos += 2;
+		/* opaque extension_data<0..2^16-1> length */
+		WPA_PUT_BE16(pos, 5);
+		pos += 2;
+
+		/*
+		 * RFC 6066, 8:
+		 * struct {
+		 *     CertificateStatusType status_type;
+		 *     select (status_type) {
+		 *         case ocsp: OCSPStatusRequest;
+		 *     } request;
+		 * } CertificateStatusRequest;
+		 *
+		 * enum { ocsp(1), (255) } CertificateStatusType;
+		 */
+		*pos++ = 1; /* status_type = ocsp(1) */
+
+		/*
+		 * struct {
+		 *     ResponderID responder_id_list<0..2^16-1>;
+		 *     Extensions  request_extensions;
+		 * } OCSPStatusRequest;
+		 *
+		 * opaque ResponderID<1..2^16-1>;
+		 * opaque Extensions<0..2^16-1>;
+		 */
+		WPA_PUT_BE16(pos, 0); /* responder_id_list(empty) */
+		pos += 2;
+		WPA_PUT_BE16(pos, 0); /* request_extensions(empty) */
+		pos += 2;
+	}
+
 	if (pos == ext_start + 2)
 		pos -= 2; /* no extensions */
 	else
diff --git a/src/tls/tlsv1_cred.c b/src/tls/tlsv1_cred.c
index 067562b..92f97c7 100644
--- a/src/tls/tlsv1_cred.c
+++ b/src/tls/tlsv1_cred.c
@@ -1,6 +1,6 @@
 /*
  * TLSv1 credentials
- * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2006-2015, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -11,6 +11,9 @@
 #include "common.h"
 #include "base64.h"
 #include "crypto/crypto.h"
+#include "crypto/sha1.h"
+#include "pkcs5.h"
+#include "pkcs8.h"
 #include "x509v3.h"
 #include "tlsv1_cred.h"
 
@@ -325,6 +328,735 @@
 }
 
 
+#ifdef PKCS12_FUNCS
+
+static int oid_is_rsadsi(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 pkcs12_is_bagtype_oid(struct asn1_oid *oid, unsigned long type)
+{
+	return oid->len == 9 &&
+		oid_is_rsadsi(oid) &&
+		oid->oid[4] == 1 /* pkcs */ &&
+		oid->oid[5] == 12 /* pkcs-12 */ &&
+		oid->oid[6] == 10 &&
+		oid->oid[7] == 1 /* bagtypes */ &&
+		oid->oid[8] == type;
+}
+
+
+static int is_oid_pkcs7(struct asn1_oid *oid)
+{
+	return oid->len == 7 &&
+		oid->oid[0] == 1 /* iso */ &&
+		oid->oid[1] == 2 /* member-body */ &&
+		oid->oid[2] == 840 /* us */ &&
+		oid->oid[3] == 113549 /* rsadsi */ &&
+		oid->oid[4] == 1 /* pkcs */ &&
+		oid->oid[5] == 7 /* pkcs-7 */;
+}
+
+
+static int is_oid_pkcs7_data(struct asn1_oid *oid)
+{
+	return is_oid_pkcs7(oid) && oid->oid[6] == 1 /* data */;
+}
+
+
+static int is_oid_pkcs7_enc_data(struct asn1_oid *oid)
+{
+	return is_oid_pkcs7(oid) && oid->oid[6] == 6 /* encryptedData */;
+}
+
+
+static int is_oid_pkcs9(struct asn1_oid *oid)
+{
+	return oid->len >= 6 &&
+		oid->oid[0] == 1 /* iso */ &&
+		oid->oid[1] == 2 /* member-body */ &&
+		oid->oid[2] == 840 /* us */ &&
+		oid->oid[3] == 113549 /* rsadsi */ &&
+		oid->oid[4] == 1 /* pkcs */ &&
+		oid->oid[5] == 9 /* pkcs-9 */;
+}
+
+
+static int is_oid_pkcs9_friendly_name(struct asn1_oid *oid)
+{
+	return oid->len == 7 && is_oid_pkcs9(oid) &&
+		oid->oid[6] == 20;
+}
+
+
+static int is_oid_pkcs9_local_key_id(struct asn1_oid *oid)
+{
+	return oid->len == 7 && is_oid_pkcs9(oid) &&
+		oid->oid[6] == 21;
+}
+
+
+static int is_oid_pkcs9_x509_cert(struct asn1_oid *oid)
+{
+	return oid->len == 8 && is_oid_pkcs9(oid) &&
+		oid->oid[6] == 22 /* certTypes */ &&
+		oid->oid[7] == 1 /* x509Certificate */;
+}
+
+
+static int pkcs12_keybag(struct tlsv1_credentials *cred,
+			 const u8 *buf, size_t len)
+{
+	/* TODO */
+	return 0;
+}
+
+
+static int pkcs12_pkcs8_keybag(struct tlsv1_credentials *cred,
+			       const u8 *buf, size_t len,
+			       const char *passwd)
+{
+	struct crypto_private_key *key;
+
+	/* PKCS8ShroudedKeyBag ::= EncryptedPrivateKeyInfo */
+	key = pkcs8_enc_key_import(buf, len, passwd);
+	if (!key)
+		return -1;
+
+	wpa_printf(MSG_DEBUG,
+		   "PKCS #12: Successfully decrypted PKCS8ShroudedKeyBag");
+	crypto_private_key_free(cred->key);
+	cred->key = key;
+
+	return 0;
+}
+
+
+static int pkcs12_certbag(struct tlsv1_credentials *cred,
+			  const u8 *buf, size_t len)
+{
+	struct asn1_hdr hdr;
+	struct asn1_oid oid;
+	char obuf[80];
+	const u8 *pos, *end;
+
+	/*
+	 * CertBag ::= SEQUENCE {
+	 *     certId      BAG-TYPE.&id   ({CertTypes}),
+	 *     certValue   [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId})
+	 * }
+	 */
+
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected SEQUENCE (CertBag) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	if (asn1_get_oid(pos, end - pos, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Failed to parse OID (certId)");
+		return -1;
+	}
+
+	asn1_oid_to_str(&oid, obuf, sizeof(obuf));
+	wpa_printf(MSG_DEBUG, "PKCS #12: certId %s", obuf);
+
+	if (!is_oid_pkcs9_x509_cert(&oid)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Ignored unsupported certificate type (certId %s)",
+			   obuf);
+	}
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC ||
+	    hdr.tag != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected [0] EXPLICIT (certValue) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	if (asn1_get_next(hdr.payload, hdr.length, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected OCTET STRING (x509Certificate) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "PKCS #12: x509Certificate",
+		    hdr.payload, hdr.length);
+	if (cred->cert) {
+		struct x509_certificate *cert;
+
+		wpa_printf(MSG_DEBUG, "PKCS #12: Ignore extra certificate");
+		cert = x509_certificate_parse(hdr.payload, hdr.length);
+		if (!cert) {
+			wpa_printf(MSG_DEBUG,
+				   "PKCS #12: Failed to parse x509Certificate");
+			return 0;
+		}
+		x509_certificate_chain_free(cert);
+
+		return 0;
+	}
+	return tlsv1_set_cert(cred, NULL, hdr.payload, hdr.length);
+}
+
+
+static int pkcs12_parse_attr_friendly_name(const u8 *pos, const u8 *end)
+{
+	struct asn1_hdr hdr;
+
+	/*
+	 * RFC 2985, 5.5.1:
+	 * friendlyName ATTRIBUTE ::= {
+	 *         WITH SYNTAX BMPString (SIZE(1..pkcs-9-ub-friendlyName))
+	 *         EQUALITY MATCHING RULE caseIgnoreMatch
+	 *         SINGLE VALUE TRUE
+	 *          ID pkcs-9-at-friendlyName
+	 * }
+	 */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_BMPSTRING) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected BMPSTRING (friendlyName) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return 0;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "PKCS #12: friendlyName",
+			  hdr.payload, hdr.length);
+	return 0;
+}
+
+
+static int pkcs12_parse_attr_local_key_id(const u8 *pos, const u8 *end)
+{
+	struct asn1_hdr hdr;
+
+	/*
+	 * RFC 2985, 5.5.2:
+	 * localKeyId ATTRIBUTE ::= {
+	 *         WITH SYNTAX OCTET STRING
+	 *         EQUALITY MATCHING RULE octetStringMatch
+	 *         SINGLE VALUE TRUE
+	 *         ID pkcs-9-at-localKeyId
+	 * }
+	 */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected OCTET STRING (localKeyID) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	wpa_hexdump_key(MSG_DEBUG, "PKCS #12: localKeyID",
+			hdr.payload, hdr.length);
+	return 0;
+}
+
+
+static int pkcs12_parse_attr(const u8 *pos, size_t len)
+{
+	const u8 *end = pos + len;
+	struct asn1_hdr hdr;
+	struct asn1_oid a_oid;
+	char obuf[80];
+
+	/*
+	 * PKCS12Attribute ::= SEQUENCE {
+	 * attrId      ATTRIBUTE.&id ({PKCS12AttrSet}),
+	 * attrValues  SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId})
+	 * }
+	 */
+
+	if (asn1_get_oid(pos, end - pos, &a_oid, &pos)) {
+		wpa_printf(MSG_DEBUG, "PKCS #12: Failed to parse OID (attrId)");
+		return -1;
+	}
+
+	asn1_oid_to_str(&a_oid, obuf, sizeof(obuf));
+	wpa_printf(MSG_DEBUG, "PKCS #12: attrId %s", obuf);
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SET) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected SET (attrValues) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	wpa_hexdump_key(MSG_MSGDUMP, "PKCS #12: attrValues",
+			hdr.payload, hdr.length);
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	if (is_oid_pkcs9_friendly_name(&a_oid))
+		return pkcs12_parse_attr_friendly_name(pos, end);
+	if (is_oid_pkcs9_local_key_id(&a_oid))
+		return pkcs12_parse_attr_local_key_id(pos, end);
+
+	wpa_printf(MSG_DEBUG, "PKCS #12: Ignore unknown attribute");
+	return 0;
+}
+
+
+static int pkcs12_safebag(struct tlsv1_credentials *cred,
+			  const u8 *buf, size_t len, const char *passwd)
+{
+	struct asn1_hdr hdr;
+	struct asn1_oid oid;
+	char obuf[80];
+	const u8 *pos = buf, *end = buf + len;
+	const u8 *value;
+	size_t value_len;
+
+	wpa_hexdump_key(MSG_MSGDUMP, "PKCS #12: SafeBag", buf, len);
+
+	/* BAG-TYPE ::= TYPE-IDENTIFIER */
+	if (asn1_get_oid(pos, end - pos, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Failed to parse OID (BAG-TYPE)");
+		return -1;
+	}
+
+	asn1_oid_to_str(&oid, obuf, sizeof(obuf));
+	wpa_printf(MSG_DEBUG, "PKCS #12: BAG-TYPE %s", obuf);
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC ||
+	    hdr.tag != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected [0] EXPLICIT (bagValue) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return 0;
+	}
+	value = hdr.payload;
+	value_len = hdr.length;
+	wpa_hexdump_key(MSG_MSGDUMP, "PKCS #12: bagValue", value, value_len);
+	pos = hdr.payload + hdr.length;
+
+	if (pos < end) {
+		/* bagAttributes  SET OF PKCS12Attribute OPTIONAL */
+		if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_UNIVERSAL ||
+		    hdr.tag != ASN1_TAG_SET) {
+			wpa_printf(MSG_DEBUG,
+				   "PKCS #12: Expected SET (bagAttributes) - found class %d tag 0x%x",
+				   hdr.class, hdr.tag);
+			return -1;
+		}
+		wpa_hexdump_key(MSG_MSGDUMP, "PKCS #12: bagAttributes",
+				hdr.payload, hdr.length);
+
+		pos = hdr.payload;
+		end = hdr.payload + hdr.length;
+		while (pos < end) {
+			/* PKCS12Attribute ::= SEQUENCE */
+			if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+			    hdr.class != ASN1_CLASS_UNIVERSAL ||
+			    hdr.tag != ASN1_TAG_SEQUENCE) {
+				wpa_printf(MSG_DEBUG,
+					   "PKCS #12: Expected SEQUENCE (PKCS12Attribute) - found class %d tag 0x%x",
+					   hdr.class, hdr.tag);
+				return -1;
+			}
+			if (pkcs12_parse_attr(hdr.payload, hdr.length) < 0)
+				return -1;
+			pos = hdr.payload + hdr.length;
+		}
+	}
+
+	if (pkcs12_is_bagtype_oid(&oid, 1))
+		return pkcs12_keybag(cred, value, value_len);
+	if (pkcs12_is_bagtype_oid(&oid, 2))
+		return pkcs12_pkcs8_keybag(cred, value, value_len, passwd);
+	if (pkcs12_is_bagtype_oid(&oid, 3))
+		return pkcs12_certbag(cred, value, value_len);
+
+	wpa_printf(MSG_DEBUG, "PKCS #12: Ignore unsupported BAG-TYPE");
+	return 0;
+}
+
+
+static int pkcs12_safecontents(struct tlsv1_credentials *cred,
+			       const u8 *buf, size_t len,
+			       const char *passwd)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+
+	/* SafeContents ::= SEQUENCE OF SafeBag */
+	if (asn1_get_next(buf, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected SEQUENCE (SafeContents) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	pos = hdr.payload;
+	end = hdr.payload + hdr.length;
+
+	/*
+	 * SafeBag ::= SEQUENCE {
+	 *   bagId          BAG-TYPE.&id ({PKCS12BagSet})
+	 *   bagValue       [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+	 *   bagAttributes  SET OF PKCS12Attribute OPTIONAL
+	 * }
+	 */
+
+	while (pos < end) {
+		if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_UNIVERSAL ||
+		    hdr.tag != ASN1_TAG_SEQUENCE) {
+			wpa_printf(MSG_DEBUG,
+				   "PKCS #12: Expected SEQUENCE (SafeBag) - found class %d tag 0x%x",
+				   hdr.class, hdr.tag);
+			return -1;
+		}
+		if (pkcs12_safebag(cred, hdr.payload, hdr.length, passwd) < 0)
+			return -1;
+		pos = hdr.payload + hdr.length;
+	}
+
+	return 0;
+}
+
+
+static int pkcs12_parse_content_data(struct tlsv1_credentials *cred,
+				     const u8 *pos, const u8 *end,
+				     const char *passwd)
+{
+	struct asn1_hdr hdr;
+
+	/* Data ::= OCTET STRING */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected OCTET STRING (Data) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	wpa_hexdump(MSG_MSGDUMP, "PKCS #12: Data", hdr.payload, hdr.length);
+
+	return pkcs12_safecontents(cred, hdr.payload, hdr.length, passwd);
+}
+
+
+static int pkcs12_parse_content_enc_data(struct tlsv1_credentials *cred,
+					 const u8 *pos, const u8 *end,
+					 const char *passwd)
+{
+	struct asn1_hdr hdr;
+	struct asn1_oid oid;
+	char buf[80];
+	const u8 *enc_alg;
+	u8 *data;
+	size_t enc_alg_len, data_len;
+	int res = -1;
+
+	/*
+	 * EncryptedData ::= SEQUENCE {
+	 *   version Version,
+	 *   encryptedContentInfo EncryptedContentInfo }
+	 */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected SEQUENCE (EncryptedData) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return 0;
+	}
+	pos = hdr.payload;
+
+	/* Version ::= INTEGER */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL || hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: No INTEGER tag found for version; class=%d tag=0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	if (hdr.length != 1 || hdr.payload[0] != 0) {
+		wpa_printf(MSG_DEBUG, "PKCS #12: Unrecognized PKCS #7 version");
+		return -1;
+	}
+	pos = hdr.payload + hdr.length;
+
+	wpa_hexdump(MSG_MSGDUMP, "PKCS #12: EncryptedContentInfo",
+		    pos, end - pos);
+
+	/*
+	 * EncryptedContentInfo ::= SEQUENCE {
+	 *   contentType ContentType,
+	 *   contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
+	 *   encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL }
+	 */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected SEQUENCE (EncryptedContentInfo) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	/* ContentType ::= OBJECT IDENTIFIER */
+	if (asn1_get_oid(pos, end - pos, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Could not find OBJECT IDENTIFIER (contentType)");
+		return -1;
+	}
+	asn1_oid_to_str(&oid, buf, sizeof(buf));
+	wpa_printf(MSG_DEBUG, "PKCS #12: EncryptedContentInfo::contentType %s",
+		   buf);
+
+	if (!is_oid_pkcs7_data(&oid)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Unsupported EncryptedContentInfo::contentType %s",
+			   buf);
+		return 0;
+	}
+
+	/* ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG, "PKCS #12: Expected SEQUENCE (ContentEncryptionAlgorithmIdentifier) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	enc_alg = hdr.payload;
+	enc_alg_len = hdr.length;
+	pos = hdr.payload + hdr.length;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC ||
+	    hdr.tag != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected [0] IMPLICIT (encryptedContent) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	/* EncryptedContent ::= OCTET STRING */
+	data = pkcs5_decrypt(enc_alg, enc_alg_len, hdr.payload, hdr.length,
+			     passwd, &data_len);
+	if (data) {
+		wpa_hexdump_key(MSG_MSGDUMP,
+				"PKCS #12: Decrypted encryptedContent",
+				data, data_len);
+		res = pkcs12_safecontents(cred, data, data_len, passwd);
+		os_free(data);
+	}
+
+	return res;
+}
+
+
+static int pkcs12_parse_content(struct tlsv1_credentials *cred,
+				const u8 *buf, size_t len,
+				const char *passwd)
+{
+	const u8 *pos = buf;
+	const u8 *end = buf + len;
+	struct asn1_oid oid;
+	char txt[80];
+	struct asn1_hdr hdr;
+
+	wpa_hexdump(MSG_MSGDUMP, "PKCS #12: ContentInfo", buf, len);
+
+	if (asn1_get_oid(pos, end - pos, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Could not find OBJECT IDENTIFIER (contentType)");
+		return 0;
+	}
+
+	asn1_oid_to_str(&oid, txt, sizeof(txt));
+	wpa_printf(MSG_DEBUG, "PKCS #12: contentType %s", txt);
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC ||
+	    hdr.tag != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected [0] EXPLICIT (content) - found class %d tag 0x%x",
+			   hdr.class, hdr.tag);
+		return 0;
+	}
+	pos = hdr.payload;
+
+	if (is_oid_pkcs7_data(&oid))
+		return pkcs12_parse_content_data(cred, pos, end, passwd);
+	if (is_oid_pkcs7_enc_data(&oid))
+		return pkcs12_parse_content_enc_data(cred, pos, end, passwd);
+
+	wpa_printf(MSG_DEBUG, "PKCS #12: Ignored unsupported contentType %s",
+		   txt);
+
+	return 0;
+}
+
+
+static int pkcs12_parse(struct tlsv1_credentials *cred,
+			const u8 *key, size_t len, const char *passwd)
+{
+	struct asn1_hdr hdr;
+	const u8 *pos, *end;
+	struct asn1_oid oid;
+	char buf[80];
+
+	/*
+	 * PFX ::= SEQUENCE {
+	 *     version     INTEGER {v3(3)}(v3,...),
+	 *     authSafe    ContentInfo,
+	 *     macData     MacData OPTIONAL
+	 * }
+	 */
+
+	if (asn1_get_next(key, len, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected SEQUENCE (PFX) - found class %d tag 0x%x; assume PKCS #12 not used",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL || hdr.tag != ASN1_TAG_INTEGER) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: No INTEGER tag found for version; class=%d tag=0x%x",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+	if (hdr.length != 1 || hdr.payload[0] != 3) {
+		wpa_printf(MSG_DEBUG, "PKCS #12: Unrecognized version");
+		return -1;
+	}
+	pos = hdr.payload + hdr.length;
+
+	/*
+	 * ContentInfo ::= SEQUENCE {
+	 *   contentType ContentType,
+	 *   content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
+	 */
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected SEQUENCE (authSafe) - found class %d tag 0x%x; assume PKCS #12 not used",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	/* ContentType ::= OBJECT IDENTIFIER */
+	if (asn1_get_oid(pos, end - pos, &oid, &pos)) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Could not find OBJECT IDENTIFIER (contentType); assume PKCS #12 not used");
+		return -1;
+	}
+	asn1_oid_to_str(&oid, buf, sizeof(buf));
+	wpa_printf(MSG_DEBUG, "PKCS #12: contentType %s", buf);
+	if (!is_oid_pkcs7_data(&oid)) {
+		wpa_printf(MSG_DEBUG, "PKCS #12: Unsupported contentType %s",
+			   buf);
+		return -1;
+	}
+
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC ||
+	    hdr.tag != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected [0] EXPLICIT (content) - found class %d tag 0x%x; assume PKCS #12 not used",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+
+	/* Data ::= OCTET STRING */
+	if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_OCTETSTRING) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected OCTET STRING (Data) - found class %d tag 0x%x; assume PKCS #12 not used",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	/*
+	 * AuthenticatedSafe ::= SEQUENCE OF ContentInfo
+	 *     -- Data if unencrypted
+	 *     -- EncryptedData if password-encrypted
+	 *     -- EnvelopedData if public key-encrypted
+	 */
+	wpa_hexdump(MSG_MSGDUMP, "PKCS #12: Data content",
+		    hdr.payload, hdr.length);
+
+	if (asn1_get_next(hdr.payload, hdr.length, &hdr) < 0 ||
+	    hdr.class != ASN1_CLASS_UNIVERSAL ||
+	    hdr.tag != ASN1_TAG_SEQUENCE) {
+		wpa_printf(MSG_DEBUG,
+			   "PKCS #12: Expected SEQUENCE within Data content - found class %d tag 0x%x; assume PKCS #12 not used",
+			   hdr.class, hdr.tag);
+		return -1;
+	}
+
+	pos = hdr.payload;
+	end = pos + hdr.length;
+
+	while (end > pos) {
+		if (asn1_get_next(pos, end - pos, &hdr) < 0 ||
+		    hdr.class != ASN1_CLASS_UNIVERSAL ||
+		    hdr.tag != ASN1_TAG_SEQUENCE) {
+			wpa_printf(MSG_DEBUG,
+				   "PKCS #12: Expected SEQUENCE (ContentInfo) - found class %d tag 0x%x; assume PKCS #12 not used",
+				   hdr.class, hdr.tag);
+			return -1;
+		}
+		if (pkcs12_parse_content(cred, hdr.payload, hdr.length,
+					 passwd) < 0)
+			return -1;
+
+		pos = hdr.payload + hdr.length;
+	}
+
+	return 0;
+}
+
+#endif /* PKCS12_FUNCS */
+
+
 static int tlsv1_set_key(struct tlsv1_credentials *cred,
 			 const u8 *key, size_t len, const char *passwd)
 {
@@ -333,6 +1065,10 @@
 		cred->key = tlsv1_set_key_pem(key, len);
 	if (cred->key == NULL)
 		cred->key = tlsv1_set_key_enc_pem(key, len, passwd);
+#ifdef PKCS12_FUNCS
+	if (!cred->key)
+		pkcs12_parse(cred, key, len, passwd);
+#endif /* PKCS12_FUNCS */
 	if (cred->key == NULL) {
 		wpa_printf(MSG_INFO, "TLSv1: Failed to parse private key");
 		return -1;