Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ of the actual application level protocol, such as HTTP, SMTP, IMAP, Telnet etc.
* [Authentication](#authentication)
* [Proxy chaining](#proxy-chaining)
* [Connection timeout](#connection-timeout)
* [SOCKS over TLS](#socks-over-tls)
* [Unix domain sockets](#unix-domain-sockets)
* [Server](#server)
* [Server connector](#server-connector)
* [Protocol version](#server-protocol-version)
* [Authentication](#server-authentication)
* [Proxy chaining](#server-proxy-chaining)
* [SOCKS over TLS](#server-socks-over-tls)
* [Unix domain sockets](#server-unix-domain-sockets)
* [Servers](#servers)
* [Using a PHP SOCKS server](#using-a-php-socks-server)
Expand Down Expand Up @@ -555,6 +557,60 @@ as usual.
> Also note how connection timeout is in fact entirely handled outside of this
SOCKS client implementation.

#### SOCKS over TLS

All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
based connections and higher level protocols.
This implies that you can also use [secure TLS connections](#secure-tls-connections)
to transfer sensitive data across SOCKS proxy servers.
This means that no eavesdropper nor the proxy server will be able to decrypt
your data.

However, the initial SOCKS communication between the client and the proxy is
usually via an unencrypted, plain TCP/IP connection.
This means that an eavesdropper may be able to see *where* you connect to and
may also be able to see your [SOCKS authentication](#authentication) details
in cleartext.

As an alternative, you may establish a secure TLS connection to your SOCKS
proxy before starting the initial SOCKS communication.
This means that no eavesdroppper will be able to see the destination address
you want to connect to or your [SOCKS authentication](#authentication) details.

You can use the `sockss://` URI scheme or use an explicit
[SOCKS protocol version](#protocol-version) like this:

```php
$client = new Client('sockss://127.0.0.1:1080', new Connector($loop));

$client = new Client('socks5s://127.0.0.1:1080', new Connector($loop));
```

See also [example 32](examples).

Simiarly, you can also combine this with [authentication](#authentication)
like this:

```php
$client = new Client('sockss://user:[email protected]:1080', new Connector($loop));
```

> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
should be used instead. SOCKS over TLS is considered advanced usage and is
used very rarely in practice.
In particular, the SOCKS server has to accept secure TLS connections, see
also [Server SOCKS over TLS](#server-socks-over-tls) for more details.
Also, PHP does not support "double encryption" over a single connection.
This means that enabling [secure TLS connections](#secure-tls-connections)
over a communication channel that has been opened with SOCKS over TLS
may not be supported.

> Note that the SOCKS protocol does not support the notion of TLS. The above
works reasonably well because TLS is only used for the connection between
client and proxy server and the SOCKS protocol data is otherwise identical.
This implies that this may also have only limited support for
[proxy chaining](#proxy-chaining) over multiple TLS paths.

#### Unix domain sockets

All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
Expand Down Expand Up @@ -756,6 +812,54 @@ Proxy chaining can happen on the server side and/or the client side:
not really know anything about chaining at all.
This means that this is a server-only property and can be implemented as above.

#### Server SOCKS over TLS

All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
based connections and higher level protocols.
This implies that you can also use [secure TLS connections](#secure-tls-connections)
to transfer sensitive data across SOCKS proxy servers.
This means that no eavesdropper nor the proxy server will be able to decrypt
your data.

However, the initial SOCKS communication between the client and the proxy is
usually via an unencrypted, plain TCP/IP connection.
This means that an eavesdropper may be able to see *where* the client connects
to and may also be able to see the [SOCKS authentication](#authentication)
details in cleartext.

As an alternative, you may listen for SOCKS over TLS connections so
that the client has to establish a secure TLS connection to your SOCKS
proxy before starting the initial SOCKS communication.
This means that no eavesdroppper will be able to see the destination address
the client wants to connect to or their [SOCKS authentication](#authentication)
details.

You can simply start your listening socket on the `tls://` URI scheme like this:

```php
$loop = \React\EventLoop\Factory::create();

// listen on tls://127.0.0.1:1080 with the given server certificate
$socket = new React\Socket\Server('tls://127.0.0.1:1080', $loop, array(
'tls' => array(
'local_cert' => __DIR__ . '/localhost.pem',
)
));
$server = new Server($loop, $socket);
```

See also [example 31](examples).

> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
should be used instead. SOCKS over TLS is considered advanced usage and is
used very rarely in practice.

> Note that the SOCKS protocol does not support the notion of TLS. The above
works reasonably well because TLS is only used for the connection between
client and proxy server and the SOCKS protocol data is otherwise identical.
This implies that this does also not support [proxy chaining](#server-proxy-chaining)
over multiple TLS paths.

#### Server Unix domain sockets

All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
Expand Down
21 changes: 21 additions & 0 deletions examples/31-server-secure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

use Clue\React\Socks\Server;
use React\Socket\Server as Socket;

require __DIR__ . '/../vendor/autoload.php';

$loop = React\EventLoop\Factory::create();

// listen on tls://127.0.0.1:1080 or first argument
$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
$socket = new Socket('tls://' . $listen, $loop, array('tls' => array(
'local_cert' => __DIR__ . '/localhost.pem',
)));

// start a new server listening for incoming connection on the given socket
$server = new Server($loop, $socket);

echo 'SOCKS over TLS server listening on ' . str_replace('tls:', 'sockss:', $socket->getAddress()) . PHP_EOL;

$loop->run();
33 changes: 33 additions & 0 deletions examples/32-http-secure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use Clue\React\Socks\Client;
use React\Socket\Connector;
use React\Socket\ConnectionInterface;

require __DIR__ . '/../vendor/autoload.php';

$proxy = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';

$loop = React\EventLoop\Factory::create();

$client = new Client('sockss://' . $proxy, new Connector($loop, array('tls' => array(
'verify_peer' => false,
'verify_peer_name' => false
))));
$connector = new Connector($loop, array(
'tcp' => $client,
'timeout' => 3.0,
'dns' => false
));

echo 'Demo SOCKS over TLS client connecting to secure SOCKS server ' . $proxy . PHP_EOL;

$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
echo 'connected' . PHP_EOL;
$stream->write("GET / HTTP/1.0\r\n\r\n");
$stream->on('data', function ($data) {
echo $data;
});
}, 'printf');

$loop->run();
49 changes: 49 additions & 0 deletions examples/localhost.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
+EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
81oh1uCH1YPLM29hPyaohxL8
-----END PRIVATE KEY-----
12 changes: 8 additions & 4 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ class Client implements ConnectorInterface

public function __construct($socksUri, ConnectorInterface $connector)
{
// support `sockss://` scheme for SOCKS over TLS
// support `socks+unix://` scheme for Unix domain socket (UDS) paths
if (preg_match('/^(socks(?:5|4|4a)?)\+?unix:\/\/(.*?@)?(.+?$)/', $socksUri, $match)) {
$socksUri = ($match[1] !== '' ? ($match[1] . '://') : '') . $match[2] . 'localhost';
if (preg_match('/^(socks(?:5|4|4a)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
// rewrite URI to parse SOCKS scheme, authentication and dummy host
$socksUri = $match[1] . '://' . $match[3] . 'localhost';

// connector uses appropriate transport scheme and explicit host given
$connector = new FixedUriConnector(
'unix://' . $match[3],
($match[2] === 's' ? 'tls://' : 'unix://') . $match[4],
$connector
);
}
Expand Down Expand Up @@ -82,7 +86,7 @@ private function setProtocolVersionFromScheme($scheme)
} elseif ($scheme === 'socks4') {
$this->protocolVersion = '4';
} else {
throw new InvalidArgumentException('Invalid protocol version given');
throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
}
}

Expand Down
14 changes: 14 additions & 0 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ public function testCtorAcceptsUriWithHostOnlyAssumesDefaultPort()
$this->assertTrue(true);
}

public function testCtorAcceptsUriWithSecureScheme()
{
$client = new Client('sockss://127.0.0.1:9050', $this->connector);

$this->assertTrue(true);
}

public function testCtorAcceptsUriWithSecureVersionScheme()
{
$client = new Client('socks5s://127.0.0.1:9050', $this->connector);

$this->assertTrue(true);
}

public function testCtorAcceptsUriWithSocksUnixScheme()
{
$client = new Client('socks+unix:///tmp/socks.socket', $this->connector);
Expand Down
45 changes: 45 additions & 0 deletions tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,51 @@ public function testConnectionSocks5()
$this->assertResolveStream($this->client->connect('www.google.com:80'));
}

/** @group internet */
public function testConnectionSocksOverTls()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
}

$socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
'local_cert' => __DIR__ . '/../examples/localhost.pem',
)));
$this->server = new Server($this->loop, $socket);

$this->connector = new Connector($this->loop, array('tls' => array(
'verify_peer' => false,
'verify_peer_name' => false
)));
$this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);

$this->assertResolveStream($this->client->connect('www.google.com:80'));
}

/**
* @group internet
* @requires PHP 5.6
*/
public function testConnectionSocksOverTlsUsesPeerNameFromSocksUri()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
}

$socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
'local_cert' => __DIR__ . '/../examples/localhost.pem',
)));
$this->server = new Server($this->loop, $socket);

$this->connector = new Connector($this->loop, array('tls' => array(
'verify_peer' => false,
'verify_peer_name' => true
)));
$this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);

$this->assertResolveStream($this->client->connect('www.google.com:80'));
}

/** @group internet */
public function testConnectionSocksOverUnix()
{
Expand Down