Skip to content

Commit dfc2cad

Browse files
authored
Merge pull request #689 from clj-commons/enable-tls-endpoint-identification-by-default
Enable TLS endpoint identification by default (breaking)
2 parents 07640ce + 4bb1cc9 commit dfc2cad

File tree

8 files changed

+158
-64
lines changed

8 files changed

+158
-64
lines changed

src/aleph/http.clj

+2
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
Param key | Description
121121
| --- | ---
122122
| `ssl-context` | an `io.netty.handler.ssl.SslContext` object or a map of SSL context options (see `aleph.netty/ssl-client-context` for more details), only required if a custom context is required
123+
| `ssl-endpoint-id-alg` | the name of the algorithm to use for SSL endpoint identification (see https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms), defaults to \"HTTPS\". Only used for HTTPS connections. Pass `nil` to disable endpoint identification.
123124
| `local-address` | an optional `java.net.SocketAddress` describing which local interface should be used
124125
| `bootstrap-transform` | a function that takes an `io.netty.bootstrap.Bootstrap` object and modifies it.
125126
| `pipeline-transform` | a function that takes an `io.netty.channel.ChannelPipeline` object, which represents a connection, and modifies it.
@@ -229,6 +230,7 @@
229230
| `raw-stream?` | if `true`, the connection will emit raw `io.netty.buffer.ByteBuf` objects rather than strings or byte-arrays. This will minimize copying, but means that care must be taken with Netty's buffer reference counting. Only recommended for advanced users.
230231
| `insecure?` | if `true`, the certificates for `wss://` will be ignored.
231232
| `ssl-context` | an `io.netty.handler.ssl.SslContext` object, only required if a custom context is required
233+
| `ssl-endpoint-id-alg` | the name of the algorithm to use for SSL endpoint identification (see https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms), defaults to \"HTTPS\". Only used for WSS connections. Pass `nil` to disable endpoint identification.
232234
| `extensions?` | if `true`, the websocket extensions will be supported.
233235
| `sub-protocols` | a string with a comma seperated list of supported sub-protocols.
234236
| `headers` | the headers that should be included in the handshake

src/aleph/http/client.clj

+5-2
Original file line numberDiff line numberDiff line change
@@ -683,14 +683,14 @@
683683
684684
Can't use an ApnHandler/ApplicationProtocolNegotiationHandler here,
685685
because it's tricky to run Manifold code on Netty threads."
686-
[{:keys [ssl? remote-address ssl-context]}]
686+
[{:keys [ssl? remote-address ssl-context ssl-endpoint-id-alg]}]
687687
(fn pipeline-builder
688688
[^ChannelPipeline pipeline]
689689
(when ssl?
690690
(do
691691
(.addLast pipeline
692692
"ssl-handler"
693-
(netty/ssl-handler (.channel pipeline) ssl-context remote-address))
693+
(netty/ssl-handler (.channel pipeline) ssl-context remote-address ssl-endpoint-id-alg))
694694
(.addLast pipeline
695695
"pause-handler"
696696
^ChannelHandler (netty/pause-handler))))))
@@ -954,6 +954,7 @@
954954
keep-alive?
955955
insecure?
956956
ssl-context
957+
ssl-endpoint-id-alg
957958
response-buffer-size
958959
epoll?
959960
transport
@@ -966,6 +967,7 @@
966967
bootstrap-transform identity
967968
pipeline-transform identity
968969
keep-alive? true
970+
ssl-endpoint-id-alg netty/default-ssl-endpoint-id-alg
969971
response-buffer-size 65536
970972
epoll? false
971973
name-resolver :default
@@ -999,6 +1001,7 @@
9991001
(assoc opts
10001002
:ssl? ssl?
10011003
:ssl-context ssl-context
1004+
:ssl-endpoint-id-alg ssl-endpoint-id-alg
10021005
:remote-address remote-address
10031006
:raw-stream? raw-stream?
10041007
:response-buffer-size response-buffer-size

src/aleph/http/websocket/client.clj

+3-1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@
232232
{:keys [raw-stream?
233233
insecure?
234234
ssl-context
235+
ssl-endpoint-id-alg
235236
headers
236237
local-address
237238
bootstrap-transform
@@ -248,6 +249,7 @@
248249
pipeline-transform identity
249250
raw-stream? false
250251
epoll? false
252+
ssl-endpoint-id-alg netty/default-ssl-endpoint-id-alg
251253
sub-protocols nil
252254
extensions? false
253255
max-frame-payload 65536
@@ -294,7 +296,7 @@
294296
(when ssl?
295297
(.addLast p
296298
"ssl-handler"
297-
(netty/ssl-handler (.channel p) ssl-context remote-address)))
299+
(netty/ssl-handler (.channel p) ssl-context remote-address ssl-endpoint-id-alg)))
298300
(.addLast p "http-client" (HttpClientCodec.))
299301
(.addLast p "aggregator" (HttpObjectAggregator. 16384))
300302
(.addLast p "websocket-frame-aggregator" (WebSocketFrameAggregator. max-frame-size))

src/aleph/netty.clj

+48-16
Original file line numberDiff line numberDiff line change
@@ -1078,10 +1078,12 @@
10781078

10791079
(defn self-signed-ssl-context
10801080
"A self-signed SSL context for servers."
1081-
[]
1082-
(let [cert (SelfSignedCertificate.)]
1083-
(ssl-server-context {:private-key (.privateKey cert)
1084-
:certificate-chain (.certificate cert)})))
1081+
([]
1082+
(self-signed-ssl-context "localhost"))
1083+
([hostname]
1084+
(let [cert (SelfSignedCertificate. hostname)]
1085+
(ssl-server-context {:private-key (.privateKey cert)
1086+
:certificate-chain (.certificate cert)}))))
10851087

