From bcc31a9a0fa77e91c4f3ad4a7f0e056d4bf5e713 Mon Sep 17 00:00:00 2001 From: Brian Wolff Date: Wed, 1 Jul 2015 01:44:12 -0600 Subject: [PATCH] Use system default location for cafile when using php fopen. Up to 5.5, PHP does not accept any certificates if cafile/capath is not set. (From 5.6 it uses the system default CA budle, which is going to be a better choice than anything we can guess.) So try to guess the location of the system default CA bundle. Won't work on windows, but that's a lost cause anyway because PHP (pre-5.6) can't handle the windows CA file format. Bug: T75203 Change-Id: I07736c150fe0783e09d297395ed25adf335edbd3 --- includes/HttpFunctions.php | 52 +++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php index 1c794853a0..24c0dfc064 100644 --- a/includes/HttpFunctions.php +++ b/includes/HttpFunctions.php @@ -865,6 +865,50 @@ class PhpHttpRequest extends MWHttpRequest { return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port']; } + /** + * Returns an array with a 'capath' or 'cafile' key that is suitable to be merged into the 'ssl' sub-array of a + * stream context options array. Uses the 'caInfo' option of the class if it is provided, otherwise uses the system + * default CA bundle if PHP supports that, or searches a few standard locations. + * @return array + * @throws DomainException + */ + protected function getCertOptions() { + $certOptions = array(); + $certLocations = array(); + if ( $this->caInfo ) { + $certLocations = array( 'manual' => $this->caInfo ); + } elseif ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) { + // Default locations, based on + // https://www.happyassassin.net/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/ + // PHP 5.5 and older doesn't have any defaults, so we try to guess ourselves. PHP 5.6+ gets the CA location + // from OpenSSL as long as it is not set manually, so we should leave capath/cafile empty there. + $certLocations = array_filter( array( + getenv( 'SSL_CERT_DIR' ), + getenv( 'SSL_CERT_PATH' ), + '/etc/pki/tls/certs/ca-bundle.crt', # Fedora et al + '/etc/ssl/certs', # Debian et al + '/etc/pki/tls/certs/ca-bundle.trust.crt', + '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', + '/System/Library/OpenSSL', # OSX + ) ); + } + + foreach( $certLocations as $key => $cert ) { + if ( is_dir( $cert ) ) { + $certOptions['capath'] = $cert; + break; + } elseif ( is_file( $cert ) ) { + $certOptions['cafile'] = $cert; + break; + } elseif ( $key === 'manual' ) { + // fail more loudly if a cert path was manually configured and it is not valid + throw new DomainException( "Invalid CA info passed: $cert" ); + } + } + + return $certOptions; + } + public function execute() { parent::execute(); @@ -926,13 +970,7 @@ class PhpHttpRequest extends MWHttpRequest { } } - if ( is_dir( $this->caInfo ) ) { - $options['ssl']['capath'] = $this->caInfo; - } elseif ( is_file( $this->caInfo ) ) { - $options['ssl']['cafile'] = $this->caInfo; - } elseif ( $this->caInfo ) { - throw new MWException( "Invalid CA info passed: {$this->caInfo}" ); - } + $options['ssl'] += $this->getCertOptions(); $context = stream_context_create( $options ); -- 2.20.1