diff mbox series

[Strawman] crypto: Handle PEM-encoded x.509 certificates

Message ID 163673838611.45802.5085223391786276660.stgit@morisot.1015granger.net (mailing list archive)
State Not Applicable
Delegated to: Herbert Xu
Headers show
Series [Strawman] crypto: Handle PEM-encoded x.509 certificates | expand

Commit Message

Chuck Lever Nov. 12, 2021, 5:39 p.m. UTC
This enables "# cat cert.pem | keyctl padd asymmetric <keyring>"

Since prep->data is a "const void *" I didn't feel comfortable with
pem_decode() simply overwriting either the pointer or the contents
of the provided buffer. A secondary buffer is therefore allocated,
and then later freed by .free_preparse.

This compiles, but is otherwise untested. I'm interested in opinions
about this approach.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 crypto/asymmetric_keys/Makefile          |    3 
 crypto/asymmetric_keys/asymmetric_type.c |    7 +
 crypto/asymmetric_keys/pem.c             |  185 ++++++++++++++++++++++++++++++
 crypto/asymmetric_keys/pem.h             |    8 +
 crypto/asymmetric_keys/pkcs8_parser.c    |    5 +
 crypto/asymmetric_keys/x509_public_key.c |    5 +
 include/linux/key-type.h                 |    2 
 7 files changed, 212 insertions(+), 3 deletions(-)
 create mode 100644 crypto/asymmetric_keys/pem.c
 create mode 100644 crypto/asymmetric_keys/pem.h

Comments

Eric Biggers Nov. 12, 2021, 6:49 p.m. UTC | #1
On Fri, Nov 12, 2021 at 12:39:52PM -0500, Chuck Lever wrote:
> This enables "# cat cert.pem | keyctl padd asymmetric <keyring>"
> 
> Since prep->data is a "const void *" I didn't feel comfortable with
> pem_decode() simply overwriting either the pointer or the contents
> of the provided buffer. A secondary buffer is therefore allocated,
> and then later freed by .free_preparse.
> 
> This compiles, but is otherwise untested. I'm interested in opinions
> about this approach.
> 
> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>

Why?  You can easily convert PEM to DER in userspace, for example with a command
like 'openssl x509 -in cert.pem -out cert.der -outform der'.  There's no need
for the kernel to do it.

- Eric
Chuck Lever Nov. 13, 2021, 7:12 p.m. UTC | #2
Hi Eric-

> On Nov 12, 2021, at 1:49 PM, Eric Biggers <ebiggers@kernel.org> wrote:
> 
> On Fri, Nov 12, 2021 at 12:39:52PM -0500, Chuck Lever wrote:
>> This enables "# cat cert.pem | keyctl padd asymmetric <keyring>"
>> 
>> Since prep->data is a "const void *" I didn't feel comfortable with
>> pem_decode() simply overwriting either the pointer or the contents
>> of the provided buffer. A secondary buffer is therefore allocated,
>> and then later freed by .free_preparse.
>> 
>> This compiles, but is otherwise untested. I'm interested in opinions
>> about this approach.
>> 
>> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
> 
> Why?  You can easily convert PEM to DER in userspace, for example with a command
> like 'openssl x509 -in cert.pem -out cert.der -outform der'.  There's no need
> for the kernel to do it.

Correct, it's not a hard requirement. However, this change does
broaden the add_key(2) API to enable it to accept a PEM-encoded
certificate directly instead of needing to run the blob through an
intermediate step, simplifying applications that might use it for
adding X.509 keys to kernel keyrings.

Certainly, the kernel could include a single set of base64 encoders
and decoders that can be used by all in-kernel consumers. See for
example net/ceph/armor.c and fs/crypto/fname.c .

Because PEM decoding does not require any policy decisions, and
because the kernel already has at least two existing partial
base64 implementations, I'm not aware of a technical reason a
system call like add_key(2) should not to accept PEM-encoded
asymmetric key material.


