All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/5] pkcs5: Add the PKCS#12 KDF
@ 2020-12-12  0:50 Andrew Zaborowski
  2020-12-12  0:50 ` [PATCH 2/5] pkcs5: Add PKCS#12 algorithms in pkcs5_cipher_from_alg_id Andrew Zaborowski
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Andrew Zaborowski @ 2020-12-12  0:50 UTC (permalink / raw)
  To: ell

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

Add the key derivation algorithm used with PKCS#12 to pkcs5.c so that it
can be found together with the two PKCS#5 KDFs, and so that it can also
be used when parsing of the PKCS#12 AlgorithmIdentifiers in the next
commit.  This KDF is not recommended for new uses.
---
 ell/pkcs5-private.h |  12 +++++
 ell/pkcs5.c         | 121 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 133 insertions(+)

diff --git a/ell/pkcs5-private.h b/ell/pkcs5-private.h
index 27ff41c..9b85fdd 100644
--- a/ell/pkcs5-private.h
+++ b/ell/pkcs5-private.h
@@ -20,6 +20,18 @@
  *
  */
 
+struct pkcs12_hash {
+	enum l_checksum_type alg;
+	unsigned int len;
+	unsigned int u;
+	unsigned int v;
+	struct asn1_oid oid;
+};
+
+uint8_t *pkcs12_pbkdf(const char *password, const struct pkcs12_hash *hash,
+			const uint8_t *salt, size_t salt_len,
+			unsigned int iterations, uint8_t id, size_t key_len);
+
 struct l_cipher *pkcs5_cipher_from_alg_id(const uint8_t *id_asn1,
 						size_t id_asn1_len,
 						const char *password,
diff --git a/ell/pkcs5.c b/ell/pkcs5.c
index 21fda2b..979cebe 100644
--- a/ell/pkcs5.c
+++ b/ell/pkcs5.c
@@ -33,6 +33,7 @@
 #include "checksum.h"
 #include "cipher.h"
 #include "util.h"
+#include "utf8.h"
 #include "asn1-private.h"
 #include "pkcs5.h"
 #include "pkcs5-private.h"
@@ -182,14 +183,134 @@ LIB_EXPORT bool l_pkcs5_pbkdf2(enum l_checksum_type type, const char *password,
 	return !dk_len;
 }
 
+/* RFC7292 Appendix B */
+uint8_t *pkcs12_pbkdf(const char *password, const struct pkcs12_hash *hash,
+			const uint8_t *salt, size_t salt_len,
+			unsigned int iterations, uint8_t id, size_t key_len)
+{
+	/* All lengths in bytes instead of bits */
+	size_t passwd_len = password ? 2 * strlen(password) + 2 : 0;
+	uint8_t *bmpstring;
+	/* Documented as v(ceiling(s/v)), usually will just equal v */
+	unsigned int s_len = (salt_len + hash->v - 1) & ~(hash->v - 1);
+	/* Documented as p(ceiling(s/p)), usually will just equal v */
+	unsigned int p_len = (passwd_len + hash->v - 1) & ~(hash->v - 1);
+	uint8_t di[hash->v + s_len + p_len];
+	uint8_t *ptr;
+	unsigned int j;
+	uint8_t *key;
+	unsigned int bytes;
+	struct l_checksum *h = l_checksum_new(hash->alg);
+
+	if (!h)
+		return NULL;
+
+	/*
+	 * The BMPString encoding, in practice same as UCS-2, can end up
+	 * at 2 * strlen(password) + 2 bytes or shorter depending on the
+	 * characters used.  Recalculate p_len after we know it.
+	 * Important: The password must be valid UTF-8 here.
+	 */
+	if (password) {
+		if (!(bmpstring = l_utf8_to_ucs2be(password, &passwd_len))) {
+			l_checksum_free(h);
+			return NULL;
+		}
+
+		p_len = (passwd_len + hash->v - 1) & ~(hash->v - 1);
+	}
+
+	memset(di, id, hash->v);
+	ptr = di + hash->v;
+
+	for (j = salt_len; j < s_len; j += salt_len, ptr += salt_len)
+		memcpy(ptr, salt, salt_len);
+
+	if (s_len) {
+		memcpy(ptr, salt, s_len + salt_len - j);
+		ptr += s_len + salt_len - j;
+	}
+
+	for (j = passwd_len; j < p_len; j += passwd_len, ptr += passwd_len)
+		memcpy(ptr, bmpstring, passwd_len);
+
+	if (p_len) {
+		memcpy(ptr, bmpstring, p_len + passwd_len - j);
+
+		explicit_bzero(bmpstring, passwd_len);
+		l_free(bmpstring);
+	}
+
+	key = l_malloc(key_len + hash->len);
+
+	for (bytes = 0; bytes < key_len; bytes += hash->u) {
+		uint8_t b[hash->v];
+		uint8_t *input = di;
+		unsigned int input_len = hash->v + s_len + p_len;
+
+		for (j = 0; j < iterations; j++) {
+			if (!l_checksum_update(h, input, input_len) ||
+					l_checksum_get_digest(h,
+							key + bytes,
+							hash->len) <= 0) {
+				l_checksum_free(h);
+				l_free(key);
+				return NULL;
+			}
+
+			input = key + bytes;
+			input_len = hash->u;
+			l_checksum_reset(h);
+		}
+
+		if (bytes + hash->u >= key_len)
+			break;
+
+		for (j = 0; j < hash->v - hash->u; j += hash->u)
+			memcpy(b + j, input, hash->u);
+
+		memcpy(b + j, input, hash->v - j);
+
+		ptr = di + hash->v;
+		for (j = 0; j < s_len + p_len; j += hash->v, ptr += hash->v) {
+			unsigned int k;
+			uint16_t carry = 1;
+
+			/*
+			 * Not specified in the RFC7292 but implementations
+			 * sum these octet strings as big-endian integers.
+			 * We could use 64-bit additions here but the benefit
+			 * may not compensate the cost of the byteswapping.
+			 */
+			for (k = hash->v - 1; k > 0; k--) {
+				carry = ptr[k] + b[k] + carry;
+				ptr[k] = carry;
+				carry >>= 8;
+			}
+
+			ptr[k] += b[k] + carry;
+			explicit_bzero(&carry, sizeof(carry));
+		}
+
+		explicit_bzero(b, sizeof(b));
+	}
+
+	explicit_bzero(di, sizeof(di));
+	l_checksum_free(h);
+	return key;
+}
+
+/* RFC8018 Section A.2 */
 static struct asn1_oid pkcs5_pbkdf2_oid = {
 	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c }
 };
 
+/* RFC8018 Section A.4 */
 static struct asn1_oid pkcs5_pbes2_oid = {
 	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0d }
 };
 
+/* RFC8018 Section A.3 */
 static const struct pkcs5_pbes1_encryption_oid {
 	enum l_checksum_type checksum_type;
 	enum l_cipher_type cipher_type;
-- 
2.27.0

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

* [PATCH 2/5] pkcs5: Add PKCS#12 algorithms in pkcs5_cipher_from_alg_id
  2020-12-12  0:50 [PATCH 1/5] pkcs5: Add the PKCS#12 KDF Andrew Zaborowski
@ 2020-12-12  0:50 ` Andrew Zaborowski
  2020-12-12  0:50 ` [PATCH 3/5] pem: Add l_pem_load_container_file Andrew Zaborowski
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Andrew Zaborowski @ 2020-12-12  0:50 UTC (permalink / raw)
  To: ell

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

Since pkcs5_cipher_from_alg_id() is used for decrypting PKCS#8 private
keys, teach it to also parse PKCS#12 AlgorithmIdentifier asn1 syntax
(almost identical to PKCS#5).  Ideally the pkcs5_* functions and pkcs5.c
should be renamed to not include the PKCS#5 string because the callers
are probably not interested in what cipher is used as long as they can
use it to decrypt data their input.  So ideally they want to call
one function that understands all the common algorithm OIDs.
---
 ell/pkcs5.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 178 insertions(+), 3 deletions(-)

diff --git a/ell/pkcs5.c b/ell/pkcs5.c
index 979cebe..25bf431 100644
--- a/ell/pkcs5.c
+++ b/ell/pkcs5.c
@@ -300,6 +300,15 @@ uint8_t *pkcs12_pbkdf(const char *password, const struct pkcs12_hash *hash,
 	return key;
 }
 
+/* RFC7292 Appendix A */
+static const struct pkcs12_hash pkcs12_sha1_hash = {
+	.alg = L_CHECKSUM_SHA1,
+	.len = 20,
+	.u   = 20,
+	.v   = 64,
+	.oid = { 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } },
+};
+
 /* RFC8018 Section A.2 */
 static struct asn1_oid pkcs5_pbkdf2_oid = {
 	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c }
@@ -327,6 +336,74 @@ static const struct pkcs5_pbes1_encryption_oid {
 	/* MD2- and RC2-based schemes 1, 4, 6 and 11 not supported */
 };
 
+/* RFC7292 Appendix C */
+static const struct pkcs12_encryption_oid {
+	enum l_cipher_type cipher_type;
+	unsigned int key_length;
+	unsigned int iv_length;
+	bool copy_k1;	/* Expand the 2-Key 3DES key for 3-Key 3DES */
+	bool is_block;
+	struct asn1_oid oid;
+} pkcs12_encryption_oids[] = {
+	{ /* pbeWithSHAAnd128BitRC4 */
+		.cipher_type = L_CIPHER_ARC4,
+		.key_length = 16,
+		.oid = { 10, {
+			0x2a, 0x86, 0x48, 0x86, 0xf7,
+			0x0d, 0x01, 0x0c, 0x01, 0x01,
+		} }
+	},
+	{ /* pbeWithSHAAnd40BitRC4 */
+		.cipher_type = L_CIPHER_ARC4,
+		.key_length = 5,
+		.oid = { 10, {
+			0x2a, 0x86, 0x48, 0x86, 0xf7,
+			0x0d, 0x01, 0x0c, 0x01, 0x02,
+		} }
+	},
+	{ /* pbeWithSHAAnd3-KeyTripleDES-CBC */
+		.cipher_type = L_CIPHER_DES3_EDE_CBC,
+		.key_length = 24,
+		.iv_length = 8,
+		.is_block = true,
+		.oid = { 10, {
+			0x2a, 0x86, 0x48, 0x86, 0xf7,
+			0x0d, 0x01, 0x0c, 0x01, 0x03,
+		} }
+	},
+	{ /* pbeWithSHAAnd2-KeyTripleDES-CBC */
+		.cipher_type = L_CIPHER_DES3_EDE_CBC,
+		.key_length = 16,
+		.iv_length = 8,
+		.copy_k1 = true,
+		.is_block = true,
+		.oid = { 10, {
+			0x2a, 0x86, 0x48, 0x86, 0xf7,
+			0x0d, 0x01, 0x0c, 0x01, 0x04,
+		} }
+	},
+	{ /* pbeWithSHAAnd128BitRC2-CBC */
+		.cipher_type = L_CIPHER_RC2_CBC,
+		.key_length = 16,
+		.iv_length = 8,
+		.is_block = true,
+		.oid = { 10, {
+			0x2a, 0x86, 0x48, 0x86, 0xf7,
+			0x0d, 0x01, 0x0c, 0x01, 0x05,
+		} }
+	},
+	{ /* pbeWithSHAAnd40BitRC2-CBC */
+		.cipher_type = L_CIPHER_RC2_CBC,
+		.key_length = 5,
+		.iv_length = 8,
+		.is_block = true,
+		.oid = { 10, {
+			0x2a, 0x86, 0x48, 0x86, 0xf7,
+			0x0d, 0x01, 0x0c, 0x01, 0x06,
+		} }
+	},
+};
+
 static const struct pkcs5_digest_alg_oid {
 	enum l_checksum_type type;
 	struct asn1_oid oid;
@@ -503,8 +580,8 @@ static struct l_cipher *pkcs5_cipher_from_pbes2_params(
 	/* RFC8018 section B.2 */
 
 	/*
-	 * Since we don't support RC2/RC5, all our PKCS#5 ciphers only
-	 * have an obligatory OCTET STRING IV parameter and a fixed key
+	 * Since we don't support the RC2/RC5 PBES2 ciphers, our parameters
+	 * only have an obligatory OCTET STRING IV parameter and a fixed key
 	 * length.
 	 */
 	if (tag != ASN1_ID_OCTET_STRING || params_len != enc_scheme->iv_size)
@@ -534,6 +611,94 @@ static struct l_cipher *pkcs5_cipher_from_pbes2_params(
 	return cipher;
 }
 
+static struct l_cipher *pkcs12_cipher_from_alg_id(
+				const struct pkcs12_encryption_oid *scheme,
+				const uint8_t *params, size_t params_len,
+				const char *password, bool *out_is_block)
+{
+	uint8_t tag;
+	const uint8_t *salt;
+	const uint8_t *iterations_data;
+	size_t salt_len;
+	size_t iterations_len;
+	unsigned int iterations;
+	uint8_t *key;
+	size_t key_len;
+	struct l_cipher *cipher;
+
+	/* Same parameters as in PKCS#5 */
+	salt = asn1_der_find_elem(params, params_len, 0, &tag, &salt_len);
+	if (!salt || tag != ASN1_ID_OCTET_STRING)
+		return NULL;
+
+	iterations_data = asn1_der_find_elem(params, params_len, 1,
+						&tag, &iterations_len);
+	if (!iterations_data || tag != ASN1_ID_INTEGER ||
+			iterations_len < 1 || iterations_len > 4)
+		return NULL;
+
+	for (iterations = 0; iterations_len; iterations_len--)
+		iterations = (iterations << 8) | *iterations_data++;
+
+	if (iterations < 1 || iterations > 8192)
+		return NULL;
+
+	if (iterations_data != params + params_len)
+		return NULL;
+
+	key_len = scheme->key_length;
+	key = pkcs12_pbkdf(password, &pkcs12_sha1_hash, salt, salt_len,
+				iterations, 1, key_len);
+	if (!key)
+		return NULL;
+
+	if (scheme->copy_k1) {
+		/*
+		 * 2-Key 3DES is like L_CIPHER_DES3_EDE_CBC except the last
+		 * of the 3 8-byte keys is not generated using a KDF and
+		 * instead is a copy of the first key.  In other words
+		 * the first half of the 16-byte key material is appended
+		 * at the end to produce the 24 bytes for DES3_EDE_CBC.
+		 */
+		uint8_t *key2 = l_malloc(24);
+
+		memcpy(key2, key, 16);
+		memcpy(key2 + 16, key, 8);
+		explicit_bzero(key, key_len);
+		l_free(key);
+		key = key2;
+		key_len = 24;
+	}
+
+	cipher = l_cipher_new(scheme->cipher_type, key, key_len);
+	explicit_bzero(key, key_len);
+	l_free(key);
+
+	if (!cipher)
+		return NULL;
+
+	if (scheme->iv_length) {
+		uint8_t *iv = pkcs12_pbkdf(password, &pkcs12_sha1_hash,
+						salt, salt_len, iterations, 2,
+						scheme->iv_length);
+
+		if (!iv || !l_cipher_set_iv(cipher, iv, scheme->iv_length)) {
+			l_cipher_free(cipher);
+			cipher = NULL;
+		}
+
+		if (iv)
+			explicit_bzero(iv, scheme->iv_length);
+
+		l_free(iv);
+	}
+
+	if (out_is_block)
+		*out_is_block = scheme->is_block;
+
+	return cipher;
+}
+
 struct l_cipher *pkcs5_cipher_from_alg_id(const uint8_t *id_asn1,
 						size_t id_asn1_len,
 						const char *password,
@@ -576,8 +741,18 @@ struct l_cipher *pkcs5_cipher_from_alg_id(const uint8_t *id_asn1,
 		}
 	}
 
-	if (!pbes1_scheme)
+	/* Check if this is a PKCS#12 OID */
+	if (!pbes1_scheme) {
+		for (i = 0; i < L_ARRAY_SIZE(pkcs12_encryption_oids); i++)
+			if (asn1_oid_eq(&pkcs12_encryption_oids[i].oid,
+					oid_len, oid))
+				return pkcs12_cipher_from_alg_id(
+						&pkcs12_encryption_oids[i],
+						params, params_len, password,
+						out_is_block);
+
 		return NULL;
+	}
 
 	salt = asn1_der_find_elem(params, params_len, 0, &tag, &tmp_len);
 	if (!salt || tag != ASN1_ID_OCTET_STRING || tmp_len != 8)
-- 
2.27.0

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

* [PATCH 3/5] pem: Add l_pem_load_container_file
  2020-12-12  0:50 [PATCH 1/5] pkcs5: Add the PKCS#12 KDF Andrew Zaborowski
  2020-12-12  0:50 ` [PATCH 2/5] pkcs5: Add PKCS#12 algorithms in pkcs5_cipher_from_alg_id Andrew Zaborowski
