Browse Source

Added Exceptions, errors and info messages for self-update command and TLS defaults to RemoteFilesystem

Pádraic Brady 11 năm trước cách đây
mục cha
commit
ca4b4696b0

+ 24 - 6
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('<error>The openssl extension is required for SSL/TLS protection.</error>');
-            $output->writeln('<error>You can disable this error, at your own risk, by setting the \'disable-tls\' option to "false".</error>');
-            return 1;
-        } elseif($config->get('disable-tls') === true) {
+
+        if($config->get('disable-tls') === true || $input->getOption('disable-tls')) {
             $output->writeln('<info>You are running Composer with SSL/TLS protection disabled.</info>');
             $baseUrl = 'http://' . self::HOMEPAGE;
+        } elseif (!extension_loaded('openssl')) {
+            $output->writeln('<error>The openssl extension is required for SSL/TLS protection.</error>');
+            $output->writeln('<error>You can disable this error, at your own risk, by enabling the \'disable-tls\' option.</error>');
+            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('<error>' . $e->getMessage() . '</error>');
+                $output->writeln('<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>');
+                $output->writeln('<error>You can disable this error, at your own risk, by enabling the \'disable-tls\' option.</error>');
+                return 1;
+            } else {
+                throw $e;
+            }
+        }
+
         $cacheDir = $config->get('cache-dir');
         $rollbackDir = $config->get('home');
         $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];

+ 166 - 1
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 <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.
+    */
+    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;
+    }
 }