Open CryptUser opened 6 years ago
I got a step further after finding the onBadCertificate parameter. Using:
SecureSocket secureSocket = await SecureSocket.connect('192.168.0.11', 9000, onBadCertificate: (X509Certificate cert) => true);
I did no longer get an error. However, even after the line
socket = WebSocket.fromUpgradedSocket(secureSocket, serverSide: false);
I did not get any web socket connection. The server-log showed after some seconds:
dropping connection to peer tcp4:192.168.0.11:51735 with abort=True: WebSocket opening handshake timeout (peer did not finish the opening handshake in time)
Note that the web socket server is written in Python and running well with native Android code. What else can I do?
In case it is not possible to use the WebSocket.fromUpgradedSocket(...) constructor with non-dart servers, I would suggest to add the onBadCertificate parameter directly to the Websocket.connect(...) constructor.
Similar to #31948 https://github.com/dart-lang/pub/issues/1882#issuecomment-415588527 might help
@zoechi Is that hint only aimed at command line apps?
Since I'm running flutter, I tried with DART_VM_OPTIONS=--root-certs-file=mycertpath.pem flutter packages get which ran without problems.
Running the app when using the constructor socket = await WebSocket.connect('wss://192.168.0.11:9000'); still resulted in the error [ERROR:flutter/shell/common/shell.cc(181)] Dart Error: Unhandled exception: E/flutter ( 2929): HandshakeException: Handshake error in client (OS Error: E/flutter ( 2929): CERTIFICATE_VERIFY_FAILED: self signed certificate(handshake.cc:363)) ...
Running with the WebSocket.fromUpgradedSocket(...) constructor gave the same result as described above.
hint only aimed at command line apps
What other kind of apps do you have in mind? In the browser this is out of Dart's reach anyway. This would leave Flutter. I guess there it's a bit tricky to get that environment variable set for the process.
I am trying to implement an Android/ios app with flutter. For that, the Dart Websocket class in principle works, but as far as I could find out not with a self-signed certificate.
As pointed out above, my idea was to use a SecureSocket which would allow a self-signed certificate, and then use the WebSocket.fromUpgradedSocket(...) constructor. Which I however did not get to work, as shown above.
If this strategy does not work, my question is, wouldn't it be best if the WebSocket.connect constructor would also get the additional parameter "onBadCertificate" to allow self-signed and pinned certificates?
I am in the same boat as @CryptUser The platform I develop for allows users to upload and use self signed certs. It would be very beneficial to have an additional callback or param to allow selfsigned certs.
Another 'me too' comment, I'm afraid.
Also in the same situation as @CryptUser and @linuxjet - I need to open a secure websocket Flutter in our development environment to our development server, and despite importing the certificate into the Android emulator, I see:
I/flutter ( 8460): 2019-03-18 10:38:58.729594: SEVERE: HandshakeException: Handshake error in client (OS Error:
I/flutter ( 8460): CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:352))
The WebSocket.fromUpgradedSocket(...)
seem feasible, but again, I also haven't had any success with getting that working...
(edit)
...because I have to either rewrite or cut'n'paste all the http request and upgrading code from websocket_impl.dart
all because I want to do something as simple as construct my own secure socket with its own security context etc. It's certainly not what I would want to ship to production code either.
Something like var ws = WebSocket.fromSecureSocket(mySecureSocket)
would solve the problem.
(/edit)
I'm having the same problem. In a nodejs environment it is actually pretty easy
var ws = new WebSocket('wss://127.0.0.1:8334/ws', {
headers: {
'Authorization': 'Basic '+new Buffer(user+':'+password).toString('base64')
},
cert: cert,
ca: [cert]
});
I also have this problem. Are there any plans to fix this in flutter?
I do some investigation to a problem. Searching possible workarounds and trying to reimplement some part of dart:io(http) websocket_impl class on my own. This led me to understand the root of the problem:
this line from sdk/lib/_http/websocket_impl.dart
HttpClient initialized statically with default factory constructor without any access to it and ability to control this step:
static final HttpClient _httpClient = new HttpClient();
Problems:
SecurityContext
factory HttpClient({SecurityContext context}) { ... }
.badCertificateCallback
for HttpClientIf SDK expose this HttpClient to outside, a lot of things can be available for websocket (SSL pinning, etc) A lot of examples @mleonhard writes here
one of the straightforward solution will be accept HttpClient like a one of the parameters in connect
function:
static Future<WebSocket> connect(
String url,
Iterable<String> protocols,
Map<String, dynamic> headers,
{CompressionOptions compression: CompressionOptions.compressionDefault,
HttpClient httpClient} //<--optional parametr
)
{...}
if param is specified - it will be used instead static field. Code behaviour will be almost the same as current implementation but now is configurable; Users can configure
SecurityContext
.badCertificateCallback
who thinks what?
Hi @nailgilaziev I'm happy to see someone want to fix this issue. I think the better way to get feedback on your solution is to create a pull request with your changes.
same problem here. I'm using a self-signed certificate for development and I can't connect to the server.
I finally gave up and used RawSocket. Something like this:
bool badCert(X509Certificate cert) {
//Do stuff here
return false;
}
Future<RawSocket> connectSocket(String host, String port, bool ssl) async {
if (ssl) {
return RawSecureSocket.connect(host, port, onBadCertificate: badCert);
} else {
return RawSocket.connect(host, port);
}
}
I still have the problem, unfortunately RawSocket does not satisfy my problem, RawSocket runs an nslookup and cannot find the server and this breaks my connection since my socket server url carries a "/ params" parameter and the websocket can handle this problem, but I need the onBadcertificate parameter. I am waiting for a possible solution.
I see. So just connect the socket to the host and make a connection to the endpoint in socket write.
here is one of my connection functions:
Future<RawSocket> connectSocket() async {
if (_authObject.isSSL) {
return RawSecureSocket.connect(_authObject.hostName, _authObject.port, onBadCertificate: badCert);
} else {
return RawSocket.connect(_authObject.hostName, _authObject.port);
}
}
void subscribe() async {
var envelope = '''<?xml version="1.0" encoding="utf-8"?>
<s:Envelope></s:Envelope>'''; //confidential - removed
var sb = new StringBuffer();
sb.write("POST /services HTTP/1.1\n");
sb.write("Host: ${_authObject.hostName} \n");
sb.write("Content-Type: text/xml; charset=utf-8\n");
sb.write("Authorization: Basic ${_authObject.getEncodedAuth()}\n");
sb.write("Content-Length: ${envelope.length}");
sb.write("\r\n");
sb.write("\r\n");
sb.write(envelope);
sb.write("\r\n");
connectSocket().then((RawSocket sock) {
rsocket = sock;
rsocket.listen(dataHandler, onError: errorHandler, onDone: doneHandler, cancelOnError: false);
rsocket.write(sb
.toString()
.codeUnits);
}).catchError(errorHandler);
}
Let me know if this helps you out.
I see. So just connect the socket to the host and make a connection to the endpoint in socket write.
here is one of my connection functions:
Future<RawSocket> connectSocket() async { if (_authObject.isSSL) { return RawSecureSocket.connect(_authObject.hostName, _authObject.port, onBadCertificate: badCert); } else { return RawSocket.connect(_authObject.hostName, _authObject.port); } } void subscribe() async { var envelope = '''<?xml version="1.0" encoding="utf-8"?> <s:Envelope></s:Envelope>'''; //confidential - removed var sb = new StringBuffer(); sb.write("POST /services HTTP/1.1\n"); sb.write("Host: ${_authObject.hostName} \n"); sb.write("Content-Type: text/xml; charset=utf-8\n"); sb.write("Authorization: Basic ${_authObject.getEncodedAuth()}\n"); sb.write("Content-Length: ${envelope.length}"); sb.write("\r\n"); sb.write("\r\n"); sb.write(envelope); sb.write("\r\n"); connectSocket().then((RawSocket sock) { rsocket = sock; rsocket.listen(dataHandler, onError: errorHandler, onDone: doneHandler, cancelOnError: false); rsocket.write(sb .toString() .codeUnits); }).catchError(errorHandler); }
Let me know if this helps you out.
Can you post your websocket with this code ?
For my case HttpOverrides
was all I needed.
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true; // add your localhost detection logic here if you want
}
}
void main() {
HttpOverrides.global = new MyHttpOverrides();
runApp(MyApp());
}
I created a PR that solves the problem, @sortie anything more I need to do? I have this running on my local machine without an issue.
After two days struggling with this problem, I gave up and started using WebSocket. It's more low level, but is easy to implement and solved my problem. Here is the code, I hope it can help you guys:
void startListening() async {
_socket = await WebSocket.connect("wss://localhost");
_socket.listen(
(event) => print('Server: $event'),
onError: (error) => print(error),
onDone: () => print("Done"),
);
await sendMessage('Hello World! 1');
await sendMessage('Hello World! 2');
await sendMessage('Hello World! 3');
}
Future<void> sendMessage(String message) async {
print('Client: $message');
_socket.add(message);
await Future.delayed(Duration(seconds: 2));
}
This is a great temporary fix ! it works on local ip with self signed certificate. (Please modify the badCertificateCallback
to your needs)
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true; // add your localhost detection logic here if you want
}
}
void main() {
HttpOverrides.global = MyHttpOverrides();
runApp(MaterialApp(home: MyApp()));
}
After two days struggling with this problem, I gave up and started using WebSocket. It's more low level, but is easy to implement and solved my problem. Here is the code, I hope it can help you guys:
void startListening() async { _socket = await WebSocket.connect("wss://localhost"); _socket.listen( (event) => print('Server: $event'), onError: (error) => print(error), onDone: () => print("Done"), ); await sendMessage('Hello World! 1'); await sendMessage('Hello World! 2'); await sendMessage('Hello World! 3'); } Future<void> sendMessage(String message) async { print('Client: $message'); _socket.add(message); await Future.delayed(Duration(seconds: 2)); }
Thanks @lmint1 !
Just letting you all know, this issue has been fixed, within WebSocket.connect() you can add a custom HTTP Client to it now.
Is there any Update on this Issue?
I have a WebSocket server at ip address 192.168.0.11 with external port 9000, and a self-signed certificate. The server is tested to work ok with an ios client using SocketRocket. When I try to connect with my flutter app using
socket = await WebSocket.connect('wss://192.168.0.11:9000');
I get the following error message: [VERBOSE-2:dart_error.cc(16)] Unhandled exception: HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: ok(handshake.cc:363))
0 _WebsocketPlaygroundHomeState.connectWebsocket (package:quano_flutter/ui/debug/websocket_playground.dart:92:14)