@ 2020-12-12  0:50 ` Andrew Zaborowski
  2020-12-14 23:53   ` Denis Kenzior
  2020-12-12  0:50 ` [PATCH 4/5] pem: Add PKCS#12 parsing Andrew Zaborowski
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Andrew Zaborowski @ 2020-12-12  0:50 UTC (permalink / raw)
  To: ell

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

Add a function that takes a file path and detects what X.509 certificate
and/or private key format it uses and parses it accordingly.  This is to
make it easier for clients to support multiple file formats, including
raw X.509 certificates -- without this they would have to load the
contents of the file and call l_cert_new_from_der().
l_pem_load_container can also be used instead of
l_pem_load_certificate_chain() and l_pem_load_private_key().

This function can now load binary certificate files which are not PEM
but there wasn't a better place for it than pem.c.  I guess the name
could also be improved to imply that it's for certificate and private
key container files, but I couldn't come up with a name that isn't too
long.

This function treats PEM files as containers that can have both private
keys and certificates at the same time, openssl can parse such files and
also produces such files in some situations and they may have some good
uses.
---
 ell/ell.sym |   1 +
 ell/pem.c   | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 ell/pem.h   |   5 ++
 3 files changed, 158 insertions(+), 5 deletions(-)

diff --git a/ell/ell.sym b/ell/ell.sym
index d94b585..03c46d0 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -415,6 +415,7 @@ global:
 	l_pem_load_file;
 	l_pem_load_private_key;
 	l_pem_load_private_key_from_data;
+	l_pem_load_container_file;
 	/* pkcs5 */
 	l_pkcs5_pbkdf1;
 	l_pkcs5_pbkdf2;
diff --git a/ell/pem.c b/ell/pem.c
index 1b995d5..90d06fb 100644
--- a/ell/pem.c
+++ b/ell/pem.c
@@ -195,7 +195,7 @@ const char *pem_next(const void *buf, size_t buf_len, char **type_label,
 
 static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
 				char **out_type_label, size_t *out_len,
-				char **out_headers)
+				char **out_headers, const char **out_endp)
 {
 	size_t base64_len;
 	const char *base64;
@@ -203,7 +203,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
 	uint8_t *ret;
 
 	base64 = pem_next(buf, buf_len, &label, &base64_len,
-				NULL, false);
+				out_endp, false);
 	if (!base64)
 		return NULL;
 
@@ -262,7 +262,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
 LIB_EXPORT uint8_t *l_pem_load_buffer(const void *buf, size_t buf_len,
 					char **type_label, size_t *out_len)
 {
-	return pem_load_buffer(buf, buf_len, type_label, out_len, NULL);
+	return pem_load_buffer(buf, buf_len, type_label, out_len, NULL, NULL);
 }
 
 struct pem_file_info {
@@ -315,7 +315,8 @@ static uint8_t *pem_load_file(const char *filename, char **out_type_label,
 		return NULL;
 
 	result = pem_load_buffer(file.data, file.st.st_size,
-					out_type_label, out_len, out_headers);
+					out_type_label, out_len, out_headers,
+					NULL);
 	pem_file_close(&file);
 	return result;
 }
@@ -888,7 +889,7 @@ LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf,
 	if (encrypted)
 		*encrypted = false;
 
-	content = pem_load_buffer(buf, buf_len, &label, &len, &headers);
+	content = pem_load_buffer(buf, buf_len, &label, &len, &headers, NULL);
 
 	if (!content)
 		return NULL;
@@ -933,3 +934,149 @@ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename,
 	return pem_load_private_key(content, len, label, passphrase, headers,
 					encrypted);
 }
+
+/*
+ * 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 unenecrypted private keys
+ *  PEM legacy PKCS#1 encrypted and unenecrypted private keys
+ *  Raw X.509 certificates (.cer, .der, .crt)
+ *
+ * The raw format contains exactly one certificate, PEM and PKCS#12 files
+ * can contain any combination of certificates and private keys.
+ */
+LIB_EXPORT void l_pem_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;
+	const char *ptr;
+	size_t len;
+	bool error = false;
+
+	if (out_encrypted)
+		*out_encrypted = false;
+
+	if (out_certchain)
+		*out_certchain = NULL;
+
+	if (out_privkey)
+		*out_privkey = NULL;
+
+	if (unlikely(!filename))
+		return;
+
+	if (pem_file_open(&file, filename) < 0)
+		return;
+
+	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) {
+		const uint8_t *seq_data;
+		const uint8_t *elem_data;
+		size_t elem_len;
+		uint8_t tag;
+
+		if (!(seq_data = asn1_der_find_elem(file.data, file.st.st_size,
+							0, &tag, &len)))
+			goto not_der_after_all;
+
+		/*
+		 * 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_data, len,
+							0, &tag, &elem_len)))
+			goto not_der_after_all;
+
+		if (tag == ASN1_ID_SEQUENCE) {
+			if (out_certchain) {
+				struct l_cert *cert;
+
+				if (!(cert = l_cert_new_from_der(file.data,
+							file.st.st_size)))
+					goto close;
+
+				*out_certchain = certchain_new_from_leaf(cert);
+			}
+
+			goto close;
+		}
+	}
+
+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;
+	while (!error && 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)))
+			break;
+
+		len -= endp - ptr;
+		ptr = endp;
+
+		if (!strcmp(type_label, "CERTIFICATE")) {
+			struct l_cert *cert;
+
+			if (!out_certchain)
+				goto next;
+
+			if (!(cert = l_cert_new_from_der(der, der_len))) {
+				l_certchain_free(*out_certchain);
+				*out_certchain = NULL;
+				error = true;
+				goto next;
+			}
+
+			if (!*out_certchain)
+				*out_certchain = certchain_new_from_leaf(cert);
+			else
+				certchain_link_issuer(*out_certchain, cert);
+
+			goto next;
+		}
+
+		/* Only use the first private key found */
+		if (out_privkey && !*out_privkey) {
+			*out_privkey = pem_load_private_key(der, der_len,
+								type_label,
+								password,
+								headers,
+								out_encrypted);
+			continue;
+		}
+
+next:
+		explicit_bzero(der, der_len);
+		l_free(der);
+		l_free(type_label);
+		l_free(headers);
+	}
+
+close:
+	pem_file_close(&file);
+}
diff --git a/ell/pem.h b/ell/pem.h
index 1c93e7a..ce5af04 100644
--- a/ell/pem.h
+++ b/ell/pem.h
@@ -50,6 +50,11 @@ struct l_key *l_pem_load_private_key_from_data(const void *buf, size_t len,
 						const char *passphrase,
 						bool *encrypted);
 
+void l_pem_load_container_file(const char *filename, const char *password,
+				struct l_certchain **out_certchain,
+				struct l_key **out_privkey,
+				bool *out_encrypted);
+
 #ifdef __cplusplus
 }
 #endif
-- 
2.27.0

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

* [PATCH 4/5] pem: Add PKCS#12 parsing
  2020-12-12  0:50 [PATCH 1/5] pkcs5: Add the PKCS#12 KDF Andrew Zaborowski
  2020-12-12  0:50 ` [PATCH 2/5] pkcs5: Add PKCS#12 algorithms in pkcs5_cipher_from_alg_id Andrew Zaborowski
  2020-12-12  0:50 ` [PATCH 3/5] pem: Add l_pem_load_container_file Andrew Zaborowski
@ 2020-12-12  0:50 ` Andrew Zaborowski
  2020-12-12  0:51 ` [PATCH 5/5] unit: Add l_pem_load_container_file tests Andrew Zaborowski
  2020-12-14 23:04 ` [PATCH 1/5] pkcs5: Add the PKCS#12 KDF Denis Kenzior
  4 siblings, 0 replies; 10+ messages in thread
