Code clarity and updates from the getcomposer.org installer
parent
cb53bd04cb
commit
fc4d94f160
|
@ -133,7 +133,8 @@ EOT
|
||||||
throw new \RuntimeException('--file and --global can not be combined');
|
throw new \RuntimeException('--file and --global can not be combined');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->config = Factory::createConfig($this->getIO());
|
$io = $this->getIO();
|
||||||
|
$this->config = Factory::createConfig($io);
|
||||||
|
|
||||||
// Get the local composer.json, global config.json, or if the user
|
// Get the local composer.json, global config.json, or if the user
|
||||||
// passed in a file to use
|
// passed in a file to use
|
||||||
|
@ -146,14 +147,14 @@ EOT
|
||||||
file_put_contents($configFile, "{\n}\n");
|
file_put_contents($configFile, "{\n}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->configFile = new JsonFile($configFile);
|
$this->configFile = new JsonFile($configFile, null, $io);
|
||||||
$this->configSource = new JsonConfigSource($this->configFile);
|
$this->configSource = new JsonConfigSource($this->configFile);
|
||||||
|
|
||||||
$authConfigFile = $input->getOption('global')
|
$authConfigFile = $input->getOption('global')
|
||||||
? ($this->config->get('home') . '/auth.json')
|
? ($this->config->get('home') . '/auth.json')
|
||||||
: dirname(realpath($configFile)) . '/auth.json';
|
: dirname(realpath($configFile)) . '/auth.json';
|
||||||
|
|
||||||
$this->authConfigFile = new JsonFile($authConfigFile);
|
$this->authConfigFile = new JsonFile($authConfigFile, null, $io);
|
||||||
$this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);
|
$this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);
|
||||||
|
|
||||||
// initialize the global file if it's not there
|
// initialize the global file if it's not there
|
||||||
|
|
|
@ -47,20 +47,22 @@ class RemoteFilesystem
|
||||||
* @param IOInterface $io The IO instance
|
* @param IOInterface $io The IO instance
|
||||||
* @param Config $config The config
|
* @param Config $config The config
|
||||||
* @param array $options The options
|
* @param array $options The options
|
||||||
|
* @param bool $disableTls
|
||||||
*/
|
*/
|
||||||
public function __construct(IOInterface $io, Config $config = null, $options = array(), $disableTls = false)
|
public function __construct(IOInterface $io, Config $config = null, array $options = array(), $disableTls = false)
|
||||||
{
|
{
|
||||||
$this->io = $io;
|
$this->io = $io;
|
||||||
|
|
||||||
/**
|
// Setup TLS options
|
||||||
* Setup TLS options
|
// The cafile option can be set via config.json
|
||||||
* The cafile option can be set via config.json
|
|
||||||
*/
|
|
||||||
if ($disableTls === false) {
|
if ($disableTls === false) {
|
||||||
$this->options = $this->getTlsDefaults();
|
$this->options = $this->getTlsDefaults();
|
||||||
if (isset($options['ssl']['cafile'])
|
if (isset($options['ssl']['cafile'])
|
||||||
&& (!is_readable($options['ssl']['cafile'])
|
&& (
|
||||||
|| !\openssl_x509_parse(file_get_contents($options['ssl']['cafile'])))) {
|
!is_readable($options['ssl']['cafile'])
|
||||||
|
|| !self::validateCaFile(file_get_contents($options['ssl']['cafile']))
|
||||||
|
)
|
||||||
|
) {
|
||||||
throw new TransportException('The configured cafile was not valid or could not be read.');
|
throw new TransportException('The configured cafile was not valid or could not be read.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -316,16 +318,18 @@ class RemoteFilesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the failure was due to a Common Name mismatch with remote SSL cert and retry once (excl normal retry)
|
// 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 (
|
||||||
if ($this->retryTls === true
|
false === $result
|
||||||
&& preg_match("|did not match expected CN|i", $errorMessage)
|
&& $this->retryTls === true
|
||||||
&& preg_match("|Peer certificate CN=`(.*)' did not match|i", $errorMessage, $matches)) {
|
&& preg_match('{did not match expected CN}i', $errorMessage)
|
||||||
|
&& preg_match("{Peer certificate CN=`(.*)' did not match}i", $errorMessage, $matches)
|
||||||
|
) {
|
||||||
$this->retryTls = false;
|
$this->retryTls = false;
|
||||||
$expectedCommonName = $matches[1];
|
$expectedCommonName = $matches[1];
|
||||||
$this->io->write(" <warning>Retrying download from ".$originUrl." with SSL Cert Common Name (CN): ".$expectedCommonName."</warning>");
|
$this->io->write(" <warning>Retrying download from ".$originUrl." with SSL Cert Common Name (CN): ".$expectedCommonName."</warning>");
|
||||||
|
|
||||||
return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress, $expectedCommonName);
|
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;
|
||||||
|
@ -485,33 +489,30 @@ class RemoteFilesystem
|
||||||
|
|
||||||
protected function getOptionsForUrl($originUrl, $additionalOptions, $validCommonName = '')
|
protected function getOptionsForUrl($originUrl, $additionalOptions, $validCommonName = '')
|
||||||
{
|
{
|
||||||
|
$tlsOptions = array();
|
||||||
|
|
||||||
// Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
|
// Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
|
||||||
if ($this->disableTls === false) {
|
if ($this->disableTls === false) {
|
||||||
if (!preg_match("|^https?://|", $this->fileUrl)) {
|
if (!preg_match('{^https?://}', $this->fileUrl)) {
|
||||||
$host = $originUrl;
|
$host = $originUrl;
|
||||||
} else {
|
} else {
|
||||||
$host = parse_url($this->fileUrl, PHP_URL_HOST);
|
$host = parse_url($this->fileUrl, PHP_URL_HOST);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* This is sheer painful, but hopefully it'll be a footnote once SAN support
|
// This is sheer painful, but hopefully it'll be a footnote once SAN support
|
||||||
* reaches PHP 5.4 and 5.5...
|
// 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.
|
// 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 (strlen($validCommonName) > 0) {
|
||||||
if (!preg_match("|".$host."$|i", $validCommonName)
|
if (
|
||||||
|| (count(explode('.', $validCommonName)) - count(explode('.', $host))) > 1) {
|
!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.');
|
throw new TransportException('Unable to read or match the Common Name (CN) from the remote SSL certificate.');
|
||||||
}
|
}
|
||||||
$host = $validCommonName;
|
$host = $validCommonName;
|
||||||
}
|
}
|
||||||
$this->options['ssl']['CN_match'] = $host;
|
$tlsOptions['ssl']['CN_match'] = $host;
|
||||||
$this->options['ssl']['SNI_server_name'] = $host;
|
$tlsOptions['ssl']['SNI_server_name'] = $host;
|
||||||
}
|
|
||||||
if (defined('HHVM_VERSION')) {
|
|
||||||
$phpVersion = 'HHVM ' . HHVM_VERSION;
|
|
||||||
} else {
|
|
||||||
$phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = array();
|
$headers = array();
|
||||||
|
@ -520,7 +521,7 @@ class RemoteFilesystem
|
||||||
$headers[] = 'Accept-Encoding: gzip';
|
$headers[] = 'Accept-Encoding: gzip';
|
||||||
}
|
}
|
||||||
|
|
||||||
$options = array_replace_recursive($this->options, $additionalOptions);
|
$options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions);
|
||||||
if (!$this->degradedMode) {
|
if (!$this->degradedMode) {
|
||||||
// degraded mode disables HTTP/1.1 which causes issues with some bad
|
// degraded mode disables HTTP/1.1 which causes issues with some bad
|
||||||
// proxies/software due to the use of chunked encoding
|
// proxies/software due to the use of chunked encoding
|
||||||
|
@ -613,20 +614,28 @@ class RemoteFilesystem
|
||||||
* The user may go download one if this occurs.
|
* The user may go download one if this occurs.
|
||||||
*/
|
*/
|
||||||
if (!isset($this->options['ssl']['cafile'])) {
|
if (!isset($this->options['ssl']['cafile'])) {
|
||||||
$result = $this->getSystemCaRootBundlePath();
|
$result = self::getSystemCaRootBundlePath();
|
||||||
if ($result) {
|
if ($result) {
|
||||||
if (preg_match("|^phar://|", $result)) {
|
if (preg_match('{^phar://}', $result)) {
|
||||||
$tmp = rtrim(sys_get_temp_dir(), '\\/');
|
$targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert.pem';
|
||||||
$target = $tmp . DIRECTORY_SEPARATOR . 'composer-cacert.pem';
|
|
||||||
$cacert = file_get_contents($result);
|
// use stream_copy_to_stream instead of copy
|
||||||
$write = file_put_contents($target, $cacert, LOCK_EX);
|
// to work around https://bugs.php.net/bug.php?id=64634
|
||||||
if (!$write) {
|
$source = fopen($result, 'r');
|
||||||
throw new TransportException('Unable to write bundled cacert.pem to: '.$target);
|
$target = fopen($targetPath, 'w+');
|
||||||
}
|
stream_copy_to_stream($source, $target);
|
||||||
$options['ssl']['cafile'] = $target;
|
fclose($source);
|
||||||
|
fclose($target);
|
||||||
|
unset($source, $target);
|
||||||
|
|
||||||
|
$options['ssl']['cafile'] = $targetPath;
|
||||||
} else {
|
} else {
|
||||||
|
if (is_dir($result)) {
|
||||||
|
$options['ssl']['capath'] = $result;
|
||||||
|
} elseif ($result) {
|
||||||
$options['ssl']['cafile'] = $result;
|
$options['ssl']['cafile'] = $result;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new TransportException('A valid cafile could not be located automatically.');
|
throw new TransportException('A valid cafile could not be located automatically.');
|
||||||
}
|
}
|
||||||
|
@ -674,15 +683,17 @@ class RemoteFilesystem
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
protected static function getSystemCaRootBundlePath()
|
private static function getSystemCaRootBundlePath()
|
||||||
{
|
{
|
||||||
if (isset($found)) {
|
static $found = null;
|
||||||
|
if ($found !== null) {
|
||||||
return $found;
|
return $found;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
|
// 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.
|
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
|
||||||
$envCertFile = getenv('SSL_CERT_FILE');
|
$envCertFile = getenv('SSL_CERT_FILE');
|
||||||
if ($envCertFile && is_readable($envCertFile) && \openssl_x509_parse(file_get_contents($envCertFile))) {
|
if ($envCertFile && is_readable($envCertFile) && self::validateCaFile(file_get_contents($envCertFile))) {
|
||||||
// Possibly throw exception instead of ignoring SSL_CERT_FILE if it's invalid?
|
// Possibly throw exception instead of ignoring SSL_CERT_FILE if it's invalid?
|
||||||
return $envCertFile;
|
return $envCertFile;
|
||||||
}
|
}
|
||||||
|
@ -696,25 +707,50 @@ class RemoteFilesystem
|
||||||
'/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
|
'/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/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?
|
'/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
|
||||||
|
'/etc/ssl/cert.pem', // OpenBSD
|
||||||
|
'/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
|
||||||
__DIR__.'/../../../res/cacert.pem', // Bundled with Composer
|
__DIR__.'/../../../res/cacert.pem', // Bundled with Composer
|
||||||
);
|
);
|
||||||
|
|
||||||
static $found = false;
|
|
||||||
$configured = ini_get('openssl.cafile');
|
$configured = ini_get('openssl.cafile');
|
||||||
if ($configured && strlen($configured) > 0 && is_readable($caBundle) && \openssl_x509_parse(file_get_contents($caBundle))) {
|
if ($configured && strlen($configured) > 0 && is_readable($caBundle) && self::validateCaFile(file_get_contents($caBundle))) {
|
||||||
$found = true;
|
$found = true;
|
||||||
$caBundle = $configured;
|
$caBundle = $configured;
|
||||||
} else {
|
} else {
|
||||||
foreach ($caBundlePaths as $caBundle) {
|
foreach ($caBundlePaths as $caBundle) {
|
||||||
if (is_readable($caBundle) && \openssl_x509_parse(file_get_contents($caBundle))) {
|
if (@is_readable($caBundle) && self::validateCaFile(file_get_contents($caBundle))) {
|
||||||
$found = true;
|
$found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!$found) {
|
||||||
|
foreach ($caBundlePaths as $caBundle) {
|
||||||
|
$caBundle = dirname($caBundle);
|
||||||
|
if (is_dir($caBundle) && glob($caBundle.'/*')) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($found) {
|
if ($found) {
|
||||||
$found = $caBundle;
|
$found = $caBundle;
|
||||||
}
|
}
|
||||||
return $found;
|
return $found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function validateCaFile($contents)
|
||||||
|
{
|
||||||
|
// assume the CA is valid if php is vulnerable to
|
||||||
|
// https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
|
||||||
|
if (
|
||||||
|
PHP_VERSION_ID <= 50327
|
||||||
|
|| (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422)
|
||||||
|
|| (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506)
|
||||||
|
) {
|
||||||
|
return !empty($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) openssl_x509_parse($contents);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue