Issues

ZF-5524: Zend_Http_Client_Adapter_Proxy doesn't support SSL Client Certificate

Description

Although Zend_Http_Client_Adapter_Socket fully support SSL Client certificate authentication, Zend_Http_Client_Adapter_Proxy doesn't.

in the write() method of Zend_Http_Client_Adapter_Proxy, there is a call to method connectHandshake() if protocol is HTTPS.

This method tries to send a CONNECT query to the proxy server and then, if ok, switch to crypto with PHP function stream_socket_enable_crypto().

This fails if the remote server requires a SSL client certificate to be presented.

The stream_socket_enable_crypto() function is not well documented but it seems that an additional parameter allows to pass the context of another socket (including a local cert) to the function.

In the connecHandshake() method, I changed the code here:

    $success = false; 
    foreach($modes as $mode) {
        $success = stream_socket_enable_crypto($this->socket, true, $mode);
        if ($success) break;
    }

to:

    $context = stream_context_create();
    if (! stream_context_set_option($context, 'ssl', 'local_cert', 'path/to/my/local_cert')) {
        die ('unable to set SSL cert');
    }

    $socket = stream_socket_client($host . ':' . $port, $errno,$errstr, (int) $this->config['timeout'], STREAM_CLIENT_CONNECT, $context);

    if ($socket === false) {
        die('Unable to set socket');
    }

    $success = false; 
    foreach($modes as $mode) {
        $success = stream_socket_enable_crypto($this->socket, true, $mode, $socket);
        if ($success) break;
    }

Currently, the apache child dies when processing this patched adapter (Apache 2.2.8 + PHP 5.2.8 on Ubuntu 8.04)

Is it a valid method to achieve what I aim to? If positive, what is going wrong?

Comments

Sorry for taking so long with this :)

Recent improvements to HTTP client allowed me to implement this by reducing most of the code in the connect() method, and simply relying on preset stream context for this task.

This should now not be any different than using the Socket adapter with an SSL certificate - you can also look at the setStreamContext and getStreamContext methods for this, if you need "advanced" stuff like peer certificate validation forcing.

Fixed in CS-17013

This change breaks backwards compatibility.

http://framework.zend.com/code/browse/…

146 - if ($this->connected_to[0] != $host || $this->connected_to[1] != $port) { 126 + if ($this->connected_to[0] != "tcp://$host" || $this->connected_to[1] != $port) {

At least in our environment there is no tcp:// prefix there as it is not required as far as I understand.

Changing the line to:

if (($this->connected_to[0] != "tcp://$host" && $this->connected_to[0] != $host) || $this->connected_to[1] != $port) {

Seems to fix this in our environment.

Reopening following previous comment

To actually fix the functionality:

http://pastebin.com/m604f9746

I commented that this bug breaks backwards compatibility and it's still present in 1.9.5

Mikko,

Can you repost the fix you added on pastebin? The paste ID is no longer valid. I'll commit tomorrow once I have a fix I can run tests against.

The following code can be used to test the issue:


$client = new Zend_Http_Client('https://test.example.com', array('sslcert' => '/etc/pki/test.pem',
                                                                 'proxy_host' => '127.0.0.1',
                                         'proxy_port' => '8888',
                                     'adapter' => 'Zend_Http_Client_Adapter_Proxy'));

var_dump($client->request());

The whole issue explained briefly:

Zend_Http_Client_Adapter_Proxy calls the parent::connect method setting $secure = false. This means that ssl context is not created for the connection. This is problematic in a scenario where the connection to proxy is over plain but SSL stream using client certs is negotiated inside that connection.

The proposed fix:


### Eclipse Workspace Patch 1.0
#P zf-trunk
Index: src/Zend/Http/Client/Adapter/Socket.php
===================================================================
--- src/Zend/Http/Client/Adapter/Socket.php (revision 18996)
+++ src/Zend/Http/Client/Adapter/Socket.php (working copy)
@@ -62,10 +62,11 @@
      * @var array
      */
     protected $config = array(
-        'persistent'    => false,
-        'ssltransport'  => 'ssl',
-        'sslcert'       => null,
-        'sslpassphrase' => null
+        'persistent'      => false,
+        'ssltransport'    => 'ssl',
+        'sslcert'         => null,
+        'sslpassphrase'   => null,
+        'sslusecontext' => false
     );
 
     /**
@@ -176,11 +177,11 @@
         if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) {
             if (is_resource($this->socket)) $this->close();
         }
-
+ 
         // Now, if we are not connected, connect
         if (! is_resource($this->socket) || ! $this->config['keepalive']) {
             $context = $this->getStreamContext();
-            if ($secure) {
+            if ($secure || $this->config['sslusecontext']) {
                 if ($this->config['sslcert'] !== null) {
                     if (! stream_context_set_option($context, 'ssl', 'local_cert',
                                                     $this->config['sslcert'])) {
Index: src/Zend/Http/Client/Adapter/Proxy.php
===================================================================
--- src/Zend/Http/Client/Adapter/Proxy.php  (revision 18996)
+++ src/Zend/Http/Client/Adapter/Proxy.php  (working copy)
@@ -91,6 +91,11 @@
         if (! $this->config['proxy_host']) {
             return parent::connect($host, $port, $secure);
         }
+        
+        /* Url might require stream context even if proxy connection doesn't */
+        if ($secure) {
+           $this->config['sslusecontext'] = true;
+        }
 
         // Connect (a non-secure connection) to the proxy server
         return parent::connect(

The issue can be tested using CA setup mentioned here (http://sial.org/howto/openssl/ca/) and setting up for example apache to require the client certificate to be present. Note that this seems to only affect proxy adapter.

Fixed in r20946

This issue is not completely fixed in 1.10.5: the

'sslusecontext' => false

entry is missing from the

protected $config = array()

causing a NOTICE (Undefined index sslusecontext) on line 92:

if ($secure || $this->config['sslusecontext']) {

next release should address this issue. There was one line missing in the merge

Reopen due to misapplied patch

Missing lines previously committed to release branch by bate.

Note: If an issue is technically not fixed, it is fine to reopen it until properly resolved. Thanks for reporting the missing line!