--
Chuck Lever
Eric Biggers Nov. 13, 2021, 11:02 p.m. UTC | #3
On Sat, Nov 13, 2021 at 07:12:44PM +0000, Chuck Lever III wrote:
> 
> Certainly, the kernel could include a single set of base64 encoders
> and decoders that can be used by all in-kernel consumers. See for
> example net/ceph/armor.c and fs/crypto/fname.c .

Not really, there are many variants of Base64 and different policy decisions
that can be made: the chosen character set, whether to pad or not pad, whether
to allow whitespace, how to handle invalid characters, how to handle invalid
padding, whether to nul-terminate, and so on.  There's lots of room for bugs and
incompatibilities.

> 
> Because PEM decoding does not require any policy decisions, and
> because the kernel already has at least two existing partial
> base64 implementations, I'm not aware of a technical reason a
> system call like add_key(2) should not to accept PEM-encoded
> asymmetric key material.

Adding kernel UAPIs expands the kernel's attack surface, causing security
vulnerabilities.  It also increases the number of UAPIs that need to be
permanently supported.  It makes no sense to add kernel UAPIs for things that
can be easily done in userspace.

They work well as April Fools' jokes, though:
https://lore.kernel.org/r/1459463613-32473-1-git-send-email-richard@nod.at
Perhaps you meant to save your patch for April 1?

- Eric
Chuck Lever Nov. 14, 2021, 2:34 a.m. UTC | #4
> On Nov 13, 2021, at 6:02 PM, Eric Biggers <ebiggers@kernel.org> wrote:
> 
> On Sat, Nov 13, 2021 at 07:12:44PM +0000, Chuck Lever III wrote:
>> 
>> Certainly, the kernel could include a single set of base64 encoders
>> and decoders that can be used by all in-kernel consumers. See for
>> example net/ceph/armor.c and fs/crypto/fname.c .
> 
> Not really, there are many variants of Base64 and different policy decisions
> that can be made: the chosen character set, whether to pad or not pad, whether
> to allow whitespace, how to handle invalid characters, how to handle invalid
> padding, whether to nul-terminate, and so on.  There's lots of room for bugs and
> incompatibilities.
> 
>> 
>> Because PEM decoding does not require any policy decisions, and
>> because the kernel already has at least two existing partial
>> base64 implementations, I'm not aware of a technical reason a
>> system call like add_key(2) should not to accept PEM-encoded
>> asymmetric key material.
> 
> Adding kernel UAPIs expands the kernel's attack surface, causing security
> vulnerabilities.  It also increases the number of UAPIs that need to be
> permanently supported.  It makes no sense to add kernel UAPIs for things that
> can be easily done in userspace.
> 
> They work well as April Fools' jokes, though:
> https://lore.kernel.org/r/1459463613-32473-1-git-send-email-richard@nod.at
> Perhaps you meant to save your patch for April 1?

That remark is uncalled for and out of line. Perhaps you just
don't know what "strawman" means or why someone would post
unfinished code to ask for direction. I'll mark that down to
your inexperience.

Interestingly, I don't see you listed as a maintainer in this
area:

$ scripts/get_maintainer.pl crypto/asymmetric_keys/
David Howells <dhowells@redhat.com> (maintainer:ASYMMETRIC KEYS)
Herbert Xu <herbert@gondor.apana.org.au> (maintainer:CRYPTO API)
"David S. Miller" <davem@davemloft.net> (maintainer:CRYPTO API)
keyrings@vger.kernel.org (open list:ASYMMETRIC KEYS)
linux-crypto@vger.kernel.org (open list:CRYPTO API)
linux-kernel@vger.kernel.org (open list)
$

I actually /have/ talked with one of these maintainers, and he
suggested PEM decoding under add_key(2) would be appropriate and
valuable. It actually wasn't my idea. I shall credit his idea in
the next version of this patch so there won't be any further
confusion.


