php / php/php-src

check_cert() uses system CA store even if user provided own cert to be used as CA

Ouverte
#20,586 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Bug Extension: openssl Status: Needs Triage
Langage dominant
C
Étoiles
40.4k
Forks
8.1k
Merge moyen
2 j 13 h
PR mergées (30 j)
96

Description

Description

I was trying to figure out why this code

<?php
$cert = file_get_contents("single-letsencrypt-domain-cert.pem")
print_r(openssl_x509_checkpurpose($cert, X509_PURPOSE_SSL_SERVER, array('letsencrypt-intermediate.pem')));

worked (returned True) on some systems while on other returned False. Turns out the key difference is that one system had CA certificates in a bundle ONLY:

/etc/pki/tls/certs/ca-bundle.crt

while the other system had above bundle BUT also individual hashed CA certs in /etc/openssl/certs/ directory.

/etc/pki/tls/certs/ca-bundle.crt
/etc/openssl/certs/*.0 (bunch of hashes as symlinks to actual individual CA cert files)

(note this is on PLD/Linux, so on your system layout can be different)

Now openssl_x509_checkpurpose() third parameter is "ca_info should be an array of trusted CA files/dirs as described in Certificate Verification."

I assume the goal is to override system CA certs. (My assumption is also based on how/when check_cert() tries to load system CA certs)

openssl_x509_checkpurpose() then uses check_cert() to load CA certs.

Unfortunately the code is like this

[code from php 8.5 git]

parse what user has specified in ca_info (calist), if some files then "nfiles++", if dirs then "ndirs++",
load what user specified (if possible) and then handle system CA certs but do it in such way:


 832     if (nfiles == 0) {
 833         file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
 834         if (file_lookup == NULL || !X509_LOOKUP_load_file(file_lookup, NULL, X509_FILETYPE_DEFAULT)) {
 835             php_openssl_store_errors();
 836         }
 837     }
 838     if (ndirs == 0) {
 839         dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
 840         if (dir_lookup == NULL || !X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT)) {
 841             php_openssl_store_errors();
 842         }
 843     }

Which means:

  • if user specified (in ca_info) any files and only files then sytem CA files won't be loaded BUT sytem directories will (due to ndirs==0)!
  • if user specified (in ca_info) any directories and only directories then sytem CA directories won't be loaded BUT sytem files will (due to nfiles==0)!
  • if user specified (in ca_info) any files and any directories then no CA files and no CA directories will be used (due to ndirs > 0 and nfiles > 0).

That makes no sense. Partial system CA is getting kind of "randomly" loaded only based on file vs directory type.

I would expect only user provided CA files/dirs to be used and no system CA store if user uses anything valid as ca_info parameter of openssl_x509_checkpurpose()

So the fix - use system CA certs in form of files and directories only if user didn't override these with own ca_info (calist) parameter.

diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c
index c21e64a1306..1f4509f40f0 100644
--- a/ext/openssl/openssl_backend_common.c
+++ b/ext/openssl/openssl_backend_common.c
@@ -829,13 +829,11 @@ X509_STORE *php_openssl_setup_verify(zval *calist, uint32_t arg_num)
                        }
                } ZEND_HASH_FOREACH_END();
        }
-       if (nfiles == 0) {
+       if (nfiles == 0 && ndirs == 0) {
                file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
                if (file_lookup == NULL || !X509_LOOKUP_load_file(file_lookup, NULL, X509_FILETYPE_DEFAULT)) {
                        php_openssl_store_errors();
                }
-       }
-       if (ndirs == 0) {
                dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
                if (dir_lookup == NULL || !X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT)) {
                        php_openssl_store_errors();
PHP Version
$ php84 --version
PHP 8.4.13 (cli) (built: Sep 27 2025 01:01:24) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.13, Copyright (c) Zend Technologies

(tests were done on this php)
Operating System

PLD/Linux current Th

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez dans ext/openssl/openssl_backend_common.c, au niveau de php_openssl_setup_verify() et du chemin check_cert(). Reproduisez l’exemple de openssl_x509_checkpurpose() avec des valeurs de ca_info contenant uniquement un fichier, uniquement un répertoire, puis un mélange des deux. C’est terminé lorsque des fichiers ou répertoires CA valides fournis par l’utilisateur empêchent le chargement partiel du magasin CA du système.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
c, php
Domaine
backend, security
Type d'issue
Bug
Difficulté
3/5
Temps estimé
1-2 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
55/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.