From 63af2fb242ec06b71081359896482141b4f68102 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 30 Jun 2026 16:31:19 +0530 Subject: [PATCH 1/3] fix(ssl): validate custom certificate and key before install --- src/helper/class-ee-site.php | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/helper/class-ee-site.php b/src/helper/class-ee-site.php index 08ad268e..4235e5f5 100644 --- a/src/helper/class-ee-site.php +++ b/src/helper/class-ee-site.php @@ -2250,6 +2250,9 @@ protected function validate_site_custom_ssl( $ssl_key, $ssl_crt ) { } if ( $this->fs->exists( $ssl_key ) && $this->fs->exists( $ssl_crt ) ) { + // Validate cert contents before storing paths, else a malformed/mismatched pair silently breaks nginx-proxy on reload. + $this->assert_valid_cert_key_pair( file_get_contents( $ssl_crt ), file_get_contents( $ssl_key ) ); + $this->site_data['ssl_key'] = realpath( $ssl_key ); $this->site_data['ssl_crt'] = realpath( $ssl_crt ); } else { @@ -2257,6 +2260,40 @@ protected function validate_site_custom_ssl( $ssl_key, $ssl_crt ) { } } + /** + * Assert that a PEM certificate and private key are valid, matching and not expired. + * + * @param string $crt_contents Certificate file contents (PEM). + * @param string $key_contents Private key file contents (PEM). + */ + protected function assert_valid_cert_key_pair( $crt_contents, $key_contents ) { + + $cert_info = openssl_x509_parse( $crt_contents ); + if ( false === $cert_info ) { + EE::error( 'The supplied --ssl-crt is not a valid/parseable certificate.' ); + } + + if ( false === openssl_pkey_get_private( $key_contents ) ) { + EE::error( 'The supplied --ssl-key is not a valid private key.' ); + } + + if ( ! openssl_x509_check_private_key( $crt_contents, $key_contents ) ) { + EE::error( 'The supplied certificate and private key do not match.' ); + } + + // Warn (but allow) on an expired or soon-to-expire cert: clone/restore may legitimately re-supply an existing expired cert, so don't block. + if ( isset( $cert_info['validTo_time_t'] ) ) { + $now = time(); + $expiry_threshold = 30 * 24 * 60 * 60; // 30 days in seconds. + + if ( $cert_info['validTo_time_t'] < $now ) { + EE::warning( sprintf( 'The supplied certificate expired on %s.', date( 'Y-m-d', $cert_info['validTo_time_t'] ) ) ); + } elseif ( $cert_info['validTo_time_t'] - $now < $expiry_threshold ) { + EE::warning( sprintf( 'The supplied certificate expires on %s (within 30 days).', date( 'Y-m-d', $cert_info['validTo_time_t'] ) ) ); + } + } + } + /** * Allow custom SSL for site. */ From 0f2d61242b14d99a15ab5f8423ba49baedae1fa8 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Thu, 24 Sep 2026 10:40:07 +0000 Subject: [PATCH 2/3] fix(ssl): reject custom certificates with a truncated or corrupt chain openssl_x509_parse() only reads the first certificate, but nginx loads every certificate in the file, so a fullchain with a truncated or corrupt intermediate passed validation and still failed nginx's config test. Check every certificate block in the file. --- src/helper/class-ee-site.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/helper/class-ee-site.php b/src/helper/class-ee-site.php index 4235e5f5..d43b6c20 100644 --- a/src/helper/class-ee-site.php +++ b/src/helper/class-ee-site.php @@ -2273,6 +2273,18 @@ protected function assert_valid_cert_key_pair( $crt_contents, $key_contents ) { EE::error( 'The supplied --ssl-crt is not a valid/parseable certificate.' ); } + // nginx loads every certificate in the file, so a truncated or corrupt chain certificate breaks it too. + $begin_count = substr_count( $crt_contents, '-----BEGIN CERTIFICATE-----' ); + $block_count = preg_match_all( '/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/s', $crt_contents, $crt_blocks ); + if ( $begin_count !== $block_count ) { + EE::error( 'The supplied --ssl-crt contains a truncated certificate.' ); + } + foreach ( $crt_blocks[0] as $crt_block ) { + if ( false === openssl_x509_parse( $crt_block ) ) { + EE::error( 'The supplied --ssl-crt contains an invalid chain certificate.' ); + } + } + if ( false === openssl_pkey_get_private( $key_contents ) ) { EE::error( 'The supplied --ssl-key is not a valid private key.' ); } From 304602d77cc92411b03d1637f191670b058a5843 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Thu, 24 Sep 2026 10:40:16 +0000 Subject: [PATCH 3/3] docs(ssl): correct assert_valid_cert_key_pair() docblock on expiry handling --- src/helper/class-ee-site.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helper/class-ee-site.php b/src/helper/class-ee-site.php index d43b6c20..37996ec6 100644 --- a/src/helper/class-ee-site.php +++ b/src/helper/class-ee-site.php @@ -2261,7 +2261,7 @@ protected function validate_site_custom_ssl( $ssl_key, $ssl_crt ) { } /** - * Assert that a PEM certificate and private key are valid, matching and not expired. + * Assert that a PEM certificate and private key are valid and matching; warn if the certificate is expired or near expiry. * * @param string $crt_contents Certificate file contents (PEM). * @param string $key_contents Private key file contents (PEM).