浏览代码

Add Common Name (CN) matching checks and TLS connection retry (by default).
For example, the communicated host will be github.com, but the CN is *.github.com. Also not matching api.github.com.
The logic detects an initial TLS CN-mismatch error, and parses the correct CN from the error, then checks if the CN and URL have same host before retrying.

Pádraic Brady 11 年之前
父节点
当前提交
c9c6849df0
共有 1 个文件被更改,包括 42 次插入16 次删除
  1. 42 16
      src/Composer/Util/RemoteFilesystem.php

+ 42 - 16
src/Composer/Util/RemoteFilesystem.php

@@ -34,6 +34,7 @@ class RemoteFilesystem
     private $lastProgress;
     private $lastProgress;
     private $options;
     private $options;
     private $disableTls = false;
     private $disableTls = false;
+    private $retryTls = true;
 
 
     /**
     /**
      * Constructor.
      * Constructor.
@@ -119,7 +120,7 @@ class RemoteFilesystem
      *
      *
      * @return bool|string
      * @return bool|string
      */
      */
-    protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
+    protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true, $expectedCommonName = '')
     {
     {
         $this->bytesMax = 0;
         $this->bytesMax = 0;
         $this->originUrl = $originUrl;
         $this->originUrl = $originUrl;
@@ -133,7 +134,7 @@ class RemoteFilesystem
             $this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2]));
             $this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2]));
         }
         }
 
 
-        $options = $this->getOptionsForUrl($originUrl, $additionalOptions);
+        $options = $this->getOptionsForUrl($originUrl, $additionalOptions, $expectedCommonName);
 
 
         if ($this->io->isDebug()) {
         if ($this->io->isDebug()) {
             $this->io->write((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl);
             $this->io->write((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl);
@@ -224,14 +225,25 @@ class RemoteFilesystem
             }
             }
         }
         }
 
 
+        // Check if the failure was due to a Common Name mismatch with remote SSL cert and retry once (excl normal retry)
+        if (false === $result) {
+            if ($this->retryTls === true
+            && preg_match("|did not match expected CN|i", $errorMessage)
+            && preg_match("|Peer certificate CN=`(.*)' did not match|i", $errorMessage, $matches)) {
+                $this->retryTls = false;
+                $expectedCommonName = $matches[1];
+                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress, $expectedCommonName);
+            }
+        }
+
         if ($this->retry) {
         if ($this->retry) {
             $this->retry = false;
             $this->retry = false;
 
 
-            return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
+            return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress, $expectedCommonName);
         }
         }
 
 
         if (false === $result) {
         if (false === $result) {
-            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode);
+            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage.' using CN='.$expectedCommonName, $errorCode);
             if (!empty($http_response_header[0])) {
             if (!empty($http_response_header[0])) {
                 $e->setHeaders($http_response_header);
                 $e->setHeaders($http_response_header);
             }
             }
@@ -325,8 +337,33 @@ class RemoteFilesystem
         throw new TransportException('RETRY');
         throw new TransportException('RETRY');
     }
     }
 
 
-    protected function getOptionsForUrl($originUrl, $additionalOptions, $disableTls = false)
+    protected function getOptionsForUrl($originUrl, $additionalOptions, $validCommonName = '')
     {
     {
+
+        // Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
+        if ($this->disableTls === false) {
+            if (!preg_match("|^https?://|", $originUrl)) {
+                $host = $originUrl;
+            } else {
+                $host = parse_url($originUrl, PHP_URL_HOST);
+            }
+            /**
+             * This is sheer painful, but hopefully it'll be a footnote once SAN support
+             * reaches PHP 5.4 and 5.5...
+             * Side-effect: We're betting on the CN being either a wildcard or www, e.g. *.github.com or www.example.com.
+             * TODO: Consider something more explicitly user based.
+             */
+            if (strlen($validCommonName) > 0) {
+                if (!preg_match("|".$host."$|i", $validCommonName)
+                || (count(explode('.', $validCommonName)) - count(explode('.', $host))) > 1) {
+                    throw new TransportException('Unable to read or match the Common Name (CN) from the remote SSL certificate.');
+                }
+                $host = $validCommonName;
+            }
+            $this->options['ssl']['CN_match'] = $host;
+            $this->options['ssl']['SNI_server_name'] = $host;
+        }
+
         $headers = array(
         $headers = array(
             sprintf(
             sprintf(
                 'User-Agent: Composer/%s (%s; %s; PHP %s.%s.%s)',
                 'User-Agent: Composer/%s (%s; %s; PHP %s.%s.%s)',
@@ -343,17 +380,6 @@ class RemoteFilesystem
             $headers[] = 'Accept-Encoding: gzip';
             $headers[] = 'Accept-Encoding: gzip';
         }
         }
 
 
-        // Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
-        if ($this->disableTls === false) {
-            if (!preg_match("|^https?://|", $originUrl)) {
-                $host = $originUrl;
-            } else {
-                $host = parse_url($originUrl, PHP_URL_HOST);
-            }
-            $this->options['ssl']['CN_match'] = $host;
-            $this->options['ssl']['SNI_server_name'] = $host;
-        }
-
         $options = array_replace_recursive($this->options, $additionalOptions);
         $options = array_replace_recursive($this->options, $additionalOptions);
 
 
         if ($this->io->hasAuthentication($originUrl)) {
         if ($this->io->hasAuthentication($originUrl)) {