From: Andrew Zaborowski @ 2020-12-12  0:50 UTC (permalink / raw)
  To: ell

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

Add ability to load PKCS#12 files in l_pem_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/pem.c          | 624 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 625 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/pem.c b/ell/pem.c
index 90d06fb..9833f5e 100644
--- a/ell/pem.c
+++ b/ell/pem.c
@@ -876,6 +876,11 @@ done:
 	return pkey;
 }
 
+/*
+ * 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.
+ */
 LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf,
 							size_t buf_len,
 							const char *passphrase,
@@ -911,6 +916,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.
  **/
@@ -935,6 +944,582 @@ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename,
 					encrypted);
 }
 
+static const uint8_t *pkcs7_unpack_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 *pkcs7_decrypt_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 ||
+			(version[0] != 0 && version[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 = pkcs5_cipher_from_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) {
+		/* 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]) {
+				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 struct 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 pkcs12_parse_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 pkcs12_parse_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 = pem_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 =
+				pem_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 (!pkcs12_parse_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 (!(pkcs12_parse_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 void pkcs12_parse_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;
+	struct 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;
+
+	if (version_len != 1 || version[0] != 3)
+		return;
+
+	/*
+	 * Since we only support the password-based integrity mode,  the
+	 * authSafe must be of PKCS#7 type "data" and not "signedData".
+	 */
+	if (!(auth_safe = pkcs7_unpack_content_info(ptr, len, 1,
+							&pkcs7_data_oid, NULL,
+							&tag,
+							&auth_safe_len)) ||
+			tag != ASN1_ID_OCTET_STRING)
+		return;
+
+	/*
+	 * 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;
+
+	if (!(mac = asn1_der_find_elem(mac_data, mac_data_len, 0, &tag,
+					&mac_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return;
+
+	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;
+
+	if (!(iterations_data = asn1_der_find_elem(mac_data, mac_data_len, 2,
+							&tag,
+							&iterations_len)) ||
+			tag != ASN1_ID_INTEGER || iterations_len > 4)
+		return;
+
+	for (iterations = 0; iterations_len; iterations_len--)
+		iterations = (iterations << 8) | *iterations_data++;
+
+	if (iterations < 1 || iterations > 8192)
+		return;
+
+	/* RFC2315 Section 9.4 */
+	if (!(digest_alg = asn1_der_find_elem(mac, mac_len, 0, &tag,
+						&digest_alg_len)) ||
+			tag != ASN1_ID_SEQUENCE)
+		return;
+
+	if (!(digest = asn1_der_find_elem(mac, mac_len, 1, &tag,
+						&digest_len)) ||
+			tag != ASN1_ID_OCTET_STRING)
+		return;
+
+	if (!(alg_id = asn1_der_find_elem(digest_alg, digest_alg_len,
+						0, &tag, &alg_id_len)) ||
+			tag != ASN1_ID_OID)
+		return;
+
+	/* 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;
+
+	if (!(key = pkcs12_pbkdf(password, mac_hash, mac_salt, mac_salt_len,
+					iterations, 3, mac_hash->u)))
+		return;
+
+	hmac = l_checksum_new_hmac(mac_hash->alg, key, mac_hash->u);
+	explicit_bzero(key, mac_hash->u);
+
+	if (!hmac)
+		return;
+
+	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;
+
+	/*
+	 * 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 (memcmp(hmac_val, digest, digest_len))
+		return;
+
+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;
+
+	i = 0;
+	while (1) {
+		struct asn1_oid data_oid;
+		const uint8_t *data;
+		size_t data_len;
+
+		if (!(data = pkcs7_unpack_content_info(auth_safe_seq,
+							auth_safe_seq_len, i++,
+							NULL, &data_oid, &tag,
+							&data_len)))
+			break;
+
+		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)
+				goto error;
+
+			/*
+			 * 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 = pkcs7_decrypt_encrypted_data(data, data_len,
+								password, &oid,
+								&plaintext_len);
+			if (!plaintext)
+				goto error;
+
+			/*
+			 * 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) &&
+				pkcs12_parse_safe_contents(plaintext,
+								plaintext_len,
+								password,
+								out_certchain,
+								out_privkey);
+			explicit_bzero(plaintext, plaintext_len);
+			l_free(plaintext);
+
+			if (!ok)
+				goto error;
+		} else if (asn1_oid_eq(&pkcs7_data_oid,
+					data_oid.asn1_len, data_oid.asn1)) {
+			if (tag != ASN1_ID_OCTET_STRING)
+				goto error;
+
+			if (!pkcs12_parse_safe_contents(data, data_len,
+							password,
+							out_certchain,
+							out_privkey))
+				goto error;
+		}
+		/* envelopedData support not needed */
+	}
+
+	return;
+
+error:
+	if (out_certchain && *out_certchain) {
+		l_certchain_free(*out_certchain);
+		*out_certchain = NULL;
+	}
+
+	if (out_privkey && *out_privkey) {
+		l_key_free(*out_privkey);
+		*out_privkey = NULL;
+	}
+}
+
 /*
  * 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
@@ -946,9 +1531,14 @@ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename,
  *  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 and unencrypted 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.
  */
 LIB_EXPORT void l_pem_load_container_file(const char *filename,
 					const char *password,
@@ -1014,6 +1604,27 @@ LIB_EXPORT void l_pem_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.
+			 */
+			if (out_encrypted)
+				*out_encrypted = true;
+
+			if (!password)
+				goto close;
+
+			pkcs12_parse_pfx(seq_data, len, password,
+						out_certchain, out_privkey);
+			goto close;
+		}
 	}
 
 not_der_after_all:
