All of lore.kernel.org
 help / color / mirror / Atom feed
From: Andrew Zaborowski <andrew.zaborowski@intel.com>
To: ell@lists.01.org
Subject: [PATCH 6/8] cert: Add PKCS#12 loading support
Date: Wed, 06 Jan 2021 20:54:44 +0100	[thread overview]
Message-ID: <20210106195446.1428769-6-andrew.zaborowski@intel.com> (raw)
In-Reply-To: <20210106195446.1428769-1-andrew.zaborowski@intel.com>

[-- Attachment #1: Type: text/plain, Size: 20907 bytes --]

Add ability to parse PKCS#12 files in l_cert_load_container_file().  This
has only been tested with the files produced by openssl so far.

Extend the static buffer in struct asn1_oid (asn1-private.h) so we can
handle some 11-byte OIDs used in PKCS#12 files.
---
 ell/asn1-private.h |   2 +-
 ell/cert.c         | 634 +++++++++++++++++++++++++++++++++++++++++++++
 ell/pem.c          |   4 +
 3 files changed, 639 insertions(+), 1 deletion(-)

diff --git a/ell/asn1-private.h b/ell/asn1-private.h
index 2a31241..079570b 100644
--- a/ell/asn1-private.h
+++ b/ell/asn1-private.h
@@ -37,7 +37,7 @@
 
 struct asn1_oid {
 	uint8_t asn1_len;
-	uint8_t asn1[10];
+	uint8_t asn1[11];
 };
 
 #define asn1_oid_eq(oid1, oid2_len, oid2_string) \
diff --git a/ell/cert.c b/ell/cert.c
index b2bf568..fb4e443 100644
--- a/ell/cert.c
+++ b/ell/cert.c
@@ -585,6 +585,11 @@ struct l_key *cert_key_from_pkcs8_private_key_info(const uint8_t *der,
 	return l_key_new(L_KEY_RSA, der, der_len);
 }
 
+/*
+ * The passphrase, if given, must have been validated as UTF-8 unless the
+ * caller knows that PKCS#12 encryption algorithms are not used.
+ * Use l_utf8_validate.
+ */
 struct l_key *cert_key_from_pkcs8_encrypted_private_key_info(const uint8_t *der,
 							size_t der_len,
 							const char *passphrase)
@@ -739,6 +744,580 @@ struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t *der,
 	return pkey;
 }
 
