Skip to content

Commit b3dc690

Browse files
committed
Only yield client streams after successful SSL handshake
The deferred returned by client constructors (`aleph.tcp/client`, `aleph.http.client/http-connection` and `aleph.http.client/websocket-connection`) would always be resolved as soon as the TCP connection was established, even when a `:ssl-context` was given and the SSL handshake had not yet taken place. With this patch, it will only be resolved once the SSL handshake was successful. Unfortunately, since TLSv1.3 the connection may still fail with a handshake error even after the handshake has seemingly succeeded. This is because only an invalid certificate is explicitly rejected while a valid certificate will be implicitly accepted. Thus, it's only possible to determine whether the handshake has fully succeeded after having succeessfully read some data. See [1] and [2] for more details. However, this patch still seems worhtwhile even for TLSv1.3 since the stream is only yielded at the earliest time it would theoretically be possible to successfully use it. This patch is the client-side dual of 9c911e3. Thus, some common code has been extracted to internal auxiliary functions in `aleph.netty`. 1: netty/netty#10502 2: https://stackoverflow.com/questions/62459802/tls-1-3-client-does-not-report-failed-handshake-when-client-certificate-verifica/62465859#62465859
1 parent 54efaf3 commit b3dc690

File tree

4 files changed

+73
-37
lines changed

4 files changed

+73
-37
lines changed

src/aleph/http/client.clj

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -680,9 +680,12 @@
680680

681681
:channel-active
682682
([_ ctx]
683-
(let [ch (.channel ctx)]
684-
(reset! in (netty/buffered-source ch (constantly 1) 16))
685-
(.handshake handshaker ch))
683+
(-> (.channel ctx)
684+
netty/maybe-ssl-handshake-future
685+
(d/on-realized (fn [ch]
686+
(reset! in (netty/buffered-source ch (constantly 1) 16))
687+
(.handshake handshaker ch))
688+
netty/ignore-ssl-handshake-errors))
686689
(.fireChannelActive ctx))
687690

688691
:user-event-triggered

src/aleph/netty.clj

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,25 @@ initialize an DnsAddressResolverGroup instance.
11311131
[dns-options]
11321132
(DnsAddressResolverGroup. (dns-resolver-group-builder dns-options)))
11331133

1134+
(defn ^:nodoc maybe-ssl-handshake-future
1135+
"Returns a deferred which resolves to the channel after a potential
1136+
SSL handshake has completed successfully. If no `SslHandler` is
1137+
present on the associated pipeline, resolves immediately."
1138+
[^Channel ch]
1139+
(if-let [^SslHandler ssl-handler (-> ch .pipeline (.get SslHandler))]
1140+
(-> ssl-handler
1141+
.handshakeFuture
1142+
wrap-future)
1143+
(d/success-deferred ch)))
1144+
1145+
(defn ^:nodoc ignore-ssl-handshake-errors
1146+
"Intended for use as error callback on a `maybe-ssl-handshake-future`
1147+
within a `:channel-active` handler. In this context, SSL handshake
1148+
errors don't need to be handled since the SSL handler will terminate
1149+
the whole pipeline by throwing `javax.net.ssl.SSLHandshakeException`
1150+
anyway."
1151+
[_])
1152+
11341153
(defn create-client
11351154
([pipeline-builder
11361155
ssl-context
@@ -1197,7 +1216,7 @@ initialize an DnsAddressResolverGroup instance.
11971216
(d/chain' (wrap-future f)
11981217
(fn [_]
11991218
(let [ch (.channel ^ChannelFuture f)]
1200-
ch))))))))
1219+
(maybe-ssl-handshake-future ch)))))))))
12011220