@@ -1060,6 +1671,19 @@ not_der_after_all:
 			goto next;
 		}
 
+		/* PEM-encoded PKCS12, probably very rare */
+		if (!strcmp(type_label, "PKCS12")) {
+			if (out_encrypted)
+				*out_encrypted = true;
+
+			if (!password)
+				goto next;
+
+			pkcs12_parse_pfx(der, der_len, password,
+						out_certchain, out_privkey);
+			goto next;
+		}
+
 		/* Only use the first private key found */
 		if (out_privkey && !*out_privkey) {
 			*out_privkey = pem_load_private_key(der, der_len,
-- 
2.27.0

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

* [PATCH 5/5] unit: Add l_pem_load_container_file tests
  2020-12-12  0:50 [PATCH 1/5] pkcs5: Add the PKCS#12 KDF Andrew Zaborowski
                   ` (2 preceding siblings ...)
  2020-12-12  0:50 ` [PATCH 4/5] pem: Add PKCS#12 parsing Andrew Zaborowski
@ 2020-12-12  0:51 ` Andrew Zaborowski
  2020-12-14 23:04 ` [PATCH 1/5] pkcs5: Add the PKCS#12 KDF Denis Kenzior
  4 siblings, 0 replies; 10+ messages in thread
From: Andrew Zaborowski @ 2020-12-12  0:51 UTC (permalink / raw)
  To: ell

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

---
 unit/test-pem.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 118 insertions(+)

diff --git a/unit/test-pem.c b/unit/test-pem.c
index f875adc..eddb6c6 100644
--- a/unit/test-pem.c
+++ b/unit/test-pem.c
@@ -337,6 +337,74 @@ 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;
+
+	l_pem_load_container_file(params->path, false, &certchain,
+					&privkey, &encrypted);
+	assert(encrypted == params->expect_encrypted);
+
+	if (encrypted) {
+		/*
+		 * Depending on the format the certificates may be encrypted
+		 * or unencrypted even when the private key was encrypted.
+		 */
+		if (certchain) {
+			assert(params->expect_privkey);
+			l_certchain_free(certchain);
+			certchain = NULL;
+		}
+
+		assert(!privkey);
+
+		l_pem_load_container_file(params->path, "abc", &certchain,
+						&privkey, &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 +477,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] 10+ messages in thread

* Re: [PATCH 1/5] pkcs5: Add the PKCS#12 KDF
  2020-12-12  0:50 [PATCH 1/5] pkcs5: Add the PKCS#12 KDF Andrew Zaborowski
                   ` (3 preceding siblings ...)
  2020-12-12  0:51 ` [PATCH 5/5] unit: Add l_pem_load_container_file tests Andrew Zaborowski
@ 2020-12-14 23:04 ` Denis Kenzior
  4 siblings, 0 replies; 10+ messages in thread
From: Denis Kenzior @ 2020-12-14 23:04 UTC (permalink / raw)
  To: ell

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

Hi Andrew,

On 12/11/20 6:50 PM, Andrew Zaborowski wrote:
> Add the key derivation algorithm used with PKCS#12 to pkcs5.c so that it
> can be found together with the two PKCS#5 KDFs, and so that it can also
> be used when parsing of the PKCS#12 AlgorithmIdentifiers in the next
> commit.  This KDF is not recommended for new uses.
> ---
>   ell/pkcs5-private.h |  12 +++++
>   ell/pkcs5.c         | 121 ++++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 133 insertions(+)
> 

Patch 1 & 2 applied, thanks.

Regards,
-Denis

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

* Re: [PATCH 3/5] pem: Add l_pem_load_container_file
  2020-12-12  0:50 ` [PATCH 3/5] pem: Add l_pem_load_container_file Andrew Zaborowski
@ 2020-12-14 23:53   ` Denis Kenzior
  2020-12-15  0:20     ` Andrew Zaborowski
  0 siblings, 1 reply; 10+ messages in thread
From: Denis Kenzior @ 2020-12-14 23:53 UTC (permalink / raw)
  To: ell

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

Hi Andrew,

On 12/11/20 6:50 PM, Andrew Zaborowski wrote:
> Add a function that takes a file path and detects what X.509 certificate
> and/or private key format it uses and parses it accordingly.  This is to
> make it easier for clients to support multiple file formats, including
> raw X.509 certificates -- without this they would have to load the
> contents of the file and call l_cert_new_from_der().
> l_pem_load_container can also be used instead of
> l_pem_load_certificate_chain() and l_pem_load_private_key().
> 
> This function can now load binary certificate files which are not PEM

So this really makes it seem that it shouldn't be in pem.c?

> but there wasn't a better place for it than pem.c.  I guess the name
> could also be improved to imply that it's for certificate and private
> key container files, but I couldn't come up with a name that isn't too
> long.

Ok, nitpicking here, but this reasoning really belongs in the metadata, not the 
commit description.

So P12 files are mostly binary, but can also be PEM encoded, correct?  Should we 
put the pkcs12 DER loading into pkcs12.c or so?  We could also consider renaming 
pkcs5.c to pkcs.c since it seems pkcs5.c is becoming a bit of a jumble of pkcs8, 
pkcs5 and pkcs12 bits...

Still, I'm really unconvinced that this logically belongs in pem.c.  Besides 
iwd's TLS module, who is going to be using this function?  Maybe this belongs in 
iwd itself?

> 
> This function treats PEM files as containers that can have both private
> keys and certificates at the same time, openssl can parse such files and
> also produces such files in some situations and they may have some good
> uses.
> ---
>   ell/ell.sym |   1 +
>   ell/pem.c   | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++--
>   ell/pem.h   |   5 ++
>   3 files changed, 158 insertions(+), 5 deletions(-)
> 
> diff --git a/ell/ell.sym b/ell/ell.sym
> index d94b585..03c46d0 100644
> --- a/ell/ell.sym
> +++ b/ell/ell.sym
> @@ -415,6 +415,7 @@ global:
>   	l_pem_load_file;
>   	l_pem_load_private_key;
>   	l_pem_load_private_key_from_data;
> +	l_pem_load_container_file;
>   	/* pkcs5 */
>   	l_pkcs5_pbkdf1;
>   	l_pkcs5_pbkdf2;
> diff --git a/ell/pem.c b/ell/pem.c
> index 1b995d5..90d06fb 100644
> --- a/ell/pem.c
> +++ b/ell/pem.c
> @@ -195,7 +195,7 @@ const char *pem_next(const void *buf, size_t buf_len, char **type_label,
>   
>   static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
>   				char **out_type_label, size_t *out_len,
> -				char **out_headers)
> +				char **out_headers, const char **out_endp)
>   {
>   	size_t base64_len;
>   	const char *base64;
> @@ -203,7 +203,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
>   	uint8_t *ret;
>   
>   	base64 = pem_next(buf, buf_len, &label, &base64_len,
> -				NULL, false);
> +				out_endp, false);
>   	if (!base64)
>   		return NULL;
>   
> @@ -262,7 +262,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
>   LIB_EXPORT uint8_t *l_pem_load_buffer(const void *buf, size_t buf_len,
>   					char **type_label, size_t *out_len)
>   {
> -	return pem_load_buffer(buf, buf_len, type_label, out_len, NULL);
> +	return pem_load_buffer(buf, buf_len, type_label, out_len, NULL, NULL);
>   }
>   
>   struct pem_file_info {
> @@ -315,7 +315,8 @@ static uint8_t *pem_load_file(const char *filename, char **out_type_label,
>   		return NULL;
>   
>   	result = pem_load_buffer(file.data, file.st.st_size,
> -					out_type_label, out_len, out_headers);
> +					out_type_label, out_len, out_headers,
> +					NULL);
>   	pem_file_close(&file);
>   	return result;
>   }
> @@ -888,7 +889,7 @@ LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf,
>   	if (encrypted)
>   		*encrypted = false;
>   
> -	content = pem_load_buffer(buf, buf_len, &label, &len, &headers);
> +	content = pem_load_buffer(buf, buf_len, &label, &len, &headers, NULL);
>   
>   	if (!content)
>   		return NULL;
> @@ -933,3 +934,149 @@ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename,
>   	return pem_load_private_key(content, len, label, passphrase, headers,
>   					encrypted);
>   }
> +
> +/*
> + * 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 unenecrypted private keys
> + *  PEM legacy PKCS#1 encrypted and unenecrypted private keys
> + *  Raw X.509 certificates (.cer, .der, .crt)
> + *
> + * The raw format contains exactly one certificate, PEM and PKCS#12 files
> + * can contain any combination of certificates and private keys.
> + */
> +LIB_EXPORT void l_pem_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;
> +	const char *ptr;
> +	size_t len;
> +	bool error = false;
> +
> +	if (out_encrypted)
> +		*out_encrypted = false;
> +
> +	if (out_certchain)
> +		*out_certchain = NULL;
> +
> +	if (out_privkey)
> +		*out_privkey = NULL;

This kind of goes against our design patterns.  As a rule our functions should 
not side-effect on failure.

> +
> +	if (unlikely(!filename))
> +		return;

And failing silently seems wrong as well...?

> +
> +	if (pem_file_open(&file, filename) < 0)
> +		return;
> +
> +	if (file.st.st_size < 1)
> +		goto close;
> +
> +	/* 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 (!(seq_data = asn1_der_find_elem(file.data, file.st.st_size,
> +							0, &tag, &len)))
> +			goto not_der_after_all;
> +
> +		/*
> +		 * 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_data, len,
> +							0, &tag, &elem_len)))
> +			goto not_der_after_all;
> +
> +		if (tag == ASN1_ID_SEQUENCE) {
> +			if (out_certchain) {
> +				struct l_cert *cert;
> +
> +				if (!(cert = l_cert_new_from_der(file.data,
> +							file.st.st_size)))
> +					goto close;
> +
> +				*out_certchain = certchain_new_from_leaf(cert);
> +			}
> +
> +			goto close;
> +		}
> +	}
> +
> +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;
> +	while (!error && 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)))
> +			break;
> +
> +		len -= endp - ptr;
> +		ptr = endp;
> +
> +		if (!strcmp(type_label, "CERTIFICATE")) {
> +			struct l_cert *cert;
> +
> +			if (!out_certchain)
> +				goto next;
> +
> +			if (!(cert = l_cert_new_from_der(der, der_len))) {
> +				l_certchain_free(*out_certchain);
> +				*out_certchain = NULL;
> +				error = true;
> +				goto next;
> +			}
> +
> +			if (!*out_certchain)
> +				*out_certchain = certchain_new_from_leaf(cert);
> +			else
> +				certchain_link_issuer(*out_certchain, cert);
> +
> +			goto next;
> +		}
> +
> +		/* Only use the first private key found */
> +		if (out_privkey && !*out_privkey) {
> +			*out_privkey = pem_load_private_key(der, der_len,
> +								type_label,
> +								password,
> +								headers,
> +								out_encrypted);
> +			continue;
> +		}
> +
> +next:
> +		explicit_bzero(der, der_len);
> +		l_free(der);
> +		l_free(type_label);
> +		l_free(headers);
> +	}
> +
> +close:
> +	pem_file_close(&file);
> +}
> diff --git a/ell/pem.h b/ell/pem.h
> index 1c93e7a..ce5af04 100644
> --- a/ell/pem.h
> +++ b/ell/pem.h
> @@ -50,6 +50,11 @@ struct l_key *l_pem_load_private_key_from_data(const void *buf, size_t len,
>   						const char *passphrase,
>   						bool *encrypted);
>   
> +void l_pem_load_container_file(const char *filename, const char *password,
> +				struct l_certchain **out_certchain,
> +				struct l_key **out_privkey,
> +				bool *out_encrypted);
> +
>   #ifdef __cplusplus
>   }
>   #endif
> 

