File leancrypto-CVE-2026-34610.patch of Package leancrypto

From 5cdcbe12bd6c3d6e87e969972a580b44a74c3a6a Mon Sep 17 00:00:00 2001
From: Stephan Mueller <smueller@chronox.de>
Date: Sun, 29 Mar 2026 19:14:41 +0200
Subject: [PATCH] X.509: subject parser - Overflow in size parser of a subject
 name component

When parsing subject and issue name components, use size_t to store the
length of the string. This prevents an overflow the parsing of both component
types. Yet, as the issue string is checked against a maximum size, the
subject string is not checked against an overflow. Therefore, this change
also fixes a wrap in the size parsing which may be used by an attacker
to craft an X.509 certificate with a long subject string where only a
sub-part of the component is matched. This may be used to impersonate
as a different subjects with a DN longer than 256 bytes.

This change eliminates also all double management of the subject/issuer
string.

Reported-by: Sunwoo Lee
Reported-by: Seunghyun Yoon
Signed-off-by: Stephan Mueller <smueller@chronox.de>
---
 CHANGES.md                           |   2 +
 SECURITY.md                          |  19 ++-
 apps/src/lc_x509_generator_checker.c |  62 +++++++---
 asn1/api/lc_x509_common.h            |   5 +-
 asn1/src/x509_cert_parser.c          | 169 +++------------------------
 asn1/src/x509_cert_parser.h          |   6 -
 6 files changed, 81 insertions(+), 182 deletions(-)

Index: leancrypto-1.6.0/SECURITY.md
===================================================================
--- leancrypto-1.6.0.orig/SECURITY.md
+++ leancrypto-1.6.0/SECURITY.md
@@ -8,12 +8,16 @@ basis.
 If you detect any new security issues, please file a bug report or send
 a private email to <smueller@chronox.de>.
 
-## 2024-01-25
+## 2026-03-29
 
-Integrate PQClean patch 3b43bc6fe46fe47be38f87af5019a7f1462ae6dd
+X.509 Subject parser: Overflow in size parser of a subject name component
 
