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 v1 09/11] lib: crypto: add x509 parser
Date: Fri, 11 Oct 2019 16:41:58 +0900	[thread overview]
Message-ID: <20191011074200.30269-10-takahiro.akashi@linaro.org> (raw)
In-Reply-To: <20191011074200.30269-1-takahiro.akashi@linaro.org>

Imported from linux kernel v5.3.

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
---
 lib/Kconfig                   |   1 +
 lib/Makefile                  |   1 +
 lib/crypto/Kconfig            |  10 +
 lib/crypto/Makefile           |  17 +
 lib/crypto/x509.asn1          |  60 +++
 lib/crypto/x509_akid.asn1     |  35 ++
 lib/crypto/x509_cert_parser.c | 697 ++++++++++++++++++++++++++++++++++
 lib/crypto/x509_parser.h      |  57 +++
 lib/crypto/x509_public_key.c  | 292 ++++++++++++++
 9 files changed, 1170 insertions(+)
 create mode 100644 lib/crypto/x509.asn1
 create mode 100644 lib/crypto/x509_akid.asn1
 create mode 100644 lib/crypto/x509_cert_parser.c
 create mode 100644 lib/crypto/x509_parser.h
 create mode 100644 lib/crypto/x509_public_key.c

diff --git a/lib/Kconfig b/lib/Kconfig
index d2955c8feb0e..aa44f52d9181 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -253,6 +253,7 @@ config AES
 	  present.
 
 source lib/rsa/Kconfig
+source lib/crypto/Kconfig
 
 config TPM
 	bool "Trusted Platform Module (TPM) Support"
diff --git a/lib/Makefile b/lib/Makefile
index 677ec33a1ce1..64ff105945b3 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_CMD_DHRYSTONE) += dhry/
 obj-$(CONFIG_ARCH_AT91) += at91/
 obj-$(CONFIG_OPTEE) += optee/
 obj-$(CONFIG_BUILD_ASN1) += asn1_decoder.o
+obj-y += crypto/
 
 obj-$(CONFIG_AES) += aes.o
 
diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig
index b8e8288d2f80..a87cf6107fc1 100644
--- a/lib/crypto/Kconfig
+++ b/lib/crypto/Kconfig
@@ -16,4 +16,14 @@ config ASYMMETRIC_PUBLIC_KEY_SUBTYPE
 	  appropriate hash algorithms (such as SHA-1) must be available.
 	  ENOPKG will be reported if the requisite algorithm is unavailable.
 
+config X509_CERTIFICATE_PARSER
+	bool "X.509 certificate parser"
+	depends on ASYMMETRIC_PUBLIC_KEY_SUBTYPE
+	select ASN1
+	select OID_REGISTRY
+	help
+	  This option provides support for parsing X.509 format blobs for key
+	  data and provides the ability to instantiate a crypto key from a
+	  public key packet found inside the certificate.
+
 endif # ASYMMETRIC_KEY_TYPE
diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile
index a284de9e0411..2ec2d81a1f3c 100644
--- a/lib/crypto/Makefile
+++ b/lib/crypto/Makefile
@@ -8,3 +8,20 @@ obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += asymmetric_keys.o
 asymmetric_keys-y := asymmetric_type.o
 
 obj-$(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE) += public_key.o
