All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/3] cert: Add PKCS#12 loading support
@ 2021-01-14 18:50 Andrew Zaborowski
  2021-01-14 18:50 ` [PATCH 2/3] cert: Split l_cert_load_container_file Andrew Zaborowski
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Andrew Zaborowski @ 2021-01-14 18:50 UTC (permalink / raw)
  To: ell

[-- Attachment #1: Type: text/plain, Size: 22103 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         | 690 +++++++++++++++++++++++++++++++++++++++++++++
 ell/pem.c          |   4 +
 3 files changed, 695 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 a37b501..cf81931 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,636 @@ 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) {
+		l_free(plaintext);
+		return NULL;
+	}
+
+	if (is_block) {
+		bool ok = true;
+
+		/* Also validate the padding */
+		if (encrypted_len < plaintext[encrypted_len - 1] ||
+				plaintext[encrypted_len - 1] > 16) {
+			plaintext[encrypted_len - 1] = 1;
+			ok = false;
+		}
+
+		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;
+}
+
+static bool cert_check_pkcs12_integrity(const uint8_t *mac_data,
+					size_t mac_data_len,
+					const uint8_t *auth_safe,
+					size_t auth_safe_len,
+					const char *password)
+{
+	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;
+	bool ok;
+	unsigned int i;
+
+	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)
+		return true;
+
+	return l_secure_memcmp(hmac_val, digest, digest_len) == 0;
+}
+
+/* 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_auth_safe_content(const uint8_t *data, size_t data_len,
+					uint8_t tag,
+					const struct asn1_oid *data_oid,
+					const char *password,
+					struct l_certchain **out_certchain,
+					struct l_key **out_privkey)
+{
+	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;
+		bool ok;
+
+		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 */
+
+	return true;
+}
+
+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 *mac_data;
+	size_t mac_data_len;
+	const uint8_t *auth_safe_seq;
+	size_t auth_safe_seq_len;
+	uint8_t tag;
+	unsigned int i;
+	struct l_certchain *certchain = NULL;
+	struct l_key *privkey = NULL;
+
+	/* 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 (!cert_check_pkcs12_integrity(mac_data, mac_data_len,
+						auth_safe, auth_safe_len,
+						password))
+		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)))
+			goto error;
+
+		if (!cert_parse_auth_safe_content(data, data_len, tag,
+							&data_oid, password,
+							out_certchain ?
+							&certchain : NULL,
+							out_privkey ?
+							&privkey : NULL))
+			goto error;
+
+		if (data + data_len == auth_safe_seq + auth_safe_seq_len)
+			break;
+	}
+
+	if (out_certchain)
+		*out_certchain = certchain;
+
+	if (out_privkey)
+		*out_privkey = privkey;
+
+	return true;
+
+error:
+	if (certchain)
+		l_certchain_free(certchain);
+
+	if (privkey)
+		l_key_free(privkey);
+
+	return false;
+}
+
 /*
  * 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 +1385,16 @@ struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t *der,
  *  PEM PKCS#8 encrypted and unencrypted private keys
  *  PEM legacy PKCS#1 encrypted and unencrypted 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 +1464,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 +1557,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

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/3] cert: Split l_cert_load_container_file
  2021-01-14 18:50 [PATCH 1/3] cert: Add PKCS#12 loading support Andrew Zaborowski
@ 2021-01-14 18:50 ` Andrew Zaborowski
  2021-01-14 18:50 ` [PATCH 3/3] unit: Add l_cert_load_container_file tests Andrew Zaborowski
  2021-01-15 21:03 ` [PATCH 1/3] cert: Add PKCS#12 loading support Denis Kenzior
  2 siblings, 0 replies; 4+ messages in thread
From: Andrew Zaborowski @ 2021-01-14 18:50 UTC (permalink / raw)
  To: ell

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