12021221
(defn start-server
12031222
[pipeline-builder

src/aleph/tcp.clj

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,7 @@
2828

2929
(defn- ^ChannelHandler server-channel-handler
3030
[handler {:keys [raw-stream?] :as options}]
31-
(let [in (atom nil)
32-
call-handler (fn [^ChannelHandlerContext ctx]
33-
(let [ch (.channel ctx)]
34-
(handler
35-
(doto
36-
(s/splice
37-
(netty/sink ch true netty/to-byte-buf)
38-
(reset! in (netty/source ch)))
39-
(reset-meta! {:aleph/channel ch}))
40-
(->TcpConnection ch))))]
31+
(let [in (atom nil)]
4132
(netty/channel-inbound-handler
4233

4334
:exception-caught
@@ -58,19 +49,16 @@
5849

5950
:channel-active
6051
([_ ctx]
61-
(if-let [^SslHandler ssl-handler (-> ctx .pipeline (.get SslHandler))]
62-
(-> ssl-handler
63-
.handshakeFuture
64-
netty/wrap-future
65-
(d/on-realized (fn [_]
66-
(call-handler ctx))
67-
;; No need to handle errors here since
68-
;; the SSL handler will terminate the
69-
;; whole pipeline by throwing a
70-
;; `javax.net.ssl.SSLHandshakeException`
71-
;; in this case anyway.
72-
(fn [_])))
73-
(call-handler ctx))
52+
(-> (.channel ctx)
53+
netty/maybe-ssl-handshake-future
54+
(d/on-realized (fn [ch]
55+
(handler
56+
(doto (s/splice
57+
(netty/sink ch true netty/to-byte-buf)
58+
(reset! in (netty/source ch)))
59+
(reset-meta! {:aleph/channel ch}))
60+
(->TcpConnection ch)))
61+
netty/ignore-ssl-handshake-errors))
7462
(.fireChannelActive ctx))
7563

7664
:channel-read
@@ -129,19 +117,20 @@
129117

130118
:channel-inactive
131119
([_ ctx]
132-
(s/close! @in)
120+
(some-> @in s/close!)
133121
(.fireChannelInactive ctx))
134122

135123
:channel-active
136124
([_ ctx]
137-
(let [ch (.channel ctx)]
138-
(d/success! d
139-
(doto
140-
(s/splice
141-
(netty/sink ch true netty/to-byte-buf)
142-
(reset! in (netty/source ch)))
143-
(reset-meta! {:aleph/channel ch}))))
144-
(.fireChannelActive ctx))
125+
(-> (.channel ctx)
126+
netty/maybe-ssl-handshake-future
127+
(d/on-realized (fn [ch]
128+
(d/success! d (doto (s/splice
129+
(netty/sink ch true netty/to-byte-buf)
130+
(reset! in (netty/source ch)))
131+
(reset-meta! {:aleph/channel ch}))))
132+
netty/ignore-ssl-handshake-errors))
133+
(.fireChannelActive ctx))
145134

146135
:channel-read
147136
([_ ctx msg]

test/aleph/tcp_ssl_test.clj

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
[aleph.tcp-test :refer [with-server]]
77
[clj-commons.byte-streams :as bs]
88
[clojure.test :refer [deftest is]]
9+
[manifold.deferred :as d]
910
[manifold.stream :as s])
1011
(:import
11-
(java.security.cert X509Certificate)))
12+
(java.security.cert X509Certificate)
13+
(java.util.concurrent TimeoutException)))
1214

1315
(netty/leak-detector-level! :paranoid)
1416

@@ -80,3 +82,26 @@
8082
(s/close! c)
8183
(is (deref connection-closed 1000 false))
8284
(is (nil? @ssl-session) "SSL session should be undefined")))))
85+
86+
(deftest test-client-yields-stream-only-after-successful-handshake
87+
(let [connection-active (promise)
88+
continue-handshake (promise)
89+
notify-connection-active #_:clj-kondo/ignore (netty/channel-inbound-handler
90+
:channel-active
91+
([_ ctx]
92+
(deliver connection-active true)
93+
(when-not (deref continue-handshake 5000 false)
94+
(throw (TimeoutException.)))
95+
(.fireChannelActive ctx)))]
96+
(with-server (tcp/start-server (fn [s _])
97+
{:port 10001
98+
:ssl-context ssl/server-ssl-context})
99+
(let [c (tcp/client {:host "localhost"
100+
:port 10001
101+
:ssl-context ssl/client-ssl-context-opts
102+
:pipeline-transform (fn [p]
103+
(.addAfter p "handler" "notify-active" notify-connection-active))})]
104+
(is (deref connection-active 1000 false))
105+
(is (not (d/realized? c)))
106+
(deliver continue-handshake true)
107+
(is (deref c 1000 false))))))

0 commit comments

Comments
 (0)