All of lore.kernel.org
 help / color / mirror / Atom feed
From: AKASHI Takahiro <takahiro.akashi@linaro.org>
To: u-boot@lists.denx.de
Subject: [U-Boot] [PATCH v3 12/16] lib: crypto: add public key utility
Date: Wed, 13 Nov 2019 09:44:58 +0900	[thread overview]
Message-ID: <20191113004502.29986-13-takahiro.akashi@linaro.org> (raw)
In-Reply-To: <20191113004502.29986-1-takahiro.akashi@linaro.org>

Imported from linux kernel v5.3:
 asymmetric-type.h with changes marked as __UBOOT__
 asymmetric_type.c with changes marked as __UBOOT__
 public_key.h with changes marked as __UBOOT__
 public_key.c with changes marked as __UBOOT__

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
---
 include/crypto/public_key.h    |  90 +++++
 include/keys/asymmetric-type.h |  88 +++++
 lib/crypto/Kconfig             |  19 +
 lib/crypto/Makefile            |  10 +
 lib/crypto/asymmetric_type.c   | 668 +++++++++++++++++++++++++++++++++
 lib/crypto/public_key.c        | 376 +++++++++++++++++++
 6 files changed, 1251 insertions(+)
 create mode 100644 include/crypto/public_key.h
 create mode 100644 include/keys/asymmetric-type.h
 create mode 100644 lib/crypto/Kconfig
 create mode 100644 lib/crypto/Makefile
 create mode 100644 lib/crypto/asymmetric_type.c
 create mode 100644 lib/crypto/public_key.c

diff --git a/include/crypto/public_key.h b/include/crypto/public_key.h
new file mode 100644
index 000000000000..436a1ee1ee64
--- /dev/null
+++ b/include/crypto/public_key.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Asymmetric public-key algorithm definitions
+ *
+ * See Documentation/crypto/asymmetric-keys.txt
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells at redhat.com)
+ */
+
+#ifndef _LINUX_PUBLIC_KEY_H
+#define _LINUX_PUBLIC_KEY_H
+
+#ifdef __UBOOT__
+#include <linux/types.h>
+#else
+#include <linux/keyctl.h>
+#endif
+#include <linux/oid_registry.h>
+
+/*
+ * Cryptographic data for the public-key subtype of the asymmetric key type.
+ *
+ * Note that this may include private part of the key as well as the public
+ * part.
+ */
+struct public_key {
+	void *key;
+	u32 keylen;
+	enum OID algo;
+	void *params;
+	u32 paramlen;
+	bool key_is_private;
+	const char *id_type;
+	const char *pkey_algo;
+};
+
+extern void public_key_free(struct public_key *key);
+
+/*
+ * Public key cryptography signature data
+ */
+struct public_key_signature {
+	struct asymmetric_key_id *auth_ids[2];
+	u8 *s;			/* Signature */
+	u32 s_size;		/* Number of bytes in signature */
+	u8 *digest;
+	u8 digest_size;		/* Number of bytes in digest */
+	const char *pkey_algo;
+	const char *hash_algo;
+	const char *encoding;
+};
+
+extern void public_key_signature_free(struct public_key_signature *sig);
+
+#ifndef __UBOOT__
+extern struct asymmetric_key_subtype public_key_subtype;
+
+struct key;
+struct key_type;
+union key_payload;
+
+extern int restrict_link_by_signature(struct key *dest_keyring,
+				      const struct key_type *type,
+				      const union key_payload *payload,
+				      struct key *trust_keyring);
+
+extern int restrict_link_by_key_or_keyring(struct key *dest_keyring,
+					   const struct key_type *type,
+					   const union key_payload *payload,
+					   struct key *trusted);
+
+extern int restrict_link_by_key_or_keyring_chain(struct key *trust_keyring,
+						 const struct key_type *type,
+						 const union key_payload *payload,
+						 struct key *trusted);
+
+extern int query_asymmetric_key(const struct kernel_pkey_params *,
+				struct kernel_pkey_query *);
+
+extern int encrypt_blob(struct kernel_pkey_params *, const void *, void *);
+extern int decrypt_blob(struct kernel_pkey_params *, const void *, void *);
+extern int create_signature(struct kernel_pkey_params *, const void *, void *);
+extern int verify_signature(const struct key *,
+			    const struct public_key_signature *);
+
+int public_key_verify_signature(const struct public_key *pkey,
+				const struct public_key_signature *sig);
+#endif /* !__UBOOT__ */
+
+#endif /* _LINUX_PUBLIC_KEY_H */
diff --git a/include/keys/asymmetric-type.h b/include/keys/asymmetric-type.h
new file mode 100644
index 000000000000..47d83917df2f
--- /dev/null
+++ b/include/keys/asymmetric-type.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Asymmetric Public-key cryptography key type interface
+ *
+ * See Documentation/crypto/asymmetric-keys.txt
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells at redhat.com)
+ */
+
+#ifndef _KEYS_ASYMMETRIC_TYPE_H
+#define _KEYS_ASYMMETRIC_TYPE_H
+
+#ifndef __UBOOT__
+#include <linux/key-type.h>
+#include <linux/verification.h>
+
+extern struct key_type key_type_asymmetric;
+
+/*
+ * The key payload is four words.  The asymmetric-type key uses them as
+ * follows:
+ */
+enum asymmetric_payload_bits {
+	asym_crypto,		/* The data representing the key */
+	asym_subtype,		/* Pointer to an asymmetric_key_subtype struct */
+	asym_key_ids,		/* Pointer to an asymmetric_key_ids struct */
+	asym_auth		/* The key's authorisation (signature, parent key ID) */
+};
+#endif /* !__UBOOT__ */
+
+/*
+ * Identifiers for an asymmetric key ID.  We have three ways of looking up a
+ * key derived from an X.509 certificate:
+ *
+ * (1) Serial Number & Issuer.  Non-optional.  This is the only valid way to
+ *     map a PKCS#7 signature to an X.509 certificate.
+ *
+ * (2) Issuer & Subject Unique IDs.  Optional.  These were the original way to
+ *     match X.509 certificates, but have fallen into disuse in favour of (3).
+ *
+ * (3) Auth & Subject Key Identifiers.  Optional.  SKIDs are only provided on
+ *     CA keys that are intended to sign other keys, so don't appear in end
+ *     user certificates unless forced.
+ *
+ * We could also support an PGP key identifier, which is just a SHA1 sum of the
+ * public key and certain parameters, but since we don't support PGP keys at
+ * the moment, we shall ignore those.
+ *
+ * What we actually do is provide a place where binary identifiers can be
+ * stashed and then compare against them when checking for an id match.
+ */
+struct asymmetric_key_id {
+	unsigned short	len;
+	unsigned char	data[];
+};
+
+struct asymmetric_key_ids {
+	void		*id[2];
+};
+
+extern bool asymmetric_key_id_same(const struct asymmetric_key_id *kid1,
+				   const struct asymmetric_key_id *kid2);
+
+extern bool asymmetric_key_id_partial(const struct asymmetric_key_id *kid1,
+				      const struct asymmetric_key_id *kid2);
+
+extern struct asymmetric_key_id *asymmetric_key_generate_id(const void *val_1,
+							    size_t len_1,
+							    const void *val_2,
+							    size_t len_2);
+#ifndef __UBOOT__
+static inline
+const struct asymmetric_key_ids *asymmetric_key_ids(const struct key *key)
+{
+	return key->payload.data[asym_key_ids];
+}
+
+extern struct key *find_asymmetric_key(struct key *keyring,
+				       const struct asymmetric_key_id *id_0,
+				       const struct asymmetric_key_id *id_1,
+				       bool partial);
+#endif
+
+/*
+ * The payload is at the discretion of the subtype.
+ */
+
+#endif /* _KEYS_ASYMMETRIC_TYPE_H */
diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig
new file mode 100644
index 000000000000..b8e8288d2f80
--- /dev/null
+++ b/lib/crypto/Kconfig
@@ -0,0 +1,19 @@
+menuconfig ASYMMETRIC_KEY_TYPE
+	bool "Asymmetric (public-key cryptographic) key Support"
+	help
+	  This option provides support for a key type that holds the data for
+	  the asymmetric keys used for public key cryptographic operations such
+	  as encryption, decryption, signature generation and signature
+	  verification.
+
+if ASYMMETRIC_KEY_TYPE
+
+config ASYMMETRIC_PUBLIC_KEY_SUBTYPE
+	bool "Asymmetric public-key crypto algorithm subtype"
+	help
+	  This option provides support for asymmetric public key type handling.
+	  If signature generation and/or verification are to be used,
+	  appropriate hash algorithms (such as SHA-1) must be available.
+	  ENOPKG will be reported if the requisite algorithm is unavailable.
+
+endif # ASYMMETRIC_KEY_TYPE
diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile
new file mode 100644
index 000000000000..a284de9e0411
--- /dev/null
+++ b/lib/crypto/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Makefile for asymmetric cryptographic keys
+#
+
+obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += asymmetric_keys.o
+
+asymmetric_keys-y := asymmetric_type.o
+
+obj-$(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE) += public_key.o
diff --git a/lib/crypto/asymmetric_type.c b/lib/crypto/asymmetric_type.c
new file mode 100644
index 000000000000..e04666c0801d
--- /dev/null
+++ b/lib/crypto/asymmetric_type.c
@@ -0,0 +1,668 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Asymmetric public-key cryptography key type
+ *
+ * See Documentation/crypto/asymmetric-keys.txt
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells at redhat.com)
+ */
+#ifndef __UBOOT__
+#include <keys/asymmetric-subtype.h>
+#include <keys/asymmetric-parser.h>
+#endif
+#include <crypto/public_key.h>
+#ifdef __UBOOT__
+#include <linux/compat.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#else
+#include <linux/seq_file.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#endif
+#ifdef __UBOOT__
+#include <keys/asymmetric-type.h>
+#else
+#include <keys/system_keyring.h>
+#include <keys/user-type.h>
+#include "asymmetric_keys.h"
+#endif
+
+MODULE_LICENSE("GPL");
+
+#ifndef __UBOOT__
+const char *const key_being_used_for[NR__KEY_BEING_USED_FOR] = {
+	[VERIFYING_MODULE_SIGNATURE]		= "mod sig",
+	[VERIFYING_FIRMWARE_SIGNATURE]		= "firmware sig",
+	[VERIFYING_KEXEC_PE_SIGNATURE]		= "kexec PE sig",
+	[VERIFYING_KEY_SIGNATURE]		= "key sig",
+	[VERIFYING_KEY_SELF_SIGNATURE]		= "key self sig",
+	[VERIFYING_UNSPECIFIED_SIGNATURE]	= "unspec sig",
+};
+EXPORT_SYMBOL_GPL(key_being_used_for);
+
+static LIST_HEAD(asymmetric_key_parsers);
+static DECLARE_RWSEM(asymmetric_key_parsers_sem);
+
+/**
+ * find_asymmetric_key - Find a key by ID.
+ * @keyring: The keys to search.
+ * @id_0: The first ID to look for or NULL.
+ * @id_1: The second ID to look for or NULL.
+ * @partial: Use partial match if true, exact if false.
+ *
+ * Find a key in the given keyring by identifier.  The preferred identifier is
+ * the id_0 and the fallback identifier is the id_1.  If both are given, the
+ * lookup is by the former, but the latter must also match.
+ */
+struct key *find_asymmetric_key(struct key *keyring,
+				const struct asymmetric_key_id *id_0,
+				const struct asymmetric_key_id *id_1,
+				bool partial)
+{
+	struct key *key;
+	key_ref_t ref;
+	const char *lookup;
+	char *req, *p;
+	int len;
+
+	BUG_ON(!id_0 && !id_1);
+
+	if (id_0) {
+		lookup = id_0->data;
+		len = id_0->len;
+	} else {
+		lookup = id_1->data;
+		len = id_1->len;
+	}
+
+	/* Construct an identifier "id:<keyid>". */
+	p = req = kmalloc(2 + 1 + len * 2 + 1, GFP_KERNEL);
+	if (!req)
+		return ERR_PTR(-ENOMEM);
+
+	if (partial) {
+		*p++ = 'i';
+		*p++ = 'd';
+	} else {
+		*p++ = 'e';
+		*p++ = 'x';
+	}
+	*p++ = ':';
+	p = bin2hex(p, lookup, len);
+	*p = 0;
+
+	pr_debug("Look up: \"%s\"\n", req);
+
+	ref = keyring_search(make_key_ref(keyring, 1),
+			     &key_type_asymmetric, req, true);
+	if (IS_ERR(ref))
+		pr_debug("Request for key '%s' err %ld\n", req, PTR_ERR(ref));
+	kfree(req);
+
+	if (IS_ERR(ref)) {
+		switch (PTR_ERR(ref)) {
+			/* Hide some search errors */
+		case -EACCES:
+		case -ENOTDIR:
+		case -EAGAIN:
+			return ERR_PTR(-ENOKEY);
+		default:
+			return ERR_CAST(ref);
+		}
+	}
+
+	key = key_ref_to_ptr(ref);
+	if (id_0 && id_1) {
+		const struct asymmetric_key_ids *kids = asymmetric_key_ids(key);
+
+		if (!kids->id[1]) {
+			pr_debug("First ID matches, but second is missing\n");
+			goto reject;
+		}
+		if (!asymmetric_key_id_same(id_1, kids->id[1])) {
+			pr_debug("First ID matches, but second does not\n");
+			goto reject;
+		}
+	}
+
+	pr_devel("<==%s() = 0 [%x]\n", __func__, key_serial(key));
+	return key;
+
+reject:
+	key_put(key);
+	return ERR_PTR(-EKEYREJECTED);
+}
+EXPORT_SYMBOL_GPL(find_asymmetric_key);
+#endif /* !__UBOOT__ */
+
+/**
+ * asymmetric_key_generate_id: Construct an asymmetric key ID
+ * @val_1: First binary blob
+ * @len_1: Length of first binary blob
+ * @val_2: Second binary blob
+ * @len_2: Length of second binary blob
+ *
+ * Construct an asymmetric key ID from a pair of binary blobs.
+ */
+struct asymmetric_key_id *asymmetric_key_generate_id(const void *val_1,
+						     size_t len_1,
+						     const void *val_2,
+						     size_t len_2)
+{
+	struct asymmetric_key_id *kid;
+
+	kid = kmalloc(sizeof(struct asymmetric_key_id) + len_1 + len_2,
+		      GFP_KERNEL);
+	if (!kid)
+		return ERR_PTR(-ENOMEM);
+	kid->len = len_1 + len_2;
+	memcpy(kid->data, val_1, len_1);
+	memcpy(kid->data + len_1, val_2, len_2);
+	return kid;
+}
+EXPORT_SYMBOL_GPL(asymmetric_key_generate_id);
+
+/**
+ * asymmetric_key_id_same - Return true if two asymmetric keys IDs are the same.
+ * @kid_1, @kid_2: The key IDs to compare
+ */
+bool asymmetric_key_id_same(const struct asymmetric_key_id *kid1,
+			    const struct asymmetric_key_id *kid2)
+{
+	if (!kid1 || !kid2)
+		return false;
+	if (kid1->len != kid2->len)
+		return false;
+	return memcmp(kid1->data, kid2->data, kid1->len) == 0;
+}
+EXPORT_SYMBOL_GPL(asymmetric_key_id_same);
+
+/**
+ * asymmetric_key_id_partial - Return true if two asymmetric keys IDs
+ * partially match
+ * @kid_1, @kid_2: The key IDs to compare
+ */
+bool asymmetric_key_id_partial(const struct asymmetric_key_id *kid1,
+			       const struct asymmetric_key_id *kid2)
+{
+	if (!kid1 || !kid2)
+		return false;
+	if (kid1->len < kid2->len)
+		return false;
+	return memcmp(kid1->data + (kid1->len - kid2->len),
+		      kid2->data, kid2->len) == 0;
+}
+EXPORT_SYMBOL_GPL(asymmetric_key_id_partial);
+
+#ifndef __UBOOT__
+/**
+ * asymmetric_match_key_ids - Search asymmetric key IDs
+ * @kids: The list of key IDs to check
+ * @match_id: The key ID we're looking for
+ * @match: The match function to use
+ */
+static bool asymmetric_match_key_ids(
+	const struct asymmetric_key_ids *kids,
+	const struct asymmetric_key_id *match_id,
+	bool (*match)(const struct asymmetric_key_id *kid1,
+		      const struct asymmetric_key_id *kid2))
+{
+	int i;
+
+	if (!kids || !match_id)
+		return false;
+	for (i = 0; i < ARRAY_SIZE(kids->id); i++)
+		if (match(kids->id[i], match_id))
+			return true;
+	return false;
+}
+
+/* helper function can be called directly with pre-allocated memory */
+inline int __asymmetric_key_hex_to_key_id(const char *id,
+				   struct asymmetric_key_id *match_id,
+				   size_t hexlen)
+{
+	match_id->len = hexlen;
+	return hex2bin(match_id->data, id, hexlen);
+}
+
+/**
+ * asymmetric_key_hex_to_key_id - Convert a hex string into a key ID.
+ * @id: The ID as a hex string.
+ */
+struct asymmetric_key_id *asymmetric_key_hex_to_key_id(const char *id)
+{
+	struct asymmetric_key_id *match_id;
+	size_t asciihexlen;
+	int ret;
+
+	if (!*id)
+		return ERR_PTR(-EINVAL);
+	asciihexlen = strlen(id);
+	if (asciihexlen & 1)
+		return ERR_PTR(-EINVAL);
+
+	match_id = kmalloc(sizeof(struct asymmetric_key_id) + asciihexlen / 2,
+			   GFP_KERNEL);
+	if (!match_id)
+		return ERR_PTR(-ENOMEM);
+	ret = __asymmetric_key_hex_to_key_id(id, match_id, asciihexlen / 2);
+	if (ret < 0) {
+		kfree(match_id);
+		return ERR_PTR(-EINVAL);
+	}
+	return match_id;
+}
+
+/*
+ * Match asymmetric keys by an exact match on an ID.
+ */
+static bool asymmetric_key_cmp(const struct key *key,
+			       const struct key_match_data *match_data)
+{
+	const struct asymmetric_key_ids *kids = asymmetric_key_ids(key);
+	const struct asymmetric_key_id *match_id = match_data->preparsed;
+
+	return asymmetric_match_key_ids(kids, match_id,
+					asymmetric_key_id_same);
+}
+
+/*
+ * Match asymmetric keys by a partial match on an IDs.
+ */
+static bool asymmetric_key_cmp_partial(const struct key *key,
+				       const struct key_match_data *match_data)
+{
+	const struct asymmetric_key_ids *kids = asymmetric_key_ids(key);
+	const struct asymmetric_key_id *match_id = match_data->preparsed;
+
+	return asymmetric_match_key_ids(kids, match_id,
+					asymmetric_key_id_partial);
+}
+
+/*
+ * Preparse the match criterion.  If we don't set lookup_type and cmp,
+ * the default will be an exact match on the key description.
+ *
+ * There are some specifiers for matching key IDs rather than by the key
+ * description:
+ *
+ *	"id:<id>" - find a key by partial match on any available ID
+ *	"ex:<id>" - find a key by exact match on any available ID
+ *
+ * These have to be searched by iteration rather than by direct lookup because
+ * the key is hashed according to its description.
+ */
+static int asymmetric_key_match_preparse(struct key_match_data *match_data)
+{
+	struct asymmetric_key_id *match_id;
+	const char *spec = match_data->raw_data;
+	const char *id;
+	bool (*cmp)(const struct key *, const struct key_match_data *) =
+		asymmetric_key_cmp;
+
+	if (!spec || !*spec)
+		return -EINVAL;
+	if (spec[0] == 'i' &&
+	    spec[1] == 'd' &&
+	    spec[2] == ':') {
+		id = spec + 3;
+		cmp = asymmetric_key_cmp_partial;
+	} else if (spec[0] == 'e' &&
+		   spec[1] == 'x' &&
+		   spec[2] == ':') {
+		id = spec + 3;
+	} else {
+		goto default_match;
+	}
+
+	match_id = asymmetric_key_hex_to_key_id(id);
+	if (IS_ERR(match_id))
+		return PTR_ERR(match_id);
+
+	match_data->preparsed = match_id;
+	match_data->cmp = cmp;
+	match_data->lookup_type = KEYRING_SEARCH_LOOKUP_ITERATE;
+	return 0;
+
+default_match:
+	return 0;
+}
+
+/*
+ * Free the preparsed the match criterion.
+ */
+static void asymmetric_key_match_free(struct key_match_data *match_data)
+{
+	kfree(match_data->preparsed);
+}
+
+/*
+ * Describe the asymmetric key
+ */
+static void asymmetric_key_describe(const struct key *key, struct seq_file *m)
+{
+	const struct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key);
+	const struct asymmetric_key_ids *kids = asymmetric_key_ids(key);
+	const struct asymmetric_key_id *kid;
+	const unsigned char *p;
+	int n;
+
+	seq_puts(m, key->description);
+
+	if (subtype) {
+		seq_puts(m, ": ");
+		subtype->describe(key, m);
+
+		if (kids && kids->id[1]) {
+			kid = kids->id[1];
+			seq_putc(m, ' ');
+			n = kid->len;
+			p = kid->data;
+			if (n > 4) {
+				p += n - 4;
+				n = 4;
+			}
+			seq_printf(m, "%*phN", n, p);
+		}
+
+		seq_puts(m, " [");
+		/* put something here to indicate the key's capabilities */
+		seq_putc(m, ']');
+	}
+}
+
+/*
+ * Preparse a asymmetric payload to get format the contents appropriately for the
+ * internal payload to cut down on the number of scans of the data performed.
+ *
+ * We also generate a proposed description from the contents of the key that
+ * can be used to name the key if the user doesn't want to provide one.
+ */
+static int asymmetric_key_preparse(struct key_preparsed_payload *prep)
+{
+	struct asymmetric_key_parser *parser;
+	int ret;
+
+	pr_devel("==>%s()\n", __func__);
+
+	if (prep->datalen == 0)
+		return -EINVAL;
+
+	down_read(&asymmetric_key_parsers_sem);
+
+	ret = -EBADMSG;
+	list_for_each_entry(parser, &asymmetric_key_parsers, link) {
+		pr_debug("Trying parser '%s'\n", parser->name);
+
+		ret = parser->parse(prep);
+		if (ret != -EBADMSG) {
+			pr_debug("Parser recognised the format (ret %d)\n",
+				 ret);
+			break;
+		}
+	}
+
+	up_read(&asymmetric_key_parsers_sem);
+	pr_devel("<==%s() = %d\n", __func__, ret);
+	return ret;
+}
+
+/*
+ * Clean up the key ID list
+ */
+static void asymmetric_key_free_kids(struct asymmetric_key_ids *kids)
+{
+	int i;
+
+	if (kids) {
+		for (i = 0; i < ARRAY_SIZE(kids->id); i++)
+			kfree(kids->id[i]);
+		kfree(kids);
+	}
+}
+
+/*
+ * Clean up the preparse data
+ */
+static void asymmetric_key_free_preparse(struct key_preparsed_payload *prep)
+{
+	struct asymmetric_key_subtype *subtype = prep->payload.data[asym_subtype];
+	struct asymmetric_key_ids *kids = prep->payload.data[asym_key_ids];
+
+	pr_devel("==>%s()\n", __func__);
+
+	if (subtype) {
+		subtype->destroy(prep->payload.data[asym_crypto],
+				 prep->payload.data[asym_auth]);
+		module_put(subtype->owner);
+	}
+	asymmetric_key_free_kids(kids);
+	kfree(prep->description);
+}
+
+/*
+ * dispose of the data dangling from the corpse of a asymmetric key
+ */
+static void asymmetric_key_destroy(struct key *key)
+{
+	struct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key);
+	struct asymmetric_key_ids *kids = key->payload.data[asym_key_ids];
+	void *data = key->payload.data[asym_crypto];
+	void *auth = key->payload.data[asym_auth];
+
+	key->payload.data[asym_crypto] = NULL;
+	key->payload.data[asym_subtype] = NULL;
+	key->payload.data[asym_key_ids] = NULL;
+	key->payload.data[asym_auth] = NULL;
+
+	if (subtype) {
+		subtype->destroy(data, auth);
+		module_put(subtype->owner);
+	}
+
+	asymmetric_key_free_kids(kids);
+}
+
+static struct key_restriction *asymmetric_restriction_alloc(
+	key_restrict_link_func_t check,
+	struct key *key)
+{
+	struct key_restriction *keyres =
+		kzalloc(sizeof(struct key_restriction), GFP_KERNEL);
+
+	if (!keyres)
+		return ERR_PTR(-ENOMEM);
+
+	keyres->check = check;
+	keyres->key = key;
+	keyres->keytype = &key_type_asymmetric;
+
+	return keyres;
+}
+
+/*
+ * look up keyring restrict functions for asymmetric keys
+ */
+static struct key_restriction *asymmetric_lookup_restriction(
+	const char *restriction)
+{
+	char *restrict_method;
+	char *parse_buf;
+	char *next;
+	struct key_restriction *ret = ERR_PTR(-EINVAL);
+
+	if (strcmp("builtin_trusted", restriction) == 0)
+		return asymmetric_restriction_alloc(
+			restrict_link_by_builtin_trusted, NULL);
+
+	if (strcmp("builtin_and_secondary_trusted", restriction) == 0)
+		return asymmetric_restriction_alloc(
+			restrict_link_by_builtin_and_secondary_trusted, NULL);
+
+	parse_buf = kstrndup(restriction, PAGE_SIZE, GFP_KERNEL);
+	if (!parse_buf)
+		return ERR_PTR(-ENOMEM);
+
+	next = parse_buf;
+	restrict_method = strsep(&next, ":");
+
+	if ((strcmp(restrict_method, "key_or_keyring") == 0) && next) {
+		char *key_text;
+		key_serial_t serial;
+		struct key *key;
+		key_restrict_link_func_t link_fn =
+			restrict_link_by_key_or_keyring;
+		bool allow_null_key = false;
+
+		key_text = strsep(&next, ":");
+
+		if (next) {
+			if (strcmp(next, "chain") != 0)
+				goto out;
+
+			link_fn = restrict_link_by_key_or_keyring_chain;
+			allow_null_key = true;
+		}
+
+		if (kstrtos32(key_text, 0, &serial) < 0)
+			goto out;
+
+		if ((serial == 0) && allow_null_key) {
+			key = NULL;
+		} else {
+			key = key_lookup(serial);
+			if (IS_ERR(key)) {
+				ret = ERR_CAST(key);
+				goto out;
+			}
+		}
+
+		ret = asymmetric_restriction_alloc(link_fn, key);
+		if (IS_ERR(ret))
+			key_put(key);
+	}
+
+out:
+	kfree(parse_buf);
+	return ret;
+}
+
+int asymmetric_key_eds_op(struct kernel_pkey_params *params,
+			  const void *in, void *out)
+{
+	const struct asymmetric_key_subtype *subtype;
+	struct key *key = params->key;
+	int ret;
+
+	pr_devel("==>%s()\n", __func__);
+
+	if (key->type != &key_type_asymmetric)
+		return -EINVAL;
+	subtype = asymmetric_key_subtype(key);
+	if (!subtype ||
+	    !key->payload.data[0])
+		return -EINVAL;
+	if (!subtype->eds_op)
+		return -ENOTSUPP;
+
+	ret = subtype->eds_op(params, in, out);
+
+	pr_devel("<==%s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int asymmetric_key_verify_signature(struct kernel_pkey_params *params,
+					   const void *in, const void *in2)
+{
+	struct public_key_signature sig = {
+		.s_size		= params->in2_len,
+		.digest_size	= params->in_len,
+		.encoding	= params->encoding,
+		.hash_algo	= params->hash_algo,
+		.digest		= (void *)in,
+		.s		= (void *)in2,
+	};
+
+	return verify_signature(params->key, &sig);
+}
+
+struct key_type key_type_asymmetric = {
+	.name			= "asymmetric",
+	.preparse		= asymmetric_key_preparse,
+	.free_preparse		= asymmetric_key_free_preparse,
+	.instantiate		= generic_key_instantiate,
+	.match_preparse		= asymmetric_key_match_preparse,
+	.match_free		= asymmetric_key_match_free,
+	.destroy		= asymmetric_key_destroy,
+	.describe		= asymmetric_key_describe,
+	.lookup_restriction	= asymmetric_lookup_restriction,
+	.asym_query		= query_asymmetric_key,
+	.asym_eds_op		= asymmetric_key_eds_op,
+	.asym_verify_signature	= asymmetric_key_verify_signature,
+};
+EXPORT_SYMBOL_GPL(key_type_asymmetric);
+
+/**
+ * register_asymmetric_key_parser - Register a asymmetric key blob parser
+ * @parser: The parser to register
+ */
+int register_asymmetric_key_parser(struct asymmetric_key_parser *parser)
+{
+	struct asymmetric_key_parser *cursor;
+	int ret;
+
+	down_write(&asymmetric_key_parsers_sem);
+
+	list_for_each_entry(cursor, &asymmetric_key_parsers, link) {
+		if (strcmp(cursor->name, parser->name) == 0) {
+			pr_err("Asymmetric key parser '%s' already registered\n",
+			       parser->name);
+			ret = -EEXIST;
+			goto out;
+		}
+	}
+
+	list_add_tail(&parser->link, &asymmetric_key_parsers);
+
+	pr_notice("Asymmetric key parser '%s' registered\n", parser->name);
+	ret = 0;
+
+out:
+	up_write(&asymmetric_key_parsers_sem);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(register_asymmetric_key_parser);
+
+/**
+ * unregister_asymmetric_key_parser - Unregister a asymmetric key blob parser
+ * @parser: The parser to unregister
+ */
+void unregister_asymmetric_key_parser(struct asymmetric_key_parser *parser)
+{
+	down_write(&asymmetric_key_parsers_sem);
+	list_del(&parser->link);
+	up_write(&asymmetric_key_parsers_sem);
+
+	pr_notice("Asymmetric key parser '%s' unregistered\n", parser->name);
+}
+EXPORT_SYMBOL_GPL(unregister_asymmetric_key_parser);
+
+/*
+ * Module stuff
+ */
+static int __init asymmetric_key_init(void)
+{
+	return register_key_type(&key_type_asymmetric);
+}
+
+static void __exit asymmetric_key_cleanup(void)
+{
+	unregister_key_type(&key_type_asymmetric);
+}
+
+module_init(asymmetric_key_init);
+module_exit(asymmetric_key_cleanup);
+#endif /* !__UBOOT__ */
diff --git a/lib/crypto/public_key.c b/lib/crypto/public_key.c
new file mode 100644
index 000000000000..634377472f40
--- /dev/null
+++ b/lib/crypto/public_key.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* In-software asymmetric public-key crypto subtype
+ *
+ * See Documentation/crypto/asymmetric-keys.txt
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells at redhat.com)
+ */
+
+#define pr_fmt(fmt) "PKEY: "fmt
+#ifdef __UBOOT__
+#include <linux/compat.h>
+#else
+#include <linux/module.h>
+#include <linux/export.h>
+#endif
+#include <linux/kernel.h>
+#ifndef __UBOOT__
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/scatterlist.h>
+#include <keys/asymmetric-subtype.h>
+#endif
+#include <crypto/public_key.h>
+#ifndef __UBOOT__
+#include <crypto/akcipher.h>
+#endif
+
+MODULE_DESCRIPTION("In-software asymmetric public-key subtype");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+#ifndef __UBOOT__
+/*
+ * Provide a part of a description of the key for /proc/keys.
+ */
+static void public_key_describe(const struct key *asymmetric_key,
+				struct seq_file *m)
+{
+	struct public_key *key = asymmetric_key->payload.data[asym_crypto];
+
+	if (key)
+		seq_printf(m, "%s.%s", key->id_type, key->pkey_algo);
+}
+#endif
+
+/*
+ * Destroy a public key algorithm key.
+ */
+void public_key_free(struct public_key *key)
+{
+	if (key) {
+		kfree(key->key);
+		kfree(key->params);
+		kfree(key);
+	}
+}
+EXPORT_SYMBOL_GPL(public_key_free);
+
+#ifdef __UBOOT__
+/*
+ * from <linux>/crypto/asymmetric_keys/signature.c
+ *
+ * Destroy a public key signature.
+ */
+void public_key_signature_free(struct public_key_signature *sig)
+{
+	int i;
+
+	if (sig) {
+		for (i = 0; i < ARRAY_SIZE(sig->auth_ids); i++)
+			free(sig->auth_ids[i]);
+		free(sig->s);
+		free(sig->digest);
+		free(sig);
+	}
+}
+EXPORT_SYMBOL_GPL(public_key_signature_free);
+
+#else
+/*
+ * Destroy a public key algorithm key.
+ */
+static void public_key_destroy(void *payload0, void *payload3)
+{
+	public_key_free(payload0);
+	public_key_signature_free(payload3);
+}
+
+/*
+ * Determine the crypto algorithm name.
+ */
+static
+int software_key_determine_akcipher(const char *encoding,
+				    const char *hash_algo,
+				    const struct public_key *pkey,
+				    char alg_name[CRYPTO_MAX_ALG_NAME])
+{
+	int n;
+
+	if (strcmp(encoding, "pkcs1") == 0) {
+		/* The data wangled by the RSA algorithm is typically padded
+		 * and encoded in some manner, such as EMSA-PKCS1-1_5 [RFC3447
+		 * sec 8.2].
+		 */
+		if (!hash_algo)
+			n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME,
+				     "pkcs1pad(%s)",
+				     pkey->pkey_algo);
+		else
+			n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME,
+				     "pkcs1pad(%s,%s)",
+				     pkey->pkey_algo, hash_algo);
+		return n >= CRYPTO_MAX_ALG_NAME ? -EINVAL : 0;
+	}
+
+	if (strcmp(encoding, "raw") == 0) {
+		strcpy(alg_name, pkey->pkey_algo);
+		return 0;
+	}
+
+	return -ENOPKG;
+}
+
+static u8 *pkey_pack_u32(u8 *dst, u32 val)
+{
+	memcpy(dst, &val, sizeof(val));
+	return dst + sizeof(val);
+}
+
+/*
+ * Query information about a key.
+ */
+static int software_key_query(const struct kernel_pkey_params *params,
+			      struct kernel_pkey_query *info)
+{
+	struct crypto_akcipher *tfm;
+	struct public_key *pkey = params->key->payload.data[asym_crypto];
+	char alg_name[CRYPTO_MAX_ALG_NAME];
+	u8 *key, *ptr;
+	int ret, len;
+
+	ret = software_key_determine_akcipher(params->encoding,
+					      params->hash_algo,
+					      pkey, alg_name);
+	if (ret < 0)
+		return ret;
+
+	tfm = crypto_alloc_akcipher(alg_name, 0, 0);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+
+	key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen,
+		      GFP_KERNEL);
+	if (!key)
+		goto error_free_tfm;
+	memcpy(key, pkey->key, pkey->keylen);
+	ptr = key + pkey->keylen;
+	ptr = pkey_pack_u32(ptr, pkey->algo);
+	ptr = pkey_pack_u32(ptr, pkey->paramlen);
+	memcpy(ptr, pkey->params, pkey->paramlen);
+
+	if (pkey->key_is_private)
+		ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen);
+	else
+		ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen);
+	if (ret < 0)
+		goto error_free_key;
+
+	len = crypto_akcipher_maxsize(tfm);
+	info->key_size = len * 8;
+	info->max_data_size = len;
+	info->max_sig_size = len;
+	info->max_enc_size = len;
+	info->max_dec_size = len;
+	info->supported_ops = (KEYCTL_SUPPORTS_ENCRYPT |
+			       KEYCTL_SUPPORTS_VERIFY);
+	if (pkey->key_is_private)
+		info->supported_ops |= (KEYCTL_SUPPORTS_DECRYPT |
+					KEYCTL_SUPPORTS_SIGN);
+	ret = 0;
+
+error_free_key:
+	kfree(key);
+error_free_tfm:
+	crypto_free_akcipher(tfm);
+	pr_devel("<==%s() = %d\n", __func__, ret);
+	return ret;
+}
+
+/*
+ * Do encryption, decryption and signing ops.
+ */
+static int software_key_eds_op(struct kernel_pkey_params *params,
+			       const void *in, void *out)
+{
+	const struct public_key *pkey = params->key->payload.data[asym_crypto];
+	struct akcipher_request *req;
+	struct crypto_akcipher *tfm;
+	struct crypto_wait cwait;
+	struct scatterlist in_sg, out_sg;
+	char alg_name[CRYPTO_MAX_ALG_NAME];
+	char *key, *ptr;
+	int ret;
+
+	pr_devel("==>%s()\n", __func__);
+
+	ret = software_key_determine_akcipher(params->encoding,
+					      params->hash_algo,
+					      pkey, alg_name);
+	if (ret < 0)
+		return ret;
+
+	tfm = crypto_alloc_akcipher(alg_name, 0, 0);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+
+	req = akcipher_request_alloc(tfm, GFP_KERNEL);
+	if (!req)
+		goto error_free_tfm;
+
+	key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen,
+		      GFP_KERNEL);
+	if (!key)
+		goto error_free_req;
+
+	memcpy(key, pkey->key, pkey->keylen);
+	ptr = key + pkey->keylen;
+	ptr = pkey_pack_u32(ptr, pkey->algo);
+	ptr = pkey_pack_u32(ptr, pkey->paramlen);
+	memcpy(ptr, pkey->params, pkey->paramlen);
+
+	if (pkey->key_is_private)
+		ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen);
+	else
+		ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen);
+	if (ret)
+		goto error_free_key;
+
+	sg_init_one(&in_sg, in, params->in_len);
+	sg_init_one(&out_sg, out, params->out_len);
+	akcipher_request_set_crypt(req, &in_sg, &out_sg, params->in_len,
+				   params->out_len);
+	crypto_init_wait(&cwait);
+	akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+				      CRYPTO_TFM_REQ_MAY_SLEEP,
+				      crypto_req_done, &cwait);
+
+	/* Perform the encryption calculation. */
+	switch (params->op) {
+	case kernel_pkey_encrypt:
+		ret = crypto_akcipher_encrypt(req);
+		break;
+	case kernel_pkey_decrypt:
+		ret = crypto_akcipher_decrypt(req);
+		break;
+	case kernel_pkey_sign:
+		ret = crypto_akcipher_sign(req);
+		break;
+	default:
+		BUG();
+	}
+
+	ret = crypto_wait_req(ret, &cwait);
+	if (ret == 0)
+		ret = req->dst_len;
+
+error_free_key:
+	kfree(key);
+error_free_req:
+	akcipher_request_free(req);
+error_free_tfm:
+	crypto_free_akcipher(tfm);
+	pr_devel("<==%s() = %d\n", __func__, ret);
+	return ret;
+}
+
+/*
+ * Verify a signature using a public key.
+ */
+int public_key_verify_signature(const struct public_key *pkey,
+				const struct public_key_signature *sig)
+{
+	struct crypto_wait cwait;
+	struct crypto_akcipher *tfm;
+	struct akcipher_request *req;
+	struct scatterlist src_sg[2];
+	char alg_name[CRYPTO_MAX_ALG_NAME];
+	char *key, *ptr;
+	int ret;
+
+	pr_devel("==>%s()\n", __func__);
+
+	BUG_ON(!pkey);
+	BUG_ON(!sig);
+	BUG_ON(!sig->s);
+
+	ret = software_key_determine_akcipher(sig->encoding,
+					      sig->hash_algo,
+					      pkey, alg_name);
+	if (ret < 0)
+		return ret;
+
+	tfm = crypto_alloc_akcipher(alg_name, 0, 0);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+
+	ret = -ENOMEM;
+	req = akcipher_request_alloc(tfm, GFP_KERNEL);
+	if (!req)
+		goto error_free_tfm;
+
+	key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen,
+		      GFP_KERNEL);
+	if (!key)
+		goto error_free_req;
+
+	memcpy(key, pkey->key, pkey->keylen);
+	ptr = key + pkey->keylen;
+	ptr = pkey_pack_u32(ptr, pkey->algo);
+	ptr = pkey_pack_u32(ptr, pkey->paramlen);
+	memcpy(ptr, pkey->params, pkey->paramlen);
+
+	if (pkey->key_is_private)
+		ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen);
+	else
+		ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen);
+	if (ret)
+		goto error_free_key;
+
+	sg_init_table(src_sg, 2);
+	sg_set_buf(&src_sg[0], sig->s, sig->s_size);
+	sg_set_buf(&src_sg[1], sig->digest, sig->digest_size);
+	akcipher_request_set_crypt(req, src_sg, NULL, sig->s_size,
+				   sig->digest_size);
+	crypto_init_wait(&cwait);
+	akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+				      CRYPTO_TFM_REQ_MAY_SLEEP,
+				      crypto_req_done, &cwait);
+	ret = crypto_wait_req(crypto_akcipher_verify(req), &cwait);
+
+error_free_key:
+	kfree(key);
+error_free_req:
+	akcipher_request_free(req);
+error_free_tfm:
+	crypto_free_akcipher(tfm);
+	pr_devel("<==%s() = %d\n", __func__, ret);
+	if (WARN_ON_ONCE(ret > 0))
+		ret = -EINVAL;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(public_key_verify_signature);
+
+static int public_key_verify_signature_2(const struct key *key,
+					 const struct public_key_signature *sig)
+{
+	const struct public_key *pk = key->payload.data[asym_crypto];
+	return public_key_verify_signature(pk, sig);
+}
+
+/*
+ * Public key algorithm asymmetric key subtype
+ */
+struct asymmetric_key_subtype public_key_subtype = {
+	.owner			= THIS_MODULE,
+	.name			= "public_key",
+	.name_len		= sizeof("public_key") - 1,
+	.describe		= public_key_describe,
+	.destroy		= public_key_destroy,
+	.query			= software_key_query,
+	.eds_op			= software_key_eds_op,
+	.verify_signature	= public_key_verify_signature_2,
+};
+EXPORT_SYMBOL_GPL(public_key_subtype);
+#endif /* !__UBOOT__ */
-- 
2.21.0

  parent reply	other threads:[~2019-11-13  0:44 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-11-13  0:44 [U-Boot] [PATCH v3 00/16] import x509/pkcs7 parsers from linux AKASHI Takahiro
2019-11-13  0:44 ` [U-Boot] [PATCH v3 01/16] linux_compat: move kmemdup() from ubifs.c to linux_compat.c AKASHI Takahiro
2019-11-26  3:20   ` Heinrich Schuchardt
2019-12-06 21:48   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 02/16] rtc.h: add struct udevice declaration AKASHI Takahiro
2019-12-06 21:48   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 03/16] rtc: move date.c from drivers/rtc/ to lib/ AKASHI Takahiro
2019-12-06 21:48   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 04/16] lib: add mktime64() for linux compatibility AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 05/16] include: kernel.h: include printk.h AKASHI Takahiro
2019-11-26  3:35   ` Heinrich Schuchardt
2019-11-27  1:02     ` AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-12-09  1:21     ` AKASHI Takahiro
2019-11-13  0:44 ` [U-Boot] [PATCH v3 06/16] linux/time.h: include vsprintf.h AKASHI Takahiro
2019-11-26  3:56   ` Heinrich Schuchardt
2019-11-26  7:31     ` Heinrich Schuchardt
2019-11-27  1:27       ` AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 07/16] cmd: add asn1_compiler AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 08/16] Makefile: add build script for asn1 parsers AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 09/16] lib: add asn1 decoder AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 10/16] doc: add README for asn1 compiler and decoder AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 11/16] lib: add oid registry utility AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-11-13  0:44 ` AKASHI Takahiro [this message]
2019-12-06 21:49   ` [U-Boot] [PATCH v3 12/16] lib: crypto: add public key utility Tom Rini
2019-11-13  0:44 ` [U-Boot] [PATCH v3 13/16] lib: crypto: add rsa public key parser AKASHI Takahiro
2019-12-06 21:49   ` Tom Rini
2019-11-13  0:45 ` [U-Boot] [PATCH v3 14/16] lib: crypto: add x509 parser AKASHI Takahiro
2019-12-06 21:50   ` Tom Rini
2019-12-07 20:51     ` Heinrich Schuchardt
2019-12-07 22:34       ` Tom Rini
2019-12-09  0:59         ` AKASHI Takahiro
2019-11-13  0:45 ` [U-Boot] [PATCH v3 15/16] lib: crypto: add pkcs7 message parser AKASHI Takahiro
2019-12-06 21:50   ` Tom Rini
2019-11-13  0:45 ` [U-Boot] [PATCH v3 16/16] test: add asn1 unit test AKASHI Takahiro
2019-12-06 21:50   ` Tom Rini

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=20191113004502.29986-13-takahiro.akashi@linaro.org \
    --to=takahiro.akashi@linaro.org \
    --cc=u-boot@lists.denx.de \
    /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.