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;
+ }
}