Skip to content

Commit b414715

Browse files
authored
Merge pull request #612 from bevuta/improved-api-for-ssl-context-creation
Improved API for SSL context management (server and client)
2 parents 22ae585 + 93e6c7b commit b414715

File tree

5 files changed

+268
-68
lines changed

5 files changed

+268
-68
lines changed

src/aleph/http.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
| `port` | the port the server will bind to. If `0`, the server will bind to a random port.
3636
| `socket-address` | a `java.net.SocketAddress` specifying both the port and interface to bind to.
3737
| `bootstrap-transform` | a function that takes an `io.netty.bootstrap.ServerBootstrap` object, which represents the server, and modifies it.
38-
| `ssl-context` | an `io.netty.handler.ssl.SslContext` object if an SSL connection is desired |
38+
| `ssl-context` | an `io.netty.handler.ssl.SslContext` object or a map of SSL context options (see `aleph.netty/ssl-server-context` for more details) if an SSL connection is desired |
3939
| `manual-ssl?` | set to `true` to indicate that SSL is active, but the caller is managing it (this implies `:ssl-context` is nil). For example, this can be used if you want to use configure SNI (perhaps in `:pipeline-transform`) to select the SSL context based on the client's indicated host name. |
4040
| `pipeline-transform` | a function that takes an `io.netty.channel.ChannelPipeline` object, which represents a connection, and modifies it.
4141
| `executor` | a `java.util.concurrent.Executor` which is used to handle individual requests. To avoid this indirection you may specify `:none`, but in this case extreme care must be taken to avoid blocking operations on the handler's thread.
@@ -107,7 +107,7 @@
107107
the `connection-options` are a map describing behavior across all connections:
108108
109109
|:---|:---
110-
| `ssl-context` | an `io.netty.handler.ssl.SslContext` object, only required if a custom context is required
110+
| `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
111111
| `local-address` | an optional `java.net.SocketAddress` describing which local interface should be used
112112
| `bootstrap-transform` | a function that takes an `io.netty.bootstrap.Bootstrap` object and modifies it.
113113
| `pipeline-transform` | a function that takes an `io.netty.channel.ChannelPipeline` object, which represents a connection, and modifies it.

src/aleph/netty.clj

Lines changed: 237 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@
3434
NioSocketChannel
3535
NioDatagramChannel]
3636
[io.netty.handler.ssl
37+
ClientAuth
3738
SslContext
3839
SslContextBuilder
39-
SslHandler]
40+
SslHandler
41+
SslProvider]
4042
[io.netty.handler.codec DecoderException]
4143
[io.netty.handler.ssl.util
42-
SelfSignedCertificate InsecureTrustManagerFactory]
44+
SelfSignedCertificate
45+
InsecureTrustManagerFactory]
4346
[io.netty.resolver
4447
AddressResolverGroup
4548
NoopAddressResolverGroup
@@ -78,7 +81,10 @@
7881
LogLevel]
7982
[java.security.cert X509Certificate]
8083
[java.security PrivateKey]
81-
[javax.net.ssl SSLHandshakeException]))
84+
[javax.net.ssl
85+
SSLHandshakeException
86+
TrustManager
87+
TrustManagerFactory]))
8288

8389
;;;
8490

@@ -727,63 +733,230 @@
727733

728734
;;;
729735

736+
(defn coerce-ssl-provider ^SslProvider [provider]
737+
(case provider
738+
:jdk SslProvider/JDK
739+
:openssl SslProvider/OPENSSL
740+
:openssl-refcnt SslProvider/OPENSSL_REFCNT))
741+
742+
(let [cert-array-class (class (into-array X509Certificate []))]
743+
(defn- add-ssl-trust-manager! ^SslContextBuilder [^SslContextBuilder builder trust-store]
744+
(cond (instance? File trust-store)
745+
(.trustManager builder ^File trust-store)
746+
(instance? InputStream trust-store)
747+
(.trustManager builder ^InputStream trust-store)
748+
(instance? TrustManager trust-store)
749+
(.trustManager builder ^TrustManager trust-store)
750+
(instance? TrustManagerFactory trust-store)
751+
(.trustManager builder ^TrustManagerFactory trust-store)
752+
(instance? cert-array-class trust-store)
753+
(.trustManager builder ^"[Ljava.security.cert.X509Certificate;" trust-store)
754+
(sequential? trust-store)
755+
(let [^"[Ljava.security.cert.X509Certificate;" trust-store' (into-array X509Certificate trust-store)]
756+
(.trustManager builder trust-store'))
757+
:else
758+
(throw
759+
(IllegalArgumentException.
760+
"ssl context arguments invalid"))))
761+
762+
(defn ssl-client-context
763+
"Creates a new client SSL context.
764+
Keyword arguments are:
765+
|:---|:----
766+
| `private-key` | a `java.io.File`, `java.io.InputStream`, or `java.security.PrivateKey` containing the client-side private key.
767+
| `certificate-chain` | a `java.io.File`, `java.io.InputStream`, sequence of `java.security.cert.X509Certificate`, or array of `java.security.cert.X509Certificate` containing the client's certificate chain.
768+
| `private-key-password` | a string, the private key's password (optional).
769+
| `trust-store` | a `java.io.File`, `java.io.InputStream`, array of `java.security.cert.X509Certificate`, `javax.net.ssl.TrustManager`, or a `javax.net.ssl.TrustManagerFactory` to initialize the context's trust manager.
770+
| `ssl-provider` | `SslContext` implementation to use, on of `:jdk`, `:openssl` or `:openssl-refcnt`. Note, that when using OpenSSL based implementations, the library should be installed and linked properly.
771+
| `ciphers` | a sequence of strings, the cipher suites to enable, in the order of preference.
772+
| `protocols` | a sequence of strings, the TLS protocol versions to enable.
773+
| `session-cache-size` | the size of the cache used for storing SSL session objects.
774+
| `session-timeout` | the timeout for the cached SSL session objects, in seconds.
775+
Note that if specified, the types of `private-key` and `certificate-chain` must be \"compatible\": either both input streams, both files, or a private key and an array of certificates."
776+
([] (ssl-client-context {}))
777+
([{:keys [private-key
778+
^String private-key-password
779+
certificate-chain
780+
trust-store
781+
ssl-provider
782+
^Iterable ciphers
783+
protocols
784+
^long session-cache-size
785+
^long session-timeout]}]
786+
(let [^SslContextBuilder
787+
builder (SslContextBuilder/forClient)
788+
789+
^SslContextBuilder
790+
builder (if (or private-key certificate-chain)
791+
(cond (and (instance? File private-key)
792+
(instance? File certificate-chain))
793+
(.keyManager builder
794+
^File certificate-chain
795+
^File private-key
796+
private-key-password)
797+
(and (instance? InputStream private-key)
798+
(instance? InputStream certificate-chain))
799+
(.keyManager builder
800+
^InputStream certificate-chain
801+
^InputStream private-key
802+
private-key-password)
803+
(and (instance? PrivateKey private-key)
804+
(instance? cert-array-class certificate-chain))
805+
(.keyManager builder
806+
^PrivateKey private-key
807+
private-key-password
808+
^"[Ljava.security.cert.X509Certificate;" certificate-chain)
809+
(and (instance? PrivateKey private-key)
810+
(sequential? certificate-chain))
811+
(let [^"[Ljava.security.cert.X509Certificate;" certificate-chain' (into-array X509Certificate certificate-chain)]
812+
(.keyManager builder
813+
^PrivateKey private-key
814+
private-key-password
815+
certificate-chain'))
816+
:else
817+
(throw
818+
(IllegalArgumentException.
819+
"ssl context arguments invalid")))
820+
builder)
821+
822+
^SslContextBuilder
823+
builder (cond-> builder
824+
(some? trust-store)
825+
(add-ssl-trust-manager! trust-store)
826+
827+
(some? ssl-provider)
828+
(.sslProvider (coerce-ssl-provider ssl-provider))
829+
830+
(some? ciphers)
831+
(.ciphers ciphers)
832+
833+
(some? protocols)
834+
(.protocols ^"[Ljava.lang.String;" (into-array String protocols))
835+
836+
(some? session-cache-size)
837+
(.sessionCacheSize session-cache-size)
838+
839+
(some? session-timeout)
840+
(.sessionTimeout session-timeout))]
841+
842+
(.build builder))))
843+
844+
(defn ssl-server-context
845+
"Creates a new server SSL context.
846+
Keyword arguments are:
847+
|:---|:----
848+
| `private-key` | a `java.io.File`, `java.io.InputStream`, or `java.security.PrivateKey` containing the server-side private key.
849+
| `certificate-chain` | a `java.io.File`, `java.io.InputStream`, or array of `java.security.cert.X509Certificate` containing the server's certificate chain.
850+
| `private-key-password` | a string, the private key's password (optional).
851+
| `trust-store` | a `java.io.File`, `java.io.InputStream`, sequence of `java.security.cert.X509Certificate`, array of `java.security.cert.X509Certificate`, `javax.net.ssl.TrustManager`, or a `javax.net.ssl.TrustManagerFactory` to initialize the context's trust manager.
852+
| `ssl-provider` | `SslContext` implementation to use, on of `:jdk`, `:openssl` or `:openssl-refcnt`. Note, that when using OpenSSL based implementations, the library should be installed and linked properly.
853+
| `ciphers` | a sequence of strings, the cipher suites to enable, in the order of preference.
854+
| `protocols` | a sequence of strings, the TLS protocol versions to enable.
855+
| `session-cache-size` | the size of the cache used for storing SSL session objects.
856+
| `session-timeout` | the timeout for the cached SSL session objects, in seconds.
857+
| `start-tls` | if the first write request shouldn't be encrypted.
858+
| `client-auth` | the client authentication mode, one of `:none`, `:optional` or `:require`.
859+
Note that if specified, the types of `private-key` and `certificate-chain` must be \"compatible\": either both input streams, both files, or a private key and an array of certificates."
860+
([] (ssl-server-context {}))
861+
([{:keys [private-key
862+
^String private-key-password
863+
certificate-chain
864+
trust-store
865+
ssl-provider
866+
^Iterable ciphers
867+
protocols
868+
^long session-cache-size
869+
^long session-timeout
870+
start-tls
871+
client-auth]}]
872+
(let [^SslContextBuilder
873+
b (cond (and (instance? File private-key)
874+
(instance? File certificate-chain))
875+
(SslContextBuilder/forServer ^File certificate-chain
876+
^File private-key
877+
private-key-password)
878+
(and (instance? InputStream private-key)
879+
(instance? InputStream certificate-chain))
880+
(SslContextBuilder/forServer ^InputStream certificate-chain
881+
^InputStream private-key
882+
private-key-password)
883+
(and (instance? PrivateKey private-key)
884+
(instance? cert-array-class certificate-chain))
885+
(SslContextBuilder/forServer ^PrivateKey private-key
886+
private-key-password
887+
^"[Ljava.security.cert.X509Certificate;" certificate-chain)
888+
(and (instance? PrivateKey private-key)
889+
(sequential? certificate-chain))
890+
(let [^"[Ljava.security.cert.X509Certificate;" certificate-chain' (into-array X509Certificate certificate-chain)]
891+
(SslContextBuilder/forServer ^PrivateKey private-key
892+
private-key-password
893+
certificate-chain'))
894+
:else
895+
(throw
896+
(IllegalArgumentException.
897+
"ssl context arguments invalid")))
898+
899+
^SslContextBuilder
900+
b (cond-> b
901+
(some? trust-store)
902+
(add-ssl-trust-manager! trust-store)
903+
904+
(some? ssl-provider)
905+
(.sslProvider (coerce-ssl-provider ssl-provider))
906+
907+
(some? ciphers)
908+
(.ciphers ciphers)
909+
910+
911+
(some? protocols)
912+
(.protocols ^"[Ljava.lang.String;" (into-array String protocols))
913+
914+
915+
(some? session-cache-size)
916+
(.sessionCacheSize session-cache-size)
917+
918+
(some? session-timeout)
919+
(.sessionTimeout session-timeout)
920+
921+
(some? start-tls)
922+
(.startTls (boolean start-tls))
923+
924+
(some? client-auth)
925+
(.clientAuth (case client-auth
926+
:none ClientAuth/NONE
927+
:optional ClientAuth/OPTIONAL
928+
:require ClientAuth/REQUIRE)))]
929+
(.build b)))))
930+
730931
(defn self-signed-ssl-context
731932
"A self-signed SSL context for servers."
732933
[]
733934
(let [cert (SelfSignedCertificate.)]
734-
(.build (SslContextBuilder/forServer (.certificate cert) (.privateKey cert)))))
935+
(ssl-server-context {:private-key (.privateKey cert)
936+
:certificate-chain (.certificate cert)})))
735937

736938
(defn insecure-ssl-client-context []
737-
(-> (SslContextBuilder/forClient)
738-
(.trustManager InsecureTrustManagerFactory/INSTANCE)
739-
.build))
740-
741-
(defn- check-ssl-args
742-
[private-key certificate-chain]
743-
(when-not
744-
(or (and (instance? File private-key) (instance? File certificate-chain))
745-
(and (instance? InputStream private-key) (instance? InputStream certificate-chain))
746-
(and (instance? PrivateKey private-key) (instance? (class (into-array X509Certificate [])) certificate-chain)))
747-
(throw (IllegalArgumentException. "ssl-client-context arguments invalid"))))
748-
749-
(set! *warn-on-reflection* false)
750-
751-
(defn ssl-client-context
752-
"Creates a new client SSL context.
753-
754-
Keyword arguments are:
755-
756-
|:---|:----
757-
| `private-key` | A `java.io.File`, `java.io.InputStream`, or `java.security.PrivateKey` containing the client-side private key.
758-
| `certificate-chain` | A `java.io.File`, `java.io.InputStream`, or array of `java.security.cert.X509Certificate` containing the client's certificate chain.
759-
| `private-key-password` | A string, the private key's password (optional).
760-
| `trust-store` | A `java.io.File`, `java.io.InputStream`, array of `java.security.cert.X509Certificate`, or a `javax.net.ssl.TrustManagerFactory` to initialize the context's trust manager.
761-
762-
Note that if specified, the types of `private-key` and `certificate-chain` must be
763-
\"compatible\": either both input streams, both files, or a private key and an array
764-
of certificates."
765-
([] (ssl-client-context {}))
766-
([{:keys [private-key private-key-password certificate-chain trust-store]}]
767-
(-> (SslContextBuilder/forClient)
768-
(#(if (and private-key certificate-chain)
769-
(do
770-
(check-ssl-args private-key certificate-chain)
771-
(if (instance? (class (into-array X509Certificate [])) certificate-chain)
772-
(.keyManager %
773-
private-key
774-
private-key-password
775-
certificate-chain)
776-
(.keyManager %
777-
certificate-chain
778-
private-key
779-
private-key-password)))
780-
%))
781-
(#(if trust-store
782-
(.trustManager % trust-store)
783-
%))
784-
.build)))
785-
786-
(set! *warn-on-reflection* true)
939+
(ssl-client-context {:trust-store InsecureTrustManagerFactory/INSTANCE}))
940+
941+
(defn- coerce-ssl-context [options->context ssl-context]
942+
(cond
943+
(instance? SslContext ssl-context)
944+
ssl-context
945+
946+
;; in future this option might be interesing
947+
;; for turning application config (e.g. ALPN)
948+
;; depending on the server's capabilities
949+
(instance? SslContextBuilder ssl-context)
950+
(.build ^SslContextBuilder ssl-context)
951+
952+
(map? ssl-context)
953+
(options->context ssl-context)))
954+
955+
(def ^:private coerce-ssl-server-context
956+
(partial coerce-ssl-context ssl-server-context))
957+
958+
(def ^:private coerce-ssl-client-context
959+
(partial coerce-ssl-context ssl-client-context))
787960

788961
(defn channel-ssl-session [^Channel ch]
789962
(some-> ch
@@ -975,7 +1148,7 @@ initialize an DnsAddressResolverGroup instance.
9751148
epoll?
9761149
nil))
9771150
([pipeline-builder
978-
^SslContext ssl-context
1151+
ssl-context
9791152
bootstrap-transform
9801153
^SocketAddress remote-address
9811154
^SocketAddress local-address
@@ -988,6 +1161,10 @@ initialize an DnsAddressResolverGroup instance.
9881161
EpollSocketChannel
9891162
NioSocketChannel)
9901163

1164+
^SslContext
1165+
ssl-context (when (some? ssl-context)
1166+
(coerce-ssl-client-context ssl-context))
1167+
9911168
pipeline-builder (if ssl-context
9921169
(fn [^ChannelPipeline p]
9931170
(.addLast p "ssl-handler"
@@ -1026,7 +1203,7 @@ initialize an DnsAddressResolverGroup instance.
10261203

10271204
(defn start-server
10281205
[pipeline-builder
1029-
^SslContext ssl-context
1206+
ssl-context
10301207
bootstrap-transform
10311208
on-close
10321209
^SocketAddress socket-address
@@ -1053,6 +1230,10 @@ initialize an DnsAddressResolverGroup instance.
10531230
transport
10541231
(if epoll? :epoll :nio)
10551232

1233+
^SslContext
1234+
ssl-context (when (some? ssl-context)
1235+
(coerce-ssl-server-context ssl-context))
1236+
10561237
pipeline-builder
10571238
(if ssl-context
10581239
(fn [^ChannelPipeline p]

0 commit comments

Comments
 (0)