10861088
(defn insecure-ssl-client-context
10871089
"An insure SSL context for servers."
@@ -1374,33 +1376,47 @@ initialize an DnsAddressResolverGroup instance.
13741376
anyway."
13751377
[_])
13761378

1379+
(def ^:const ^:no-doc default-ssl-endpoint-id-alg "HTTPS")
1380+
13771381
(defn ssl-handler
13781382
"Generates a new SslHandler for the given SslContext.
13791383
1380-
The 2-arity version is for the server.
1381-
The 3-arity version is for the client. The `remote-address` must be provided"
1382-
(^ChannelHandler
1384+
The 2-ary version is for servers.
1385+
1386+
The 3- and 4-ary version are for clients.
1387+
For these, the `remote-address` must be provided.
1388+
The `ssl-endpoint-id-alg` is the name of the algorithm to use for endpoint identification (see https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms). It defaults to \"HTTPS\" in the 3-ary version which is a reasonable default for non-HTTPS uses, too. Pass `nil` to disable endpoint identification."
1389+
(^SslHandler
13831390
[^Channel ch ^SslContext ssl-ctx]
13841391
(.newHandler ssl-ctx
13851392
(.alloc ch)))
1386-
(^ChannelHandler
1393+
(^SslHandler
13871394
[^Channel ch ^SslContext ssl-ctx ^InetSocketAddress remote-address]
1388-
(.newHandler ssl-ctx
1389-
(.alloc ch)
1390-
(.getHostName ^InetSocketAddress remote-address)
1391-
(.getPort ^InetSocketAddress remote-address))))
1395+
(ssl-handler ch ssl-ctx remote-address default-ssl-endpoint-id-alg))
1396+
(^SslHandler
1397+
[^Channel ch ^SslContext ssl-ctx ^InetSocketAddress remote-address ssl-endpoint-id-alg]
1398+
(let [ssl-handler (.newHandler ssl-ctx
1399+
(.alloc ch)
1400+
(.getHostName ^InetSocketAddress remote-address)
1401+
(.getPort ^InetSocketAddress remote-address))]
1402+
(when ssl-endpoint-id-alg
1403+
(let [ssl-engine (.engine ssl-handler)
1404+
ssl-params (.getSSLParameters ssl-engine)]
1405+
(.setEndpointIdentificationAlgorithm ssl-params ssl-endpoint-id-alg)
1406+
(.setSSLParameters ssl-engine ssl-params)))
1407+
ssl-handler)))
13921408