Regards,
-Denis

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

* Re: [PATCH 3/5] pem: Add l_pem_load_container_file
  2020-12-14 23:53   ` Denis Kenzior
@ 2020-12-15  0:20     ` Andrew Zaborowski
  2020-12-15  2:12       ` Denis Kenzior
  0 siblings, 1 reply; 10+ messages in thread
From: Andrew Zaborowski @ 2020-12-15  0:20 UTC (permalink / raw)
  To: ell

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

Hi Denis,

On Tue, 15 Dec 2020 at 00:53, Denis Kenzior <denkenz@gmail.com> wrote:
> On 12/11/20 6:50 PM, Andrew Zaborowski wrote:
> > Add a function that takes a file path and detects what X.509 certificate
> > and/or private key format it uses and parses it accordingly.  This is to
> > make it easier for clients to support multiple file formats, including
> > raw X.509 certificates -- without this they would have to load the
> > contents of the file and call l_cert_new_from_der().
> > l_pem_load_container can also be used instead of
> > l_pem_load_certificate_chain() and l_pem_load_private_key().
> >
> > This function can now load binary certificate files which are not PEM
>
> So this really makes it seem that it shouldn't be in pem.c?
>
> > but there wasn't a better place for it than pem.c.  I guess the name
> > could also be improved to imply that it's for certificate and private
> > key container files, but I couldn't come up with a name that isn't too
> > long.
>
> Ok, nitpicking here, but this reasoning really belongs in the metadata, not the
> commit description.

Ok, maybe a code comment would have been better (or just omitting
this.)  Anyway..

>
> So P12 files are mostly binary, but can also be PEM encoded, correct?  Should we
> put the pkcs12 DER loading into pkcs12.c or so?  We could also consider renaming
> pkcs5.c to pkcs.c since it seems pkcs5.c is becoming a bit of a jumble of pkcs8,
> pkcs5 and pkcs12 bits...

I initially wanted to add it in cert.c, that's why I exposed
pem_key_from_pkcs8_private_key_info() and
pem_key_from_pkcs8_encrypted_private_key_info() in pem-private.h, but
I ended up using them only from within pem.c because cert.c didn't
seem right for private key stuff.  But maybe that is a good idea.

So cert.c could contain the high-level file loading/parsing/decoding,
and then rather than pkcs.c I'd call it cert-crypto.c or similar for
the low-level crypto stuff?

The reasoning is that PKCS defined some of those formats and
algorithms but some of it comes from openssl code, and a lot is really
currently defined by RFCs.  Initially I think each PKCS standard was
republished as an RFC but each of those RFCs has since been obsoleted
by sets of newer RFCs.

>
> Still, I'm really unconvinced that this logically belongs in pem.c.  Besides
> iwd's TLS module, who is going to be using this function?  Maybe this belongs in
> iwd itself?

I see it more as library code (like gnutls), anyone using TLS will
need certificate loading.  ell's examples/https-* should also be
switched to use this function.

>
> >
> > This function treats PEM files as containers that can have both private
> > keys and certificates at the same time, openssl can parse such files and
> > also produces such files in some situations and they may have some good
> > uses.
> > ---
> >   ell/ell.sym |   1 +
> >   ell/pem.c   | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++--
> >   ell/pem.h   |   5 ++
> >   3 files changed, 158 insertions(+), 5 deletions(-)
> >
> > diff --git a/ell/ell.sym b/ell/ell.sym
> > index d94b585..03c46d0 100644
> > --- a/ell/ell.sym
> > +++ b/ell/ell.sym
> > @@ -415,6 +415,7 @@ global:
> >       l_pem_load_file;
> >       l_pem_load_private_key;
> >       l_pem_load_private_key_from_data;
> > +     l_pem_load_container_file;
> >       /* pkcs5 */
> >       l_pkcs5_pbkdf1;
> >       l_pkcs5_pbkdf2;
> > diff --git a/ell/pem.c b/ell/pem.c
> > index 1b995d5..90d06fb 100644
> > --- a/ell/pem.c
> > +++ b/ell/pem.c
> > @@ -195,7 +195,7 @@ const char *pem_next(const void *buf, size_t buf_len, char **type_label,
> >
> >   static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
> >                               char **out_type_label, size_t *out_len,
> > -                             char **out_headers)
> > +                             char **out_headers, const char **out_endp)
> >   {
> >       size_t base64_len;
> >       const char *base64;
> > @@ -203,7 +203,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
> >       uint8_t *ret;
> >
> >       base64 = pem_next(buf, buf_len, &label, &base64_len,
> > -                             NULL, false);
> > +                             out_endp, false);
> >       if (!base64)
> >               return NULL;
> >
> > @@ -262,7 +262,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
> >   LIB_EXPORT uint8_t *l_pem_load_buffer(const void *buf, size_t buf_len,
> >                                       char **type_label, size_t *out_len)
> >   {
> > -     return pem_load_buffer(buf, buf_len, type_label, out_len, NULL);
> > +     return pem_load_buffer(buf, buf_len, type_label, out_len, NULL, NULL);
> >   }
> >
> >   struct pem_file_info {
> > @@ -315,7 +315,8 @@ static uint8_t *pem_load_file(const char *filename, char **out_type_label,
> >               return NULL;
> >
> >       result = pem_load_buffer(file.data, file.st.st_size,
> > -                                     out_type_label, out_len, out_headers);
> > +                                     out_type_label, out_len, out_headers,
> > +                                     NULL);
> >       pem_file_close(&file);
> >       return result;
> >   }
> > @@ -888,7 +889,7 @@ LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf,
> >       if (encrypted)
> >               *encrypted = false;
> >
> > -     content = pem_load_buffer(buf, buf_len, &label, &len, &headers);
> > +     content = pem_load_buffer(buf, buf_len, &label, &len, &headers, NULL);
> >
> >       if (!content)
> >               return NULL;
> > @@ -933,3 +934,149 @@ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename,
> >       return pem_load_private_key(content, len, label, passphrase, headers,
> >                                       encrypted);
> >   }
> > +
> > +/*
> > + * 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 unenecrypted private keys
> > + *  PEM legacy PKCS#1 encrypted and unenecrypted private keys
> > + *  Raw X.509 certificates (.cer, .der, .crt)
> > + *
> > + * The raw format contains exactly one certificate, PEM and PKCS#12 files
> > + * can contain any combination of certificates and private keys.
> > + */
> > +LIB_EXPORT void l_pem_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;
> > +     const char *ptr;
> > +     size_t len;
> > +     bool error = false;
> > +
> > +     if (out_encrypted)
> > +             *out_encrypted = false;
> > +
> > +     if (out_certchain)
> > +             *out_certchain = NULL;
> > +
> > +     if (out_privkey)
> > +             *out_privkey = NULL;
>
> This kind of goes against our design patterns.  As a rule our functions should
> not side-effect on failure.

In this case this is how we signal the failure to load any
certificates and private keys, this just replaces the return value
since I needed a python tuple-like type.

So this should be read as something like:

  r = (NULL, NULL, false);

>
> > +
> > +     if (unlikely(!filename))
> > +             return;
>
> And failing silently seems wrong as well...?

And this as:

  return r;

I can change the actual return type from void to bool if you prefer.
I'll still need a way to signal whether the failure is due to
encryption.  In the existing API we do this by setting *out_encrypted.

Best regards

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

* Re: [PATCH 3/5] pem: Add l_pem_load_container_file
  2020-12-15  0:20     ` Andrew Zaborowski
