diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 8c300e117..5e2244ee0 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -17,6 +17,7 @@ use Composer\Factory; use Composer\Util\Filesystem; use Composer\Util\RemoteFilesystem; use Composer\Downloader\FilesystemException; +use Composer\Downloader\TransportException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; @@ -57,17 +58,34 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { $config = Factory::createConfig(); - if (!extension_loaded('openssl')) { - $output->writeln('The openssl extension is required for SSL/TLS protection.'); - $output->writeln('You can disable this error, at your own risk, by setting the \'disable-tls\' option to "false".'); - return 1; - } elseif($config->get('disable-tls') === true) { + + if($config->get('disable-tls') === true || $input->getOption('disable-tls')) { $output->writeln('You are running Composer with SSL/TLS protection disabled.'); $baseUrl = 'http://' . self::HOMEPAGE; + } elseif (!extension_loaded('openssl')) { + $output->writeln('The openssl extension is required for SSL/TLS protection.'); + $output->writeln('You can disable this error, at your own risk, by enabling the \'disable-tls\' option.'); + return 1; } else { $baseUrl = 'https://' . self::HOMEPAGE; } - $remoteFilesystem = new RemoteFilesystem($this->getIO()); + + try { + if (!is_null($config->get('cafile'))) { + $remoteFilesystemOptions = array('ssl'=>array('cafile'=>$config->get('cafile'))); + } + $remoteFilesystem = new RemoteFilesystem($this->getIO(), $remoteFilesystemOptions); + } catch (TransportException $e) { + if (preg_match('|cafile|', $e->getMessage())) { + $output->writeln('' . $e->getMessage() . ''); + $output->writeln('Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'); + $output->writeln('You can disable this error, at your own risk, by enabling the \'disable-tls\' option.'); + return 1; + } else { + throw $e; + } + } + $cacheDir = $config->get('cache-dir'); $rollbackDir = $config->get('home'); $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index d3ecec03d..e084999a8 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -43,7 +43,20 @@ class RemoteFilesystem public function __construct(IOInterface $io, $options = array()) { $this->io = $io; - $this->options = $options; + + /** + * Setup TLS options + * The cafile option can be set via config.json + */ + $this->options = $this->getTlsDefaults(); + if (isset($options['ssl']['cafile']) + && (!is_readable($options['ssl']['cafile']) + || !openssl_x509_parse(file_get_contents($options['ssl']['cafile'])))) { //check return value and test (it's subject to change) + throw new TransportException('The configured cafile could was not valid or could not be read.'); + } + + // handle the other externally set options normally. + $this->options = array_replace_recursive($this->options, $options); } /** @@ -344,6 +357,158 @@ class RemoteFilesystem $options['http']['header'][] = $header; } + /** + * Setup TLS options CN_match and SNI_server_name based on URL given + */ + $parts = parse_url($originUrl); + + return $options; } + + protected function getTlsDefaults() + { + $ciphers = implode(':', array( + 'ECDHE-RSA-AES128-GCM-SHA256', + 'ECDHE-ECDSA-AES128-GCM-SHA256', + 'ECDHE-RSA-AES256-GCM-SHA384', + 'ECDHE-ECDSA-AES256-GCM-SHA384', + 'DHE-RSA-AES128-GCM-SHA256', + 'DHE-DSS-AES128-GCM-SHA256', + 'kEDH+AESGCM', + 'ECDHE-RSA-AES128-SHA256', + 'ECDHE-ECDSA-AES128-SHA256', + 'ECDHE-RSA-AES128-SHA', + 'ECDHE-ECDSA-AES128-SHA', + 'ECDHE-RSA-AES256-SHA384', + 'ECDHE-ECDSA-AES256-SHA384', + 'ECDHE-RSA-AES256-SHA', + 'ECDHE-ECDSA-AES256-SHA', + 'DHE-RSA-AES128-SHA256', + 'DHE-RSA-AES128-SHA', + 'DHE-DSS-AES128-SHA256', + 'DHE-RSA-AES256-SHA256', + 'DHE-DSS-AES256-SHA', + 'DHE-RSA-AES256-SHA', + 'AES128-GCM-SHA256', + 'AES256-GCM-SHA384', + 'ECDHE-RSA-RC4-SHA', + 'ECDHE-ECDSA-RC4-SHA', + 'AES128', + 'AES256', + 'RC4-SHA', + 'HIGH', + '!aNULL', + '!eNULL', + '!EXPORT', + '!DES', + '!3DES', + '!MD5', + '!PSK' + )); + + /** + * CN_match and SNI_server_name are only known once a URL is passed. + * They will be set in the getOptionsForUrl() method which receives a URL. + * + * cafile or capath can be overridden by passing in those options to constructor. + */ + $options = array( + 'ssl' => array( + 'ciphers' => $ciphers, + 'verify_peer' => true, + 'verify_depth' => 7, + 'SNI_enabled' => true, + ) + ); + + /** + * Attempt to find a local cafile or throw an exception. + * The user may go download one if this occurs. + */ + $result = $this->getSystemCaRootBundlePath(); + if ($result) { + $options['ssl']['cafile'] = $result; + } else { + throw new TransportException('A valid cafile could not be located automatically.'); + } + + /** + * Disable TLS compression to prevent CRIME attacks where supported. + */ + if (version_compare(PHP_VERSION, '5.4.13') >= 0) { + $options['ssl']['disable_compression'] = true; + } + + return $options; + } + + /** + * 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. + */ + protected static function getSystemCaRootBundlePath() + { + if (isset($found)) { + return $found; + } + // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. + // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. + $envCertFile = getenv('SSL_CERT_FILE'); + if ($envCertFile && is_readable($envCertFile) && openssl_x509_parse(file_get_contents($envCertFile))) { + // Possibly throw exception instead of ignoring SSL_CERT_FILE if it's invalid? + return $envCertFile; + } + + $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) + '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) + '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package) + '/usr/ssl/certs/ca-bundle.crt', // Cygwin + '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package + '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) + '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? + ); + + static $found = false; + foreach ($caBundlePaths as $caBundle) { + if (is_readable($caBundle) && openssl_x509_parse(file_get_contents($caBundle))) { + $found = true; + break; + } + } + if ($found) { + $found = $caBundle; + } + return $found; + } }