php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #80770 It is not possible to get client peer certificate with stream_socket_server
Submitted: 2021-02-18 16:11 UTC Modified: 2021-02-18 17:13 UTC
Votes:1
Avg. Score:3.0 ± 0.0
Reproduced:0 of 0 (0.0%)
From: mcmic@php.net Assigned:
Status: Verified Package: OpenSSL related
PHP Version: 7.3.27 OS:
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: mcmic@php.net
New email:
PHP Version: OS:

 

 [2021-02-18 16:11 UTC] mcmic@php.net
Description:
------------
It is not possible to get the client certificate when accepting TLS connections, without forcing the client to provide a (valid) certificate.

This forbids writing a fully compliant Gemini server in PHP, because it is not possible to accept client certificates without forcing them on all pages.

See https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html

Test script:
---------------
$context = stream_context_create(
    [
        'ssl' => [
            'allow_self_signed' => true,
            'SNI_enabled'       => true,
            'SNI_server_certs'  => ['example.com' => '/path/to/cert.pem'],
            'capture_peer_cert' => true,
        ]
    ]
);
$socket = stream_socket_server(
    'tcp://[::]:' . $port,
    $errno,
    $errstr,
    STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
    $context,
);

if ($socket === false) {
    throw new \Exception($errstr, $errno);
} else {
    while ($conn = stream_socket_accept($socket, -1, $peername)) {
        $tlsSuccess = stream_socket_enable_crypto(
            $conn,
            true,
            STREAM_CRYPTO_METHOD_TLS_SERVER
        );
        if ($tlsSuccess !== true) {
            fclose($conn);
            continue;
        }
        var_dump(stream_context_get_options($conn));
    }
}

Expected result:
----------------
Have the client certificate in 'peer_certificate' key of $conn, if the client sends one.

Actual result:
--------------
No 'peer_certificate' option.

If I set 'verify_peer' to true, it works, but then it is not possible for a client to connect without a certificate.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-02-18 16:26 UTC] daverandom@php.net
Note that there is a test for this behaviour which is not marked as XFAIL, so I believe this functionality should work - although I cannot immediately see an issue with your sample code

https://heap.space/xref/php-src/ext/openssl/tests/capture_peer_cert_001.phpt?r=1fab01be#38
 [2021-02-18 16:26 UTC] daverandom@php.net
Ah no I think I see the problem, you need to pass $context instead of $conn to stream_context_get_options()
 [2021-02-18 16:32 UTC] mcmic@php.net
@daverandom:
The test you linked to is about getting the server certificate from the client, I want the other way around, get the client certificate from the server.
And without making the client certificate mandatory, I still need to accept certificate-less requests.
 [2021-02-18 16:34 UTC] mcmic@php.net
See section 4.3 of https://gemini.circumlunar.space/docs/specification.html for more information about client certificate use in Gemini protocol.
 [2021-02-18 17:13 UTC] daverandom@php.net
My apologies, you are correct and I have actually now remembered encountering the same limitation a few months ago. I do also have a (theoretical) work-around, which is to obtain the ClientHello message (and potentially other traffic) via stream_socket_recvfrom() and STREAM_PEEK before calling stream_socket_enable_crypto().

I think I even wrote a PoC partial implementation of it, I cannot find it at the moment but if I do I will make a gist and link it here.

As for a proper fix for the issue, I have touched this code in the (distant) past and I from what I remember it should be reasonably easy to optionally store a client certificate into the stream's context during the peer verification callback, though I think this would not work if peer verification was completely disabled. I'm not sure if this would be considered an acceptable limitation.
 [2021-02-18 17:13 UTC] daverandom@php.net
-Status: Open +Status: Verified
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Fri Jul 30 11:01:23 2021 UTC