@ 2020-12-15  2:12       ` Denis Kenzior
  2020-12-15  9:07         ` Andrew Zaborowski
  0 siblings, 1 reply; 10+ messages in thread
From: Denis Kenzior @ 2020-12-15  2:12 UTC (permalink / raw)
  To: ell

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

Hi Andrew,

>>
>> So P12 files are mostly binary, but can also be PEM encoded, correct?  Should we
>> put the pkcs12 DER loading into pkcs12.c or so?  We could also consider renaming
>> pkcs5.c to pkcs.c since it seems pkcs5.c is becoming a bit of a jumble of pkcs8,
>> pkcs5 and pkcs12 bits...
> 
> I initially wanted to add it in cert.c, that's why I exposed
> pem_key_from_pkcs8_private_key_info() and
> pem_key_from_pkcs8_encrypted_private_key_info() in pem-private.h, but
> I ended up using them only from within pem.c because cert.c didn't
> seem right for private key stuff.  But maybe that is a good idea.
> 
> So cert.c could contain the high-level file loading/parsing/decoding,
> and then rather than pkcs.c I'd call it cert-crypto.c or similar for
> the low-level crypto stuff?

Yes, something like this...

> 
> The reasoning is that PKCS defined some of those formats and
> algorithms but some of it comes from openssl code, and a lot is really
> currently defined by RFCs.  Initially I think each PKCS standard was
> republished as an RFC but each of those RFCs has since been obsoleted
> by sets of newer RFCs.
> 

