[ Index ] |
PHP Cross Reference of YOURLS |
[Summary view] [Print] [Text view]
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 <[email protected]> 19 * @author Jordi Boggiano <[email protected]> 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 <[email protected]> 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/[email protected]/cert.pem', // OS X homebrew, [email protected] 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) "[email protected]"' 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Jan 21 05:10:11 2025 | Cross-referenced by PHPXref 0.7.1 |