13931409
(defn- add-ssl-handler-to-pipeline-builder
13941410
"Adds an `SslHandler` to the pipeline builder."
13951411
([pipeline-builder ssl-ctx]
1396-
(add-ssl-handler-to-pipeline-builder pipeline-builder ssl-ctx nil))
1397-
([pipeline-builder ssl-ctx remote-address]
1412+
(add-ssl-handler-to-pipeline-builder pipeline-builder ssl-ctx nil nil))
1413+
([pipeline-builder ssl-ctx remote-address ssl-endpoint-id-alg]
13981414
(fn [^ChannelPipeline p]
13991415
(.addFirst p
14001416
"ssl-handler"
14011417
(let [ch (.channel p)]
14021418
(if remote-address
1403-
(ssl-handler ch ssl-ctx remote-address)
1419+
(ssl-handler ch ssl-ctx remote-address ssl-endpoint-id-alg)
14041420
(ssl-handler ch ssl-ctx))))
14051421
(pipeline-builder p))))
14061422

@@ -1471,6 +1487,22 @@ initialize an DnsAddressResolverGroup instance.
14711487
([pipeline-builder
14721488
ssl-context
14731489
bootstrap-transform
1490+
remote-address
1491+
local-address
1492+
epoll?
1493+
name-resolver]
1494+
(create-client pipeline-builder
1495+
ssl-context
1496+
default-ssl-endpoint-id-alg
1497+
bootstrap-transform
1498+
remote-address
1499+
local-address
1500+
epoll?
1501+
name-resolver))
1502+
([pipeline-builder
1503+
ssl-context
1504+
ssl-endpoint-id-alg
1505+
bootstrap-transform
14741506
^SocketAddress remote-address
14751507
^SocketAddress local-address
14761508
epoll?
@@ -1480,7 +1512,7 @@ initialize an DnsAddressResolverGroup instance.
14801512
(coerce-ssl-client-context ssl-context))
14811513

14821514
pipeline-builder (if ssl-context
1483-
(add-ssl-handler-to-pipeline-builder pipeline-builder ssl-context remote-address)
1515+
(add-ssl-handler-to-pipeline-builder pipeline-builder ssl-context remote-address ssl-endpoint-id-alg)
14841516
pipeline-builder)]
14851517
(create-client-chan {:pipeline-builder pipeline-builder
14861518
:bootstrap-transform bootstrap-transform

src/aleph/tcp.clj

+5-3
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,16 @@
162162
| `remote-address` | a `java.net.SocketAddress` specifying the server's address.
163163
| `local-address` | a `java.net.SocketAddress` specifying the local network interface to use.
164164
| `ssl-context` | an explicit `io.netty.handler.ssl.SslHandler` or a map of SSL context options (see `aleph.netty/ssl-client-context` for more details) to use. Defers to `ssl?` and `insecure?` configuration if omitted.
165+
| `ssl-endpoint-id-alg` | the name of the algorithm to use for SSL endpoint identification (see https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms), defaults to \"HTTPS\" which is a reasonable default for non-HTTPS uses, too. Only used by SSL connections. Pass `nil` to disable endpoint identification.
165166
| `ssl?` | if true, the client attempts to establish a secure connection with the server.
166167
| `insecure?` | if true, the client will ignore the server's certificate.
167168
| `bootstrap-transform` | a function that takes an `io.netty.bootstrap.Bootstrap` object, which represents the client, and modifies it.
168169
| `pipeline-transform` | a function that takes an `io.netty.channel.ChannelPipeline` object, which represents a connection, and modifies it.
169170
| `raw-stream?` | if true, messages from the stream will be `io.netty.buffer.ByteBuf` objects rather than byte-arrays. This will minimize copying, but means that care must be taken with Netty's buffer reference counting. Only recommended for advanced users.
170171
| `transport` | the transport to use, one of `:nio`, `:epoll`, `:kqueue` or `:io-uring` (defaults to `:nio`)."
171-
[{:keys [host port remote-address local-address ssl-context ssl? insecure? pipeline-transform bootstrap-transform epoll? transport]
172-
:or {bootstrap-transform identity
172+
[{:keys [host port remote-address local-address ssl-context ssl-endpoint-id-alg ssl? insecure? pipeline-transform bootstrap-transform epoll? transport]
173+
:or {ssl-endpoint-id-alg netty/default-ssl-endpoint-id-alg
174+
bootstrap-transform identity
173175
epoll? false}
174176
:as options}]
175177
(let [[s ^ChannelHandler handler] (client-channel-handler options)
@@ -185,7 +187,7 @@
185187
(when ssl?
186188
(.addLast pipeline
187189
"ssl-handler"
188-
(netty/ssl-handler (.channel pipeline) ssl-context remote-address)))
190+
(netty/ssl-handler (.channel pipeline) ssl-context remote-address ssl-endpoint-id-alg)))
189191
(.addLast pipeline "handler" handler)
190192
(when pipeline-transform
191193
(pipeline-transform pipeline)))]

test/aleph/http_test.clj

+70-42
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,16 @@
186186
(apply concat)
187187
(partition 2)))
188188

189+
(def default-ssl-server-options
190+
{:port port
191+
:ssl-context ssl/server-ssl-context})
192+
193+
(def default-ssl-client-options
194+
{:ssl-context ssl/client-ssl-context-opts})
195+
189196
(defmacro with-server [server & body]
190197
`(let [server# ~server]
191-
(binding [*pool* (http/connection-pool {:connection-options (merge *connection-options* {:insecure? true})})]
198+
(binding [*pool* (http/connection-pool {:connection-options *connection-options*})]
192199
(try
193200
~@body
194201
(finally
@@ -207,7 +214,7 @@
207214
(with-server (http/start-server ~handler {:port port :compression-level 3 :shutdown-timeout 0})
208215
~@body)))
209216

210-
(def default-ssl-options {:port port, :ssl-context (netty/self-signed-ssl-context)})
217+
211218

212219
(defmacro with-handler-options
213220
[handler options & body]
@@ -247,14 +254,15 @@
247254

248255

249256
(deftest test-ssl-response-formats
250-
(with-handler-options basic-handler default-ssl-options
251-
(doseq [[path result] expected-results]
252-
(is
253-
(= result
254-
(bs/to-string
255-
(:body
256-
@(http-get (str "https://localhost:" port "/" path)))))
257-
(str path "path failed")))))
257+
(binding [*connection-options* default-ssl-client-options]
258+
(with-handler-options basic-handler default-ssl-server-options
259+
(doseq [[path result] expected-results]
260+
(is
261+
(= result
262+
(bs/to-string
263+
(:body
264+
@(http-get (str "https://localhost:" port "/" path)))))
265+
(str path "path failed"))))))
258266

259267
(deftest test-files
260268
(let [client-url (str "http://localhost:" port)]
@@ -269,49 +277,69 @@
269277
{:body (io/file "test/file.txt")}))))))))
270278

271279
(deftest test-ssl-files
272-
(let [client-url (str "https://localhost:" port)
273-
client-options {:connection-options {:ssl-context ssl/client-ssl-context}}
274-
client-pool (http/connection-pool client-options)]
275-
(with-handler-options identity (merge default-ssl-options {:ssl-context ssl/server-ssl-context})
276-
(is (str/blank?
277-
(bs/to-string
278-
(:body @(http-put client-url
279-
{:body (io/file "test/empty.txt")
280-
:pool client-pool})))))
281-
(is (= (slurp "test/file.txt" :encoding "UTF-8")
280+
(binding [*connection-options* default-ssl-client-options]
281+
(let [client-url (str "https://localhost:" port)]
282+
(with-handler-options identity default-ssl-server-options
283+
(is (str/blank?
282284
(bs/to-string
283285
(:body @(http-put client-url
284-
{:body (io/file "test/file.txt")
285-
:pool client-pool}))))))))
286+
{:body (io/file "test/empty.txt")})))))
287+
(is (= (slurp "test/file.txt" :encoding "UTF-8")
288+
(bs/to-string
289+
(:body @(http-put client-url
290+
{:body (io/file "test/file.txt")})))))))))
286291

287292
(defn ssl-session-capture-handler [ssl-session-atom]
288293
(fn [req]
289294
(reset! ssl-session-atom (http.core/ring-request-ssl-session req))
290295
{:status 200 :body "ok"}))
291296

292297
(deftest test-ssl-session-access
293-
(let [ssl-session (atom nil)]
294-
(with-handler-options
295-
(ssl-session-capture-handler ssl-session)
296-
default-ssl-options
297-
(is (= 200 (:status @(http-get (str "https://localhost:" port)))))
298-
(is (some? @ssl-session))
299-
(when-let [^SSLSession s @ssl-session]
300-
(is (.isValid s))
301-
(is (not (str/includes? "NULL" (.getCipherSuite s))))))))
298+
(binding [*connection-options* default-ssl-client-options]
299+
(let [ssl-session (atom nil)]
300+
(with-handler-options
301+
(ssl-session-capture-handler ssl-session)
302+
default-ssl-server-options
303+
(is (= 200 (:status @(http-get (str "https://localhost:" port)))))
304+
(is (some? @ssl-session))
305+
(when-let [^SSLSession s @ssl-session]
306+
(is (.isValid s))
307+
(is (not (str/includes? "NULL" (.getCipherSuite s)))))))))
302308

303309
(deftest test-ssl-with-plain-client-request
304-
(let [ssl-session (atom nil)]
305-
(with-handler-options
306-
(ssl-session-capture-handler ssl-session)
307-
default-ssl-options
308-
;; Note the intentionally wrong "http" scheme here
309-
(is (some-> (http-get (str "http://localhost:" port))
310-
(d/catch identity)
311-
deref
312-
ex-message
313-
(str/includes? "connection was closed")))
314-
(is (nil? @ssl-session)))))
310+
(binding [*connection-options* default-ssl-client-options]
311+
(let [ssl-session (atom nil)]
312+
(with-handler-options
313+
(ssl-session-capture-handler ssl-session)
314+
default-ssl-server-options
315+
;; Note the intentionally wrong "http" scheme here
316+
(is (some-> (http-get (str "http://localhost:" port))
317+
(d/catch identity)
318+
deref
319+
ex-message
320+
(str/includes? "connection was closed")))
321+
(is (nil? @ssl-session))))))
322+
323+
(deftest test-ssl-endpoint-identification
324+
(binding [*connection-options* {:ssl-context ssl/wrong-hostname-client-ssl-context-opts}]
325+
(let [ssl-session (atom nil)]
326+
(with-handler-options
327+
(ssl-session-capture-handler ssl-session)
328+
(assoc default-ssl-server-options :ssl-context ssl/wrong-hostname-server-ssl-context-opts)
329+
(is (thrown-with-msg? javax.net.ssl.SSLHandshakeException
330+
#"^No name matching localhost found$"
331+
@(http-get (str "https://localhost:" port))))
332+
(is (nil? @ssl-session))))))
333+
334+
(deftest test-disabling-ssl-endpoint-identification
335+
(binding [*connection-options* {:ssl-context ssl/wrong-hostname-client-ssl-context-opts
336+
:ssl-endpoint-id-alg nil}]
337+
(let [ssl-session (atom nil)]
338+
(with-handler-options
339+
(ssl-session-capture-handler ssl-session)
340+
(assoc default-ssl-server-options :ssl-context ssl/wrong-hostname-server-ssl-context-opts)
341+
(is (= 200 (:status @(http-get (str "https://localhost:" port)))))
342+
(is (some? @ssl-session))))))
315343

316344
(deftest test-invalid-body
317345
(let [client-url (str "http://localhost:" port)]

test/aleph/ssl.clj

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
ApplicationProtocolConfig$SelectedListenerFailureBehavior
99
ApplicationProtocolConfig$SelectorFailureBehavior
1010
ApplicationProtocolNames)
11+
(io.netty.handler.ssl.util SelfSignedCertificate)
1112
(java.io ByteArrayInputStream)
1213
(java.security KeyFactory PrivateKey)
1314
(java.security.cert CertificateFactory X509Certificate)
@@ -75,3 +76,14 @@
7576

7677
(def client-ssl-context
7778
(netty/ssl-client-context client-ssl-context-opts))
79+
80+
(def wrong-hostname-cert
81+
(SelfSignedCertificate. "some-random-hostname"))
82+
83+
(def wrong-hostname-server-ssl-context-opts
84+
{:private-key (.privateKey wrong-hostname-cert)
85+
:certificate-chain (.certificate wrong-hostname-cert)})
86+
87+
(def wrong-hostname-client-ssl-context-opts
88+
(assoc client-ssl-context-opts
89+
:trust-store (.certificate wrong-hostname-cert)))

0 commit comments

Comments
 (0)