I mean ideally we'd have each file format being handled in a separate file, 
either named according to the RFC or well known name.  So pkcs5.c, pkcs12.c, 
etc.  But if you want to put this all into one file that is also fine since 
they're all closely related.

>>
>> Still, I'm really unconvinced that this logically belongs in pem.c.  Besides
>> iwd's TLS module, who is going to be using this function?  Maybe this belongs in
>> iwd itself?
> 
> I see it more as library code (like gnutls), anyone using TLS will
> need certificate loading.  ell's examples/https-* should also be
> switched to use this function.

Hmm, where does this end though since we also need to support the system CA 
store, CAs loaded externally and given via keyring ids, TPM/TPM2 keys, etc... 
Not against the idea, but not sure that it is worth it for 1 real user...

> 
>>
>>>
>>> This function treats PEM files as containers that can have both private
>>> keys and certificates at the same time, openssl can parse such files and
>>> also produces such files in some situations and they may have some good
>>> uses.
>>> ---
>>>    ell/ell.sym |   1 +
>>>    ell/pem.c   | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++--
>>>    ell/pem.h   |   5 ++
>>>    3 files changed, 158 insertions(+), 5 deletions(-)
>>>
>>> diff --git a/ell/ell.sym b/ell/ell.sym
>>> index d94b585..03c46d0 100644
>>> --- a/ell/ell.sym
>>> +++ b/ell/ell.sym
>>> @@ -415,6 +415,7 @@ global:
>>>        l_pem_load_file;
>>>        l_pem_load_private_key;
>>>        l_pem_load_private_key_from_data;
>>> +     l_pem_load_container_file;
>>>        /* pkcs5 */
>>>        l_pkcs5_pbkdf1;
>>>        l_pkcs5_pbkdf2;
>>> diff --git a/ell/pem.c b/ell/pem.c
>>> index 1b995d5..90d06fb 100644
>>> --- a/ell/pem.c
>>> +++ b/ell/pem.c
>>> @@ -195,7 +195,7 @@ const char *pem_next(const void *buf, size_t buf_len, char **type_label,
>>>
>>>    static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
>>>                                char **out_type_label, size_t *out_len,
>>> -                             char **out_headers)
>>> +                             char **out_headers, const char **out_endp)
>>>    {
>>>        size_t base64_len;
>>>        const char *base64;
>>> @@ -203,7 +203,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
>>>        uint8_t *ret;
>>>
>>>        base64 = pem_next(buf, buf_len, &label, &base64_len,
>>> -                             NULL, false);
>>> +                             out_endp, false);
>>>        if (!base64)
>>>                return NULL;
>>>
>>> @@ -262,7 +262,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
>>>    LIB_EXPORT uint8_t *l_pem_load_buffer(const void *buf, size_t buf_len,
>>>                                        char **type_label, size_t *out_len)
>>>    {
>>> -     return pem_load_buffer(buf, buf_len, type_label, out_len, NULL);
>>> +     return pem_load_buffer(buf, buf_len, type_label, out_len, NULL, NULL);
>>>    }
>>>
>>>    struct pem_file_info {
>>> @@ -315,7 +315,8 @@ static uint8_t *pem_load_file(const char *filename, char **out_type_label,
>>>                return NULL;
>>>
>>>        result = pem_load_buffer(file.data, file.st.st_size,
>>> -                                     out_type_label, out_len, out_headers);
>>> +                                     out_type_label, out_len, out_headers,
>>> +                                     NULL);
>>>        pem_file_close(&file);
>>>        return result;
>>>    }
>>> @@ -888,7 +889,7 @@ LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf,
>>>        if (encrypted)
>>>                *encrypted = false;
>>>
>>> -     content = pem_load_buffer(buf, buf_len, &label, &len, &headers);
>>> +     content = pem_load_buffer(buf, buf_len, &label, &len, &headers, NULL);
>>>
>>>        if (!content)
>>>                return NULL;
>>> @@ -933,3 +934,149 @@ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename,
>>>        return pem_load_private_key(content, len, label, passphrase, headers,
>>>                                        encrypted);
>>>    }
>>> +
>>> +/*
>>> + * 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 unenecrypted private keys
>>> + *  PEM legacy PKCS#1 encrypted and unenecrypted private keys
>>> + *  Raw X.509 certificates (.cer, .der, .crt)
>>> + *
>>> + * The raw format contains exactly one certificate, PEM and PKCS#12 files
>>> + * can contain any combination of certificates and private keys.
>>> + */
>>> +LIB_EXPORT void l_pem_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;
>>> +     const char *ptr;
>>> +     size_t len;
>>> +     bool error = false;
>>> +
>>> +     if (out_encrypted)
>>> +             *out_encrypted = false;
>>> +
>>> +     if (out_certchain)
>>> +             *out_certchain = NULL;
>>> +
>>> +     if (out_privkey)
>>> +             *out_privkey = NULL;
>>
>> This kind of goes against our design patterns.  As a rule our functions should
>> not side-effect on failure.
> 
> In this case this is how we signal the failure to load any
> certificates and private keys, this just replaces the return value
> since I needed a python tuple-like type.
> 
> So this should be read as something like:
> 
>    r = (NULL, NULL, false);

ell isn't python though, and this is completely out of sync with the rest of the 
API.  Consistency trumps everything else...

> 
>>
>>> +
>>> +     if (unlikely(!filename))
>>> +             return;
>>
>> And failing silently seems wrong as well...?
> 
> And this as:
> 
>    return r;
> 
> I can change the actual return type from void to bool if you prefer.
> I'll still need a way to signal whether the failure is due to
> encryption.  In the existing API we do this by setting *out_encrypted.
> 

The latter would be more consistent and thus more preferable.  Maybe we should 
have made these return an int and set a negative errno for the special cases...

Regarrds
-Denis

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

* Re: [PATCH 3/5] pem: Add l_pem_load_container_file
  2020-12-15  2:12       ` Denis Kenzior
@ 2020-12-15  9:07         ` Andrew Zaborowski
  0 siblings, 0 replies; 10+ messages in thread
From: Andrew Zaborowski @ 2020-12-15  9:07 UTC (permalink / raw)
  To: ell

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

On Tue, 15 Dec 2020 at 03:12, Denis Kenzior <denkenz@gmail.com> wrote:
> >> So P12 files are mostly binary, but can also be PEM encoded, correct?  Should we
> >> put the pkcs12 DER loading into pkcs12.c or so?  We could also consider renaming
> >> pkcs5.c to pkcs.c since it seems pkcs5.c is becoming a bit of a jumble of pkcs8,
> >> pkcs5 and pkcs12 bits...
> >
> > I initially wanted to add it in cert.c, that's why I exposed
> > pem_key_from_pkcs8_private_key_info() and
> > pem_key_from_pkcs8_encrypted_private_key_info() in pem-private.h, but
> > I ended up using them only from within pem.c because cert.c didn't
> > seem right for private key stuff.  But maybe that is a good idea.
> >
> > So cert.c could contain the high-level file loading/parsing/decoding,
> > and then rather than pkcs.c I'd call it cert-crypto.c or similar for
> > the low-level crypto stuff?
>
> Yes, something like this...
>
> >
> > The reasoning is that PKCS defined some of those formats and
> > algorithms but some of it comes from openssl code, and a lot is really
> > currently defined by RFCs.  Initially I think each PKCS standard was
> > republished as an RFC but each of those RFCs has since been obsoleted
> > by sets of newer RFCs.
> >
>
> I mean ideally we'd have each file format being handled in a separate file,
> either named according to the RFC or well known name.  So pkcs5.c, pkcs12.c,
> etc.  But if you want to put this all into one file that is also fine since
> they're all closely related.