Move the DER file format detection and loading into one function and the
PEM detection and loading into another, splitting the
l_cert_load_container_file code into three functions.
---
 ell/cert.c | 272 +++++++++++++++++++++++++++++++----------------------
 1 file changed, 158 insertions(+), 114 deletions(-)

diff --git a/ell/cert.c b/ell/cert.c
index cf81931..c26051f 100644
--- a/ell/cert.c
+++ b/ell/cert.c
@@ -1374,147 +1374,112 @@ error:
 	return false;
 }
 
-/*
- * 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
- * a certificate chain object, and load the first private key as an l_key
- * object.
- *
- * Currently supported are:
- *  PEM X.509 certificates
- *  PEM PKCS#8 encrypted and unencrypted private keys
- *  PEM legacy PKCS#1 encrypted and unencrypted 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
- * be set to NULL when nothing could be loaded only due to missing password,
- * and *out_encrypted (if provided) will be set accordingly.  It will also
- * be set on success to indicate whether the password was used.
- * *out_certchain and/or *out_privkey will also be NULL if the container
- * was loaded but there were no certificates or private keys in it.
- */
-LIB_EXPORT bool l_cert_load_container_file(const char *filename,
+static int cert_try_load_der_format(const uint8_t *content, size_t content_len,
 					const char *password,
 					struct l_certchain **out_certchain,
 					struct l_key **out_privkey,
 					bool *out_encrypted)
 {
-	struct pem_file_info file;
-	const char *ptr;
-	size_t len;
-	bool error = true;
-	bool done = false;
-	struct l_certchain *certchain = NULL;
-	struct l_key *privkey = NULL;
-	bool encrypted = false;
+	const uint8_t *seq;
+	size_t seq_len;
+	const uint8_t *elem_data;
+	size_t elem_len;
+	uint8_t tag;
 
-	if (unlikely(!filename))
-		return false;
+	if (!(seq = asn1_der_find_elem(content, content_len,
+					0, &tag, &seq_len)))
+		/* May not have been a DER file after all */
+		return -ENOMSG;
 
-	if (pem_file_open(&file, filename) < 0)
-		return false;
+	/*
+	 * See if the first sub-element is another sequence, then, out of
+	 * the formats that we currently support this can only be a raw
+	 * certificate.  If integer, it's going to be PKCS#12.  If we wish
+	 * to add any more formats we'll probably need to start guessing
+	 * from the filename suffix.
+	 */
+	if (!(elem_data = asn1_der_find_elem(seq, seq_len,
+						0, &tag, &elem_len)))
+		return -ENOMSG;
 
-	if (file.st.st_size < 1)
-		goto close;
+	if (tag == ASN1_ID_SEQUENCE) {
+		if (out_certchain) {
+			struct l_cert *cert;
 
-	/* See if we have a DER sequence tag at the start */
-	if (file.data[0] == ASN1_ID_SEQUENCE) {
-		const uint8_t *seq_data;
-		const uint8_t *elem_data;
-		size_t elem_len;
-		uint8_t tag;
+			if (!(cert = l_cert_new_from_der(content, content_len)))
+				return -EINVAL;
 
-		if (!(seq_data = asn1_der_find_elem(file.data, file.st.st_size,
-							0, &tag, &len)))
-			goto not_der_after_all;
+			*out_certchain = certchain_new_from_leaf(cert);
 
+			if (out_privkey)
+				*out_privkey = NULL;
+
+			return 0;
+		}
+
+		return -EINVAL;
+	}
+
+	if (tag == ASN1_ID_INTEGER) {
 		/*
-		 * See if the first sub-element is another sequence, then, out
-		 * of the formats that we currently support this can only be a
-		 * raw certificate.  If integer, it's going to be PKCS#12.  If
-		 * we wish to add any more formats we'll probably need to start
-		 * guessing from the filename suffix.
+		 * 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.
 		 */
-		if (!(elem_data = asn1_der_find_elem(seq_data, len,
-							0, &tag, &elem_len)))
-			goto not_der_after_all;
+		if (out_encrypted)
+			*out_encrypted = true;
 
-		if (tag == ASN1_ID_SEQUENCE) {
-			if (out_certchain) {
-				struct l_cert *cert;
+		if (!password) {
+			if (!out_encrypted)
+				return -EINVAL;
 
-				if (!(cert = l_cert_new_from_der(file.data,
-							file.st.st_size)))
-					goto close;
+			if (out_certchain)
+				*out_certchain = NULL;
 
-				error = false;
-				certchain = certchain_new_from_leaf(cert);
-			}
+			if (out_privkey)
+				*out_privkey = NULL;
 
-			goto close;
+			return 0;
 		}
 
-		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 (cert_parse_pkcs12_pfx(seq, seq_len, password,
+						out_certchain, out_privkey))
+			return 0;
+		else
+			return -EINVAL;
+	}
 
-			if (!password) {
-				error = !out_encrypted;
-				done = true;
-				goto close;
-			}
+	return -ENOMSG;
+}
 
-			error = !cert_parse_pkcs12_pfx(seq_data, len, password,
-							out_certchain ?
-							&certchain : NULL,
-							out_privkey ?
-							&privkey : NULL);
-			goto close;
-		}
-	}
+static bool cert_try_load_pem_format(const char *content, size_t content_len,
+					const char *password,
+					struct l_certchain **out_certchain,
+					struct l_key **out_privkey,
+					bool *out_encrypted)
+{
+	bool error = false;
+	bool done = false;
+	struct l_certchain *certchain = NULL;
+	struct l_key *privkey = NULL;
+	bool encrypted = false;
 
-not_der_after_all:
-	/*
-	 * RFC 7486 allows whitespace and possibly other data before the
-	 * PEM "encapsulation boundary" so rather than check if the start
-	 * of the data looks like PEM, we fall back to this format if the
-	 * data didn't look like anything else we knew about.
-	 */
-	ptr = (const char *) file.data;
-	len = file.st.st_size;
-	error = false;
-	while (!done && !error && len) {
+	while (!done && !error && content_len) {
 		uint8_t *der;
 		size_t der_len;
 		char *type_label;
 		char *headers;
 		const char *endp;
 
-		if (!(der = pem_load_buffer(ptr, len, &type_label, &der_len,
-						&headers, &endp)))
+		if (!(der = pem_load_buffer(content, content_len, &type_label,
+						&der_len, &headers, &endp)))
 			break;
 
-		len -= endp - ptr;
-		ptr = endp;
+		content_len -= endp - content;
+		content = endp;
 
 		if (out_certchain && L_IN_STRSET(type_label, "CERTIFICATE")) {
 			struct l_cert *cert;
@@ -1587,9 +1552,6 @@ next:
 		l_free(headers);
 	}
 
-close:
-	pem_file_close(&file);
-
 	if (error) {
 		if (certchain)
 			l_certchain_free(certchain);
@@ -1611,3 +1573,85 @@ close:
 
 	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
+ * a certificate chain object, and load the first private key as an l_key
+ * object.
+ *
+ * Currently supported are:
+ *  PEM X.509 certificates
+ *  PEM PKCS#8 encrypted and unencrypted private keys
+ *  PEM legacy PKCS#1 encrypted and unencrypted 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
+ * be set to NULL when nothing could be loaded only due to missing password,
+ * and *out_encrypted (if provided) will be set accordingly.  It will also
+ * be set on success to indicate whether the password was used.
+ * *out_certchain and/or *out_privkey will also be NULL if the container
+ * was loaded but there were no certificates or private keys in it.
+ */
+LIB_EXPORT bool l_cert_load_container_file(const char *filename,
+					const char *password,
+					struct l_certchain **out_certchain,
+					struct l_key **out_privkey,
+					bool *out_encrypted)
+{
+	struct pem_file_info file;
+	bool error = true;
+
+	if (unlikely(!filename))
+		return false;
+
+	if (pem_file_open(&file, filename) < 0)
+		return false;
+
+	if (file.st.st_size < 1)
+		goto close;
+
+	/* See if we have a DER sequence tag@the start */
+	if (file.data[0] == ASN1_ID_SEQUENCE) {
+		int err;
+
+		err = cert_try_load_der_format(file.data, file.st.st_size,
+						password, out_certchain,
+						out_privkey, out_encrypted);
+		if (!err) {
+			error = false;
+			goto close;
+		}
+
+		if (err != -ENOMSG)
+			goto close;
+
+		/* Try PEM */
+	}
+
+	/*
+	 * RFC 7486 allows whitespace and possibly other data before the
+	 * PEM "encapsulation boundary" so rather than check if the start
+	 * of the data looks like PEM, we fall back to this format if the
+	 * data didn't look like anything else we knew about.
+	 */
+	if (cert_try_load_pem_format((const char *) file.data, file.st.st_size,
+					password, out_certchain, out_privkey,
+					out_encrypted))
+		error = false;
+
+close:
+	pem_file_close(&file);
+	return !error;
+}
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 3/3] unit: Add l_cert_load_container_file tests
  2021-01-14 18:50 [PATCH 1/3] cert: Add PKCS#12 loading support Andrew Zaborowski
  2021-01-14 18:50 ` [PATCH 2/3] cert: Split l_cert_load_container_file Andrew Zaborowski
@ 2021-01-14 18:50 ` Andrew Zaborowski
  2021-01-15 21:03 ` [PATCH 1/3] cert: Add PKCS#12 loading support Denis Kenzior
  2 siblings, 0 replies; 4+ messages in thread
From: Andrew Zaborowski @ 2021-01-14 18:50 UTC (permalink / raw)
  To: ell

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

Most of the tests are abour PEM files so place them together in
test-pem.ca.
---
 unit/test-pem.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 110 insertions(+)

diff --git a/unit/test-pem.c b/unit/test-pem.c
index f875adc..64dfb81 100644
--- a/unit/test-pem.c
+++ b/unit/test-pem.c
@@ -337,6 +337,66 @@ static void test_encrypted_pkey(const void *data)
 	l_key_free(pkey2);
 }
 
+static bool test_cert_count(struct l_cert *cert, void *user_data)
+{
+	int *count = user_data;
+
+	(*count)++;
+	return false;
+}
+
+struct test_load_file_params {
+	const char *path;
+	bool expect_cert;
+	bool expect_certchain;
+	bool expect_privkey;
+	bool expect_encrypted;
+};
+
+#define TEST_LOAD_PARAMS(fn, cert, certchain, privkey, encrypted)	\
+	(&(struct test_load_file_params) {				\
+		CERTDIR fn, (cert), (certchain), (privkey), (encrypted) })
+
+static void test_load_file(const void *data)
+{
+	const struct test_load_file_params *params = data;
+	struct l_certchain *certchain;
+	struct l_key *privkey;
+	bool encrypted;
+
+	assert(l_cert_load_container_file(params->path, NULL, &certchain,
+						&privkey, &encrypted));
+	assert(encrypted == params->expect_encrypted);
+
+	if (encrypted) {
+		assert(!certchain && !privkey);
+
+		assert(l_cert_load_container_file(params->path, "abc",
+							&certchain, &privkey,
+							&encrypted));
+		assert(encrypted);
+	}
+
+	assert(!!certchain == params->expect_cert);
+	assert(!!privkey == params->expect_privkey);
+
+	if (certchain) {
+		int count = 0;
+
+		l_certchain_walk_from_leaf(certchain, test_cert_count, &count);
+		assert(count == (params->expect_certchain ? 3 : 1));
+
+		if (params->expect_certchain)
+			assert(l_certchain_verify(certchain, NULL, NULL));
+	}
+
+	if (certchain)
+		l_certchain_free(certchain);
+
+	if (privkey)
+		l_key_free(privkey);
+}
+
 int main(int argc, char *argv[])
 {
 	l_test_init(&argc, &argv);
@@ -409,6 +469,56 @@ int main(int argc, char *argv[])
 				CERTDIR "cert-client-key-pkcs1-aes256.pem");
 	}
 
+	l_test_add("detect-format/PEM PKCS#1 unencrypted private key",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-client-key-pkcs1.pem",
+						false, false, true, false));
+	l_test_add("detect-format/PEM PKCS#1 encrypted private key",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-client-key-pkcs1-des.pem",
+						false, false, true, true));
+	l_test_add("detect-format/PEM PKCS#8 unencrypted private key",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-client-key-pkcs8.pem",
+						false, false, true, false));
+	l_test_add("detect-format/PEM PKCS#8 encrypted private key",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-client-key-pkcs8-sha1-des.pem",
+						false, false, true, true));
+	l_test_add("detect-format/PEM X.509 certificate",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-client.pem",
+						true, false, false, false));
+	l_test_add("detect-format/DER X.509 certificate",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-client.crt",
+						true, false, false, false));
+	l_test_add("detect-format/PEM combined",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-entity-combined.pem",
+						true, true, true, true));
+	l_test_add("detect-format/DER PKCS#12 combined",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-entity-pkcs12-nomac.p12",
+						true, false, true, true));
+
+	l_test_add("pkcs#12/Combined RC2-based ciphers + SHA1",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-entity-pkcs12-rc2-sha1.p12",
+						true, true, true, true));
+	l_test_add("pkcs#12/Combined DES-based ciphers + SHA256",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-entity-pkcs12-des-sha256.p12",
+						true, true, true, true));
+	l_test_add("pkcs#12/Combined RC4-based ciphers + SHA384",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-entity-pkcs12-rc4-sha384.p12",
+						true, true, true, true));
+	l_test_add("pkcs#12/Combined PKCS#5 ciphers + SHA512",
+			test_load_file,
+			TEST_LOAD_PARAMS("cert-entity-pkcs12-pkcs5-sha512.p12",
+						true, true, true, true));
+
 done:
 	return l_test_run();
 }
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 1/3] cert: Add PKCS#12 loading support
  2021-01-14 18:50 [PATCH 1/3] cert: Add PKCS#12 loading support Andrew Zaborowski
  2021-01-14 18:50 ` [PATCH 2/3] cert: Split l_cert_load_container_file Andrew Zaborowski
  2021-01-14 18:50 ` [PATCH 3/3] unit: Add l_cert_load_container_file tests Andrew Zaborowski
@ 2021-01-15 21:03 ` Denis Kenzior
  2 siblings, 0 replies; 4+ messages in thread
From: Denis Kenzior @ 2021-01-15 21:03 UTC (permalink / raw)
  To: ell

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

Hi Andrew,

On 1/14/21 12:50 PM, Andrew Zaborowski wrote:
> 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         | 690 +++++++++++++++++++++++++++++++++++++++++++++
>   ell/pem.c          |   4 +
>   3 files changed, 695 insertions(+), 1 deletion(-)
> 

All applied, thanks.

Regards,
-Denis

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2021-01-15 21:03 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-01-14 18:50 [PATCH 1/3] cert: Add PKCS#12 loading support Andrew Zaborowski
2021-01-14 18:50 ` [PATCH 2/3] cert: Split l_cert_load_container_file Andrew Zaborowski
2021-01-14 18:50 ` [PATCH 3/3] unit: Add l_cert_load_container_file tests Andrew Zaborowski
2021-01-15 21:03 ` [PATCH 1/3] cert: Add PKCS#12 loading support Denis Kenzior

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.