فهرست منبع

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 $options;
     private $disableTls = false;
+    private $retryTls = true;
 
     /**
      * Constructor.
@@ -119,7 +120,7 @@ class RemoteFilesystem
      *
      * @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->originUrl = $originUrl;
@@ -133,7 +134,7 @@ class RemoteFilesystem
             $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()) {
             $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) {
             $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) {
-            $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])) {
                 $e->setHeaders($http_response_header);
             }
@@ -325,8 +337,33 @@ class RemoteFilesystem
         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(
             sprintf(
                 'User-Agent: Composer/%s (%s; %s; PHP %s.%s.%s)',
@@ -343,17 +380,6 @@ class RemoteFilesystem
             $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);
 
         if ($this->io->hasAuthentication($originUrl)) {