I'm open to ideas, but we could end up with too many files...  In the
case of pkcs5 it defines some crypto algorithms but those are not the
only ones that can be used (in pkcs8 etc.), the RFCs have added
algorithms that were not in any PKCS standards and deprected some too.
But then PKCS just stands for public-key-cryptography-standards, so I
guess pkcs.c is as good as cert.c, but I'd separate the hardcore
crypto stuff into pkcs-crypto.c or cert-crypto.c.

>
> >>
> >> Still, I'm really unconvinced that this logically belongs in pem.c.  Besides
> >> iwd's TLS module, who is going to be using this function?  Maybe this belongs in
> >> iwd itself?
> >
> > I see it more as library code (like gnutls), anyone using TLS will
> > need certificate loading.  ell's examples/https-* should also be
> > switched to use this function.
>
> Hmm, where does this end though since we also need to support the system CA
> store, CAs loaded externally and given via keyring ids, TPM/TPM2 keys, etc...

I'm ok moving all of the pkcs parsing into IWD if you prefer but I
don't see a real reason to keep any of those things outside ell
either... maybe except API stability.

Loading files is pretty basic though, we need some of it even for
ell's own unit tests to be able to load test data.

> Not against the idea, but not sure that it is worth it for 1 real user...

You could also say this about l_tls, l_dhcp, l_genl, etc.

>
> >
> >>
> >>>
> >>> This function treats PEM files as containers that can have both private
> >>> keys and certificates at the same time, openssl can parse such files and
> >>> also produces such files in some situations and they may have some good
> >>> uses.
> >>> ---
> >>>    ell/ell.sym |   1 +
> >>>    ell/pem.c   | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++--
> >>>    ell/pem.h   |   5 ++
> >>>    3 files changed, 158 insertions(+), 5 deletions(-)
> >>>
> >>> diff --git a/ell/ell.sym b/ell/ell.sym
> >>> index d94b585..03c46d0 100644
> >>> --- a/ell/ell.sym
> >>> +++ b/ell/ell.sym
> >>> @@ -415,6 +415,7 @@ global:
> >>>        l_pem_load_file;
> >>>        l_pem_load_private_key;
> >>>        l_pem_load_private_key_from_data;
> >>> +     l_pem_load_container_file;
> >>>        /* pkcs5 */
> >>>        l_pkcs5_pbkdf1;
> >>>        l_pkcs5_pbkdf2;
> >>> diff --git a/ell/pem.c b/ell/pem.c
> >>> index 1b995d5..90d06fb 100644
> >>> --- a/ell/pem.c
> >>> +++ b/ell/pem.c
> >>> @@ -195,7 +195,7 @@ const char *pem_next(const void *buf, size_t buf_len, char **type_label,
> >>>
> >>>    static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
> >>>                                char **out_type_label, size_t *out_len,
> >>> -                             char **out_headers)
> >>> +                             char **out_headers, const char **out_endp)
> >>>    {
> >>>        size_t base64_len;
> >>>        const char *base64;
> >>> @@ -203,7 +203,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
> >>>        uint8_t *ret;
> >>>
> >>>        base64 = pem_next(buf, buf_len, &label, &base64_len,
> >>> -                             NULL, false);
> >>> +                             out_endp, false);
> >>>        if (!base64)
> >>>                return NULL;
> >>>
> >>> @@ -262,7 +262,7 @@ static uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
> >>>    LIB_EXPORT uint8_t *l_pem_load_buffer(const void *buf, size_t buf_len,
> >>>                                        char **type_label, size_t *out_len)
> >>>    {
> >>> -     return pem_load_buffer(buf, buf_len, type_label, out_len, NULL);
> >>> +     return pem_load_buffer(buf, buf_len, type_label, out_len, NULL, NULL);
> >>>    }
> >>>
> >>>    struct pem_file_info {
> >>> @@ -315,7 +315,8 @@ static uint8_t *pem_load_file(const char *filename, char **out_type_label,
> >>>                return NULL;
> >>>
> >>>        result = pem_load_buffer(file.data, file.st.st_size,
> >>> -                                     out_type_label, out_len, out_headers);
> >>> +                                     out_type_label, out_len, out_headers,
> >>> +                                     NULL);
> >>>        pem_file_close(&file);
> >>>        return result;
> >>>    }
> >>> @@ -888,7 +889,7 @@ LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf,
> >>>        if (encrypted)
> >>>                *encrypted = false;
> >>>
> >>> -     content = pem_load_buffer(buf, buf_len, &label, &len, &headers);
> >>> +     content = pem_load_buffer(buf, buf_len, &label, &len, &headers, NULL);
> >>>
> >>>        if (!content)
> >>>                return NULL;
> >>> @@ -933,3 +934,149 @@ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename,
> >>>        return pem_load_private_key(content, len, label, passphrase, headers,
> >>>                                        encrypted);
> >>>    }
> >>> +
> >>> +/*
> >>> + * 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 unenecrypted private keys
> >>> + *  PEM legacy PKCS#1 encrypted and unenecrypted private keys
> >>> + *  Raw X.509 certificates (.cer, .der, .crt)
> >>> + *
> >>> + * The raw format contains exactly one certificate, PEM and PKCS#12 files
> >>> + * can contain any combination of certificates and private keys.
> >>> + */
> >>> +LIB_EXPORT void l_pem_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;
> >>> +     const char *ptr;
> >>> +     size_t len;
> >>> +     bool error = false;
> >>> +
> >>> +     if (out_encrypted)
> >>> +             *out_encrypted = false;
> >>> +
> >>> +     if (out_certchain)
> >>> +             *out_certchain = NULL;
> >>> +
> >>> +     if (out_privkey)
> >>> +             *out_privkey = NULL;
> >>
> >> This kind of goes against our design patterns.  As a rule our functions should
> >> not side-effect on failure.
> >
> > In this case this is how we signal the failure to load any
> > certificates and private keys, this just replaces the return value
> > since I needed a python tuple-like type.
> >
> > So this should be read as something like:
> >
> >    r = (NULL, NULL, false);
>
> ell isn't python though, and this is completely out of sync with the rest of the
> API.  Consistency trumps everything else...
>
> >
> >>
> >>> +
> >>> +     if (unlikely(!filename))
> >>> +             return;
> >>
> >> And failing silently seems wrong as well...?
> >
> > And this as:
> >
> >    return r;
> >
> > I can change the actual return type from void to bool if you prefer.
> > I'll still need a way to signal whether the failure is due to
> > encryption.  In the existing API we do this by setting *out_encrypted.
> >
>
> The latter would be more consistent and thus more preferable.

Ok.

> Maybe we should
> have made these return an int and set a negative errno for the special cases...

I can return the errno too... but we start being a little redundant.

You can also treat those situations as a "nothing was loaded" reply
rather than an error, and the caller can decide what it wants to do
with it.

Best regards

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

end of thread, other threads:[~2020-12-15  9:07 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-12  0:50 [PATCH 1/5] pkcs5: Add the PKCS#12 KDF Andrew Zaborowski
2020-12-12  0:50 ` [PATCH 2/5] pkcs5: Add PKCS#12 algorithms in pkcs5_cipher_from_alg_id Andrew Zaborowski
2020-12-12  0:50 ` [PATCH 3/5] pem: Add l_pem_load_container_file Andrew Zaborowski
2020-12-14 23:53   ` Denis Kenzior
2020-12-15  0:20     ` Andrew Zaborowski
2020-12-15  2:12       ` Denis Kenzior
2020-12-15  9:07         ` Andrew Zaborowski
2020-12-12  0:50 ` [PATCH 4/5] pem: Add PKCS#12 parsing Andrew Zaborowski
2020-12-12  0:51 ` [PATCH 5/5] unit: Add l_pem_load_container_file tests Andrew Zaborowski
2020-12-14 23:04 ` [PATCH 1/5] pkcs5: Add the PKCS#12 KDF 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.