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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxx>
---
 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

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@xxxxxxxxxx>
+ *
+ * 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;




[Index of Archives]     [Kernel]     [Gnu Classpath]     [Gnu Crypto]     [DM Crypt]     [Netfilter]     [Bugtraq]

  Powered by Linux