--
Chuck Lever
Eric Biggers Nov. 14, 2021, 3 a.m. UTC | #5
On Sun, Nov 14, 2021 at 02:34:07AM +0000, Chuck Lever III wrote:
> > Adding kernel UAPIs expands the kernel's attack surface, causing security
> > vulnerabilities.  It also increases the number of UAPIs that need to be
> > permanently supported.  It makes no sense to add kernel UAPIs for things that
> > can be easily done in userspace.
> > 
> > They work well as April Fools' jokes, though:
> > https://lore.kernel.org/r/1459463613-32473-1-git-send-email-richard@nod.at
> > Perhaps you meant to save your patch for April 1?
> 
> That remark is uncalled for and out of line. Perhaps you just
> don't know what "strawman" means or why someone would post
> unfinished code to ask for direction. I'll mark that down to
> your inexperience.
> 
> Interestingly, I don't see you listed as a maintainer in this
> area:
> 
> $ scripts/get_maintainer.pl crypto/asymmetric_keys/
> David Howells <dhowells@redhat.com> (maintainer:ASYMMETRIC KEYS)
> Herbert Xu <herbert@gondor.apana.org.au> (maintainer:CRYPTO API)
> "David S. Miller" <davem@davemloft.net> (maintainer:CRYPTO API)
> keyrings@vger.kernel.org (open list:ASYMMETRIC KEYS)
> linux-crypto@vger.kernel.org (open list:CRYPTO API)
> linux-kernel@vger.kernel.org (open list)
> $
> 
> I actually /have/ talked with one of these maintainers, and he
> suggested PEM decoding under add_key(2) would be appropriate and
> valuable. It actually wasn't my idea. I shall credit his idea in
> the next version of this patch so there won't be any further
> confusion.

It's not appropriate to add UAPIs with no regards for increasing the kernel's
attack surface, especially for things that can easily be done in userspace.  The
kernel community is already struggling with thousands of syzbot reports and
constant security vulnerabilites.  I understand that your patch is not yet
finished, but it doesn't really matter; this is no need for this patch at all as
you can just convert PEM => DER in userspace.

PEM decoding is just some data processing which can be implemented in userspace
in any programming language, so it's not fundamentally different from
sys_leftpad().  So in my opinion the comparison is relevant.

- Eric
diff mbox series

Patch

diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index 28b91adba2ae..1df8c976dd2f 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -8,7 +8,8 @@  obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += asymmetric_keys.o
 asymmetric_keys-y := \
 	asymmetric_type.o \
 	restrict.o \
-	signature.o
+	signature.o \
+	pem.o
 
 obj-$(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE) += public_key.o
 obj-$(CONFIG_ASYMMETRIC_TPM_KEY_SUBTYPE) += asym_tpm.o
diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index ad8af3d70ac0..f5d810c079b6 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -12,10 +12,12 @@ 
 #include <linux/seq_file.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/vmalloc.h>
 #include <linux/ctype.h>
 #include <keys/system_keyring.h>
 #include <keys/user-type.h>
 #include "asymmetric_keys.h"
+#include "pem.h"
 
 MODULE_LICENSE("GPL");
 
@@ -378,6 +380,10 @@  static int asymmetric_key_preparse(struct key_preparsed_payload *prep)
 	if (prep->datalen == 0)
 		return -EINVAL;
 
+	ret = pem_decode(prep);
+	if (ret < 0)
+		return ret;
+
 	down_read(&asymmetric_key_parsers_sem);
 
 	ret = -EBADMSG;
@@ -428,6 +434,7 @@  static void asymmetric_key_free_preparse(struct key_preparsed_payload *prep)
 	}
 	asymmetric_key_free_kids(kids);
 	kfree(prep->description);
