All of lore.kernel.org
 help / color / mirror / Atom feed
From: Andrew Zaborowski <andrew.zaborowski@intel.com>
To: ell@lists.01.org
Subject: [PATCH 07/10] cipher: Add l_cipher_from_pkcs5_id
Date: Fri, 11 Aug 2017 01:10:13 +0200	[thread overview]
Message-ID: <20170810231016.29445-7-andrew.zaborowski@intel.com> (raw)
In-Reply-To: <20170810231016.29445-1-andrew.zaborowski@intel.com>

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

Add a utility to parse all of the PKCS#5 DER-encoded encryption scheme
information, use the right PBKDF function to calculate the key,
create the appropriate l_cipher object type (if we support that PKCS#5
cipher type) with that key and set the IV on the l_cipher object.

We don't support any of the RC2 or RC5 based encryption schemes or
those that use MD2, SHA512-224 or SHA512-256 for the hash, because
there's no kernel support for those algorithms.  OpenSSL can create
RSA private keys encrypted with some of these algorithms but MD5+DES
and DES3-EDE are the default and the popular algorithms.  OpenSSL also
supports camellia ciphers in those scenarios which we could add.

Note that the caller still needs to add or remove the padding from the
data encrypted or decrypted with the l_cipher object so technically this
doesn't implement 100% of PKCS#5 PBES1 or PBES2.
Thise code may be better moved to a separate file from cipher.c.  If we
do that then it could be wrapped in a separate object that would do the
padding after calling the l_cipher operation, too, though the padding
is really simple anway, may not be worth the overhead of another wrapper.
---
 ell/cipher.c | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ell/cipher.h |   4 +
 2 files changed, 294 insertions(+)

diff --git a/ell/cipher.c b/ell/cipher.c
index 8e60d61..f9efd2b 100644
--- a/ell/cipher.c
+++ b/ell/cipher.c
@@ -34,6 +34,7 @@
 #include "cipher.h"
 #include "private.h"
 #include "random.h"
+#include "asn1-private.h"
 #include "checksum.h"
 
 #ifndef HAVE_LINUX_IF_ALG_H
@@ -759,3 +760,292 @@ static bool pkcs5_pbkdf2(enum l_checksum_type type, const char *password,
 
 	return !dk_len;
 }
