Description
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)
#1 _WebsocketPlaygroundHomeState.build. (package:quano_flutter/ui/debug/websocket_playground.dart:138:19)
#2 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:494:14)
#3 _InkResponseState.build. (package:flutter/src/material/ink_well.dart:549:30)
#4 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
#5 TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:161:9)
#6 TapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:94:7)
#7 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:315:9)
#8 <…>
Note that I cannot just replace the server certificate with one signed by a CA: The reason I want to use a pinned, self-signed certificate is security. The public part of the certificate will be saved in the app, and only connections to a server with the matching certificate will be allowed, thus preventing man-in-the-middle attacks. Pinning a self-signed certificate has the advantage, that we as app developers do not need to trust any CA.
My idea then was to make the app aware of the certificate using a SecurityContext, but WebSocket.connect(...) does not seem to take a SecurityContext. Having a String "cert" that contains the certificate, I instead tried the following code that makes use of the fromUpgradedSocket constructor:
var securityContext = SecurityContext();
var bytesList = utf8.encode(cert);
var bytes = bytesList is Uint8List ? bytesList : Uint8List.fromList(bytesList);
securityContext.setTrustedCertificatesBytes(bytes);
SecureSocket secureSocket = await SecureSocket.connect('192.168.0.11', 9000, context: securityContext);
socket = WebSocket.fromUpgradedSocket(secureSocket);
However, I got the following error:
[VERBOSE-2:dart_error.cc(16)] Unhandled exception:
HandshakeException: Handshake error in client (OS Error:
CERTIFICATE_VERIFY_FAILED: ok(handshake.cc:363))
#0 _SecureFilterImpl.handshake (dart:io/runtime/binsecure_socket_patch.dart:96:51)
#1 _RawSecureSocket._secureHandshake (dart:io/secure_socket.dart:779:21)
#2 _RawSecureSocket._tryFilter. (dart:io/secure_socket.dart:900:13)
#3 _RootZone.runUnary (dart:async/zone.dart:1381:54)
#4 _FutureListener.handleValue (dart:async/future_impl.dart:129:18)
#5 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:633:45)
#6 Future._propagateToListeners (dart:async/future_impl.dart:662:32)
#7 Future._completeWithValue (dart:async/future_impl.dart:477:5)
#8 Future._asyncComplete. (dart:async/future_impl.dart:507:7)
#9 _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
#10 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
My version details are:
Dart VM version: 2.0.0-dev.66.0 (Fri Jun 29 11:19:05 2018 +0200) on "macos_x64"
The question is, how to connect to a WebSocket server with a self-signed certificate, and how to only allow the connection if the certificate matches the one saved in the app (certificate pinning)?