+	vfree(prep->decoded);
 }
 
 /*
diff --git a/crypto/asymmetric_keys/pem.c b/crypto/asymmetric_keys/pem.c
new file mode 100644
index 000000000000..b989fe7c1049
--- /dev/null
+++ b/crypto/asymmetric_keys/pem.c
@@ -0,0 +1,185 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Unwrap a PEM-encoded asymmetric key. This implementation unwraps
+ * the interoperable text encoding format specified in RFC 7468.
+ *
+ * Author: Chuck Lever <chuck.lever@oracle.com>
+ *
+ * Copyright (c) 2021, Oracle and/or its affiliates.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/key-type.h>
+
+#include "pem.h"
+
+/* Encapsulation boundaries */
+#define PEM_EB_MARKER		"-----"
+#define PEM_BEGIN_MARKER	PEM_EB_MARKER "BEGIN"
+#define PEM_END_MARKER		PEM_EB_MARKER "END"
+
+/*
+ * Unremarkable table-driven base64 decoder based on the public domain
+ * implementation provided at:
+ *   https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64
+ *
+ * XXX: Replace this implementation with one that handles EBCDIC input properly.
+ */
+
+#define WHITESPACE 253
+#define EQUALS     254
+#define INVALID    255
+
+static const u8 alphabet[] = {
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, WHITESPACE, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, 62,      INVALID, INVALID, INVALID, 63,
+	52,      53,      54,      55,      56,      57,      58,      59,
+	60,      61,      INVALID, INVALID, INVALID, EQUALS,  INVALID, INVALID,
+	INVALID, 0,       1,       2,       3,       4,       5,       6,
+	7,       8,       9,       10,      11,      12,      13,      14,
+	15,      16,      17,      18,      19,      20,      21,      22,
+	23,      24,      25,      INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, 26,      27,      28,      29,      30,      31,      32,
+	33,      34,      35,      36,      37,      38,      39,      40,
+	41,      42,      43,      44,      45,      46,      47,      48,
+	49,      50,      51,      INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+	INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID,
+};
+
+static bool base64decode(unsigned char *in, size_t inLen,
+			 unsigned char *out, size_t *outLen)
+{
+	unsigned char *end = in + inLen;
+	char iter = 0;
+	uint32_t buf = 0;
+	size_t len = 0;
+
+	while (in < end) {
+		u8 c = alphabet[*in++];
+
+		switch (c) {
+		case WHITESPACE:
+			continue;
+		case INVALID:
+			return false;
+		case EQUALS:
+			in = end;
+			continue;
+		default:
+			buf = buf << 6 | c;
+			iter++;
+
+			if (iter == 4) {
+				if ((len += 3) > *outLen)
+					return false;
+				*(out++) = (buf >> 16) & 255;
+				*(out++) = (buf >> 8) & 255;
+				*(out++) = buf & 255;
+				buf = 0;
+				iter = 0;
+			}
+		}
+	}
+
+	if (iter == 3) {
+		if ((len += 2) > *outLen)
+			return false;
+		*(out++) = (buf >> 10) & 255;
+		*(out++) = (buf >> 2) & 255;
+	} else if (iter == 2) {
+		if (++len > *outLen)
+			return false;
+		*(out++) = (buf >> 4) & 255;
+	}
+
+	*outLen = len;
+	return true;
+}
+
+/**
+ * pem_decode - Attempt to decode a PEM-encoded data blob.
+ * @prep: Data content to examine
+ *
+ * Assumptions:
+ * - The input data buffer is not more than a few pages in size.
+ * - The input data buffer has already been vetted for proper
+ *   kernel read access.
+ * - The input data buffer might not be NUL-terminated.
+ *
+ * PEM type labels are ignored. Subsequent parsing of the
+ * decoded message adequately identifies its content.
+ *
+ * On success, a pointer to a dynamically-allocated buffer
+ * containing the decoded content is returned. This buffer is
+ * vfree'd in the .free_preparse method.
+ *
+ * Return values:
+ *   %1: @prep.decoded points to the decoded message
+ *   %0: @prep did not contain a PEM-encoded message
+ *
+ * A negative errno is returned if an unexpected error has
+ * occurred (eg, memory exhaustion).
+ */
+int pem_decode(struct key_preparsed_payload *prep)
+{
+	const unsigned char *in = prep->data;
+	unsigned char *begin, *end, *out;
+	size_t outlen;
+
+	prep->decoded = NULL;
+	prep->decoded_len = 0;
+
+	/* Find the beginning encapsulation boundary */
+	begin = strnstr(in, PEM_BEGIN_MARKER, prep->datalen);
+	if (!begin)
+		goto out_not_pem;
+	begin = strnstr(begin, PEM_EB_MARKER, begin - in);
+	if (!begin)
+		goto out_not_pem;
+	begin += sizeof(PEM_EB_MARKER);
+
+	/* Find the ending encapsulation boundary */
+	end = strnstr(begin, PEM_END_MARKER, begin - in);
+	if (!end)
+		goto out_not_pem;
+	if (!strnstr(end, PEM_EB_MARKER, end - in))
+		goto out_not_pem;
+	end--;
+
+	/* Attempt to decode */
+	out = vmalloc(end - begin);
+	if (!out)
+		return -ENOMEM;
+	if (!base64decode(begin, end - begin, out, &outlen)) {
+		vfree(out);
+		goto out_not_pem;
+	}
+
+	prep->decoded = out;
+	prep->decoded_len = outlen;
+	return 1;
+
+out_not_pem:
+	return 0;
+}
diff --git a/crypto/asymmetric_keys/pem.h b/crypto/asymmetric_keys/pem.h
new file mode 100644
index 000000000000..51b9db517f94
--- /dev/null
+++ b/crypto/asymmetric_keys/pem.h
@@ -0,0 +1,8 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __ASYMMETRIC_PEM_H
+#define __ASYMMETRIC_PEM_H
+
+int pem_decode(struct key_preparsed_payload *prep);
+
+#endif /* __ASYMMETRIC_PEM_H */
diff --git a/crypto/asymmetric_keys/pkcs8_parser.c b/crypto/asymmetric_keys/pkcs8_parser.c
index 105dcce27f71..b7ce76d3a767 100644
--- a/crypto/asymmetric_keys/pkcs8_parser.c
+++ b/crypto/asymmetric_keys/pkcs8_parser.c
@@ -137,7 +137,10 @@  static int pkcs8_key_preparse(struct key_preparsed_payload *prep)
 {
 	struct public_key *pub;
 
-	pub = pkcs8_parse(prep->data, prep->datalen);
+	if (prep->decoded)
+		pub = pkcs8_parse(prep->decoded, prep->decoded_len);
+	else
+		pub = pkcs8_parse(prep->data, prep->datalen);
 	if (IS_ERR(pub))
 		return PTR_ERR(pub);
 
diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c
index 3d45161b271a..d8b559ac6d7c 100644
--- a/crypto/asymmetric_keys/x509_public_key.c
+++ b/crypto/asymmetric_keys/x509_public_key.c
@@ -167,7 +167,10 @@  static int x509_key_preparse(struct key_preparsed_payload *prep)
 	char *desc = NULL, *p;
 	int ret;
 
-	cert = x509_cert_parse(prep->data, prep->datalen);
+	if (prep->decoded)
+		cert = x509_cert_parse(prep->decoded, prep->decoded_len);
+	else
+		cert = x509_cert_parse(prep->data, prep->datalen);
 	if (IS_ERR(cert))
 		return PTR_ERR(cert);
 
diff --git a/include/linux/key-type.h b/include/linux/key-type.h
index 7d985a1dfe4a..1672f9599afa 100644
--- a/include/linux/key-type.h
+++ b/include/linux/key-type.h
@@ -34,6 +34,8 @@  struct key_preparsed_payload {
 	union key_payload payload;	/* Proposed payload */
 	const void	*data;		/* Raw data */
 	size_t		datalen;	/* Raw datalen */
+	const void	*decoded;	/* PEM-decoded data */
+	size_t		decoded_len;	/* Length of PEM-decoded data */
 	size_t		quotalen;	/* Quota length for proposed payload */
 	time64_t	expiry;		/* Expiry time of key */
 } __randomize_layout;