+
+static struct asn1_oid pkcs5_pbkdf2_oid = {
+	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c }
+};
+
+static struct asn1_oid pkcs5_pbes2_oid = {
+	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0d }
+};
+
+static const struct pkcs5_pbes1_encryption_oid {
+	enum l_checksum_type checksum_type;
+	enum l_cipher_type cipher_type;
+	struct asn1_oid oid;
+} pkcs5_pbes1_encryption_oids[] = {
+	{ /* pbeWithMD5AndDES-CBC */
+		L_CHECKSUM_MD5, L_CIPHER_DES_CBC,
+		{ 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x03 } },
+	},
+	{ /* pbeWithSHA1AndDES-CBC */
+		L_CHECKSUM_SHA1, L_CIPHER_DES_CBC,
+		{ 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0a } },
+	},
+	/* MD2- and RC2-based schemes 1, 4, 6 and 11 not supported */
+};
+
+static const struct pkcs5_digest_alg_oid {
+	enum l_checksum_type type;
+	struct asn1_oid oid;
+} pkcs5_digest_alg_oids[] = {
+	{ /* hmacWithSHA1 */
+		L_CHECKSUM_SHA1,
+		{ 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x07 } },
+	},
+	{ /* hmacWithSHA224 */
+		L_CHECKSUM_SHA224,
+		{ 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08 } },
+	},
+	{ /* hmacWithSHA256 */
+		L_CHECKSUM_SHA256,
+		{ 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09 } },
+	},
+	{ /* hmacWithSHA384 */
+		L_CHECKSUM_SHA384,
+		{ 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a } },
+	},
+	{ /* hmacWithSHA512 */
+		L_CHECKSUM_SHA512,
+		{ 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b } },
+	},
+	/* hmacWithSHA512-224 and hmacWithSHA512-256 not supported */
+};
+
+static const struct pkcs5_enc_alg_oid {
+	enum l_cipher_type cipher_type;
+	uint8_t key_size, iv_size;
+	struct asn1_oid oid;
+} pkcs5_enc_alg_oids[] = {
+	{ /* desCBC */
+		L_CIPHER_DES_CBC, 8, 8,
+		{ 5, { 0x2b, 0x0e, 0x03, 0x02, 0x07 } },
+	},
+	{ /* des-EDE3-CBC */
+		L_CIPHER_DES3_EDE_CBC, 24, 8,
+		{ 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, 0x07 } },
+	},
+	/* RC2/RC5-based schemes 2 and 9 not supported */
+	{ /* aes128-CBC-PAD */
+		L_CIPHER_AES_CBC, 16, 16,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02 } },
+	},
+	{ /* aes192-CBC-PAD */
+		L_CIPHER_AES_CBC, 24, 16,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x16 } },
+	},
+	{ /* aes256-CBC-PAD */
+		L_CIPHER_AES_CBC, 32, 16,
+		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a } },
+	},
+};
+
+static struct l_cipher *cipher_from_pkcs5_pbes2_params(
+						const uint8_t *pbes2_params,
+						size_t pbes2_params_len,
+						const char *password)
+{
+	uint8_t tag;
+	const uint8_t *kdf_sequence, *enc_sequence, *oid, *params,
+		*salt, *iter_count_buf, *key_len_buf, *prf_sequence;
+	size_t kdf_len, enc_len, params_len, salt_len, key_len, tmp_len;
+	unsigned int i, iter_count;
+	enum l_checksum_type prf_alg = L_CHECKSUM_NONE;
+	const struct pkcs5_enc_alg_oid *enc_scheme = NULL;
+	uint8_t derived_key[64];
+	struct l_cipher *cipher;
+
+	/* RFC8018 section A.4 */
+
+	kdf_sequence = der_find_elem(pbes2_params, pbes2_params_len, 0,
+					&tag, &kdf_len);
+	if (!kdf_sequence || tag != ASN1_ID_SEQUENCE)
+		return NULL;
+
+	enc_sequence = der_find_elem(pbes2_params, pbes2_params_len, 1,
+					&tag, &enc_len);
+	if (!enc_sequence || tag != ASN1_ID_SEQUENCE)
+		return NULL;
+
+	if (der_find_elem(pbes2_params, pbes2_params_len, 2, &tag, &tmp_len))
+		return NULL;
+
+	/* RFC8018 section A.2 */
+
+	oid = der_find_elem(kdf_sequence, kdf_len, 0, &tag, &tmp_len);
+	if (!oid || tag != ASN1_ID_OID)
+		return NULL;
+
+	if (!asn1_oid_eq(&pkcs5_pbkdf2_oid, tmp_len, oid))
+		return NULL;
+
+	params = der_find_elem(kdf_sequence, kdf_len, 1, &tag, &params_len);
+	if (!params || tag != ASN1_ID_SEQUENCE)
+		return NULL;
+
+	if (der_find_elem(kdf_sequence, kdf_len, 2, &tag, &tmp_len))
+		return NULL;
+
+	salt = der_find_elem(params, params_len, 0, &tag, &salt_len);
+	if (!salt || tag != ASN1_ID_OCTET_STRING ||
+			salt_len < 1 || salt_len > 512)
+		return NULL;
+
+	iter_count_buf = der_find_elem(params, params_len, 1, &tag, &tmp_len);
+	if (!iter_count_buf || tag != ASN1_ID_INTEGER ||
+			tmp_len < 1 || tmp_len > 4)
+		return NULL;
+
+	iter_count = 0;
+	while (tmp_len--)
+		iter_count = (iter_count << 8) | *iter_count_buf++;
+
+	key_len_buf = der_find_elem(params, params_len, 2, &tag, &tmp_len);
+	if (key_len_buf) {
+		if (tag != ASN1_ID_INTEGER || tmp_len != 1)
+			return NULL;
+
+		key_len = 0;
+		while (tmp_len--)
+			key_len = (key_len << 8) | *key_len_buf++;
+	} else
+		key_len = 0;
+
+	prf_sequence = der_find_elem(params, params_len, 3, &tag, &tmp_len);
+	if (prf_sequence) {
+		if (tag != ASN1_ID_SEQUENCE)
+			return NULL;
+
+		oid = der_find_elem(prf_sequence, tmp_len, 0, &tag, &tmp_len);
+		if (!oid || tag != ASN1_ID_OID)
+			return NULL;
+
+		for (i = 0; i < L_ARRAY_SIZE(pkcs5_digest_alg_oids); i++)
+			if (!asn1_oid_eq(&pkcs5_digest_alg_oids[i].oid,
+						tmp_len, oid))
+				prf_alg = pkcs5_digest_alg_oids[i].type;
+		if (prf_alg == L_CHECKSUM_NONE)
+			return NULL;
+	} else
+		prf_alg = L_CHECKSUM_SHA1;
+
+	oid = der_find_elem(enc_sequence, enc_len, 0, &tag, &tmp_len);
+	if (!oid || tag != ASN1_ID_OID)
+		return NULL;
+
+	for (i = 0; i < L_ARRAY_SIZE(pkcs5_enc_alg_oids); i++)
+		if (asn1_oid_eq(&pkcs5_enc_alg_oids[i].oid, tmp_len, oid)) {
+			enc_scheme = &pkcs5_enc_alg_oids[i];
+			break;
+		}
+	if (!enc_scheme)
+		return NULL;
+
+	params = der_find_elem(enc_sequence, enc_len, 1, &tag, &params_len);
+	if (!params)
+		return NULL;
+
+	/* 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
+	 * length.
+	 */
+	if (tag != ASN1_ID_OCTET_STRING || params_len != enc_scheme->iv_size)
+		return NULL;
+
+	if (key_len && enc_scheme->key_size != key_len)
+		return NULL;
+	key_len = enc_scheme->key_size;
+
+	if (der_find_elem(enc_sequence, enc_len, 2, &tag, &tmp_len))
+		return NULL;
+
+	/* RFC8018 section 6.2 */
+
+	if (!pkcs5_pbkdf2(prf_alg, password, salt, salt_len, iter_count,
+				key_len, derived_key))
+		return NULL;
+
+	cipher = l_cipher_new(enc_scheme->cipher_type, derived_key, key_len);
+	if (!cipher)
+		return NULL;
+
+	if (l_cipher_set_iv(cipher, params, enc_scheme->iv_size))
+		return cipher;
+
+	l_cipher_free(cipher);
+	return NULL;
+}
+
+LIB_EXPORT struct l_cipher *l_cipher_from_pkcs5_id(const uint8_t *id_asn1,
+							size_t id_asn1_len,
+							const char *password)
+{
+	uint8_t tag;
+	const uint8_t *oid, *params, *salt, *iter_count_buf;
+	size_t oid_len, params_len, tmp_len;
+	unsigned int i, iter_count;
+	const struct pkcs5_pbes1_encryption_oid *pbes1_scheme = NULL;
+	uint8_t derived_key[16];
+	struct l_cipher *cipher;
+
+	oid = der_find_elem(id_asn1, id_asn1_len, 0, &tag, &oid_len);
+	if (!oid || tag != ASN1_ID_OID)
+		return NULL;
+
+	params = der_find_elem(id_asn1, id_asn1_len, 1, &tag, &params_len);
+	if (!params || tag != ASN1_ID_SEQUENCE)
+		return NULL;
+
+	if (der_find_elem(id_asn1, id_asn1_len, 2, &tag, &tmp_len))
+		return NULL;
+
+	if (asn1_oid_eq(&pkcs5_pbes2_oid, oid_len, oid))
+		return cipher_from_pkcs5_pbes2_params(params, params_len,
+							password);
+
+	/* RFC8018 section A.3 */
+
+	for (i = 0; i < L_ARRAY_SIZE(pkcs5_pbes1_encryption_oids); i++)
+		if (asn1_oid_eq(&pkcs5_pbes1_encryption_oids[i].oid,
+					oid_len, oid)) {
+			pbes1_scheme = &pkcs5_pbes1_encryption_oids[i];
+			break;
+		}
+	if (!pbes1_scheme)
+		return NULL;
+
+	salt = der_find_elem(params, params_len, 0, &tag, &tmp_len);
+	if (!salt || tag != ASN1_ID_OCTET_STRING || tmp_len != 8)
+		return NULL;
+
+	iter_count_buf = der_find_elem(params, params_len, 1, &tag, &tmp_len);
+	if (!iter_count_buf || tag != ASN1_ID_INTEGER ||
+			tmp_len < 1 || tmp_len > 4)
+		return NULL;
+
+	iter_count = 0;
+	while (tmp_len--)
+		iter_count = (iter_count << 8) | *iter_count_buf++;
+
+	if (der_find_elem(params, params_len, 2, &tag, &tmp_len))
+		return NULL;
+
+	/* RFC8018 section 6.1 */
+
+	if (!pkcs5_pbkdf1(pbes1_scheme->checksum_type,
+				password, salt, 8, iter_count, 16, derived_key))
+		return NULL;
+
+	cipher = l_cipher_new(pbes1_scheme->cipher_type, derived_key + 0, 8);
+	if (!cipher)
+		return NULL;
+
+	if (l_cipher_set_iv(cipher, derived_key + 8, 8))
+		return cipher;
+
+	l_cipher_free(cipher);
+	return NULL;
+}
diff --git a/ell/cipher.h b/ell/cipher.h
index d787f33..3c79aee 100644
--- a/ell/cipher.h
+++ b/ell/cipher.h
@@ -41,6 +41,10 @@ enum l_cipher_type {
 struct l_cipher *l_cipher_new(enum l_cipher_type type,
 				const void *key, size_t key_length);
 
+struct l_cipher *l_cipher_from_pkcs5_id(const uint8_t *id_asn1,
+					size_t id_asn1_len,
+					const char *password);
+
 void l_cipher_free(struct l_cipher *cipher);
 
 bool l_cipher_encrypt(struct l_cipher *cipher,
-- 
2.11.0


  parent reply	other threads:[~2017-08-10 23:10 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-08-10 23:10 [PATCH 01/10] checksum: Add SHA224 checksum type Andrew Zaborowski
2017-08-10 23:10 ` [PATCH 02/10] cipher: Add DES-CBC cipher type Andrew Zaborowski
2017-08-11 16:08   ` Denis Kenzior
2017-08-10 23:10 ` [PATCH 03/10] tls: Move DER / ASN.1 related definitions to asn1-private.h Andrew Zaborowski
2017-08-11 16:17   ` Denis Kenzior
2017-08-12  0:08     ` Andrew Zaborowski
2017-08-10 23:10 ` [PATCH 04/10] tls: Report error if private key loading fails Andrew Zaborowski
2017-08-11 16:18   ` Denis Kenzior
2017-08-10 23:10 ` [PATCH 05/10] asn1: Use const pointers in ASN1 parsing utilities Andrew Zaborowski
2017-08-10 23:10 ` [PATCH 06/10][RFC] cipher: Implement PKCS#5 key derivation functions Andrew Zaborowski
2017-08-11 16:28   ` Denis Kenzior
2017-08-12  0:12     ` Andrew Zaborowski
2017-08-10 23:10 ` Andrew Zaborowski [this message]
2017-08-11 16:55   ` [PATCH 07/10] cipher: Add l_cipher_from_pkcs5_id Denis Kenzior
2017-08-12  0:17     ` Andrew Zaborowski
2017-08-12  0:30       ` Denis Kenzior
2017-08-12  0:40         ` Andrew Zaborowski
2017-08-12  0:42           ` Denis Kenzior
2017-08-10 23:10 ` [PATCH 08/10] pem: Support encrypted keys in l_pem_load_private_key Andrew Zaborowski
2017-08-11 20:23   ` Denis Kenzior
2017-08-12  0:30     ` Andrew Zaborowski
2017-08-12  0:40       ` Denis Kenzior
2017-08-12  0:45         ` Andrew Zaborowski
2017-08-12  0:49           ` Denis Kenzior
2017-08-10 23:10 ` [PATCH 09/10] unit: Update for l_pem_load_private_key() parameter change Andrew Zaborowski
2017-08-10 23:10 ` [PATCH 10/10] unit: Test loading encrypted private key PEM files Andrew Zaborowski
2017-08-11 20:26   ` Denis Kenzior
2017-08-11 16:07 ` [PATCH 01/10] checksum: Add SHA224 checksum type Denis Kenzior

Reply instructions:

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

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

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

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

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

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

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