-* Kyber used division operations that might leak side-channel information.
-[PR #534](https://github.com/PQClean/PQClean/pull/534) addressed this for the `clean` and `avx2` implementations.
+* With this, an attacker can craft a certificate where only a sub-part of a name
+  component is matched instead of the full component string. Therefore an
+  impersonation with a wrongly crafted certificate that has a valid signature is
+  possible.
+
+* Credits: Sunwoo Lee and Seunghyun Yoon (Korea Institute of Energy Technology, KENTECH).
 
 ## 2024-06-03
 
@@ -23,3 +27,10 @@ Integrate https://github.com/pq-crystals
 * Fixed secret-dependent branch in poly_frommsg introduced by recent
   versions of clang with some flags (Thanks to Antoon Purnal for pointing
   this out!)
+
+## 2024-01-25
+
+Integrate PQClean patch 3b43bc6fe46fe47be38f87af5019a7f1462ae6dd
+
+* Kyber used division operations that might leak side-channel information.
+[PR #534](https://github.com/PQClean/PQClean/pull/534) addressed this for the `clean` and `avx2` implementations.
Index: leancrypto-1.6.0/apps/src/lc_x509_generator_checker.c
===================================================================
--- leancrypto-1.6.0.orig/apps/src/lc_x509_generator_checker.c
+++ leancrypto-1.6.0/apps/src/lc_x509_generator_checker.c
@@ -29,6 +29,18 @@
  * X.509 tests
  ******************************************************************************/
 
+static void
+print_x509_name_component(const struct lc_x509_certificate_name_component *comp)
+{
+	char buf[LC_ASN1_MAX_ISSUER_NAME + 1] = { 0 };
+
+	if (!comp->size)
+		return;
+
+	memcpy(buf, comp->value, min_size(comp->size, LC_ASN1_MAX_ISSUER_NAME));
+	printf("%s", buf);
+}
+
 int apply_checks_x509(const struct lc_x509_certificate *x509,
 		      const struct x509_checker_options *parsed_opts)
 {
@@ -173,8 +185,10 @@ int apply_checks_x509(const struct lc_x5
 	if (parsed_opts->issuer_cn) {
 		if (strncmp(x509->issuer, parsed_opts->issuer_cn,
 			    sizeof(x509->issuer))) {
-			printf("Issuers mismatch, expected %s, actual %s\n",
-			       parsed_opts->issuer_cn, x509->issuer);
+			printf("Issuers mismatch, expected %s, actual ",
+			       parsed_opts->issuer_cn);
+			print_x509_name_component(&x509->issuer_segments.cn);
+			printf("\n");
 			return -EINVAL;
 		} else {
 			printf("Issuer matches expected value\n");
@@ -541,8 +555,17 @@ int apply_checks_pkcs7(const struct lc_p
 		int found = 0;
 
 		while (x509) {
-			if (!strncmp(x509->issuer, parsed_opts->issuer_cn,
-				     sizeof(x509->issuer))) {
+			struct lc_x509_certificate_name
+			search_name = { .cn = {
+						.value = parsed_opts->issuer_cn,
+						.size = strlen(
+							parsed_opts->issuer_cn),
+					} };
+
+			if (lc_x509_policy_cert_subject_match(
+				x509, &search_name,
+				lc_x509_policy_cert_subject_match_issuer_only) ==
+				LC_X509_POL_TRUE) {
 				found = 1;
 				break;
 			}
@@ -562,8 +585,17 @@ int apply_checks_pkcs7(const struct lc_p
 		int found = 0;
 
 		while (x509) {
-			if (!strncmp(x509->subject, parsed_opts->subject_cn,
-				     sizeof(x509->subject))) {
+			struct lc_x509_certificate_name
+			search_name = { .cn = {
+						.value = parsed_opts->subject_cn,
+						.size = strlen(
+							parsed_opts->subject_cn),
+					} };
+
+			if (lc_x509_policy_cert_subject_match(
+				x509, &search_name,
+				lc_x509_policy_cert_subject_match_dn_only) ==
+				LC_X509_POL_TRUE) {
 				found = 1;
 				break;
 			}
Index: leancrypto-1.6.0/asn1/api/lc_x509_common.h
===================================================================
--- leancrypto-1.6.0.orig/asn1/api/lc_x509_common.h
+++ leancrypto-1.6.0/asn1/api/lc_x509_common.h
@@ -246,7 +246,7 @@ struct lc_public_key_signature {
 
 struct lc_x509_certificate_name_component {
 	const char *value;
-	uint8_t size;
+	size_t size;
 };
 
 struct lc_x509_certificate_name {
@@ -331,9 +331,6 @@ struct lc_x509_certificate {
 	size_t raw_akid_size;
 	const uint8_t *raw_akid; /* authority key Id binary format */
 	unsigned int index;
-	char issuer[LC_ASN1_MAX_ISSUER_NAME + 1]; /* Name of certificate issuer */
-	char subject[LC_ASN1_MAX_ISSUER_NAME +
-		     1]; /* Name of certificate subject */
 
 	uint8_t x509_version; /* X.509 Version of certificate */
 	unsigned int seen : 1; /* Infinite recursion prevention */
Index: leancrypto-1.6.0/asn1/src/x509_cert_parser.c
===================================================================
--- leancrypto-1.6.0.orig/asn1/src/x509_cert_parser.c
+++ leancrypto-1.6.0/asn1/src/x509_cert_parser.c
@@ -261,34 +261,28 @@ int x509_extract_name_segment(void *cont
 #pragma GCC diagnostic ignored "-Wswitch-enum"
 	switch (ctx->last_oid) {
 	case OID_commonName:
-		ctx->cn_size = (uint8_t)vlen;
-		ctx->cn_offset = (uint16_t)(value - ctx->data);
 		name->cn.value = (char *)value;
-		name->cn.size = (uint8_t)vlen;
+		name->cn.size = vlen;
 		break;
 	case OID_organizationName:
-		ctx->o_size = (uint8_t)vlen;
-		ctx->o_offset = (uint16_t)(value - ctx->data);
 		name->o.value = (char *)value;
-		name->o.size = (uint8_t)vlen;
+		name->o.size = vlen;
 		break;
 	case OID_email_address:
-		ctx->email_size = (uint8_t)vlen;
-		ctx->email_offset = (uint16_t)(value - ctx->data);
 		name->email.value = (char *)value;
-		name->email.size = (uint8_t)vlen;
+		name->email.size = vlen;
 		break;
 	case OID_countryName:
 		name->c.value = (char *)value;
-		name->c.size = (uint8_t)vlen;
+		name->c.size = vlen;
 		break;
 	case OID_stateOrProvinceName:
 		name->st.value = (char *)value;
-		name->st.size = (uint8_t)vlen;
+		name->st.size = vlen;
 		break;
 	case OID_organizationUnitName:
 		name->ou.value = (char *)value;
-		name->ou.size = (uint8_t)vlen;
+		name->ou.size = vlen;
 		break;
 	default:
 		break;
@@ -324,131 +318,6 @@ int x509_attribute_value_continue(void *
 	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[LC_ASN1_MAX_ISSUER_NAME], size_t vlen,
-			       int subject)
-{
-	const uint8_t *name, *data = (const void *)ctx->data;
-	struct lc_x509_certificate *cert = ctx->cert;
-	size_t namesize;
-	int ret = 0;
-
-	(void)hdrlen;
-	(void)tag;
-	(void)vlen;
-
-	/*
-	 * A SAN takes precedence over the DN for identifying the certificate
-	 * and marking its subject.
-	 */
-	if (subject && cert->san_dns_len) {
-		namesize = min_size(cert->san_dns_len, LC_ASN1_MAX_ISSUER_NAME);
-		memcpy(_name, cert->san_dns, namesize);
-		_name[namesize] = '\0';
-
-		return 0;
-	}
-
-	if (subject && cert->san_ip_len) {
-		if (cert->san_ip_len == 4) {
-			/* IPv4 Address */
-			snprintf(_name, LC_ASN1_MAX_ISSUER_NAME, "%u.%u.%u.%u",
-				 cert->san_ip[0], cert->san_ip[1],
-				 cert->san_ip[2], cert->san_ip[3]);
-
-		} else if (cert->san_ip_len == 16) {
-			/* IPv6 Address */
-			size_t i, offset;
-
-			for (i = 0; i < cert->san_ip_len; i++) {
-				offset = i * 3;
-				snprintf(_name + offset,
-					 LC_ASN1_MAX_ISSUER_NAME - offset,
-					 "%.02x:", cert->san_ip[i]);
-			}
-			/* Eliminate the last ":" and place a NULL terminator */
-			_name[(i * 3) - 1] = '\0';
-
-		} else {
-			/*
-			 * Something else, do a best-effort by converting it
-			 * into Hex.
-			 */
-			lc_bin2hex(cert->san_ip, cert->san_ip_len, _name,
-				   LC_ASN1_MAX_ISSUER_NAME, 1);
-			_name[min_size(cert->san_ip_len,
-				       LC_ASN1_MAX_ISSUER_NAME)] = '\0';
-		}
-
-		return 0;
-	}
-
-	/* Empty name string if no material */
-	if (!ctx->cn_size && !ctx->o_size && !ctx->email_size) {
-		_name[0] = 0;
-		goto out;
-	}
-
-	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 &&
-		    lc_memcmp_secure(data + ctx->cn_offset, ctx->cn_size,
-				     data + ctx->o_offset, ctx->o_size) == 0)
-			goto single_component;
-		if (ctx->cn_size >= 7 && ctx->o_size >= 7 &&
-		    lc_memcmp_secure(data + ctx->cn_offset, 7,
-				     data + ctx->o_offset, 7) == 0)
-			goto single_component;
-
-		if (ctx->o_size + 2 + ctx->cn_size + 1 >=
-		    LC_ASN1_MAX_ISSUER_NAME) {
-			ret = -EOVERFLOW;
-			goto out;
-		}
-
-		memcpy(_name, data + ctx->o_offset, ctx->o_size);
-		_name[ctx->o_size + 0] = ':';
-		_name[ctx->o_size + 1] = ' ';
-		memcpy(_name + ctx->o_size + 2, data + ctx->cn_offset,
-		       ctx->cn_size);
-		_name[ctx->o_size + 2 + ctx->cn_size] = '\0';
-
-		goto out;
-
-	} 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:
-	if (namesize >= LC_ASN1_MAX_ISSUER_NAME) {
-		ret = -EOVERFLOW;
-		goto out;
-	}
-	memcpy(_name, name, namesize);
-	_name[namesize] = '\0';
-
-out:
-	ctx->cn_size = 0;
-	ctx->o_size = 0;
-	ctx->email_size = 0;
-	return ret;
-}
-
 int x509_note_issuer(void *context, size_t hdrlen, unsigned char tag,
 		     const uint8_t *value, size_t vlen)
 {
@@ -457,6 +326,9 @@ int x509_note_issuer(void *context, size
 	struct lc_public_key_signature *sig = &cert->sig;
 	int ret = 0;
 
+	(void)hdrlen;
+	(void)tag;
+
 	cert->raw_issuer = value;
 	cert->raw_issuer_size = vlen;
 
@@ -465,8 +337,6 @@ int x509_note_issuer(void *context, size
 						 NULL, 0));
 	}
 
-	CKINT(x509_fabricate_name(ctx, hdrlen, tag, cert->issuer, vlen, 0));
-
 out:
 	return ret;
 }
@@ -480,9 +350,12 @@ int x509_note_subject(void *context, siz
 	struct x509_parse_context *ctx = context;
 	struct lc_x509_certificate *cert = ctx->cert;
 
+	(void)hdrlen;
+	(void)tag;
+
 	cert->raw_subject = value;
 	cert->raw_subject_size = vlen;
-	return x509_fabricate_name(ctx, hdrlen, tag, cert->subject, vlen, 1);
+	return 0;
 }
 
 /*
@@ -653,7 +526,7 @@ int x509_san_dns(void *context, size_t h
 	cert->san_dns = (char *)value;
 	cert->san_dns_len = vlen;
 
-	return x509_fabricate_name(ctx, hdrlen, tag, cert->subject, vlen, 1);
+	return 0;
 }
 
 /*
Index: leancrypto-1.6.0/asn1/src/x509_cert_parser.h
===================================================================
--- leancrypto-1.6.0.orig/asn1/src/x509_cert_parser.h
+++ leancrypto-1.6.0/asn1/src/x509_cert_parser.h
@@ -42,15 +42,9 @@ struct x509_parse_context {
 	const uint8_t *akid_raw_issuer; /* Raw directoryName in authorityKeyId */
 	size_t akid_raw_issuer_size;
 	unsigned int extension_critical : 1;
-	uint16_t o_offset; /* Offset of organizationName (O) */
-	uint16_t cn_offset; /* Offset of commonName (CN) */
-	uint16_t email_offset; /* Offset of emailAddress */
 	enum OID key_algo; /* Algorithm used by the cert's key */
 	enum OID last_oid; /* Last OID encountered */
 	enum OID sig_algo; /* Algorithm used to sign the cert */
-	uint8_t o_size; /* Size of organizationName (O) */
-	uint8_t cn_size; /* Size of commonName (CN) */
-	uint8_t email_size; /* Size of emailAddress */
 };
 
 struct x509_flag_name {
openSUSE Build Service is sponsored by