@ -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,12 @@ class RemoteFilesystem
return $options;
}
private function getTlsDefaults()
/**
* @param array $options
*
* @return array
*/
private function getTlsDefaults(array $options)
{
$ciphers = implode(':', array(
'ECDHE-RSA-AES128-GCM-SHA256',
@ -622,7 +619,7 @@ class RemoteFilesystem
*
* cafile or capath can be overridden by passing in those options to constructor.
*/
$option s = array(
$default s = array(
'ssl' => array(
'ciphers' => $ciphers,
'verify_peer' => true,
@ -631,80 +628,86 @@ class RemoteFilesystem
)
);
if (isset($options['ssl'])) {
$defaults['ssl'] = array_replace_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.
*/
if (!isset($this->option s['ssl']['cafile'])) {
if (!isset($defaul ts['ssl']['cafile']) & & !isset($defaults['ssl']['capath '])) {
$result = $this->getSystemCaRootBundlePath();
if ($result) {
if (preg_match('{^phar://}', $result)) {
$targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert.pem';
// 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);
unset($source, $target);
$options['ssl']['cafile'] = $targetPath;
} else {
if (is_dir($result)) {
$options['ssl']['capath'] = $result;
} elseif ($result) {
$options['ssl']['cafile'] = $result;
}
if (preg_match('{^phar://}', $result)) {
$hash = hash_file('sha256', $result);
$targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert-' . $hash . '.pem';
if (!file_exists($targetPath) || $hash !== hash_file('sha256', $targetPath)) {
$this->streamCopy($result, $targetPath);
chmod($targetPath, 0666);
}
$defaults['ssl']['cafile'] = $targetPath;
} elseif (is_dir($result)) {
$defaults['ssl']['capath'] = $result;
} else {
throw new TransportException('A valid cafile could not be located automatically.');
$defaults['ssl']['cafile'] = $result;
}
}
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($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.');
}
/**
* Disable TLS compression to prevent CRIME attacks where supported.
*/
if (PHP_VERSION_ID >= 50413) {
$options['ssl']['disable_compression'] = true;
$default s['ssl']['disable_compression'] = true;
}
return $options;
return $default s;
}
/**
* This method was adapted from Sslurp.
* https://github.com/EvanDotPro/Sslurp
*
* (c) Evan Coury < me @ evancoury . com >
*
* 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 < me @ evancoury . com >
*
* 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;
@ -721,6 +724,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 +740,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,9 +755,14 @@ class RemoteFilesystem
}
}
return $caPath = false;
return $caPath = __DIR__.'/../../../res/cacert.pem'; // Bundled with Composer, last resort
}
/**
* @param string $filename
*
* @return bool
*/
private function validateCaFile($filename)
{
if ($this->io->isDebug()) {
@ -775,4 +782,22 @@ class RemoteFilesystem
return (bool) openssl_x509_parse($contents);
}
/**
* 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 streamCopy($source, $target)
{
$source = fopen($source, 'r');
$target = fopen($target, 'w+');
stream_copy_to_stream($source, $target);
fclose($source);
fclose($target);
unset($source, $target);
}
}