+
+#
+# X.509 Certificate handling
+#
+obj-$(CONFIG_X509_CERTIFICATE_PARSER) += x509_key_parser.o
+x509_key_parser-y := \
+	x509.asn1.o \
+	x509_akid.asn1.o \
+	x509_cert_parser.o \
+	x509_public_key.o
+
+$(obj)/x509_cert_parser.o: \
+	$(obj)/x509.asn1.h \
+	$(obj)/x509_akid.asn1.h
+
+$(obj)/x509.asn1.o: $(obj)/x509.asn1.c $(obj)/x509.asn1.h
+$(obj)/x509_akid.asn1.o: $(obj)/x509_akid.asn1.c $(obj)/x509_akid.asn1.h
diff --git a/lib/crypto/x509.asn1 b/lib/crypto/x509.asn1
new file mode 100644
index 000000000000..5c9f4e4a5231
--- /dev/null
+++ b/lib/crypto/x509.asn1
@@ -0,0 +1,60 @@
+Certificate ::= SEQUENCE {
+	tbsCertificate		TBSCertificate ({ x509_note_tbs_certificate }),
+	signatureAlgorithm	AlgorithmIdentifier,
+	signature		BIT STRING ({ x509_note_signature })
+	}
+
+TBSCertificate ::= SEQUENCE {
+	version           [ 0 ]	Version DEFAULT,
+	serialNumber		CertificateSerialNumber ({ x509_note_serial }),
+	signature		AlgorithmIdentifier ({ x509_note_pkey_algo }),
+	issuer			Name ({ x509_note_issuer }),
+	validity		Validity,
+	subject			Name ({ x509_note_subject }),
+	subjectPublicKeyInfo	SubjectPublicKeyInfo,
+	issuerUniqueID    [ 1 ]	IMPLICIT UniqueIdentifier OPTIONAL,
+	subjectUniqueID   [ 2 ]	IMPLICIT UniqueIdentifier OPTIONAL,
+	extensions        [ 3 ]	Extensions OPTIONAL
+	}
+
+Version ::= INTEGER
+CertificateSerialNumber ::= INTEGER
+
+AlgorithmIdentifier ::= SEQUENCE {
+	algorithm		OBJECT IDENTIFIER ({ x509_note_OID }),
+	parameters		ANY OPTIONAL ({ x509_note_params })
+}
+
+Name ::= SEQUENCE OF RelativeDistinguishedName
+
+RelativeDistinguishedName ::= SET OF AttributeValueAssertion
+
+AttributeValueAssertion ::= SEQUENCE {
+	attributeType		OBJECT IDENTIFIER ({ x509_note_OID }),
+	attributeValue		ANY ({ x509_extract_name_segment })
+	}
+
+Validity ::= SEQUENCE {
+	notBefore		Time ({ x509_note_not_before }),
+	notAfter		Time ({ x509_note_not_after })
+	}
+
+Time ::= CHOICE {
+	utcTime			UTCTime,
+	generalTime		GeneralizedTime
+	}
+
+SubjectPublicKeyInfo ::= SEQUENCE {
+	algorithm		AlgorithmIdentifier,
+	subjectPublicKey	BIT STRING ({ x509_extract_key_data })
+	}
+
+UniqueIdentifier ::= BIT STRING
+
+Extensions ::= SEQUENCE OF Extension
+
+Extension ::= SEQUENCE {
+	extnid			OBJECT IDENTIFIER ({ x509_note_OID }),
+	critical		BOOLEAN DEFAULT,
+	extnValue		OCTET STRING ({ x509_process_extension })
+	}
diff --git a/lib/crypto/x509_akid.asn1 b/lib/crypto/x509_akid.asn1
new file mode 100644
index 000000000000..1a33231a75a8
--- /dev/null
+++ b/lib/crypto/x509_akid.asn1
@@ -0,0 +1,35 @@
+-- X.509 AuthorityKeyIdentifier
+-- rfc5280 section 4.2.1.1
+
+AuthorityKeyIdentifier ::= SEQUENCE {
+	keyIdentifier			[0] IMPLICIT KeyIdentifier		OPTIONAL,
+	authorityCertIssuer		[1] IMPLICIT GeneralNames		OPTIONAL,
+	authorityCertSerialNumber	[2] IMPLICIT CertificateSerialNumber	OPTIONAL
+	}
+
+KeyIdentifier ::= OCTET STRING ({ x509_akid_note_kid })
+
+CertificateSerialNumber ::= INTEGER ({ x509_akid_note_serial })
+
+GeneralNames ::= SEQUENCE OF GeneralName
+
+GeneralName ::= CHOICE {
+	otherName			[0] ANY,
+	rfc822Name			[1] IA5String,
+	dNSName				[2] IA5String,
+	x400Address			[3] ANY,
+	directoryName			[4] Name ({ x509_akid_note_name }),
+	ediPartyName			[5] ANY,
+	uniformResourceIdentifier	[6] IA5String,
+	iPAddress			[7] OCTET STRING,
+	registeredID			[8] OBJECT IDENTIFIER
+	}
+
+Name ::= SEQUENCE OF RelativeDistinguishedName
+
+RelativeDistinguishedName ::= SET OF AttributeValueAssertion
+
+AttributeValueAssertion ::= SEQUENCE {
+	attributeType		OBJECT IDENTIFIER ({ x509_note_OID }),
+	attributeValue		ANY ({ x509_extract_name_segment })
+	}
diff --git a/lib/crypto/x509_cert_parser.c b/lib/crypto/x509_cert_parser.c
new file mode 100644
index 000000000000..e6d2a426a0bc
--- /dev/null
+++ b/lib/crypto/x509_cert_parser.c
@@ -0,0 +1,697 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* X.509 certificate parser
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells at redhat.com)
+ */
+
+#define pr_fmt(fmt) "X.509: "fmt
+#include <linux/kernel.h>
+#ifndef __UBOOT__
+#include <linux/export.h>
+#include <linux/slab.h>
+#endif
+#include <linux/err.h>
+#include <linux/oid_registry.h>
+#ifdef __UBOOT__
+#include <linux/string.h>
+#endif
+#include <crypto/public_key.h>
+#include "x509_parser.h"
+#include "x509.asn1.h"
+#include "x509_akid.asn1.h"
+
+struct x509_parse_context {
+	struct x509_certificate	*cert;		/* Certificate being constructed */
+	unsigned long	data;			/* Start of data */
+	const void	*cert_start;		/* Start of cert content */
+	const void	*key;			/* Key data */
+	size_t		key_size;		/* Size of key data */
+	const void	*params;		/* Key parameters */
+	size_t		params_size;		/* Size of key parameters */
+	enum OID	key_algo;		/* Public key algorithm */
+	enum OID	last_oid;		/* Last OID encountered */
+	enum OID	algo_oid;		/* Algorithm OID */
+	unsigned char	nr_mpi;			/* Number of MPIs stored */
+	u8		o_size;			/* Size of organizationName (O) */
+	u8		cn_size;		/* Size of commonName (CN) */
+	u8		email_size;		/* Size of emailAddress */
+	u16		o_offset;		/* Offset of organizationName (O) */
+	u16		cn_offset;		/* Offset of commonName (CN) */
+	u16		email_offset;		/* Offset of emailAddress */
+	unsigned	raw_akid_size;
+	const void	*raw_akid;		/* Raw authorityKeyId in ASN.1 */
+	const void	*akid_raw_issuer;	/* Raw directoryName in authorityKeyId */
+	unsigned	akid_raw_issuer_size;
+};
+
+/*
+ * Free an X.509 certificate
+ */
+void x509_free_certificate(struct x509_certificate *cert)
+{
+	if (cert) {
+		public_key_free(cert->pub);
+		public_key_signature_free(cert->sig);
+		kfree(cert->issuer);
+		kfree(cert->subject);
+		kfree(cert->id);
+		kfree(cert->skid);
+		kfree(cert);
+	}
+}
+EXPORT_SYMBOL_GPL(x509_free_certificate);
+
+/*
+ * Parse an X.509 certificate
+ */
+struct x509_certificate *x509_cert_parse(const void *data, size_t datalen)
+{
+	struct x509_certificate *cert;
+	struct x509_parse_context *ctx;
+	struct asymmetric_key_id *kid;
+	long ret;
+
+	ret = -ENOMEM;
+	cert = kzalloc(sizeof(struct x509_certificate), GFP_KERNEL);
+	if (!cert)
+		goto error_no_cert;
+	cert->pub = kzalloc(sizeof(struct public_key), GFP_KERNEL);
+	if (!cert->pub)
+		goto error_no_ctx;
+	cert->sig = kzalloc(sizeof(struct public_key_signature), GFP_KERNEL);
+	if (!cert->sig)
+		goto error_no_ctx;
+	ctx = kzalloc(sizeof(struct x509_parse_context), GFP_KERNEL);
+	if (!ctx)
+		goto error_no_ctx;
+
+	ctx->cert = cert;
+	ctx->data = (unsigned long)data;
+
+	/* Attempt to decode the certificate */
+	ret = asn1_ber_decoder(&x509_decoder, ctx, data, datalen);
+	if (ret < 0)
+		goto error_decode;
+
+	/* Decode the AuthorityKeyIdentifier */
+	if (ctx->raw_akid) {
+		pr_devel("AKID: %u %*phN\n",
+			 ctx->raw_akid_size, ctx->raw_akid_size, ctx->raw_akid);
+		ret = asn1_ber_decoder(&x509_akid_decoder, ctx,
+				       ctx->raw_akid, ctx->raw_akid_size);
+		if (ret < 0) {
+			pr_warn("Couldn't decode AuthKeyIdentifier\n");
+			goto error_decode;
+		}
+	}
+
+	ret = -ENOMEM;
+	cert->pub->key = kmemdup(ctx->key, ctx->key_size, GFP_KERNEL);
+	if (!cert->pub->key)
+		goto error_decode;
+
+	cert->pub->keylen = ctx->key_size;
+
+	cert->pub->params = kmemdup(ctx->params, ctx->params_size, GFP_KERNEL);
+	if (!cert->pub->params)
+		goto error_decode;
+
+	cert->pub->paramlen = ctx->params_size;
+	cert->pub->algo = ctx->key_algo;
+
+	/* Grab the signature bits */
+	ret = x509_get_sig_params(cert);
+	if (ret < 0)
+		goto error_decode;
+
+	/* Generate cert issuer + serial number key ID */
+	kid = asymmetric_key_generate_id(cert->raw_serial,
+					 cert->raw_serial_size,
+					 cert->raw_issuer,
+					 cert->raw_issuer_size);
+	if (IS_ERR(kid)) {
+		ret = PTR_ERR(kid);
+		goto error_decode;
+	}
+	cert->id = kid;
+
+#ifndef __UBOOT__
+	/* Detect self-signed certificates */
+	ret = x509_check_for_self_signed(cert);
+	if (ret < 0)
+		goto error_decode;
+#endif
+
+	kfree(ctx);
+	return cert;
+
+error_decode:
+	kfree(ctx);
+error_no_ctx:
+	x509_free_certificate(cert);
+error_no_cert:
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(x509_cert_parse);
+
+/*
+ * Note an OID when we find one for later processing when we know how
+ * to interpret it.
+ */
+int x509_note_OID(void *context, size_t hdrlen,
+	     unsigned char tag,
+	     const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+
+	ctx->last_oid = look_up_OID(value, vlen);
+	if (ctx->last_oid == OID__NR) {
+		char buffer[50];
+		sprint_oid(value, vlen, buffer, sizeof(buffer));
+		pr_debug("Unknown OID: [%lu] %s\n",
+			 (unsigned long)value - ctx->data, buffer);
+	}
+	return 0;
+}
+
+/*
+ * Save the position of the TBS data so that we can check the signature over it
+ * later.
+ */
+int x509_note_tbs_certificate(void *context, size_t hdrlen,
+			      unsigned char tag,
+			      const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+
+	pr_debug("x509_note_tbs_certificate(,%zu,%02x,%ld,%zu)!\n",
+		 hdrlen, tag, (unsigned long)value - ctx->data, vlen);
+
+	ctx->cert->tbs = value - hdrlen;
+	ctx->cert->tbs_size = vlen + hdrlen;
+	return 0;
+}
+
+/*
+ * Record the public key algorithm
+ */
+int x509_note_pkey_algo(void *context, size_t hdrlen,
+			unsigned char tag,
+			const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+
+	pr_debug("PubKey Algo: %u\n", ctx->last_oid);
+
+	switch (ctx->last_oid) {
+	case OID_md2WithRSAEncryption:
+	case OID_md3WithRSAEncryption:
+	default:
+		return -ENOPKG; /* Unsupported combination */
+
+	case OID_md4WithRSAEncryption:
+		ctx->cert->sig->hash_algo = "md4";
+		goto rsa_pkcs1;
+
+	case OID_sha1WithRSAEncryption:
+		ctx->cert->sig->hash_algo = "sha1";
+		goto rsa_pkcs1;
+
+	case OID_sha256WithRSAEncryption:
+		ctx->cert->sig->hash_algo = "sha256";
+		goto rsa_pkcs1;
+
+	case OID_sha384WithRSAEncryption:
+		ctx->cert->sig->hash_algo = "sha384";
+		goto rsa_pkcs1;
+
+	case OID_sha512WithRSAEncryption:
+		ctx->cert->sig->hash_algo = "sha512";
+		goto rsa_pkcs1;
+
+	case OID_sha224WithRSAEncryption:
+		ctx->cert->sig->hash_algo = "sha224";
+		goto rsa_pkcs1;
+
+	case OID_gost2012Signature256:
+		ctx->cert->sig->hash_algo = "streebog256";
+		goto ecrdsa;
+
+	case OID_gost2012Signature512:
+		ctx->cert->sig->hash_algo = "streebog512";
+		goto ecrdsa;
+	}
+
+rsa_pkcs1:
+	ctx->cert->sig->pkey_algo = "rsa";
+	ctx->cert->sig->encoding = "pkcs1";
+	ctx->algo_oid = ctx->last_oid;
+	return 0;
+ecrdsa:
+	ctx->cert->sig->pkey_algo = "ecrdsa";
+	ctx->cert->sig->encoding = "raw";
+	ctx->algo_oid = ctx->last_oid;
+	return 0;
+}
+
+/*
+ * Note the whereabouts and type of the signature.
+ */
+int x509_note_signature(void *context, size_t hdrlen,
+			unsigned char tag,
+			const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+
+	pr_debug("Signature type: %u size %zu\n", ctx->last_oid, vlen);
+
+	if (ctx->last_oid != ctx->algo_oid) {
+		pr_warn("Got cert with pkey (%u) and sig (%u) algorithm OIDs\n",
+			ctx->algo_oid, ctx->last_oid);
+		return -EINVAL;
+	}
+
+	if (strcmp(ctx->cert->sig->pkey_algo, "rsa") == 0 ||
+	    strcmp(ctx->cert->sig->pkey_algo, "ecrdsa") == 0) {
+		/* Discard the BIT STRING metadata */
+		if (vlen < 1 || *(const u8 *)value != 0)
+			return -EBADMSG;
+
+		value++;
+		vlen--;
+	}
+
+	ctx->cert->raw_sig = value;
+	ctx->cert->raw_sig_size = vlen;
+	return 0;
+}
+
+/*
+ * Note the certificate serial number
+ */
+int x509_note_serial(void *context, size_t hdrlen,
+		     unsigned char tag,
+		     const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+	ctx->cert->raw_serial = value;
+	ctx->cert->raw_serial_size = vlen;
+	return 0;
+}
+
+/*
+ * Note some of the name segments from which we'll fabricate a name.
+ */
+int x509_extract_name_segment(void *context, size_t hdrlen,
+			      unsigned char tag,
+			      const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+
+	switch (ctx->last_oid) {
+	case OID_commonName:
+		ctx->cn_size = vlen;
+		ctx->cn_offset = (unsigned long)value - ctx->data;
+		break;
+	case OID_organizationName:
+		ctx->o_size = vlen;
+		ctx->o_offset = (unsigned long)value - ctx->data;
+		break;
+	case OID_email_address:
+		ctx->email_size = vlen;
+		ctx->email_offset = (unsigned long)value - ctx->data;
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/*
+ * Fabricate and save the issuer and subject names
+ */
+static int x509_fabricate_name(struct x509_parse_context *ctx, size_t hdrlen,
+			       unsigned char tag,
+			       char **_name, size_t vlen)
+{
+	const void *name, *data = (const void *)ctx->data;
+	size_t namesize;
+	char *buffer;
+
+	if (*_name)
+		return -EINVAL;
+
+	/* Empty name string if no material */
+	if (!ctx->cn_size && !ctx->o_size && !ctx->email_size) {
+		buffer = kmalloc(1, GFP_KERNEL);
+		if (!buffer)
+			return -ENOMEM;
+		buffer[0] = 0;
+		goto done;
+	}
+
+	if (ctx->cn_size && ctx->o_size) {
+		/* Consider combining O and CN, but use only the CN if it is
+		 * prefixed by the O, or a significant portion thereof.
+		 */
+		namesize = ctx->cn_size;
+		name = data + ctx->cn_offset;
+		if (ctx->cn_size >= ctx->o_size &&
+		    memcmp(data + ctx->cn_offset, data + ctx->o_offset,
+			   ctx->o_size) == 0)
+			goto single_component;
+		if (ctx->cn_size >= 7 &&
+		    ctx->o_size >= 7 &&
+		    memcmp(data + ctx->cn_offset, data + ctx->o_offset, 7) == 0)
+			goto single_component;
+
+		buffer = kmalloc(ctx->o_size + 2 + ctx->cn_size + 1,
+				 GFP_KERNEL);
+		if (!buffer)
+			return -ENOMEM;
+
+		memcpy(buffer,
+		       data + ctx->o_offset, ctx->o_size);
+		buffer[ctx->o_size + 0] = ':';
+		buffer[ctx->o_size + 1] = ' ';
+		memcpy(buffer + ctx->o_size + 2,
+		       data + ctx->cn_offset, ctx->cn_size);
+		buffer[ctx->o_size + 2 + ctx->cn_size] = 0;
+		goto done;
+
+	} else if (ctx->cn_size) {
+		namesize = ctx->cn_size;
+		name = data + ctx->cn_offset;
+	} else if (ctx->o_size) {
+		namesize = ctx->o_size;
+		name = data + ctx->o_offset;
+	} else {
+		namesize = ctx->email_size;
+		name = data + ctx->email_offset;
+	}
+
+single_component:
+	buffer = kmalloc(namesize + 1, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+	memcpy(buffer, name, namesize);
+	buffer[namesize] = 0;
+
+done:
+	*_name = buffer;
+	ctx->cn_size = 0;
+	ctx->o_size = 0;
+	ctx->email_size = 0;
+	return 0;
+}
+
+int x509_note_issuer(void *context, size_t hdrlen,
+		     unsigned char tag,
+		     const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+	ctx->cert->raw_issuer = value;
+	ctx->cert->raw_issuer_size = vlen;
+	return x509_fabricate_name(ctx, hdrlen, tag, &ctx->cert->issuer, vlen);
+}
+
+int x509_note_subject(void *context, size_t hdrlen,
+		      unsigned char tag,
+		      const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+	ctx->cert->raw_subject = value;
+	ctx->cert->raw_subject_size = vlen;
+	return x509_fabricate_name(ctx, hdrlen, tag, &ctx->cert->subject, vlen);
+}
+
+/*
+ * Extract the parameters for the public key
+ */
+int x509_note_params(void *context, size_t hdrlen,
+		     unsigned char tag,
+		     const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+
+	/*
+	 * AlgorithmIdentifier is used three times in the x509, we should skip
+	 * first and ignore third, using second one which is after subject and
+	 * before subjectPublicKey.
+	 */
+	if (!ctx->cert->raw_subject || ctx->key)
+		return 0;
+	ctx->params = value - hdrlen;
+	ctx->params_size = vlen + hdrlen;
+	return 0;
+}
+
+/*
+ * Extract the data for the public key algorithm
+ */
+int x509_extract_key_data(void *context, size_t hdrlen,
+			  unsigned char tag,
+			  const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+
+	ctx->key_algo = ctx->last_oid;
+	if (ctx->last_oid == OID_rsaEncryption)
+		ctx->cert->pub->pkey_algo = "rsa";
+	else if (ctx->last_oid == OID_gost2012PKey256 ||
+		 ctx->last_oid == OID_gost2012PKey512)
+		ctx->cert->pub->pkey_algo = "ecrdsa";
+	else
+		return -ENOPKG;
+
+	/* Discard the BIT STRING metadata */
+	if (vlen < 1 || *(const u8 *)value != 0)
+		return -EBADMSG;
+	ctx->key = value + 1;
+	ctx->key_size = vlen - 1;
+	return 0;
+}
+
+/* The keyIdentifier in AuthorityKeyIdentifier SEQUENCE is tag(CONT,PRIM,0) */
+#define SEQ_TAG_KEYID (ASN1_CONT << 6)
+
+/*
+ * Process certificate extensions that are used to qualify the certificate.
+ */
+int x509_process_extension(void *context, size_t hdrlen,
+			   unsigned char tag,
+			   const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+	struct asymmetric_key_id *kid;
+	const unsigned char *v = value;
+
+	pr_debug("Extension: %u\n", ctx->last_oid);
+
+	if (ctx->last_oid == OID_subjectKeyIdentifier) {
+		/* Get hold of the key fingerprint */
+		if (ctx->cert->skid || vlen < 3)
+			return -EBADMSG;
+		if (v[0] != ASN1_OTS || v[1] != vlen - 2)
+			return -EBADMSG;
+		v += 2;
+		vlen -= 2;
+
+		ctx->cert->raw_skid_size = vlen;
+		ctx->cert->raw_skid = v;
+		kid = asymmetric_key_generate_id(v, vlen, "", 0);
+		if (IS_ERR(kid))
+			return PTR_ERR(kid);
+		ctx->cert->skid = kid;
+		pr_debug("subjkeyid %*phN\n", kid->len, kid->data);
+		return 0;
+	}
+
+	if (ctx->last_oid == OID_authorityKeyIdentifier) {
+		/* Get hold of the CA key fingerprint */
+		ctx->raw_akid = v;
+		ctx->raw_akid_size = vlen;
+		return 0;
+	}
+
+	return 0;
+}
+
+/**
+ * x509_decode_time - Decode an X.509 time ASN.1 object
+ * @_t: The time to fill in
+ * @hdrlen: The length of the object header
+ * @tag: The object tag
+ * @value: The object value
+ * @vlen: The size of the object value
+ *
+ * Decode an ASN.1 universal time or generalised time field into a struct the
+ * kernel can handle and check it for validity.  The time is decoded thus:
+ *
+ *	[RFC5280 ยง4.1.2.5]
+ *	CAs conforming to this profile MUST always encode certificate validity
+ *	dates through the year 2049 as UTCTime; certificate validity dates in
+ *	2050 or later MUST be encoded as GeneralizedTime.  Conforming
+ *	applications MUST be able to process validity dates that are encoded in
+ *	either UTCTime or GeneralizedTime.
+ */
+int x509_decode_time(time64_t *_t,  size_t hdrlen,
+		     unsigned char tag,
+		     const unsigned char *value, size_t vlen)
+{
+	static const unsigned char month_lengths[] = { 31, 28, 31, 30, 31, 30,
+						       31, 31, 30, 31, 30, 31 };
+	const unsigned char *p = value;
+	unsigned year, mon, day, hour, min, sec, mon_len;
+
+#define dec2bin(X) ({ unsigned char x = (X) - '0'; if (x > 9) goto invalid_time; x; })
+#define DD2bin(P) ({ unsigned x = dec2bin(P[0]) * 10 + dec2bin(P[1]); P += 2; x; })
+
+	if (tag == ASN1_UNITIM) {
+		/* UTCTime: YYMMDDHHMMSSZ */
+		if (vlen != 13)
+			goto unsupported_time;
+		year = DD2bin(p);
+		if (year >= 50)
+			year += 1900;
+		else
+			year += 2000;
+	} else if (tag == ASN1_GENTIM) {
+		/* GenTime: YYYYMMDDHHMMSSZ */
+		if (vlen != 15)
+			goto unsupported_time;
+		year = DD2bin(p) * 100 + DD2bin(p);
+		if (year >= 1950 && year <= 2049)
+			goto invalid_time;
+	} else {
+		goto unsupported_time;
+	}
+
+	mon  = DD2bin(p);
+	day = DD2bin(p);
+	hour = DD2bin(p);
+	min  = DD2bin(p);
+	sec  = DD2bin(p);
+
+	if (*p != 'Z')
+		goto unsupported_time;
+
+	if (year < 1970 ||
+	    mon < 1 || mon > 12)
+		goto invalid_time;
+
+	mon_len = month_lengths[mon - 1];
+	if (mon == 2) {
+		if (year % 4 == 0) {
+			mon_len = 29;
+			if (year % 100 == 0) {
+				mon_len = 28;
+				if (year % 400 == 0)
+					mon_len = 29;
+			}
+		}
+	}
+
+	if (day < 1 || day > mon_len ||
+	    hour > 24 || /* ISO 8601 permits 24:00:00 as midnight tomorrow */
+	    min > 59 ||
+	    sec > 60) /* ISO 8601 permits leap seconds [X.680 46.3] */
+		goto invalid_time;
+
+	*_t = mktime64(year, mon, day, hour, min, sec);
+	return 0;
+
+unsupported_time:
+	pr_debug("Got unsupported time [tag %02x]: '%*phN'\n",
+		 tag, (int)vlen, value);
+	return -EBADMSG;
+invalid_time:
+	pr_debug("Got invalid time [tag %02x]: '%*phN'\n",
+		 tag, (int)vlen, value);
+	return -EBADMSG;
+}
+EXPORT_SYMBOL_GPL(x509_decode_time);
+
+int x509_note_not_before(void *context, size_t hdrlen,
+			 unsigned char tag,
+			 const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+	return x509_decode_time(&ctx->cert->valid_from, hdrlen, tag, value, vlen);
+}
+
+int x509_note_not_after(void *context, size_t hdrlen,
+			unsigned char tag,
+			const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+	return x509_decode_time(&ctx->cert->valid_to, hdrlen, tag, value, vlen);
+}
+
+/*
+ * Note a key identifier-based AuthorityKeyIdentifier
+ */
+int x509_akid_note_kid(void *context, size_t hdrlen,
+		       unsigned char tag,
+		       const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+	struct asymmetric_key_id *kid;
+
+	pr_debug("AKID: keyid: %*phN\n", (int)vlen, value);
+
+	if (ctx->cert->sig->auth_ids[1])
+		return 0;
+
+	kid = asymmetric_key_generate_id(value, vlen, "", 0);
+	if (IS_ERR(kid))
+		return PTR_ERR(kid);
+	pr_debug("authkeyid %*phN\n", kid->len, kid->data);
+	ctx->cert->sig->auth_ids[1] = kid;
+	return 0;
+}
+
+/*
+ * Note a directoryName in an AuthorityKeyIdentifier
+ */
+int x509_akid_note_name(void *context, size_t hdrlen,
+			unsigned char tag,
+			const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+
+	pr_debug("AKID: name: %*phN\n", (int)vlen, value);
+
+	ctx->akid_raw_issuer = value;
+	ctx->akid_raw_issuer_size = vlen;
+	return 0;
+}
+
+/*
+ * Note a serial number in an AuthorityKeyIdentifier
+ */
+int x509_akid_note_serial(void *context, size_t hdrlen,
+			  unsigned char tag,
+			  const void *value, size_t vlen)
+{
+	struct x509_parse_context *ctx = context;
+	struct asymmetric_key_id *kid;
+
+	pr_debug("AKID: serial: %*phN\n", (int)vlen, value);
+
+	if (!ctx->akid_raw_issuer || ctx->cert->sig->auth_ids[0])
+		return 0;
+
+	kid = asymmetric_key_generate_id(value,
+					 vlen,
+					 ctx->akid_raw_issuer,
+					 ctx->akid_raw_issuer_size);
+	if (IS_ERR(kid))
+		return PTR_ERR(kid);
+
+	pr_debug("authkeyid %*phN\n", kid->len, kid->data);
+	ctx->cert->sig->auth_ids[0] = kid;
+	return 0;
+}
diff --git a/lib/crypto/x509_parser.h b/lib/crypto/x509_parser.h
new file mode 100644
index 000000000000..c233f136fb35
--- /dev/null
+++ b/lib/crypto/x509_parser.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* X.509 certificate parser internal definitions
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells at redhat.com)
+ */
+
+#include <linux/time.h>
+#include <crypto/public_key.h>
+#include <keys/asymmetric-type.h>
+
+struct x509_certificate {
+	struct x509_certificate *next;
+	struct x509_certificate *signer;	/* Certificate that signed this one */
+	struct public_key *pub;			/* Public key details */
+	struct public_key_signature *sig;	/* Signature parameters */
+	char		*issuer;		/* Name of certificate issuer */
+	char		*subject;		/* Name of certificate subject */
+	struct asymmetric_key_id *id;		/* Issuer + Serial number */
+	struct asymmetric_key_id *skid;		/* Subject + subjectKeyId (optional) */
+	time64_t	valid_from;
+	time64_t	valid_to;
+	const void	*tbs;			/* Signed data */
+	unsigned	tbs_size;		/* Size of signed data */
+	unsigned	raw_sig_size;		/* Size of sigature */
+	const void	*raw_sig;		/* Signature data */
+	const void	*raw_serial;		/* Raw serial number in ASN.1 */
+	unsigned	raw_serial_size;
+	unsigned	raw_issuer_size;
+	const void	*raw_issuer;		/* Raw issuer name in ASN.1 */
+	const void	*raw_subject;		/* Raw subject name in ASN.1 */
+	unsigned	raw_subject_size;
+	unsigned	raw_skid_size;
+	const void	*raw_skid;		/* Raw subjectKeyId in ASN.1 */
+	unsigned	index;
+	bool		seen;			/* Infinite recursion prevention */
+	bool		verified;
+	bool		self_signed;		/* T if self-signed (check unsupported_sig too) */
+	bool		unsupported_key;	/* T if key uses unsupported crypto */
+	bool		unsupported_sig;	/* T if signature uses unsupported crypto */
+	bool		blacklisted;
+};
+
+/*
+ * x509_cert_parser.c
+ */
+extern void x509_free_certificate(struct x509_certificate *cert);
+extern struct x509_certificate *x509_cert_parse(const void *data, size_t datalen);
+extern int x509_decode_time(time64_t *_t,  size_t hdrlen,
+			    unsigned char tag,
+			    const unsigned char *value, size_t vlen);
+
+/*
+ * x509_public_key.c
+ */
+extern int x509_get_sig_params(struct x509_certificate *cert);
+extern int x509_check_for_self_signed(struct x509_certificate *cert);
diff --git a/lib/crypto/x509_public_key.c b/lib/crypto/x509_public_key.c
new file mode 100644
index 000000000000..04bdb672b496
--- /dev/null
+++ b/lib/crypto/x509_public_key.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Instantiate a public key crypto key from an X.509 Certificate
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells at redhat.com)
+ */
+
+#define pr_fmt(fmt) "X.509: "fmt
+#ifdef __UBOOT__
+#include <common.h>
+#include <linux/compat.h>
+#include <linux/errno.h>
+#else
+#include <linux/module.h>
+#endif
+#include <linux/kernel.h>
+#ifndef __UBOOT__
+#include <linux/slab.h>
+#include <keys/asymmetric-subtype.h>
+#include <keys/asymmetric-parser.h>
+#include <keys/system_keyring.h>
+#include <crypto/hash.h>
+#include "asymmetric_keys.h"
+#endif
+#include "x509_parser.h"
+
+/*
+ * Set up the signature parameters in an X.509 certificate.  This involves
+ * digesting the signed data and extracting the signature.
+ */
+int x509_get_sig_params(struct x509_certificate *cert)
+{
+	struct public_key_signature *sig = cert->sig;
+#ifndef __UBOOT__
+	struct crypto_shash *tfm;
+	struct shash_desc *desc;
+	size_t desc_size;
+#endif
+	int ret;
+
+	pr_devel("==>%s()\n", __func__);
+
+	if (!cert->pub->pkey_algo)
+		cert->unsupported_key = true;
+
+	if (!sig->pkey_algo)
+		cert->unsupported_sig = true;
+
+	/* We check the hash if we can - even if we can't then verify it */
+	if (!sig->hash_algo) {
+		cert->unsupported_sig = true;
+		return 0;
+	}
+
+	sig->s = kmemdup(cert->raw_sig, cert->raw_sig_size, GFP_KERNEL);
+	if (!sig->s)
+		return -ENOMEM;
+
+	sig->s_size = cert->raw_sig_size;
+
+#ifdef __UBOOT__
+	/*
+	 * Note:
+	 * This part (filling sig->digest) should be implemented if
+	 * x509_check_for_self_signed() is enabled x509_cert_parse().
+	 * Currently, this check won't affect UEFI secure boot.
+	 */
+	ret = 0;
+#else
+	/* Allocate the hashing algorithm we're going to need and find out how
+	 * big the hash operational data will be.
+	 */
+	tfm = crypto_alloc_shash(sig->hash_algo, 0, 0);
+	if (IS_ERR(tfm)) {
+		if (PTR_ERR(tfm) == -ENOENT) {
+			cert->unsupported_sig = true;
+			return 0;
+		}
+		return PTR_ERR(tfm);
+	}
+
+	desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
+	sig->digest_size = crypto_shash_digestsize(tfm);
+
+	ret = -ENOMEM;
+	sig->digest = kmalloc(sig->digest_size, GFP_KERNEL);
+	if (!sig->digest)
+		goto error;
+
+	desc = kzalloc(desc_size, GFP_KERNEL);
+	if (!desc)
+		goto error;
+
+	desc->tfm = tfm;
+
+	ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size, sig->digest);
+	if (ret < 0)
+		goto error_2;
+
+	ret = is_hash_blacklisted(sig->digest, sig->digest_size, "tbs");
+	if (ret == -EKEYREJECTED) {
+		pr_err("Cert %*phN is blacklisted\n",
+		       sig->digest_size, sig->digest);
+		cert->blacklisted = true;
+		ret = 0;
+	}
+
+error_2:
+	kfree(desc);
+error:
+	crypto_free_shash(tfm);
+#endif /* __UBOOT__ */
+	pr_devel("<==%s() = %d\n", __func__, ret);
+	return ret;
+}
+
+#ifndef __UBOOT__
+/*
+ * Check for self-signedness in an X.509 cert and if found, check the signature
+ * immediately if we can.
+ */
+int x509_check_for_self_signed(struct x509_certificate *cert)
+{
+	int ret = 0;
+
+	pr_devel("==>%s()\n", __func__);
+
+	if (cert->raw_subject_size != cert->raw_issuer_size ||
+	    memcmp(cert->raw_subject, cert->raw_issuer,
+		   cert->raw_issuer_size) != 0)
+		goto not_self_signed;
+
+	if (cert->sig->auth_ids[0] || cert->sig->auth_ids[1]) {
+		/* If the AKID is present it may have one or two parts.  If
+		 * both are supplied, both must match.
+		 */
+		bool a = asymmetric_key_id_same(cert->skid, cert->sig->auth_ids[1]);
+		bool b = asymmetric_key_id_same(cert->id, cert->sig->auth_ids[0]);
+
+		if (!a && !b)
+			goto not_self_signed;
+
+		ret = -EKEYREJECTED;
+		if (((a && !b) || (b && !a)) &&
+		    cert->sig->auth_ids[0] && cert->sig->auth_ids[1])
+			goto out;
+	}
+
+	ret = -EKEYREJECTED;
+	if (strcmp(cert->pub->pkey_algo, cert->sig->pkey_algo) != 0)
+		goto out;
+
+	ret = public_key_verify_signature(cert->pub, cert->sig);
+	if (ret < 0) {
+		if (ret == -ENOPKG) {
+			cert->unsupported_sig = true;
+			ret = 0;
+		}
+		goto out;
+	}
+
+	pr_devel("Cert Self-signature verified");
+	cert->self_signed = true;
+
+out:
+	pr_devel("<==%s() = %d\n", __func__, ret);
+	return ret;
+
+not_self_signed:
+	pr_devel("<==%s() = 0 [not]\n", __func__);
+	return 0;
+}
+
+/*
+ * Attempt to parse a data blob for a key as an X509 certificate.
+ */
+static int x509_key_preparse(struct key_preparsed_payload *prep)
+{
+	struct asymmetric_key_ids *kids;
+	struct x509_certificate *cert;
+	const char *q;
+	size_t srlen, sulen;
+	char *desc = NULL, *p;
+	int ret;
+
+	cert = x509_cert_parse(prep->data, prep->datalen);
+	if (IS_ERR(cert))
+		return PTR_ERR(cert);
+
+	pr_devel("Cert Issuer: %s\n", cert->issuer);
+	pr_devel("Cert Subject: %s\n", cert->subject);
+
+	if (cert->unsupported_key) {
+		ret = -ENOPKG;
+		goto error_free_cert;
+	}
+
+	pr_devel("Cert Key Algo: %s\n", cert->pub->pkey_algo);
+	pr_devel("Cert Valid period: %lld-%lld\n", cert->valid_from, cert->valid_to);
+
+	cert->pub->id_type = "X509";
+
+	if (cert->unsupported_sig) {
+		public_key_signature_free(cert->sig);
+		cert->sig = NULL;
+	} else {
+		pr_devel("Cert Signature: %s + %s\n",
+			 cert->sig->pkey_algo, cert->sig->hash_algo);
+	}
+
+	/* Don't permit addition of blacklisted keys */
+	ret = -EKEYREJECTED;
+	if (cert->blacklisted)
+		goto error_free_cert;
+
+	/* Propose a description */
+	sulen = strlen(cert->subject);
+	if (cert->raw_skid) {
+		srlen = cert->raw_skid_size;
+		q = cert->raw_skid;
+	} else {
+		srlen = cert->raw_serial_size;
+		q = cert->raw_serial;
+	}
+
+	ret = -ENOMEM;
+	desc = kmalloc(sulen + 2 + srlen * 2 + 1, GFP_KERNEL);
+	if (!desc)
+		goto error_free_cert;
+	p = memcpy(desc, cert->subject, sulen);
+	p += sulen;
+	*p++ = ':';
+	*p++ = ' ';
+	p = bin2hex(p, q, srlen);
+	*p = 0;
+
+	kids = kmalloc(sizeof(struct asymmetric_key_ids), GFP_KERNEL);
+	if (!kids)
+		goto error_free_desc;
+	kids->id[0] = cert->id;
+	kids->id[1] = cert->skid;
+
+	/* We're pinning the module by being linked against it */
+	__module_get(public_key_subtype.owner);
+	prep->payload.data[asym_subtype] = &public_key_subtype;
+	prep->payload.data[asym_key_ids] = kids;
+	prep->payload.data[asym_crypto] = cert->pub;
+	prep->payload.data[asym_auth] = cert->sig;
+	prep->description = desc;
+	prep->quotalen = 100;
+
+	/* We've finished with the certificate */
+	cert->pub = NULL;
+	cert->id = NULL;
+	cert->skid = NULL;
+	cert->sig = NULL;
+	desc = NULL;
+	ret = 0;
+
+error_free_desc:
+	kfree(desc);
+error_free_cert:
+	x509_free_certificate(cert);
+	return ret;
+}
+
+static struct asymmetric_key_parser x509_key_parser = {
+	.owner	= THIS_MODULE,
+	.name	= "x509",
+	.parse	= x509_key_preparse,
+};
+
+/*
+ * Module stuff
+ */
+static int __init x509_key_init(void)
+{
+	return register_asymmetric_key_parser(&x509_key_parser);
+}
+
+static void __exit x509_key_exit(void)
+{
+	unregister_asymmetric_key_parser(&x509_key_parser);
+}
+
+module_init(x509_key_init);
+module_exit(x509_key_exit);
+#endif /* !__UBOOT__ */
+
+MODULE_DESCRIPTION("X.509 certificate parser");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
-- 
2.21.0


  parent reply	other threads:[~2019-10-11  7:41 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-10-11  7:41 [U-Boot] [PATCH v1 00/11] import x509/pkcs7 parsers from linux AKASHI Takahiro
2019-10-11  7:41 ` [U-Boot] [PATCH v1 01/11] linux_compat: add kmemdup() AKASHI Takahiro
2019-10-12 11:22   ` Heinrich Schuchardt
2019-10-17  3:04     ` AKASHI Takahiro
2019-10-11  7:41 ` [U-Boot] [PATCH v1 02/11] include: time.h: define time64_t AKASHI Takahiro
2019-10-12 11:40   ` Heinrich Schuchardt
2019-10-17  5:39     ` AKASHI Takahiro
2019-10-17  5:51       ` Heinrich Schuchardt
2019-10-11  7:41 ` [U-Boot] [PATCH v1 03/11] include: kernel.h: include printk.h AKASHI Takahiro
2019-10-12 11:47   ` Heinrich Schuchardt
2019-10-17  5:58     ` AKASHI Takahiro
2019-10-17  6:17       ` AKASHI Takahiro
2019-10-11  7:41 ` [U-Boot] [PATCH v1 04/11] cmd: add asn1_compiler AKASHI Takahiro
2019-10-12 12:22   ` Heinrich Schuchardt
2019-10-17  6:25     ` AKASHI Takahiro
2019-10-11  7:41 ` [U-Boot] [PATCH v1 05/11] Makefile: add build script for asn1 parsers AKASHI Takahiro
2019-10-12 12:36   ` Heinrich Schuchardt
2019-10-17  6:40     ` AKASHI Takahiro
2019-10-11  7:41 ` [U-Boot] [PATCH v1 06/11] lib: add asn1 decoder AKASHI Takahiro
2019-10-12 12:29   ` Heinrich Schuchardt
2019-10-17  7:02     ` AKASHI Takahiro
2019-10-11  7:41 ` [U-Boot] [PATCH v1 07/11] lib: add oid registry utility AKASHI Takahiro
2019-10-12 12:58   ` Heinrich Schuchardt
2019-10-11  7:41 ` [U-Boot] [PATCH v1 08/11] lib: crypto: add public key utility AKASHI Takahiro
2019-10-11  7:41 ` AKASHI Takahiro [this message]
2019-10-11  7:41 ` [U-Boot] [PATCH v1 10/11] lib: crypto: add pkcs7 message parser AKASHI Takahiro
2019-10-11  7:42 ` [U-Boot] [PATCH v1 11/11] lib: crypto: add rsa public key parser AKASHI Takahiro
2019-10-12 13:11   ` Heinrich Schuchardt
2019-10-11  7:55 ` [U-Boot] [PATCH v1 00/11] import x509/pkcs7 parsers from linux AKASHI Takahiro
2019-10-12 13:02   ` Heinrich Schuchardt
2019-10-15  3:18     ` AKASHI Takahiro
2019-10-15  5:33       ` Heinrich Schuchardt
2019-10-15  8:56         ` AKASHI Takahiro
2019-10-15 11:10           ` Heinrich Schuchardt
2019-10-15  9:25         ` AKASHI Takahiro
2019-10-17 15:23           ` Tom Rini
2019-10-18  8:36             ` AKASHI Takahiro
2019-10-18 12:35               ` Tom Rini
2019-10-23  6:43                 ` AKASHI Takahiro

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=20191011074200.30269-10-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.