Updated to hostap_2_6
[mech_eap.git] / libeap / src / tls / tlsv1_cred.c
index 1ea6827..52c1ae0 100644 (file)
@@ -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"
 
@@ -33,6 +36,8 @@ void tlsv1_cred_free(struct tlsv1_credentials *cred)
        crypto_private_key_free(cred->key);
        os_free(cred->dh_p);
        os_free(cred->dh_g);
+       os_free(cred->ocsp_stapling_response);
+       os_free(cred->ocsp_stapling_response_multi);
        os_free(cred);
 }
 
@@ -190,6 +195,43 @@ 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 (cert && os_strncmp(cert, "hash://", 7) == 0) {
+               const char *pos = cert + 7;
+               if (os_strncmp(pos, "server/sha256/", 14) != 0) {
+                       wpa_printf(MSG_DEBUG,
+                                  "TLSv1: Unsupported ca_cert hash value '%s'",
+                                  cert);
+                       return -1;
+               }
+               pos += 14;
+               if (os_strlen(pos) != 32 * 2) {
+                       wpa_printf(MSG_DEBUG,
+                                  "TLSv1: Unexpected SHA256 hash length in ca_cert '%s'",
+                                  cert);
+                       return -1;
+               }
+               if (hexstr2bin(pos, cred->srv_cert_hash, 32) < 0) {
+                       wpa_printf(MSG_DEBUG,
+                                  "TLSv1: Invalid SHA256 hash value in ca_cert '%s'",
+                                  cert);
+                       return -1;
+               }
+               cred->server_cert_only = 1;
+               cred->ca_cert_verify = 0;
+               wpa_printf(MSG_DEBUG,
+                          "TLSv1: Checking only server certificate match");
+               return 0;
+       }
+
+       if (cert && os_strncmp(cert, "probe://", 8) == 0) {
+               cred->cert_probe = 1;
+               cred->ca_cert_verify = 0;
+               wpa_printf(MSG_DEBUG, "TLSv1: Only probe server certificate");
+               return 0;
+       }
+
+       cred->ca_cert_verify = cert || cert_blob || path;
+
        if (tlsv1_set_cert_chain(&cred->trusted_certs, cert,
                                 cert_blob, cert_blob_len) < 0)
                return -1;
@@ -288,6 +330,735 @@ static struct crypto_private_key * tlsv1_set_key_enc_pem(const u8 *key,
 }
 
 
+#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)
 {
@@ -296,6 +1067,10 @@ static int tlsv1_set_key(struct tlsv1_credentials *cred,
                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;