From c1488f65bf7c91f44f5ad960c8c10212b3c6e4bf Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 20 Jan 2016 21:20:18 +0100 Subject: [PATCH 01/13] a quick stab at adding capath --- doc/06-config.md | 12 ++++++--- src/Composer/Command/ConfigCommand.php | 4 +++ src/Composer/Config.php | 2 ++ src/Composer/Factory.php | 12 +++++++-- src/Composer/Util/RemoteFilesystem.php | 35 ++++++++++---------------- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/doc/06-config.md b/doc/06-config.md index 59390ea42..f6c2da532 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -55,9 +55,15 @@ php_openssl extension in php.ini. ## cafile -A way to set the path to the openssl CA file. In PHP 5.6+ you should rather -set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to -detect your system CA file automatically. +Location of Certificate Authority file on local filesystem. In PHP 5.6+ you +should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should +be able to detect your system CA file automatically. + +## capath + +If cafile is not specified or if the certificate is not found there, the +directory pointed to by capath is searched for a suitable certificate. +capath must be a correctly hashed certificate directory. ## http-basic diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 8378bc5b4..0bf8f97d5 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -333,6 +333,10 @@ EOT function ($val) { return file_exists($val) && is_readable($val); }, function ($val) { return $val === 'null' ? null : $val; } ), + 'capath' => array( + function ($val) { return is_dir($val) && is_readable($val); }, + function ($val) { return $val === 'null' ? null : $val; } + ), 'github-expose-hostname' => array($booleanValidator, $booleanNormalizer), ); $multiConfigValues = array( diff --git a/src/Composer/Config.php b/src/Composer/Config.php index c7d8efa8e..a1b0246b9 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -47,6 +47,7 @@ class Config 'github-domains' => array('github.com'), 'disable-tls' => false, 'cafile' => null, + 'capath' => null, 'github-expose-hostname' => true, 'gitlab-domains' => array('gitlab.com'), 'store-auths' => 'prompt', @@ -179,6 +180,7 @@ class Config case 'cache-repo-dir': case 'cache-vcs-dir': case 'cafile': + case 'capath': // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 60431229b..042445a2a 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -590,9 +590,17 @@ class Factory $remoteFilesystemOptions = array(); if ($disableTls === false) { if ($config && $config->get('cafile')) { - $remoteFilesystemOptions = array('ssl' => array('cafile' => $config->get('cafile'))); + $remoteFilesystemOptions = array_merge_recursive( + $remoteFilesystemOptions, + array('ssl' => array('cafile' => $config->get('cafile'))) + ); + } + if ($config && $config->get('capath')) { + $remoteFilesystemOptions = array_merge_recursive( + $remoteFilesystemOptions, + array('ssl' => array('capath' => $config->get('capath'))) + ); } - $remoteFilesystemOptions = array_merge_recursive($remoteFilesystemOptions, $options); } try { $remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls); diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 4754e304b..48551be1b 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -54,15 +54,7 @@ class RemoteFilesystem // Setup TLS options // The cafile option can be set via config.json if ($disableTls === false) { - $this->options = $this->getTlsDefaults(); - if (isset($options['ssl']['cafile']) - && ( - !is_readable($options['ssl']['cafile']) - || !$this->validateCaFile($options['ssl']['cafile']) - ) - ) { - throw new TransportException('The configured cafile was not valid or could not be read.'); - } + $this->options = $this->getTlsDefaults($options); } else { $this->disableTls = true; } @@ -575,7 +567,7 @@ class RemoteFilesystem return $options; } - private function getTlsDefaults() + private function getTlsDefaults(array $options) { $ciphers = implode(':', array( 'ECDHE-RSA-AES128-GCM-SHA256', @@ -622,7 +614,7 @@ class RemoteFilesystem * * cafile or capath can be overridden by passing in those options to constructor. */ - $options = array( + $defaults = array( 'ssl' => array( 'ciphers' => $ciphers, 'verify_peer' => true, @@ -635,7 +627,7 @@ class RemoteFilesystem * Attempt to find a local cafile or throw an exception if none pre-set * The user may go download one if this occurs. */ - if (!isset($this->options['ssl']['cafile'])) { + if (!isset($options['ssl']['cafile']) && !isset($options['ssl']['capath'])) { $result = $this->getSystemCaRootBundlePath(); if ($result) { if (preg_match('{^phar://}', $result)) { @@ -659,7 +651,7 @@ class RemoteFilesystem } } } else { - throw new TransportException('A valid cafile could not be located automatically.'); + throw new TransportException('A valid cafile or capath could not be located automatically.'); } } @@ -667,10 +659,10 @@ class RemoteFilesystem * Disable TLS compression to prevent CRIME attacks where supported. */ if (PHP_VERSION_ID >= 50413) { - $options['ssl']['disable_compression'] = true; + $defaults['ssl']['disable_compression'] = true; } - return $options; + return $defaults; } /** @@ -721,6 +713,11 @@ class RemoteFilesystem return $caPath = $envCertFile; } + $configured = ini_get('openssl.cafile'); + if ($configured && strlen($configured) > 0 && is_readable($configured) && $this->validateCaFile($configured)) { + return $caPath = $configured; + } + $caBundlePaths = array( '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) @@ -732,14 +729,8 @@ class RemoteFilesystem '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? '/etc/ssl/cert.pem', // OpenBSD '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x - __DIR__.'/../../../res/cacert.pem', // Bundled with Composer ); - $configured = ini_get('openssl.cafile'); - if ($configured && strlen($configured) > 0 && is_readable($configured) && $this->validateCaFile($configured)) { - return $caPath = $configured; - } - foreach ($caBundlePaths as $caBundle) { if (@is_readable($caBundle) && $this->validateCaFile($caBundle)) { return $caPath = $caBundle; @@ -753,7 +744,7 @@ class RemoteFilesystem } } - return $caPath = false; + return $caPath = __DIR__.'/../../../res/cacert.pem'; // Bundled with Composer, last resort } private function validateCaFile($filename) From 008cce8d859f577b87220d2a9b3da6bdce75faa0 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 20 Jan 2016 21:24:13 +0100 Subject: [PATCH 02/13] add back sanity checks --- src/Composer/Util/RemoteFilesystem.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 48551be1b..610dcb32e 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -655,6 +655,14 @@ class RemoteFilesystem } } + if (isset($options['ssl']['cafile']) && (!is_readable($options['ssl']['cafile']) || !$this->validateCaFile($options['ssl']['cafile']))) { + throw new TransportException('The configured cafile was not valid or could not be read.'); + } + + if (isset($options['ssl']['capath']) && (!is_dir($options['ssl']['capath']) || !is_readable($options['ssl']['capath']))) { + throw new TransportException('The configured capath was not valid or could not be read.'); + } + /** * Disable TLS compression to prevent CRIME attacks where supported. */ From b95b0c2ab6b05bf9683f12bdf441555253795f68 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 20 Jan 2016 21:27:26 +0100 Subject: [PATCH 03/13] wrong array --- src/Composer/Util/RemoteFilesystem.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 610dcb32e..afb8820a9 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -642,12 +642,12 @@ class RemoteFilesystem fclose($target); unset($source, $target); - $options['ssl']['cafile'] = $targetPath; + $defaults['ssl']['cafile'] = $targetPath; } else { if (is_dir($result)) { - $options['ssl']['capath'] = $result; + $defaults['ssl']['capath'] = $result; } elseif ($result) { - $options['ssl']['cafile'] = $result; + $defaults['ssl']['cafile'] = $result; } } } else { @@ -655,11 +655,11 @@ class RemoteFilesystem } } - if (isset($options['ssl']['cafile']) && (!is_readable($options['ssl']['cafile']) || !$this->validateCaFile($options['ssl']['cafile']))) { + if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !$this->validateCaFile($defaults['ssl']['cafile']))) { throw new TransportException('The configured cafile was not valid or could not be read.'); } - if (isset($options['ssl']['capath']) && (!is_dir($options['ssl']['capath']) || !is_readable($options['ssl']['capath']))) { + if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($defaults['ssl']['capath']))) { throw new TransportException('The configured capath was not valid or could not be read.'); } From 94947ee772948de260cb6638ecfd60f5bf173f2c Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 20 Jan 2016 21:29:55 +0100 Subject: [PATCH 04/13] merge isset() calls --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index afb8820a9..5f2d0bfa5 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -627,7 +627,7 @@ class RemoteFilesystem * Attempt to find a local cafile or throw an exception if none pre-set * The user may go download one if this occurs. */ - if (!isset($options['ssl']['cafile']) && !isset($options['ssl']['capath'])) { + if (!isset($options['ssl']['cafile'], $options['ssl']['capath'])) { $result = $this->getSystemCaRootBundlePath(); if ($result) { if (preg_match('{^phar://}', $result)) { From f79255df29503eca455c2cf045cd332c805b3730 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 20 Jan 2016 21:35:06 +0100 Subject: [PATCH 05/13] make sure passed options are merged into defaults before checking --- src/Composer/Util/RemoteFilesystem.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 5f2d0bfa5..da5dcd25f 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -623,6 +623,10 @@ class RemoteFilesystem ) ); + if (isset($options['ssl'])) { + $defaults['ssl'] = array_merge_recursive($defaults['ssl'], $options['ssl']); + } + /** * Attempt to find a local cafile or throw an exception if none pre-set * The user may go download one if this occurs. From 4482a1dca08a2454549269a3d6cd30aa6d2442d9 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 20 Jan 2016 21:53:49 +0100 Subject: [PATCH 06/13] also wrong array --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index da5dcd25f..d3c6c565c 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -631,7 +631,7 @@ class RemoteFilesystem * Attempt to find a local cafile or throw an exception if none pre-set * The user may go download one if this occurs. */ - if (!isset($options['ssl']['cafile'], $options['ssl']['capath'])) { + if (!isset($defaults['ssl']['cafile'], $defaults['ssl']['capath'])) { $result = $this->getSystemCaRootBundlePath(); if ($result) { if (preg_match('{^phar://}', $result)) { From 446f1b3e3164c5fac1d4bae8f5e52383643c5af5 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Thu, 21 Jan 2016 10:22:12 +0100 Subject: [PATCH 07/13] fix zip test --- tests/Composer/Test/Downloader/ZipDownloaderTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index cb5a56569..07a358fb9 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -64,6 +64,10 @@ class ZipDownloaderTest extends \PHPUnit_Framework_TestCase ->with('cafile') ->will($this->returnValue(null)); $config->expects($this->at(2)) + ->method('get') + ->with('capath') + ->will($this->returnValue(null)); + $config->expects($this->at(3)) ->method('get') ->with('vendor-dir') ->will($this->returnValue($this->testDir)); From cef97904d00860702ee01b7a8ed126ddcee03e2f Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Thu, 21 Jan 2016 15:07:51 +0100 Subject: [PATCH 08/13] dont rewrite temp CA file if it already exists and make it readable by everyone the first time we create it --- src/Composer/Util/RemoteFilesystem.php | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index d3c6c565c..58e95d53e 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -633,10 +633,15 @@ class RemoteFilesystem */ if (!isset($defaults['ssl']['cafile'], $defaults['ssl']['capath'])) { $result = $this->getSystemCaRootBundlePath(); - if ($result) { - if (preg_match('{^phar://}', $result)) { - $targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert.pem'; + if (!$result) { + throw new TransportException('A valid cafile or capath could not be located automatically.'); + } + + if (preg_match('{^phar://}', $result)) { + $targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert.pem'; + + if (!file_exists($targetPath)) { // use stream_copy_to_stream instead of copy // to work around https://bugs.php.net/bug.php?id=64634 $source = fopen($result, 'r'); @@ -644,18 +649,15 @@ class RemoteFilesystem stream_copy_to_stream($source, $target); fclose($source); fclose($target); + chmod($targetPath, 0744); unset($source, $target); - - $defaults['ssl']['cafile'] = $targetPath; - } else { - if (is_dir($result)) { - $defaults['ssl']['capath'] = $result; - } elseif ($result) { - $defaults['ssl']['cafile'] = $result; - } } + + $defaults['ssl']['cafile'] = $targetPath; + } elseif (is_dir($result)) { + $defaults['ssl']['capath'] = $result; } else { - throw new TransportException('A valid cafile or capath could not be located automatically.'); + $defaults['ssl']['cafile'] = $result; } } From c232566e5266366ca3d26030056e86c68356bc73 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Thu, 21 Jan 2016 16:02:44 +0100 Subject: [PATCH 09/13] add a hash to make sure CA file gets recreated if the content changes --- src/Composer/Util/RemoteFilesystem.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 58e95d53e..a13f6f449 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -639,7 +639,8 @@ class RemoteFilesystem } if (preg_match('{^phar://}', $result)) { - $targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert.pem'; + $hash = md5(file_get_contents($result)); + $targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert-' . $hash . '.pem'; if (!file_exists($targetPath)) { // use stream_copy_to_stream instead of copy From 474541e9aa47fa35a99037e73b727eec2ef5be75 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Fri, 22 Jan 2016 09:14:37 +0100 Subject: [PATCH 10/13] apply comments - add capath to json schema - simplify factory - hash_file and sha256 for CA checking - remove exception as scenario should not occur - remove executable bit from CA file - make CA file also group/world writable (we overwrite invalid content anyway) to avoid permission errors as much as possible --- res/composer-schema.json | 4 + src/Composer/Factory.php | 10 +-- src/Composer/Util/RemoteFilesystem.php | 115 +++++++++++++++---------- 3 files changed, 74 insertions(+), 55 deletions(-) diff --git a/res/composer-schema.json b/res/composer-schema.json index 966b19b86..9d36b6721 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -149,6 +149,10 @@ "type": "string", "description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically." }, + "capath": { + "type": "string", + "description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory." + }, "http-basic": { "type": "object", "description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.", diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 042445a2a..7998c8be6 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -590,16 +590,10 @@ class Factory $remoteFilesystemOptions = array(); if ($disableTls === false) { if ($config && $config->get('cafile')) { - $remoteFilesystemOptions = array_merge_recursive( - $remoteFilesystemOptions, - array('ssl' => array('cafile' => $config->get('cafile'))) - ); + $remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile'); } if ($config && $config->get('capath')) { - $remoteFilesystemOptions = array_merge_recursive( - $remoteFilesystemOptions, - array('ssl' => array('capath' => $config->get('capath'))) - ); + $remoteFilesystemOptions['ssl']['capath'] = $config->get('capath'); } } try { diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index a13f6f449..cee448715 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -567,6 +567,11 @@ class RemoteFilesystem return $options; } + /** + * @param array $options + * + * @return array + */ private function getTlsDefaults(array $options) { $ciphers = implode(':', array( @@ -631,27 +636,16 @@ class RemoteFilesystem * Attempt to find a local cafile or throw an exception if none pre-set * The user may go download one if this occurs. */ - if (!isset($defaults['ssl']['cafile'], $defaults['ssl']['capath'])) { + if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) { $result = $this->getSystemCaRootBundlePath(); - if (!$result) { - throw new TransportException('A valid cafile or capath could not be located automatically.'); - } - if (preg_match('{^phar://}', $result)) { - $hash = md5(file_get_contents($result)); + $hash = hash_file('sha256', $result); $targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert-' . $hash . '.pem'; - if (!file_exists($targetPath)) { - // use stream_copy_to_stream instead of copy - // to work around https://bugs.php.net/bug.php?id=64634 - $source = fopen($result, 'r'); - $target = fopen($targetPath, 'w+'); - stream_copy_to_stream($source, $target); - fclose($source); - fclose($target); - chmod($targetPath, 0744); - unset($source, $target); + if (!file_exists($targetPath) || $hash !== hash_file('sha256', $targetPath)) { + $this->safeCopy($result, $targetPath); + chmod($targetPath, 0644); } $defaults['ssl']['cafile'] = $targetPath; @@ -681,37 +675,39 @@ class RemoteFilesystem } /** - * This method was adapted from Sslurp. - * https://github.com/EvanDotPro/Sslurp - * - * (c) Evan Coury - * - * For the full copyright and license information, please see below: - * - * Copyright (c) 2013, Evan Coury - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ + * This method was adapted from Sslurp. + * https://github.com/EvanDotPro/Sslurp + * + * (c) Evan Coury + * + * For the full copyright and license information, please see below: + * + * Copyright (c) 2013, Evan Coury + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @return string + */ private function getSystemCaRootBundlePath() { static $caPath = null; @@ -762,6 +758,11 @@ class RemoteFilesystem return $caPath = __DIR__.'/../../../res/cacert.pem'; // Bundled with Composer, last resort } + /** + * @param string $filename + * + * @return bool + */ private function validateCaFile($filename) { if ($this->io->isDebug()) { @@ -781,4 +782,24 @@ class RemoteFilesystem return (bool) openssl_x509_parse($contents); } + + /** + * Safely copy a file. + * + * Uses stream_copy_to_stream instead of copy to work around https://bugs.php.net/bug.php?id=64634 + * + * @param string $source + * @param string $target + */ + private function safeCopy($source, $target) + { + $source = fopen($source, 'r'); + $target = fopen($target, 'w+'); + + stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + + unset($source, $target); + } } From 2393222826b7014352e455a2ef24c3e8adadbdc1 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Fri, 22 Jan 2016 09:20:43 +0100 Subject: [PATCH 11/13] more appropriate name --- src/Composer/Util/RemoteFilesystem.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index cee448715..f501d52b3 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -644,8 +644,8 @@ class RemoteFilesystem $targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert-' . $hash . '.pem'; if (!file_exists($targetPath) || $hash !== hash_file('sha256', $targetPath)) { - $this->safeCopy($result, $targetPath); - chmod($targetPath, 0644); + $this->streamCopy($result, $targetPath); + chmod($targetPath, 0666); } $defaults['ssl']['cafile'] = $targetPath; @@ -784,14 +784,12 @@ class RemoteFilesystem } /** - * Safely copy a file. - * * Uses stream_copy_to_stream instead of copy to work around https://bugs.php.net/bug.php?id=64634 * * @param string $source * @param string $target */ - private function safeCopy($source, $target) + private function streamCopy($source, $target) { $source = fopen($source, 'r'); $target = fopen($target, 'w+'); From d6be2a693b6c35e2294d839335f1908b4339df12 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Fri, 22 Jan 2016 14:27:08 +0100 Subject: [PATCH 12/13] switch to array-replace-recursive --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index f501d52b3..9a8a25d81 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -629,7 +629,7 @@ class RemoteFilesystem ); if (isset($options['ssl'])) { - $defaults['ssl'] = array_merge_recursive($defaults['ssl'], $options['ssl']); + $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']); } /** From 5b85ee409cba29f1c70f2cff6115eb5ec265a1c9 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Fri, 22 Jan 2016 14:29:29 +0100 Subject: [PATCH 13/13] add missing array-replace-recursive --- src/Composer/Factory.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 7998c8be6..e6718278f 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -595,6 +595,7 @@ class Factory if ($config && $config->get('capath')) { $remoteFilesystemOptions['ssl']['capath'] = $config->get('capath'); } + $remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options); } try { $remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls);