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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.1.0

- Add `onBadCertificate` to `SOCKSSocket.create()` and `SocksConnection.start()`, passed through to `RawSecureSocket.secure`. `allowBadCertificates: true` still accepts everything.

## 2.0.0

- **Breaking:** Remove `SOCKSSocket.socket` so transport operations cannot bypass the wrapper's write queue and lifecycle tracking. Use `destroy()`, `closeOutput()`, `close()`, `write()`, `outputStream`, and `inputStream` instead. `closeOutput()` preserves the native socket's write-side half-close behavior.
Expand Down
5 changes: 4 additions & 1 deletion lib/socks_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class SocksConnection {
required int targetPort,
String? tlsHost,
SecurityContext? securityContext,
bool Function(X509Certificate)? onBadCertificate,
SocksCredentials? credentials,
List<String>? supportedProtocols,
Duration timeout = const Duration(seconds: 30),
Expand All @@ -55,7 +56,7 @@ class SocksConnection {
final request = encodeSocksDestination(targetHost, targetPort);
final operation = _ConnectOperation();
operation.run(proxyHost, proxyPort, request, tlsHost, securityContext,
credentials, supportedProtocols, timeout);
onBadCertificate, credentials, supportedProtocols, timeout);
return ConnectionTask.fromSocket(operation.result.future, operation.cancel);
}
}
Expand Down Expand Up @@ -90,6 +91,7 @@ class _ConnectOperation {
List<int> request,
String? tlsHost,
SecurityContext? context,
bool Function(X509Certificate)? onBadCertificate,
SocksCredentials? credentials,
List<String>? supportedProtocols,
Duration timeout) async {
Expand Down Expand Up @@ -127,6 +129,7 @@ class _ConnectOperation {
subscription: channel.detach(),
host: tlsHost,
context: context,
onBadCertificate: onBadCertificate,
supportedProtocols: supportedProtocols);
_raw = secured;
if (_aborted) {
Expand Down
9 changes: 8 additions & 1 deletion lib/socks_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ class SOCKSSocket {

final SecurityContext? _securityContext;

/// Passed to [RawSecureSocket.secure]; ignored with [allowBadCertificates].
final bool Function(X509Certificate)? _onBadCertificate;

final bool? _requireIsolation;

final bool _closeOnPeerEof;
Expand Down Expand Up @@ -199,6 +202,7 @@ class SOCKSSocket {
this._operationTimeout,
this._allowBadCertificates,
this._securityContext,
this._onBadCertificate,
this._requireIsolation,
this._closeOnPeerEof);

Expand All @@ -216,6 +220,7 @@ class SOCKSSocket {
Duration operationTimeout = const Duration(seconds: 30),
bool allowBadCertificates = false,
SecurityContext? securityContext,
bool Function(X509Certificate)? onBadCertificate,
bool? requireIsolation,
bool closeOnPeerEof = true,
}) async {
Expand Down Expand Up @@ -245,6 +250,7 @@ class SOCKSSocket {
operationTimeout,
allowBadCertificates,
securityContext,
onBadCertificate,
requireIsolation,
closeOnPeerEof);
await instance._init();
Expand Down Expand Up @@ -408,7 +414,8 @@ class SOCKSSocket {
subscription: channel.detach(),
host: domain,
context: _securityContext,
onBadCertificate: _allowBadCertificates ? (_) => true : null,
onBadCertificate:
_allowBadCertificates ? (_) => true : _onBadCertificate,
);
try {
_channel = RawChannel(await _handshake(() => upgrade));
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: socks_socket
description: SOCKS version 5 sockets for Dart and Flutter, *eg.* ElectrumX and/or Fulcrum over Tor via socket(s).
version: 2.0.0
version: 2.1.0
repository: https://github.com/cypherstack/socks_socket

environment:
Expand Down
27 changes: 27 additions & 0 deletions test/socks_connection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ConnectionTask<Socket> connect(TunnelProxy proxy,
{String host = 'unresolvable.invalid',
String? tlsHost,
SecurityContext? context,
bool Function(X509Certificate)? onBadCertificate,
SocksCredentials? credentials,
List<String>? supportedProtocols,
Duration timeout = const Duration(seconds: 3)}) =>
Expand All @@ -20,6 +21,7 @@ ConnectionTask<Socket> connect(TunnelProxy proxy,
targetPort: 443,
tlsHost: tlsHost,
securityContext: context,
onBadCertificate: onBadCertificate,
credentials: credentials,
supportedProtocols: supportedProtocols,
timeout: timeout);
Expand Down Expand Up @@ -252,6 +254,31 @@ void main() {
await proxy.disconnected.future.timeout(const Duration(seconds: 2));
});
}
test('onBadCertificate can accept an untrusted certificate', () async {
final server = await SecureServerSocket.bind(
InternetAddress.loopbackIPv4, 0, certificates.serverContext());
final accepted = <Socket>[];
server.listen(accepted.add, onError: (Object _) {});
addTearDown(() async {
for (final socket in accepted) {
socket.destroy();
}
await server.close();
});
final proxy = TunnelProxy(upstreamPort: server.port);
await proxy.start();
addTearDown(proxy.close);
X509Certificate? offered;
final socket = await connect(proxy,
tlsHost: 'localhost', context: SecurityContext(withTrustedRoots: false),
onBadCertificate: (certificate) {
offered = certificate;
return true;
}).socket;
addTearDown(socket.destroy);
expect(socket, isA<SecureSocket>());
expect(offered, isNotNull);
});
test('renegotiate is unsupported on the TLS socket', () async {
final server = await SecureServerSocket.bind(
InternetAddress.loopbackIPv4, 0, certificates.serverContext());
Expand Down
25 changes: 25 additions & 0 deletions test/tls_configuration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,29 @@ void main() {
await socket.write('custom TLS');
expect(utf8.decode(await reply), 'custom TLS');
});

test('SOCKSSocket passes onBadCertificate to the TLS upgrade', () async {
final certificates = TestCertificates.generate();
final proxy = MockSocksServer()
..sslEnabled = true
..securityContext = certificates.serverContext();
await proxy.start();
addTearDown(proxy.stop);
X509Certificate? offered;
final socket = await SOCKSSocket.create(
proxyHost: InternetAddress.loopbackIPv4.address,
proxyPort: proxy.port,
sslEnabled: true,
securityContext: SecurityContext(withTrustedRoots: false),
onBadCertificate: (certificate) {
offered = certificate;
return true;
},
);
addTearDown(socket.close);
await socket.connect();
await socket.connectTo('localhost', 443);
expect(socket.state, ConnectionState.connected);
expect(offered, isNotNull);
});
}
Loading