+static const uint8_t *cert_unpack_pkcs7_content_info(const uint8_t *container,
+					size_t container_len, int pos,
+					const struct asn1_oid *expected_oid,
+					struct asn1_oid *out_oid,
+					uint8_t *out_tag, size_t *out_len)
+{
+	const uint8_t *content_info;
+	size_t content_info_len;
+	const uint8_t *type;
+	size_t type_len;
+	const uint8_t *ret;
+	uint8_t tag;
+
+	if (!(content_info = asn1_der_find_elem(container, container_len, pos,
+						&tag, &content_info_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return NULL;
+
+	if (!(type = asn1_der_find_elem(content_info, content_info_len, 0,
+					&tag, &type_len)) ||
+			tag != ASN1_ID_OID ||
+			type_len > sizeof(out_oid->asn1))
+		return NULL;
+
+	if (expected_oid && !asn1_oid_eq(expected_oid, type_len, type))
+		return NULL;
+
+	if (!(ret = asn1_der_find_elem(content_info, content_info_len,
+					ASN1_CONTEXT_EXPLICIT(0),
+					out_tag, out_len)) ||
+			ret + *out_len != content_info + content_info_len)
+		return NULL;
+
+	if (out_oid) {
+		out_oid->asn1_len = type_len;
+		memcpy(out_oid->asn1, type, type_len);
+	}
+
+	return ret;
+}
+
+/* RFC5652 Section 8 */
+static uint8_t *cert_decrypt_pkcs7_encrypted_data(const uint8_t *data,
+						size_t data_len,
+						const char *password,
+						struct asn1_oid *out_oid,
+						size_t *out_len)
+{
+	const uint8_t *version;
+	size_t version_len;
+	const uint8_t *encrypted_info;
+	size_t encrypted_info_len;
+	const uint8_t *type;
+	size_t type_len;
+	const uint8_t *alg_id;
+	size_t alg_id_len;
+	const uint8_t *encrypted;
+	size_t encrypted_len;
+	uint8_t tag;
+	struct l_cipher *alg;
+	uint8_t *plaintext;
+	int i;
+	bool ok;
+	bool is_block;
+
+	if (!(version = asn1_der_find_elem(data, data_len, 0, &tag,
+						&version_len)) ||
+			tag != ASN1_ID_INTEGER || version_len != 1 ||
+			!L_IN_SET(version[0], 0, 2))
+		return NULL;
+
+	if (!(encrypted_info = asn1_der_find_elem(data, data_len, 1, &tag,
+							&encrypted_info_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return NULL;
+
+	if (!(type = asn1_der_find_elem(encrypted_info, encrypted_info_len, 0,
+					&tag, &type_len)) ||
+			tag != ASN1_ID_OID ||
+			type_len > sizeof(out_oid->asn1))
+		return NULL;
+
+	if (!(alg_id = asn1_der_find_elem(encrypted_info, encrypted_info_len, 1,
+					&tag, &alg_id_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return NULL;
+
+	/* Not optional in our case, defined [0] IMPLICIT OCTET STRING */
+	if (!(encrypted = asn1_der_find_elem(encrypted_info, encrypted_info_len,
+						ASN1_CONTEXT_IMPLICIT(0),
+						&tag, &encrypted_len)) ||
+			tag != ASN1_ID(ASN1_CLASS_CONTEXT, 0, 0) ||
+			encrypted_len < 8)
+		return NULL;
+
+	if (!(alg = cert_cipher_from_pkcs_alg_id(alg_id, alg_id_len, password,
+							&is_block)))
+		return NULL;
+
+	plaintext = l_malloc(encrypted_len);
+	ok = l_cipher_decrypt(alg, encrypted, plaintext, encrypted_len);
+	l_cipher_free(alg);
+
+	if (!ok)
+		return NULL;
+
+	if (is_block) {
+		bool ok = true;
+
+		/* Also validate the padding */
+		if (encrypted_len < plaintext[encrypted_len - 1] ||
+				plaintext[encrypted_len - 1] > 16)
+			return NULL;
+
+		for (i = 1; i < plaintext[encrypted_len - 1]; i++)
+			if (plaintext[encrypted_len - 1 - i] !=
+					plaintext[encrypted_len - 1])
+				ok = false;
+
+		if (!ok) {
+			explicit_bzero(plaintext, encrypted_len);
+			l_free(plaintext);
+			return NULL;
+		}
+
+		encrypted_len -= plaintext[encrypted_len - 1];
+	}
+
+	if (out_oid) {
+		out_oid->asn1_len = type_len;
+		memcpy(out_oid->asn1, type, type_len);
+	}
+
+	*out_len = encrypted_len;
+	return plaintext;
+}
+
+/* RFC7292 Appendix A. */
+static const struct cert_pkcs12_hash pkcs12_mac_algs[] = {
+	{
+		L_CHECKSUM_MD5,    16, 16, 64,
+		{ 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0f, 0x02, 0x05 } }
+	},
+	{
+		L_CHECKSUM_SHA1,   20, 20, 64,
+		{ 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } }
+	},
+	{
+		L_CHECKSUM_SHA224, 28, 28, 64,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04 } }
+	},
+	{
+		L_CHECKSUM_SHA256, 32, 32, 64,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 } }
+	},
+	{
+		L_CHECKSUM_SHA384, 48, 48, 128,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 } }
+	},
+	{
+		L_CHECKSUM_SHA512, 64, 64, 128,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 } }
+	},
+	{
+		L_CHECKSUM_SHA512, 64, 28, 128,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x05 } }
+	},
+	{
+		L_CHECKSUM_SHA512, 64, 32, 128,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06 } }
+	},
+};
+
+static const struct asn1_oid pkcs12_key_bag_oid = {
+	11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x01 }
+};
+
+static const struct asn1_oid pkcs12_pkcs8_shrouded_key_bag_oid = {
+	11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02 }
+};
+
+static const struct asn1_oid pkcs12_cert_bag_oid = {
+	11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x03 }
+};
+
+static const struct asn1_oid pkcs12_safe_contents_bag_oid = {
+	11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x06 }
+};
+
+static const struct asn1_oid pkcs9_x509_certificate_oid = {
+	10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x16, 0x01 }
+};
+
+/* RFC7292 Section 4.2.3 */
+static bool cert_parse_pkcs12_cert_bag(const uint8_t *data, size_t data_len,
+					struct l_certchain **out_certchain)
+{
+	const uint8_t *cert_bag;
+	size_t cert_bag_len;
+	const uint8_t *cert_id;
+	size_t cert_id_len;
+	const uint8_t *cert_value;
+	size_t cert_value_len;
+	uint8_t tag;
+	struct l_cert *cert;
+
+	if (!(cert_bag = asn1_der_find_elem(data, data_len, 0,
+						&tag, &cert_bag_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return false;
+
+	if (!(cert_id = asn1_der_find_elem(cert_bag, cert_bag_len, 0,
+						&tag, &cert_id_len)) ||
+			tag != ASN1_ID_OID)
+		return false;
+
+	if (!(cert_value = asn1_der_find_elem(cert_bag, cert_bag_len,
+						ASN1_CONTEXT_EXPLICIT(0),
+						&tag, &cert_value_len)) ||
+			tag != ASN1_ID_OCTET_STRING ||
+			cert_value + cert_value_len != data + data_len)
+		return false;
+
+	/* Skip unsupported certificate types */
+	if (!asn1_oid_eq(&pkcs9_x509_certificate_oid, cert_id_len, cert_id))
+		return true;
+
+	if (!(cert = l_cert_new_from_der(cert_value, cert_value_len)))
+		return false;
+
+	if (!*out_certchain)
+		*out_certchain = certchain_new_from_leaf(cert);
+	else
+		certchain_link_issuer(*out_certchain, cert);
+
+	return true;
+}
+
+static bool cert_parse_pkcs12_safe_contents(const uint8_t *data,
+					size_t data_len, const char *password,
+					struct l_certchain **out_certchain,
+					struct l_key **out_privkey)
+{
+	const uint8_t *safe_contents;
+	size_t safe_contents_len;
+	uint8_t tag;
+
+	if (!(safe_contents = asn1_der_find_elem(data, data_len, 0, &tag,
+							&safe_contents_len)) ||
+			tag != ASN1_ID_SEQUENCE ||
+			data + data_len != safe_contents + safe_contents_len)
+		return false;
+
+	/* RFC7292 Section 4.2 */
+	while (safe_contents_len) {
+		const uint8_t *safe_bag;
+		size_t safe_bag_len;
+		const uint8_t *bag_id;
+		size_t bag_id_len;
+		const uint8_t *bag_value;
+		int bag_value_len;
+
+		/* RFC7292 Section 4.2 */
+		if (!(safe_bag = asn1_der_find_elem(safe_contents,
+							safe_contents_len, 0,
+							&tag, &safe_bag_len)) ||
+				tag != ASN1_ID_SEQUENCE)
+			return false;
+
+		if (!(bag_id = asn1_der_find_elem(safe_bag, safe_bag_len, 0,
+							&tag, &bag_id_len)) ||
+				tag != ASN1_ID_OID)
+			return false;
+
+		/*
+		 * The bagValue is EXPLICITly tagged but we don't want to
+		 * unpack the inner TLV yet so don't use asn1_der_find_elem.
+		 */
+		safe_bag_len -= bag_id + bag_id_len - safe_bag;
+		safe_bag = bag_id + bag_id_len;
+
+		if (safe_bag_len < 4)
+			return false;
+
+		tag = *safe_bag++;
+		safe_bag_len--;
+		bag_value_len = asn1_parse_definite_length(&safe_bag,
+								&safe_bag_len);
+		bag_value = safe_bag;
+
+		if (bag_value_len < 0 || bag_value_len > (int) safe_bag_len ||
+				tag != ASN1_ID(ASN1_CLASS_CONTEXT, 1, 0))
+			return false;
+
+		/* PKCS#9 attributes ignored */
+
+		safe_contents_len -= (safe_bag + safe_bag_len - safe_contents);
+		safe_contents = safe_bag + safe_bag_len;
+
+		if (asn1_oid_eq(&pkcs12_key_bag_oid, bag_id_len, bag_id)) {
+			if (!out_privkey || *out_privkey)
+				continue;
+
+			*out_privkey =
+				cert_key_from_pkcs8_private_key_info(bag_value,
+								bag_value_len);
+			if (!*out_privkey)
+				return false;
+		} else if (asn1_oid_eq(&pkcs12_pkcs8_shrouded_key_bag_oid,
+					bag_id_len, bag_id)) {
+			if (!out_privkey || *out_privkey)
+				continue;
+
+			*out_privkey =
+				cert_key_from_pkcs8_encrypted_private_key_info(
+								bag_value,
+								bag_value_len,
+								password);
+			if (!*out_privkey)
+				return false;
+		} else if (asn1_oid_eq(&pkcs12_cert_bag_oid,
+					bag_id_len, bag_id)) {
+			if (!out_certchain)
+				continue;
+
+			if (!cert_parse_pkcs12_cert_bag(bag_value, bag_value_len,
+							out_certchain))
+				return false;
+		} else if (asn1_oid_eq(&pkcs12_safe_contents_bag_oid,
+					bag_id_len, bag_id)) {
+			/* TODO: depth check */
+			if (!(cert_parse_pkcs12_safe_contents(bag_value,
+								bag_value_len,
+								password,
+								out_certchain,
+								out_privkey)))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+/* RFC5652 Section 4 */
+static const struct asn1_oid pkcs7_data_oid = {
+	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01 }
+};
+
+/* RFC5652 Section 8 */
+static const struct asn1_oid pkcs7_encrypted_data_oid = {
+	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x06 }
+};
+
+static bool cert_parse_pkcs12_pfx(const uint8_t *ptr, size_t len,
+					const char *password,
+					struct l_certchain **out_certchain,
+					struct l_key **out_privkey)
+{
+	const uint8_t *version;
+	size_t version_len;
+	const uint8_t *auth_safe;
+	size_t auth_safe_len;
+	const uint8_t *auth_safe_seq;
+	size_t auth_safe_seq_len;
+	const uint8_t *mac_data;
+	size_t mac_data_len;
+	const uint8_t *mac;
+	size_t mac_len;
+	const uint8_t *mac_salt;
+	size_t mac_salt_len;
+	const uint8_t *iterations_data;
+	size_t iterations_len;
+	unsigned int iterations;
+	const uint8_t *digest_alg;
+	size_t digest_alg_len;
+	const uint8_t *digest;
+	size_t digest_len;
+	const uint8_t *alg_id;
+	size_t alg_id_len;
+	const struct cert_pkcs12_hash *mac_hash;
+	L_AUTO_FREE_VAR(uint8_t *, key) = NULL;
+	struct l_checksum *hmac;
+	uint8_t hmac_val[64];
+	uint8_t tag;
+	unsigned int i;
+	bool ok;
+
+	/* RFC7292 Section 4 */
+	if (!(version = asn1_der_find_elem(ptr, len, 0, &tag, &version_len)) ||
+			tag != ASN1_ID_INTEGER)
+		return false;
+
+	if (version_len != 1 || version[0] != 3)
+		return false;
+
+	/*
+	 * Since we only support the password-based integrity mode,  the
+	 * authSafe must be of PKCS#7 type "data" and not "signedData".
+	 */
+	if (!(auth_safe = cert_unpack_pkcs7_content_info(ptr, len, 1,
+							&pkcs7_data_oid, NULL,
+							&tag,
+							&auth_safe_len)) ||
+			tag != ASN1_ID_OCTET_STRING)
+		return false;
+
+	/*
+	 * openssl can generate PFX structures without macData not signed
+	 * with a public key so handle this case, otherwise the macData
+	 * would not be optional.
+	 */
+	if (auth_safe + auth_safe_len == ptr + len)
+		goto integrity_check_done;
+
+	if (!(mac_data = asn1_der_find_elem(ptr, len, 2, &tag,
+						&mac_data_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return false;
+
+	if (!(mac = asn1_der_find_elem(mac_data, mac_data_len, 0, &tag,
+					&mac_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return false;
+
+	if (!(mac_salt = asn1_der_find_elem(mac_data, mac_data_len, 1, &tag,
+						&mac_salt_len)) ||
+			tag != ASN1_ID_OCTET_STRING || mac_salt_len > 1024)
+		return false;
+
+	if (!(iterations_data = asn1_der_find_elem(mac_data, mac_data_len, 2,
+							&tag,
+							&iterations_len)) ||
+			tag != ASN1_ID_INTEGER || iterations_len > 4)
+		return false;
+
+	for (iterations = 0; iterations_len; iterations_len--)
+		iterations = (iterations << 8) | *iterations_data++;
+
+	if (iterations < 1 || iterations > 8192)
+		return false;
+
+	/* RFC2315 Section 9.4 */
+	if (!(digest_alg = asn1_der_find_elem(mac, mac_len, 0, &tag,
+						&digest_alg_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return false;
+
+	if (!(digest = asn1_der_find_elem(mac, mac_len, 1, &tag,
+						&digest_len)) ||
+			tag != ASN1_ID_OCTET_STRING)
+		return false;
+
+	if (!(alg_id = asn1_der_find_elem(digest_alg, digest_alg_len,
+						0, &tag, &alg_id_len)) ||
+			tag != ASN1_ID_OID)
+		return false;
+
+	/* This is going to be used for both the MAC and its key derivation */
+	for (i = 0; i < L_ARRAY_SIZE(pkcs12_mac_algs); i++)
+		if (asn1_oid_eq(&pkcs12_mac_algs[i].oid, alg_id_len, alg_id)) {
+			mac_hash = &pkcs12_mac_algs[i];
+			break;
+		}
+
+	if (i == L_ARRAY_SIZE(pkcs12_mac_algs) || digest_len != mac_hash->u)
+		return false;
+
+	if (!(key = cert_pkcs12_pbkdf(password, mac_hash,
+					mac_salt, mac_salt_len,
+					iterations, 3, mac_hash->u)))
+		return false;
+
+	hmac = l_checksum_new_hmac(mac_hash->alg, key, mac_hash->u);
+	explicit_bzero(key, mac_hash->u);
+
+	if (!hmac)
+		return false;
+
+	ok = l_checksum_update(hmac, auth_safe, auth_safe_len) &&
+		l_checksum_get_digest(hmac, hmac_val, mac_hash->len) > 0;
+	l_checksum_free(hmac);
+
+	if (!ok)
+		return false;
+
+	/*
+	 * SHA-512/224 and SHA-512/256 are not supported.  We can truncate the
+	 * output for key derivation but we can't do this inside the HMAC
+	 * algorithms based on these hashes.  We skip the MAC verification
+	 * if one of these hashes is used (identified by .u != .len)
+	 */
+	if (mac_hash->u != mac_hash->len)
+		goto integrity_check_done;
+
+	if (l_secure_memcmp(hmac_val, digest, digest_len))
+		return false;
+
+integrity_check_done:
+	if (!(auth_safe_seq = asn1_der_find_elem(auth_safe, auth_safe_len, 0,
+						&tag, &auth_safe_seq_len)) ||
+			tag != ASN1_ID_SEQUENCE ||
+			auth_safe + auth_safe_len !=
+			auth_safe_seq + auth_safe_seq_len)
+		return false;
+
+	i = 0;
+	while (1) {
+		struct asn1_oid data_oid;
+		const uint8_t *data;
+		size_t data_len;
+
+		if (!(data = cert_unpack_pkcs7_content_info(auth_safe_seq,
+							auth_safe_seq_len, i++,
+							NULL, &data_oid, &tag,
+							&data_len)))
+			return false;
+
+		if (asn1_oid_eq(&pkcs7_encrypted_data_oid,
+					data_oid.asn1_len, data_oid.asn1)) {
+			uint8_t *plaintext;
+			size_t plaintext_len;
+			struct asn1_oid oid;
+
+			if (tag != ASN1_ID_SEQUENCE)
+				return false;
+
+			/*
+			 * This is same as PKCS#7 encryptedData but the ciphers
+			 * used are from PKCS#12 (broken but still the default
+			 * everywhere) and PKCS#5 (recommended).
+			 */
+			plaintext = cert_decrypt_pkcs7_encrypted_data(data,
+								data_len,
+								password, &oid,
+								&plaintext_len);
+			if (!plaintext)
+				return false;
+
+			/*
+			 * Since we only support PKCS#7 data and encryptedData
+			 * types, and there's no point re-encrypting
+			 * encryptedData, the plaintext must be a PKCS#7
+			 * "data".
+			 */
+			ok = asn1_oid_eq(&pkcs7_data_oid,
+						oid.asn1_len, oid.asn1) &&
+				cert_parse_pkcs12_safe_contents(plaintext,
+								plaintext_len,
+								password,
+								out_certchain,
+								out_privkey);
+			explicit_bzero(plaintext, plaintext_len);
+			l_free(plaintext);
+
+			if (!ok)
+				return false;
+		} else if (asn1_oid_eq(&pkcs7_data_oid,
+					data_oid.asn1_len, data_oid.asn1)) {
+			if (tag != ASN1_ID_OCTET_STRING)
+				return false;
+
+			if (!cert_parse_pkcs12_safe_contents(data, data_len,
+								password,
+								out_certchain,
+								out_privkey))
+				return false;
+		}
+		/* envelopedData support not needed */
+
+		if (data + data_len == auth_safe_seq + auth_safe_seq_len)
+			return true;
+	}
+}
+
 /*
  * Look at a file, try to detect which of the few X.509 certificate and/or
  * private key container formats it uses and load any certificates in it as
@@ -750,10 +1329,16 @@ struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t *der,
  *  PEM PKCS#8 encrypted and unenecrypted private keys
  *  PEM legacy PKCS#1 encrypted and unenecrypted private keys
  *  Raw X.509 certificates (.cer, .der, .crt)
+ *  PKCS#12 certificates
+ *  PKCS#12 encrypted private keys
  *
  * The raw format contains exactly one certificate, PEM and PKCS#12 files
  * can contain any combination of certificates and private keys.
  *
+ * The password must have been validated as UTF-8 (use l_utf8_validate)
+ * unless the caller knows that no PKCS#12-defined encryption algorithm
+ * or MAC is used.
+ *
  * Returns false on "unrecoverable" errors, and *out_certchain,
  * *out_privkey and *out_encrypted (if provided) are not modified.  However
  * when true is returned, *out_certchain and *out_privkey (if provided) may
@@ -823,6 +1408,32 @@ LIB_EXPORT bool l_cert_load_container_file(const char *filename,
 
 			goto close;
 		}
+
+		if (tag == ASN1_ID_INTEGER) {
+			/*
+			 * Since we don't support public key-protected PKCS#12
+			 * modes, we always require the password at least for
+			 * the integrity check.  Strictly speaking encryption
+			 * may not actually be in use.  We also don't support
+			 * files with different integrity and privacy
+			 * passwords, they must be identical if privacy is
+			 * enabled.
+			 */
+			encrypted = true;
+
+			if (!password) {
+				error = !out_encrypted;
+				done = true;
+				goto close;
+			}
+
+			error = !cert_parse_pkcs12_pfx(seq_data, len, password,
+							out_certchain ?
+							&certchain : NULL,
+							out_privkey ?
+							&privkey : NULL);
+			goto close;
+		}
 	}
 
 not_der_after_all:
@@ -890,6 +1501,29 @@ not_der_after_all:
 			continue;
 		}
 
+		/* Cisco/gnutls-type PEM-encoded PKCS#12, probably rare */
+		if (L_IN_STRSET(type_label, "PKCS12")) {
+			encrypted = true;
+
+			if (!password) {
+				if (certchain && out_privkey) {
+					l_certchain_free(certchain);
+					certchain = NULL;
+				}
+
+				error = !out_encrypted;
+				done = true;
+				goto next;
+			}
+
+			error = !cert_parse_pkcs12_pfx(der, der_len, password,
+							out_certchain ?
+							&certchain : NULL,
+							out_privkey ?
+							&privkey : NULL);
+			goto next;
+		}
+
 next:
 		explicit_bzero(der, der_len);
 		l_free(der);
diff --git a/ell/pem.c b/ell/pem.c
index bc2865a..67d2af1 100644
--- a/ell/pem.c
+++ b/ell/pem.c
@@ -791,6 +791,10 @@ LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf,
  * success case and on error when NULL is returned.  This can be used to
  * check if a passphrase is required without prior information.
  *
+ * The passphrase, if given, must have been validated as UTF-8 unless the
+ * caller knows that PKCS#12 encryption algorithms are not used.
+ * Use l_utf8_validate.
+ *
  * Returns: An l_key object to be freed with an l_key_free* function,
  * or NULL.
  **/
-- 
2.27.0

  parent reply	other threads:[~2021-01-06 19:54 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-01-06 19:54 [PATCH 1/8] util: Add L_IN_SET macros Andrew Zaborowski
2021-01-06 19:54 ` [PATCH 2/8] unit: Add an L_IN_SET test Andrew Zaborowski
2021-01-06 19:54 ` [PATCH 3/8] pem: Move PKCS private key parsing to cert.c Andrew Zaborowski
2021-01-06 19:54 ` [PATCH 4/8] pkcs5: Rename to cert-crypto Andrew Zaborowski
2021-01-06 19:54 ` [PATCH 5/8] cert: Add l_cert_load_container_file Andrew Zaborowski
2021-01-08  4:37   ` Denis Kenzior
2021-01-09  0:51     ` Andrew Zaborowski
2021-01-09  2:39       ` Denis Kenzior
2021-01-06 19:54 ` Andrew Zaborowski [this message]
2021-01-08  4:59   ` [PATCH 6/8] cert: Add PKCS#12 loading support Denis Kenzior
2021-01-09  1:04     ` Andrew Zaborowski
2021-01-09  2:29       ` Denis Kenzior
2021-01-09  2:46         ` Andrew Zaborowski
2021-01-06 19:54 ` [PATCH 7/8] unit: Update tests after l_pkcs5_* renaming Andrew Zaborowski
2021-01-06 19:54 ` [PATCH 8/8] unit: Add l_cert_load_container_file tests Andrew Zaborowski
2021-01-07 20:01 ` [PATCH 1/8] util: Add L_IN_SET macros Denis Kenzior

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210106195446.1428769-6-andrew.zaborowski@intel.com \
    --to=andrew.zaborowski@intel.com \
    --cc=ell@lists.01.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.