From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 89BB51FF12C for ; Wed, 05 Aug 2026 16:11:28 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 31C7E21970; Wed, 05 Aug 2026 16:11:28 +0200 (CEST) From: Thomas Ellmenreich To: pve-devel@lists.proxmox.com Subject: [PATCH common v4 1/3] fix #5978: pem parser: relax parsing of chain entries Date: Wed, 5 Aug 2026 16:11:11 +0200 Message-ID: <20260805141114.190477-2-t.ellmenreich@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260805141114.190477-1-t.ellmenreich@proxmox.com> References: <20260805141114.190477-1-t.ellmenreich@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1785939069209 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.072 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_LOW -0.7 Sender listed at https://www.dnswl.org/, low trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: 7T3TDFFU5W5N4H22V7CM7OJN7MKTCGM4 X-Message-ID-Hash: 7T3TDFFU5W5N4H22V7CM7OJN7MKTCGM4 X-MailFrom: t.ellmenreich@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Thomas Ellmenreich X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Relaxes the parser to allow for text and whitespaces inbetween certchain entries. The splitting of PEM chains was also reworked to split each entry at its end, grouping it with its leading text. Signed-off-by: Thomas Ellmenreich --- src/PVE/Certificate.pm | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/PVE/Certificate.pm b/src/PVE/Certificate.pm index b8415e2..53619a3 100644 --- a/src/PVE/Certificate.pm +++ b/src/PVE/Certificate.pm @@ -134,26 +134,49 @@ sub strip_leading_text { return $content; } +# Splits the pem chain into entries with their leading text sub split_pem { my ($content, %opts) = @_; - my $label = $opts{label} // 'CERTIFICATE'; - my $header = $header_re->($label); - return split(/(?=$header)/, $content); + my $footer = $footer_re->($opts{label} // 'CERTIFICATE'); + + return $content =~ /(.*?$footer)/sg; } +# Parses the pem or pem chain for complete validity and returns +# only the pem/pem chain removing any extra text sub check_pem { my ($content, %opts) = @_; - $content = strip_leading_text($content); + my $label = $opts{label} // 'CERTIFICATE'; + my $re = $pem_re->($label); + + my @split = + $opts{multiple} + ? split_pem($content, label => $label) + : ($content); - my $re = $pem_re->($opts{label} // 'CERTIFICATE'); - $re = qr/($re\n+)*$re/ if $opts{multiple}; + if (!@split) { + return undef if $opts{noerr}; + die "PEM chain could not be split into separate entries\n"; + } + + my $result_pem = ""; + for my $entry (@split) { + my $stripped = strip_leading_text($entry); + + if ($stripped !~ /^$re$/) { + $result_pem = ""; + last; + } + + $result_pem .= $stripped; + } - return $content if $content =~ /^$re$/; # OK + return $result_pem if $result_pem; return undef if $opts{noerr}; - die "not a valid PEM-formatted string.\n"; + die "not a valid PEM-formatted string\n"; } sub pem_to_der { -- 2.47.3