[ Index ]

PHP Cross Reference of YOURLS

title

Body

[close]

/includes/vendor/composer/ca-bundle/src/ -> CaBundle.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of composer/ca-bundle.
   5   *
   6   * (c) Composer <https://github.com/composer>
   7   *
   8   * For the full copyright and license information, please view
   9   * the LICENSE file that was distributed with this source code.
  10   */
  11  
  12  namespace Composer\CaBundle;
  13  
  14  use Psr\Log\LoggerInterface;
  15  use Symfony\Component\Process\PhpProcess;
  16  
  17  /**
  18   * @author Chris Smith <chris@cs278.org>
  19   * @author Jordi Boggiano <j.boggiano@seld.be>
  20   */
  21  class CaBundle
  22  {
  23      /** @var string|null */
  24      private static $caPath;
  25      /** @var array<string, bool> */
  26      private static $caFileValidity = array();
  27      /** @var bool|null */
  28      private static $useOpensslParse;
  29  
  30      /**
  31       * Returns the system CA bundle path, or a path to the bundled one
  32       *
  33       * This method was adapted from Sslurp.
  34       * https://github.com/EvanDotPro/Sslurp
  35       *
  36       * (c) Evan Coury <me@evancoury.com>
  37       *
  38       * For the full copyright and license information, please see below:
  39       *
  40       * Copyright (c) 2013, Evan Coury
  41       * All rights reserved.
  42       *
  43       * Redistribution and use in source and binary forms, with or without modification,
  44       * are permitted provided that the following conditions are met:
  45       *
  46       *     * Redistributions of source code must retain the above copyright notice,
  47       *       this list of conditions and the following disclaimer.
  48       *
  49       *     * Redistributions in binary form must reproduce the above copyright notice,
  50       *       this list of conditions and the following disclaimer in the documentation
  51       *       and/or other materials provided with the distribution.
  52       *
  53       * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  54       * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  55       * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  56       * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  57       * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  58       * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  59       * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  60       * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  61       * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  62       * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  63       *
  64       * @param  LoggerInterface $logger optional logger for information about which CA files were loaded
  65       * @return string          path to a CA bundle file or directory
  66       */
  67      public static function getSystemCaRootBundlePath(LoggerInterface $logger = null)
  68      {
  69          if (self::$caPath !== null) {
  70              return self::$caPath;
  71          }
  72          $caBundlePaths = array();
  73  
  74          // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
  75          // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
  76          $caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE');
  77  
  78          // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
  79          // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
  80          $caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR');
  81  
  82          $caBundlePaths[] = ini_get('openssl.cafile');
  83          $caBundlePaths[] = ini_get('openssl.capath');
  84  
  85          $otherLocations = array(
  86              '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
  87              '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
  88              '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
  89              '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
  90              '/usr/ssl/certs/ca-bundle.crt', // Cygwin
  91              '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
  92              '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
  93              '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
  94              '/etc/ssl/cert.pem', // OpenBSD
  95              '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
  96              '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
  97              '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
  98          );
  99  
 100          foreach($otherLocations as $location) {
 101              $otherLocations[] = dirname($location);
 102          }
 103  
 104          $caBundlePaths = array_merge($caBundlePaths, $otherLocations);
 105  
 106          foreach ($caBundlePaths as $caBundle) {
 107              if ($caBundle && self::caFileUsable($caBundle, $logger)) {
 108                  return self::$caPath = $caBundle;
 109              }
 110  
 111              if ($caBundle && self::caDirUsable($caBundle, $logger)) {
 112                  return self::$caPath = $caBundle;
 113              }
 114          }
 115  
 116          return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
 117      }
 118  
 119      /**
 120       * Returns the path to the bundled CA file
 121       *
 122       * In case you don't want to trust the user or the system, you can use this directly
 123       *
 124       * @return string path to a CA bundle file
 125       */
 126      public static function getBundledCaBundlePath()
 127      {
 128          $caBundleFile = __DIR__.'/../res/cacert.pem';
 129  
 130          // cURL does not understand 'phar://' paths
 131          // see https://github.com/composer/ca-bundle/issues/10
 132          if (0 === strpos($caBundleFile, 'phar://')) {
 133              $tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-');
 134              if (false === $tempCaBundleFile) {
 135                  throw new \RuntimeException('Could not create a temporary file to store the bundled CA file');
 136              }
 137  
 138              file_put_contents(
 139                  $tempCaBundleFile,
 140                  file_get_contents($caBundleFile)
 141              );
 142  
 143              register_shutdown_function(function() use ($tempCaBundleFile) {
 144                  @unlink($tempCaBundleFile);
 145              });
 146  
 147              $caBundleFile = $tempCaBundleFile;
 148          }
 149  
 150          return $caBundleFile;
 151      }
 152  
 153      /**
 154       * Validates a CA file using opensl_x509_parse only if it is safe to use
 155       *
 156       * @param string          $filename
 157       * @param LoggerInterface $logger   optional logger for information about which CA files were loaded
 158       *
 159       * @return bool
 160       */
 161      public static function validateCaFile($filename, LoggerInterface $logger = null)
 162      {
 163          static $warned = false;
 164  
 165          if (isset(self::$caFileValidity[$filename])) {
 166              return self::$caFileValidity[$filename];
 167          }
 168  
 169          $contents = file_get_contents($filename);
 170  
 171          // assume the CA is valid if php is vulnerable to
 172          // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
 173          if (!static::isOpensslParseSafe()) {
 174              if (!$warned && $logger) {
 175                  $logger->warning(sprintf(
 176                      'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.',
 177                      PHP_VERSION
 178                  ));
 179                  $warned = true;
 180              }
 181  
 182              $isValid = !empty($contents);
 183          } elseif (is_string($contents) && strlen($contents) > 0) {
 184              $contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents);
 185              if (null === $contents) {
 186                  // regex extraction failed
 187                  $isValid = false;
 188              } else {
 189                  $isValid = (bool) openssl_x509_parse($contents);
 190              }
 191          } else {
 192              $isValid = false;
 193          }
 194  
 195          if ($logger) {
 196              $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid'));
 197          }
 198  
 199          return self::$caFileValidity[$filename] = $isValid;
 200      }
 201  
 202      /**
 203       * Test if it is safe to use the PHP function openssl_x509_parse().
 204       *
 205       * This checks if OpenSSL extensions is vulnerable to remote code execution
 206       * via the exploit documented as CVE-2013-6420.
 207       *
 208       * @return bool
 209       */
 210      public static function isOpensslParseSafe()
 211      {
 212          if (null !== self::$useOpensslParse) {
 213              return self::$useOpensslParse;
 214          }
 215  
 216          if (PHP_VERSION_ID >= 50600) {
 217              return self::$useOpensslParse = true;
 218          }
 219  
 220          // Vulnerable:
 221          // PHP 5.3.0 - PHP 5.3.27
 222          // PHP 5.4.0 - PHP 5.4.22
 223          // PHP 5.5.0 - PHP 5.5.6
 224          if (
 225                 (PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328)
 226              || (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423)
 227              || PHP_VERSION_ID >= 50507
 228          ) {
 229              // This version of PHP has the fix for CVE-2013-6420 applied.
 230              return self::$useOpensslParse = true;
 231          }
 232  
 233          if (defined('PHP_WINDOWS_VERSION_BUILD')) {
 234              // Windows is probably insecure in this case.
 235              return self::$useOpensslParse = false;
 236          }
 237  
 238          $compareDistroVersionPrefix = function ($prefix, $fixedVersion) {
 239              $regex = '{^'.preg_quote($prefix).'([0-9]+)$}';
 240  
 241              if (preg_match($regex, PHP_VERSION, $m)) {
 242                  return ((int) $m[1]) >= $fixedVersion;
 243              }
 244  
 245              return false;
 246          };
 247  
 248          // Hard coded list of PHP distributions with the fix backported.
 249          if (
 250              $compareDistroVersionPrefix('5.3.3-7+squeeze', 18) // Debian 6 (Squeeze)
 251              || $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) // Debian 7 (Wheezy)
 252              || $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) // Ubuntu 12.04 (Precise)
 253          ) {
 254              return self::$useOpensslParse = true;
 255          }
 256  
 257          // Symfony Process component is missing so we assume it is unsafe at this point
 258          if (!class_exists('Symfony\Component\Process\PhpProcess')) {
 259              return self::$useOpensslParse = false;
 260          }
 261  
 262          // This is where things get crazy, because distros backport security
 263          // fixes the chances are on NIX systems the fix has been applied but
 264          // it's not possible to verify that from the PHP version.
 265          //
 266          // To verify exec a new PHP process and run the issue testcase with
 267          // known safe input that replicates the bug.
 268  
 269          // Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415
 270          // changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593
 271          $cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K';
 272          $script = <<<'EOT'
 273  
 274  error_reporting(-1);
 275  $info = openssl_x509_parse(base64_decode('%s'));
 276  var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']);
 277  
 278  EOT;
 279          $script = '<'."?php\n".sprintf($script, $cert);
 280  
 281          try {
 282              $process = new PhpProcess($script);
 283              $process->mustRun();
 284          } catch (\Exception $e) {
 285              // In the case of any exceptions just accept it is not possible to
 286              // determine the safety of openssl_x509_parse and bail out.
 287              return self::$useOpensslParse = false;
 288          }
 289  
 290          $output = preg_split('{\r?\n}', trim($process->getOutput()));
 291          $errorOutput = trim($process->getErrorOutput());
 292  
 293          if (
 294              is_array($output)
 295              && count($output) === 3
 296              && $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION)
 297              && $output[1] === 'string(27) "stefan.esser@sektioneins.de"'
 298              && $output[2] === 'int(-1)'
 299              && preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}', $errorOutput)
 300          ) {
 301              // This PHP has the fix backported probably by a distro security team.
 302              return self::$useOpensslParse = true;
 303          }
 304  
 305          return self::$useOpensslParse = false;
 306      }
 307  
 308      /**
 309       * Resets the static caches
 310       * @return void
 311       */
 312      public static function reset()
 313      {
 314          self::$caFileValidity = array();
 315          self::$caPath = null;
 316          self::$useOpensslParse = null;
 317      }
 318  
 319      /**
 320       * @param  string $name
 321       * @return string|false
 322       */
 323      private static function getEnvVariable($name)
 324      {
 325          if (isset($_SERVER[$name])) {
 326              return (string) $_SERVER[$name];
 327          }
 328  
 329          if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) {
 330              return (string) $value;
 331          }
 332  
 333          return false;
 334      }
 335  
 336      /**
 337       * @param  string|false $certFile
 338       * @param  LoggerInterface|null $logger
 339       * @return bool
 340       */
 341      private static function caFileUsable($certFile, LoggerInterface $logger = null)
 342      {
 343          return $certFile
 344              && static::isFile($certFile, $logger)
 345              && static::isReadable($certFile, $logger)
 346              && static::validateCaFile($certFile, $logger);
 347      }
 348  
 349      /**
 350       * @param  string|false $certDir
 351       * @param  LoggerInterface|null $logger
 352       * @return bool
 353       */
 354      private static function caDirUsable($certDir, LoggerInterface $logger = null)
 355      {
 356          return $certDir
 357              && static::isDir($certDir, $logger)
 358              && static::isReadable($certDir, $logger)
 359              && static::glob($certDir . '/*', $logger);
 360      }
 361  
 362      /**
 363       * @param  string $certFile
 364       * @param  LoggerInterface|null $logger
 365       * @return bool
 366       */
 367      private static function isFile($certFile, LoggerInterface $logger = null)
 368      {
 369          $isFile = @is_file($certFile);
 370          if (!$isFile && $logger) {
 371              $logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile));
 372          }
 373  
 374          return $isFile;
 375      }
 376  
 377      /**
 378       * @param  string $certDir
 379       * @param  LoggerInterface|null $logger
 380       * @return bool
 381       */
 382      private static function isDir($certDir, LoggerInterface $logger = null)
 383      {
 384          $isDir = @is_dir($certDir);
 385          if (!$isDir && $logger) {
 386              $logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir));
 387          }
 388  
 389          return $isDir;
 390      }
 391  
 392      /**
 393       * @param  string $certFileOrDir
 394       * @param  LoggerInterface|null $logger
 395       * @return bool
 396       */
 397      private static function isReadable($certFileOrDir, LoggerInterface $logger = null)
 398      {
 399          $isReadable = @is_readable($certFileOrDir);
 400          if (!$isReadable && $logger) {
 401              $logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir));
 402          }
 403  
 404          return $isReadable;
 405      }
 406  
 407      /**
 408       * @param  string $pattern
 409       * @param  LoggerInterface|null $logger
 410       * @return bool
 411       */
 412      private static function glob($pattern, LoggerInterface $logger = null)
 413      {
 414          $certs = glob($pattern);
 415          if ($certs === false) {
 416              if ($logger) {
 417                  $logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern));
 418              }
 419              return false;
 420          }
 421  
 422          if (count($certs) === 0) {
 423              if ($logger) {
 424                  $logger->debug(sprintf("No CA files found for pattern: %s", $pattern));
 425              }
 426              return false;
 427          }
 428  
 429          return true;
 430      }
 431  }


Generated: Tue Sep 27 05:10:01 2022 Cross-referenced by PHPXref 0.7.1