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.pull/2745/head
parent
30c6aa3183
commit
c9c6849df0
|
@ -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)) {
|
||||||
|
|
Loading…
Reference in New Issue