Commit 507c152ce4 for openssl.org
commit 507c152ce408bbef7031f9e2b25d3f8c1a7dc98d
Author: Bob Beck <beck@openssl.org>
Date: Mon Aug 24 10:31:48 2026 -0600
Limit proxy pathlen to 0..255
Treat an out-of-range pcPathLengthConstraint as invalid, as already
done for basicConstraints. X509_set_proxy_pathlen() now rejects
out-of-range values, failing closed to 0.
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Merge-date: Mon Oct 5 10:17:27 2026
Merged-from: https://github.com/openssl/openssl/pull/32471
diff --git a/crypto/x509/v3_purp.c b/crypto/x509/v3_purp.c
index ac8899c580..9832a98a70 100644
--- a/crypto/x509/v3_purp.c
+++ b/crypto/x509/v3_purp.c
@@ -605,10 +605,22 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
|| X509_get_ext_by_NID(const_x, NID_issuer_alt_name, -1) >= 0) {
tmp_ex_flags |= EXFLAG_INVALID;
}
- if (pci->pcPathLengthConstraint != NULL)
- tmp_ex_pcpathlen = ASN1_INTEGER_get(pci->pcPathLengthConstraint);
- else
+ if (pci->pcPathLengthConstraint != NULL) {
+ if (pci->pcPathLengthConstraint->type == V_ASN1_NEG_INTEGER) {
+ ERR_raise(ERR_LIB_X509V3, X509V3_R_NEGATIVE_PATHLEN);
+ tmp_ex_flags |= EXFLAG_INVALID;
+ tmp_ex_pcpathlen = -1;
+ } else {
+ tmp_ex_pcpathlen = ASN1_INTEGER_get(pci->pcPathLengthConstraint);
+ if (tmp_ex_pcpathlen < 0 || tmp_ex_pcpathlen > 255) {
+ ERR_raise(ERR_LIB_X509V3, X509V3_R_PATHLEN_TOO_LARGE);
+ tmp_ex_flags |= EXFLAG_INVALID;
+ tmp_ex_pcpathlen = -1;
+ }
+ }
+ } else {
tmp_ex_pcpathlen = -1;
+ }
PROXY_CERT_INFO_EXTENSION_free(pci);
tmp_ex_flags |= EXFLAG_PROXY;
} else if (i != -1) {
@@ -884,6 +896,14 @@ void X509_set_proxy_flag(X509 *x)
void X509_set_proxy_pathlen(X509 *x, long l)
{
+ /*
+ * Out of range values cannot be reported from a void function, so
+ * fail closed: the caller asked for a constraint, and 0 permits no
+ * further delegation and can never be more permissive than the
+ * requested value.
+ */
+ if (l < -1 || l > 255)
+ l = 0;
x->ex_pcpathlen = l;
}
diff --git a/doc/man3/X509_get_extension_flags.pod b/doc/man3/X509_get_extension_flags.pod
index aa37d23fd1..44f6d2bf96 100644
--- a/doc/man3/X509_get_extension_flags.pod
+++ b/doc/man3/X509_get_extension_flags.pod
@@ -27,7 +27,7 @@ X509_get_proxy_pathlen - retrieve certificate extension data
const GENERAL_NAMES *X509_get0_authority_issuer(const X509 *x);
const ASN1_INTEGER *X509_get0_authority_serial(const X509 *x);
void X509_set_proxy_flag(X509 *x);
- void X509_set_proxy_pathlen(int l);
+ void X509_set_proxy_pathlen(X509 *x, long l);
long X509_get_proxy_pathlen(const X509 *x);
=head1 DESCRIPTION
@@ -156,6 +156,9 @@ such, as OpenSSL only detects RFC3820 compliant ones.
X509_set_proxy_pathlen() sets the proxy certificate path length for the given
certificate B<x>. This is for the users who need to mark non-RFC3820 proxy
certificates as such, as OpenSSL only detects RFC3820 compliant ones.
+B<l> must be between 0 and 255 inclusive, or -1 to indicate that no
+path length constraint is set. For any other value the path length is
+instead set to 0, permitting no further delegation.
X509_get_proxy_pathlen() returns the proxy certificate path length for the
given certificate B<x> if it is a proxy certificate.
@@ -193,7 +196,9 @@ pointer to an B<ASN1_OCTET_STRING> structure or B<NULL> if the extension
is absent or an error occurred during parsing.
X509_get_proxy_pathlen() returns the path length value if the given
-certificate is a proxy one and has a path length set, and -1 otherwise.
+certificate is a proxy one and has a path length set, and -1 otherwise,
+including when the path length is out of range or the certificate's
+extensions could not be parsed.
=head1 SEE ALSO
diff --git a/test/v3ext.c b/test/v3ext.c
index 33a25f279a..ba8e3d25f7 100644
--- a/test/v3ext.c
+++ b/test/v3ext.c
@@ -158,6 +158,62 @@ end:
return ret;
}
+/*
+ * Build a proxy cert whose proxyCertInfo extension carries the given
+ * pcPathLengthConstraint and check whether extension caching accepts it.
+ * Values outside 0..255 must be rejected and reported as no constraint.
+ */
+static int test_proxy_pathlen_range_value(int idx)
+{
+ long pathlen = pathlen_range_values[idx].pathlen;
+ int valid = pathlen_range_values[idx].valid;
+ X509 *cert = NULL;
+ PROXY_CERT_INFO_EXTENSION *pci = NULL;
+ int ret = 0;
+
+ if (!TEST_ptr(cert = X509_new())
+ || !TEST_true(X509_set_version(cert, X509_VERSION_3))
+ || !TEST_ptr(pci = PROXY_CERT_INFO_EXTENSION_new())
+ || !TEST_ptr(pci->pcPathLengthConstraint = ASN1_INTEGER_new())
+ || !TEST_true(ASN1_INTEGER_set(pci->pcPathLengthConstraint, pathlen)))
+ goto end;
+ pci->proxyPolicy->policyLanguage = OBJ_nid2obj(NID_id_ppl_inheritAll);
+ if (!TEST_int_gt(X509_add1_ext_i2d(cert, NID_proxyCertInfo, pci, 1, 0), 0)
+ || !TEST_int_eq(X509_check_purpose(cert, -1, 0), valid ? 1 : -1)
+ || !TEST_long_eq(X509_get_proxy_pathlen(cert), valid ? pathlen : -1))
+ goto end;
+
+ ret = 1;
+end:
+ PROXY_CERT_INFO_EXTENSION_free(pci);
+ X509_free(cert);
+ return ret;
+}
+
+/*
+ * Check that X509_set_proxy_pathlen() accepts -1 (no constraint) and
+ * 0..255, and fails closed to a constraint of 0 for anything else.
+ */
+static int test_set_proxy_pathlen_value(int idx)
+{
+ long pathlen = pathlen_range_values[idx].pathlen;
+ long expected = pathlen >= -1 && pathlen <= 255 ? pathlen : 0;
+ X509 *cert = NULL;
+ int ret = 0;
+
+ if (!TEST_ptr(cert = X509_new()))
+ goto end;
+ X509_set_proxy_flag(cert);
+ X509_set_proxy_pathlen(cert, pathlen);
+ if (!TEST_long_eq(X509_get_proxy_pathlen(cert), expected))
+ goto end;
+
+ ret = 1;
+end:
+ X509_free(cert);
+ return ret;
+}
+
static int test_pathlen(void)
{
X509 *x = NULL;
@@ -1387,6 +1443,8 @@ int setup_tests(void)
ADD_TEST(test_pathlen);
ADD_ALL_TESTS(test_pathlen_range_value, OSSL_NELEM(pathlen_range_values));
+ ADD_ALL_TESTS(test_proxy_pathlen_range_value, OSSL_NELEM(pathlen_range_values));
+ ADD_ALL_TESTS(test_set_proxy_pathlen_value, OSSL_NELEM(pathlen_range_values));
ADD_ALL_TESTS(test_duplicate_field, OSSL_NELEM(duplicate_field_configs));
ADD_MFAIL_TEST(test_asn1_multi_mfail);
#ifndef OPENSSL_NO_RFC3779
diff --git a/test/verify_extra_test.c b/test/verify_extra_test.c
index 661d127df8..aa86fda6ee 100644
--- a/test/verify_extra_test.c
+++ b/test/verify_extra_test.c
@@ -836,6 +836,30 @@ static const char *ca_cert_pathlen_max[] = {
NULL,
};
+static const char *bad_pc7_cert[] = {
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5zZXJ2\n"
+ "ZXIuZXhhbXBsZTAgFw0yNjA5MDMxOTIwMDJaGA8yMTI2MDkwNDE5MjAwMlowKzEX\n"
+ "MBUGA1UEAwwOc2VydmVyLmV4YW1wbGUxEDAOBgNVBAMMB3Byb3h5IDcwggEiMA0G\n"
+ "CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/cun2FqC9pusaIZIPZFe9rEfEqMgC\n"
+ "7dnLm83Uxnr4KPjZ5GlygYCb9Zm3zKzL4XXEkHh//1slh51jgA9XiNpVoQGJ2TAg\n"
+ "pQ4ZhFOCIo4FOByinqQpKpPZu8/ODi6Ap9mGtvgqpPzouQZiADQr8kX+jkVeiSoO\n"
+ "DJcMLK9Ym+urY30dJWHQAV1jg7V+y4KC0gZPE7FCgZ1lkqnnR/75vq/A1L8WQiLI\n"
+ "1gXmzz5JOxZIlcpJeFtWsBpVPYlKbls1kXmLWL1CFUrg+0JrgH1kBeNe8xN+YJzz\n"
+ "dmcpzEQ2rT3a59xk3i788pLjvG8zksST4Q7iX9DU8ngg4pF3JlSW5c99AgMBAAGj\n"
+ "gZEwgY4wHQYDVR0OBBYEFCRqfkUTT5aj35YYpcN+XRVWS5XpMDUGA1UdIwQuMCyA\n"
+ "FOeb4iqtimw6y3ZR5Y4HmCKX4XOioRGkDzANMQswCQYDVQQDDAJDQYIBAjAJBgNV\n"
+ "HRMEAjAAMCsGCCsGAQUFBwEOAQH/BBwwGgIIf/////////8wDgYIKwYBBQUHFQAE\n"
+ "AkFCMA0GCSqGSIb3DQEBCwUAA4IBAQBR9qfkgHxYhlhf1lRDaKv+HgECFy4Kzlzs\n"
+ "cwAgS0vJ+EV/3TCF8SmbnmM8E0YhW+1KiFNnX9D+24swDjC94sNRL2F2Lk+JeNIP\n"
+ "7NiJUf67pnjwm4ajnZpCgUatLVg9Y/nUjFfLr1m4XLS34j98Xm3HZ+HPLBZZCzV4\n"
+ "Vr0lvc9+avniOkaQTh+lBTK1W26djm26hnssCBqDMbfVnjOzp9QCuQpNm6kxElPk\n"
+ "J5TaCMuVlaCgo4O+nfONkkHmA5yoQb6H7cprDR8JI3YNCK7uPKRfR7fkl+bOHW/p\n"
+ "aDxj2bUKdXsI0WlHwm+Bta3c0U53QWX4FB9/Ia2jnCWRS4H0GFPJ\n"
+ "-----END CERTIFICATE-----\n",
+ NULL,
+};
+
/* Verification time for the path length chains, within all cert lifetimes */
#define PATHLEN_CHAIN_TIME 1790000000
@@ -869,6 +893,9 @@ static const struct {
{ { "pc1-cert.pem", NULL },
{ { "ee-client.pem", NULL }, { NULL, ca_cert_pathlen_max } }, 1,
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY },
+ { { NULL, bad_pc7_cert },
+ { { "ee-client.pem", NULL }, { "ca-cert.pem", NULL } }, 1,
+ X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY },
};
static X509 *load_pathlen_cert(const PATHLEN_CERT *pc)