From ed480ba152d072e609be5d67519b3ba39f2ae3c9 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Thu, 23 Aug 2012 13:50:41 -0500 Subject: [PATCH 1/5] Zend\Http\Client: patch for checking if crypto/ssl is available --- src/Client/Adapter/Socket.php | 57 ++++++++++++++++++++++------------- test/Client/SocketTest.php | 28 ++++++++++++++--- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/Client/Adapter/Socket.php b/src/Client/Adapter/Socket.php index 4fa5f86149..b1ca1a6250 100644 --- a/src/Client/Adapter/Socket.php +++ b/src/Client/Adapter/Socket.php @@ -179,45 +179,45 @@ public function connect($host, $port = 80, $secure = false) // If we are connected to the wrong host, disconnect first if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { - if (is_resource($this->socket)) $this->close(); + if (is_resource($this->socket)) { + $this->close(); + } } // Now, if we are not connected, connect - if (! is_resource($this->socket) || ! $this->config['keepalive']) { + if (!is_resource($this->socket) || ! $this->config['keepalive']) { $context = $this->getStreamContext(); + if ($secure || $this->config['sslusecontext']) { if ($this->config['sslverifypeer'] !== null) { - if (! stream_context_set_option($context, 'ssl', 'verify_peer', - $this->config['sslverifypeer'])) { + if (!stream_context_set_option($context, 'ssl', 'verify_peer', $this->config['sslverifypeer'])) { throw new AdapterException\RuntimeException('Unable to set sslverifypeer option'); } - if (! stream_context_set_option($context, 'ssl', 'capath', - $this->config['sslcapath'])) { + if (!stream_context_set_option($context, 'ssl', 'capath', $this->config['sslcapath'])) { throw new AdapterException\RuntimeException('Unable to set sslcapath option'); } if ($this->config['sslallowselfsigned'] !== null) { - if (! stream_context_set_option($context, 'ssl', 'allow_self_signed', - $this->config['sslallowselfsigned'])) { + if (!stream_context_set_option($context, 'ssl', 'allow_self_signed', $this->config['sslallowselfsigned'])) { throw new AdapterException\RuntimeException('Unable to set sslallowselfsigned option'); } } } if ($this->config['sslcert'] !== null) { - if (! stream_context_set_option($context, 'ssl', 'local_cert', - $this->config['sslcert'])) { + if (!stream_context_set_option($context, 'ssl', 'local_cert', $this->config['sslcert'])) { throw new AdapterException\RuntimeException('Unable to set sslcert option'); } } if ($this->config['sslpassphrase'] !== null) { - if (! stream_context_set_option($context, 'ssl', 'passphrase', - $this->config['sslpassphrase'])) { + if (!stream_context_set_option($context, 'ssl', 'passphrase', $this->config['sslpassphrase'])) { throw new AdapterException\RuntimeException('Unable to set sslpassphrase option'); } } } $flags = STREAM_CLIENT_CONNECT; - if ($this->config['persistent']) $flags |= STREAM_CLIENT_PERSISTENT; + if ($this->config['persistent']) { + $flags |= STREAM_CLIENT_PERSISTENT; + } ErrorHandler::start(); $this->socket = stream_socket_client( @@ -230,21 +230,36 @@ public function connect($host, $port = 80, $secure = false) ); $error = ErrorHandler::stop(); - if (! $this->socket) { + if (!$this->socket) { $this->close(); - throw new AdapterException\RuntimeException(sprintf( - 'Unable to connect to %s:%d%s', - $host, - $port, - ($error ? '. Error #' . $error->getCode() . ': ' . $error->getMessage() : '') - ), 0, $error); + throw new AdapterException\RuntimeException( + sprintf( + 'Unable to connect to %s:%d%s', + $host, + $port, + ($error ? '. Error #' . $error->getCode() . ': ' . $error->getMessage() : '') + ), + 0, + $error + ); } // Set the stream timeout - if (! stream_set_timeout($this->socket, (int) $this->config['timeout'])) { + if (!stream_set_timeout($this->socket, (int) $this->config['timeout'])) { throw new AdapterException\RuntimeException('Unable to set the connection timeout'); } + if ($secure) { + if (!@stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT)) { + $errorString = ''; + while (($sslError = openssl_error_string()) != false) { + $errorString .= "; SSL error: $sslError"; + } + $this->close(); + throw new AdapterException\RuntimeException("Unable to enable crypto on TCP connection {$host}: $errorString"); + } + } + // Update connected_to $this->connected_to = array($host, $port); } diff --git a/test/Client/SocketTest.php b/test/Client/SocketTest.php index c59f3fb2f9..9dd70ae454 100644 --- a/test/Client/SocketTest.php +++ b/test/Client/SocketTest.php @@ -130,7 +130,8 @@ public function testSetConfigInvalidConfig($config) public function testGetNewStreamContext() { - $adapter = new $this->config['adapter']; + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; $context = $adapter->getStreamContext(); $this->assertEquals('stream-context', get_resource_type($context)); @@ -138,7 +139,8 @@ public function testGetNewStreamContext() public function testSetNewStreamContextResource() { - $adapter = new $this->config['adapter']; + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; $context = stream_context_create(); $adapter->setStreamContext($context); @@ -148,7 +150,8 @@ public function testSetNewStreamContextResource() public function testSetNewStreamContextOptions() { - $adapter = new $this->config['adapter']; + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; $options = array( 'socket' => array( 'bindto' => '1.2.3.4:0' @@ -176,7 +179,8 @@ public function testSetInvalidContextOptions($invalid) 'Zend\Http\Client\Adapter\Exception\InvalidArgumentException', 'Expecting either a stream context resource or array'); - $adapter = new $this->config['adapter']; + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; $adapter->setStreamContext($invalid); } @@ -186,7 +190,8 @@ public function testSetHttpsStreamContextParam() $this->markTestSkipped(); } - $adapter = new $this->config['adapter']; + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; $adapter->setStreamContext(array( 'ssl' => array( 'capture_peer_cert' => true, @@ -244,6 +249,19 @@ public function testMultibyteChunkedResponseZF6218() $this->assertEquals($md5, md5($response->getBody())); } + + /** + * @group ZF2-490 + */ + public function testSocketThrowsExceptionWhenSslCaPathNotProvided() + { + $request = new \Zend\Http\Request(); + $request->setUri('https://www.google.com'); + + //$this->setExpectedException('Zend\Http\Client\Adapter\Exception\RuntimeException', 'Invalid sslcapath provided; not a directory'); + $this->client->send($request); + } + /** * Data Providers */ From 4934bc88f3e8bd3b4cb2e9c213b9b6eb2794c757 Mon Sep 17 00:00:00 2001 From: Shahar Evron Date: Thu, 23 Aug 2012 23:23:48 +0300 Subject: [PATCH 2/5] Attept to partially fix ZF2-490 by better error detection - Separated TCP connection from SSL crypto initiation - If error is in SSL stage, see if peer verification is on and no ca path was set, then set message in accordance --- src/Client/Adapter/Socket.php | 54 ++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/Client/Adapter/Socket.php b/src/Client/Adapter/Socket.php index b1ca1a6250..74b0b5b6a4 100644 --- a/src/Client/Adapter/Socket.php +++ b/src/Client/Adapter/Socket.php @@ -27,6 +27,18 @@ */ class Socket implements HttpAdapter, StreamInterface { + /** + * Map SSL transport wrappers to stream crypto method constants + * + * @var array + */ + protected static $sslCryptoTypes = array( + 'ssl' => STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + 'sslv2' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT, + 'sslv3' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + 'tls' => STREAM_CRYPTO_METHOD_TLS_CLIENT + ); + /** * The socket for server connection * @@ -174,9 +186,6 @@ public function getStreamContext() */ public function connect($host, $port = 80, $secure = false) { - // If the URI should be accessed via SSL, prepend the Hostname with ssl:// - $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host; - // If we are connected to the wrong host, disconnect first if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { if (is_resource($this->socket)) { @@ -193,20 +202,26 @@ public function connect($host, $port = 80, $secure = false) if (!stream_context_set_option($context, 'ssl', 'verify_peer', $this->config['sslverifypeer'])) { throw new AdapterException\RuntimeException('Unable to set sslverifypeer option'); } + } + + if ($this->config['sslcapath']) { if (!stream_context_set_option($context, 'ssl', 'capath', $this->config['sslcapath'])) { throw new AdapterException\RuntimeException('Unable to set sslcapath option'); } - if ($this->config['sslallowselfsigned'] !== null) { - if (!stream_context_set_option($context, 'ssl', 'allow_self_signed', $this->config['sslallowselfsigned'])) { - throw new AdapterException\RuntimeException('Unable to set sslallowselfsigned option'); - } + } + + if ($this->config['sslallowselfsigned'] !== null) { + if (!stream_context_set_option($context, 'ssl', 'allow_self_signed', $this->config['sslallowselfsigned'])) { + throw new AdapterException\RuntimeException('Unable to set sslallowselfsigned option'); } } + if ($this->config['sslcert'] !== null) { if (!stream_context_set_option($context, 'ssl', 'local_cert', $this->config['sslcert'])) { throw new AdapterException\RuntimeException('Unable to set sslcert option'); } } + if ($this->config['sslpassphrase'] !== null) { if (!stream_context_set_option($context, 'ssl', 'passphrase', $this->config['sslpassphrase'])) { throw new AdapterException\RuntimeException('Unable to set sslpassphrase option'); @@ -249,14 +264,33 @@ public function connect($host, $port = 80, $secure = false) throw new AdapterException\RuntimeException('Unable to set the connection timeout'); } - if ($secure) { - if (!@stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT)) { + if ($secure || $this->config['sslusecontext']) { + if ($this->config['ssltransport'] && isset(self::$sslCryptoTypes[$this->config['ssltransport']])) { + $sslCryptoMethod = self::$sslCryptoTypes[$this->config['ssltransport']]; + } else { + $sslCryptoMethod = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + } + + if (!@stream_socket_enable_crypto($this->socket, true, $sslCryptoMethod)) { + // Error handling is kind of difficult when it comes to SSL $errorString = ''; while (($sslError = openssl_error_string()) != false) { $errorString .= "; SSL error: $sslError"; } $this->close(); - throw new AdapterException\RuntimeException("Unable to enable crypto on TCP connection {$host}: $errorString"); + + if ((! $errorString) && $this->config['sslverifypeer']) { + // There's good chance our error is due to sslcapath not being properly set + if (! ($this->config['sslcapath'] && is_dir($this->config['sslcapath']))) { + $errorString = 'make sure the "sslcapath" option points to a valid SSL certificate directory'; + } + } + + if ($errorString) { + $errorString = ": $errorString"; + } + + throw new AdapterException\RuntimeException("Unable to enable crypto on TCP connection {$host}{$errorString}"); } } From 918d03d850a25160ac1cac88adee85991c921cae Mon Sep 17 00:00:00 2001 From: Shahar Evron Date: Thu, 23 Aug 2012 23:30:39 +0300 Subject: [PATCH 3/5] fixing connected_to value to include transport wrapper --- src/Client/Adapter/Socket.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Client/Adapter/Socket.php b/src/Client/Adapter/Socket.php index 74b0b5b6a4..c5b95b835d 100644 --- a/src/Client/Adapter/Socket.php +++ b/src/Client/Adapter/Socket.php @@ -292,6 +292,10 @@ public function connect($host, $port = 80, $secure = false) throw new AdapterException\RuntimeException("Unable to enable crypto on TCP connection {$host}{$errorString}"); } + + $host = $this->config['ssltransport'] . "://" . $host; + } else { + $host = 'tcp://' . $host; } // Update connected_to From 34ef3d589a083307b7a8c11961ec41ca82562be8 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Thu, 23 Aug 2012 15:44:38 -0500 Subject: [PATCH 4/5] Zend\Http: removed SocketTest test. --- test/Client/SocketTest.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/Client/SocketTest.php b/test/Client/SocketTest.php index 9dd70ae454..7956b00fcf 100644 --- a/test/Client/SocketTest.php +++ b/test/Client/SocketTest.php @@ -249,19 +249,6 @@ public function testMultibyteChunkedResponseZF6218() $this->assertEquals($md5, md5($response->getBody())); } - - /** - * @group ZF2-490 - */ - public function testSocketThrowsExceptionWhenSslCaPathNotProvided() - { - $request = new \Zend\Http\Request(); - $request->setUri('https://www.google.com'); - - //$this->setExpectedException('Zend\Http\Client\Adapter\Exception\RuntimeException', 'Invalid sslcapath provided; not a directory'); - $this->client->send($request); - } - /** * Data Providers */ From 7befdef9322bb753f9f73824692fe09527c0bf75 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 23 Aug 2012 16:00:06 -0500 Subject: [PATCH 5/5] [zendframework/zf2#2230] Replaced @ operator with ErrorHandler --- src/Client/Adapter/Socket.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Client/Adapter/Socket.php b/src/Client/Adapter/Socket.php index c5b95b835d..20d9d37de6 100644 --- a/src/Client/Adapter/Socket.php +++ b/src/Client/Adapter/Socket.php @@ -271,7 +271,10 @@ public function connect($host, $port = 80, $secure = false) $sslCryptoMethod = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; } - if (!@stream_socket_enable_crypto($this->socket, true, $sslCryptoMethod)) { + ErrorHandler::start(); + $test = stream_socket_enable_crypto($this->socket, true, $sslCryptoMethod); + $error = ErrorHandler::stop(); + if (!$test || $error) { // Error handling is kind of difficult when it comes to SSL $errorString = ''; while (($sslError = openssl_error_string()) != false) { @@ -290,7 +293,11 @@ public function connect($host, $port = 80, $secure = false) $errorString = ": $errorString"; } - throw new AdapterException\RuntimeException("Unable to enable crypto on TCP connection {$host}{$errorString}"); + throw new AdapterException\RuntimeException(sprintf( + 'Unable to enable crypto on TCP connection %s%s', + $host, + $errorString + ), 0, $error); } $host = $this->config['ssltransport'] . "://" . $host;