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:3
Avg. Score:3.7 ± 0.5
Reproduced:2 of 2 (100.0%)
Same Version:0 (0.0%)
Same OS: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
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
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

Pull Requests

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
 [2022-12-31 05:09 UTC] marlynrasavong at gmail dot com
Hey it is not possible to get certificate without TLS. 
(https://www.benefitscal.biz/)github.com
 
PHP Copyright © 2001-2025 The PHP Group
All rights reserved.
Last updated: Tue Mar 11 19:01:31 2025 UTC