Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sni support #1954

Merged
merged 35 commits into from
Apr 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
32fae94
Flesh out SNI support for NetServer/NetClient based on CN attribute i…
vietj Apr 12, 2017
0d01a44
Minor
vietj Apr 13, 2017
012ed75
Refactor a bit the net tests
vietj Apr 14, 2017
de3608a
Start to support sni for http
vietj Apr 17, 2017
cf042da
Check obtainedcertificate for SNI tests and add isSsl() / peerCertifi…
vietj Apr 17, 2017
68e4710
Support wildcard certificate matching for SNI
vietj Apr 18, 2017
08318b8
Support certificate subject alternative names
vietj Apr 18, 2017
ca01622
Refactor to use a Future<Channel> in VertxSniHandler instead of a lis…
vietj Apr 19, 2017
34c1085
Add a test for SNI + ALPN and improve a bit the channel configuration…
vietj Apr 19, 2017
d65bfc8
Support SNI with startTLS
vietj Apr 19, 2017
9f74e5f
Minor
vietj Apr 19, 2017
aa177e2
Rework a bit the HttpClient SNI support
vietj Apr 19, 2017
37809fc
Minor
vietj Apr 19, 2017
691f31e
Use the peer host as host in the pool connection key
vietj Apr 19, 2017
06c4d2e
Minor rename getSni -> isSni option
vietj Apr 19, 2017
1a7b60b
Rework a bit the tests
vietj Apr 20, 2017
5140c79
Test SNI in NetServer options
vietj Apr 23, 2017
301bcdf
Add test for P12 key stores
vietj Apr 23, 2017
93cf661
Sni pfx test should use a PfxStore
vietj Apr 23, 2017
4ceee83
Support SNI for PemKeyCertOptions
vietj Apr 23, 2017
846a79b
Start SNI doc
vietj Apr 24, 2017
5837615
Typo
vietj Apr 24, 2017
435b507
Typo
vietj Apr 24, 2017
186ba9e
Doc adjustment
vietj Apr 24, 2017
6f6c160
Add test with SNI and proxy
vietj Apr 24, 2017
cdf0eda
Add test for websocket and SNI
vietj Apr 24, 2017
05643b5
More doc
vietj Apr 24, 2017
0a2d53c
Remove hardcoded password and replace by null as private keys are not…
vietj Apr 24, 2017
12208e7
Check disabled SNI
vietj Apr 25, 2017
82e4ccf
Allow to get the SNI server name on the connection
vietj Apr 26, 2017
c06dbd2
Rework the SNI client disabled/enabled test
vietj Apr 26, 2017
aaa89d5
Rework the client SNI feedback according to the PR
vietj Apr 26, 2017
9501567
Fix incorrect test
vietj Apr 26, 2017
32bb613
PR feedback
vietj Apr 26, 2017
ffe83c0
Remove unused imports
vietj Apr 27, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 46 additions & 8 deletions src/main/asciidoc/dataobjects.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,11 @@ Add an enabled cipher suite, appended to the ordered suites.
+++
Add an enabled SSL/TLS protocols, appended to the ordered protocols.
+++
|[[forceSni]]`forceSni`|`Boolean`|
+++
By default, the server name is only sent for Fully Qualified Domain Name (FQDN), setting
this property to <code>true</code> forces the server name to be always sent.
+++
|[[http2ClearTextUpgrade]]`http2ClearTextUpgrade`|`Boolean`|
+++
Set to <code>true</code> when an <i>h2c</i> connection is established using an HTTP/1.1 upgrade request, and <code>false</code>
Expand Down Expand Up @@ -1042,6 +1047,10 @@ Set the value of reuse address
+++
Set the TCP send buffer size
+++
|[[sni]]`sni`|`Boolean`|
+++
Set whether the server supports Server Name Indiciation
+++
|[[soLinger]]`soLinger`|`Number (int)`|
+++
Set whether SO_linger keep alive is enabled
Expand Down Expand Up @@ -1392,6 +1401,10 @@ Set the value of reuse address
+++
Set the TCP send buffer size
+++
|[[sni]]`sni`|`Boolean`|
+++
Set whether the server supports Server Name Indiciation
+++
|[[soLinger]]`soLinger`|`Number (int)`|
+++
Set whether SO_linger keep alive is enabled
Expand Down Expand Up @@ -1617,11 +1630,11 @@ Sets whether or not this option can receive a value.
== PemKeyCertOptions

++++
Key store options configuring a private key and its certificate based on
Key store options configuring a list of private key and its certificate based on
<i>Privacy-enhanced Electronic Email</i> (PEM) files.
<p>

The key file must contain a <b>non encrypted</b> private key in <b>PKCS8</b> format wrapped in a PEM
A key file must contain a <b>non encrypted</b> private key in <b>PKCS8</b> format wrapped in a PEM
block, for example:
<p>

Expand All @@ -1633,7 +1646,7 @@ Sets whether or not this option can receive a value.
-----END PRIVATE KEY-----
</pre><p>

The certificate file must contain an X.509 certificate wrapped in a PEM block, for example:
A certificate file must contain an X.509 certificate wrapped in a PEM block, for example:
<p>

<pre>
Expand All @@ -1644,7 +1657,7 @@ Sets whether or not this option can receive a value.
-----END CERTIFICATE-----
</pre>

The key and certificate can either be loaded by Vert.x from the filesystem:
Keys and certificates can either be loaded by Vert.x from the filesystem:
<p>
<pre>
HttpServerOptions options = new HttpServerOptions();
Expand All @@ -1658,6 +1671,15 @@ Sets whether or not this option can receive a value.
Buffer cert = vertx.fileSystem().readFileSync("/mycert.pem");
options.setPemKeyCertOptions(new PemKeyCertOptions().setKeyValue(key).setCertValue(cert));
</pre>

Several key/certificate pairs can be used:
<p>
<pre>
HttpServerOptions options = new HttpServerOptions();
options.setPemKeyCertOptions(new PemKeyCertOptions()
.addKeyPath("/mykey1.pem").addCertPath("/mycert1.pem")
.addKeyPath("/mykey2.pem").addCertPath("/mycert2.pem"));
</pre>
++++
'''

Expand All @@ -1667,19 +1689,35 @@ Sets whether or not this option can receive a value.
^|Name | Type ^| Description
|[[certPath]]`certPath`|`String`|
+++
Set the path to the certificate
Set the path of the first certificate, replacing the previous certificates paths
+++
|[[certPaths]]`certPaths`|`Array of String`|
+++
Set all the paths to the certificates files
+++
|[[certValue]]`certValue`|`Buffer`|
+++
Set the certificate as a buffer
Set the first certificate as a buffer, replacing the previous certificates buffers
+++
|[[certValues]]`certValues`|`Array of Buffer`|
+++
Set all the certificates as a list of buffer
+++
|[[keyPath]]`keyPath`|`String`|
+++
Set the path to the key file
Set the path of the first key file, replacing the keys paths
+++
|[[keyPaths]]`keyPaths`|`Array of String`|
+++
Set all the paths to the keys files
+++
|[[keyValue]]`keyValue`|`Buffer`|
+++
Set the key a a buffer
Set the first key a a buffer, replacing the previous keys buffers
+++
|[[keyValues]]`keyValues`|`Array of Buffer`|
+++
Set all the keys as a list of buffer
+++
|===

Expand Down
6 changes: 6 additions & 0 deletions src/main/asciidoc/java/http.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,12 @@ client SSL/TLS (such as trust, key/certificate, ciphers, ALPN, ...) will be reus
Likewise `link:../../apidocs/io/vertx/core/http/HttpClient.html#requestAbs-io.vertx.core.http.HttpMethod-java.lang.String-[requestAbs]` scheme
also overrides the default client setting.

==== Server Name Indication (SNI)

Vert.x http servers can be configured to use SNI in exactly the same way as net servers.

Vert.x http client will present the actual hostname as _server name_ during the TLS handshake.

=== WebSockets

http://en.wikipedia.org/wiki/WebSocket[WebSockets] are a web technology that allows a full duplex socket-like
Expand Down
72 changes: 72 additions & 0 deletions src/main/asciidoc/java/net.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,78 @@ options = new NetServerOptions().
setOpenSslEngineOptions(new OpenSSLEngineOptions());
----

==== Server Name Indication (SNI)

Server Name Indication (SNI) is a TLS extension by which a client specifies an hostname attempting to connect: during
the TLS handshake the clients gives a server name and the server can use it to respond with a specific certificate
for this server name instead of the default deployed certificate.

When SNI is active the server uses

* the certificate CN or SAN DNS (Subject Alternative Name with DNS) to do an exact match, e.g `www.example.com`
* the certificate CN or SAN DNS certificate to match a wildcard name, e.g `*.example.com`
* otherwise the first certificate when the client does not present a server name or the presenter server name cannot be matched

You can enable SNI on the server by setting `link:../../apidocs/io/vertx/core/net/NetServerOptions.html#setSni-boolean-[setSni]` to `true` and
configured the server with multiple key/certificate pairs.

Java KeyStore files or PKCS12 files can store multiple key/cert pairs out of the box.

[source,java]
----
JksOptions keyCertOptions = new JksOptions().setPath("keystore.jks").setPassword("wibble");

NetServer netServer = vertx.createNetServer(new NetServerOptions()
.setKeyStoreOptions(keyCertOptions)
.setSsl(true)
.setSni(true)
);
----

`link:../../apidocs/io/vertx/core/net/PemKeyCertOptions.html[PemKeyCertOptions]` can be configured to hold multiple entries:

[source,java]
----
PemKeyCertOptions keyCertOptions = new PemKeyCertOptions()
.setKeyPaths(Arrays.asList("default-key.pem", "host1-key.pem", "etc..."))
.setCertPaths(Arrays.asList("default-cert.pem", "host2-key.pem", "etc...")
);

NetServer netServer = vertx.createNetServer(new NetServerOptions()
.setPemKeyCertOptions(keyCertOptions)
.setSsl(true)
.setSni(true)
);
----

The client implicitly sends the connecting host as an SNI server name for Fully Qualified Domain Name (FQDN).

You can provide an explicit server name when connecting a socket

[source,java]
----
NetClient client = vertx.createNetClient(new NetClientOptions()
.setTrustStoreOptions(trustOptions)
.setSsl(true)
);

// Connect to 'localhost' and present 'server.name' server name
client.connect(1234, "localhost", "server.name", res -> {
if (res.succeeded()) {
System.out.println("Connected!");
NetSocket socket = res.result();
} else {
System.out.println("Failed to connect: " + res.cause().getMessage());
}
});
----

It can be used for different purposes:

* present a server name different than the server host
* present a server name while connecting to an IP
* force to present a server name when using shortname

==== Application-Layer Protocol Negotiation (ALPN)

Application-Layer Protocol Negotiation (ALPN) is a TLS extension for application layer protocol negotiation. It is used by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public static void fromJson(JsonObject json, HttpClientOptions obj) {
if (json.getValue("defaultPort") instanceof Number) {
obj.setDefaultPort(((Number)json.getValue("defaultPort")).intValue());
}
if (json.getValue("forceSni") instanceof Boolean) {
obj.setForceSni((Boolean)json.getValue("forceSni"));
}
if (json.getValue("http2ClearTextUpgrade") instanceof Boolean) {
obj.setHttp2ClearTextUpgrade((Boolean)json.getValue("http2ClearTextUpgrade"));
}
Expand Down Expand Up @@ -113,6 +116,7 @@ public static void toJson(HttpClientOptions obj, JsonObject json) {
json.put("defaultHost", obj.getDefaultHost());
}
json.put("defaultPort", obj.getDefaultPort());
json.put("forceSni", obj.isForceSni());
json.put("http2ClearTextUpgrade", obj.isHttp2ClearTextUpgrade());
json.put("http2ConnectionWindowSize", obj.getHttp2ConnectionWindowSize());
json.put("http2MaxPoolSize", obj.getHttp2MaxPoolSize());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public static void fromJson(JsonObject json, NetServerOptions obj) {
if (json.getValue("port") instanceof Number) {
obj.setPort(((Number)json.getValue("port")).intValue());
}
if (json.getValue("sni") instanceof Boolean) {
obj.setSni((Boolean)json.getValue("sni"));
}
}

public static void toJson(NetServerOptions obj, JsonObject json) {
Expand All @@ -54,5 +57,6 @@ public static void toJson(NetServerOptions obj, JsonObject json) {
json.put("host", obj.getHost());
}
json.put("port", obj.getPort());
json.put("sni", obj.isSni());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,69 @@ public static void fromJson(JsonObject json, PemKeyCertOptions obj) {
if (json.getValue("certPath") instanceof String) {
obj.setCertPath((String)json.getValue("certPath"));
}
if (json.getValue("certPaths") instanceof JsonArray) {
java.util.ArrayList<java.lang.String> list = new java.util.ArrayList<>();
json.getJsonArray("certPaths").forEach( item -> {
if (item instanceof String)
list.add((String)item);
});
obj.setCertPaths(list);
}
if (json.getValue("certValue") instanceof String) {
obj.setCertValue(io.vertx.core.buffer.Buffer.buffer(java.util.Base64.getDecoder().decode((String)json.getValue("certValue"))));
}
if (json.getValue("certValues") instanceof JsonArray) {
java.util.ArrayList<io.vertx.core.buffer.Buffer> list = new java.util.ArrayList<>();
json.getJsonArray("certValues").forEach( item -> {
if (item instanceof String)
list.add(io.vertx.core.buffer.Buffer.buffer(java.util.Base64.getDecoder().decode((String)item)));
});
obj.setCertValues(list);
}
if (json.getValue("keyPath") instanceof String) {
obj.setKeyPath((String)json.getValue("keyPath"));
}
if (json.getValue("keyPaths") instanceof JsonArray) {
java.util.ArrayList<java.lang.String> list = new java.util.ArrayList<>();
json.getJsonArray("keyPaths").forEach( item -> {
if (item instanceof String)
list.add((String)item);
});
obj.setKeyPaths(list);
}
if (json.getValue("keyValue") instanceof String) {
obj.setKeyValue(io.vertx.core.buffer.Buffer.buffer(java.util.Base64.getDecoder().decode((String)json.getValue("keyValue"))));
}
if (json.getValue("keyValues") instanceof JsonArray) {
java.util.ArrayList<io.vertx.core.buffer.Buffer> list = new java.util.ArrayList<>();
json.getJsonArray("keyValues").forEach( item -> {
if (item instanceof String)
list.add(io.vertx.core.buffer.Buffer.buffer(java.util.Base64.getDecoder().decode((String)item)));
});
obj.setKeyValues(list);
}
}

public static void toJson(PemKeyCertOptions obj, JsonObject json) {
if (obj.getCertPath() != null) {
json.put("certPath", obj.getCertPath());
if (obj.getCertPaths() != null) {
JsonArray array = new JsonArray();
obj.getCertPaths().forEach(item -> array.add(item));
json.put("certPaths", array);
}
if (obj.getCertValue() != null) {
json.put("certValue", obj.getCertValue().getBytes());
if (obj.getCertValues() != null) {
JsonArray array = new JsonArray();
obj.getCertValues().forEach(item -> array.add(item.getBytes()));
json.put("certValues", array);
}
if (obj.getKeyPath() != null) {
json.put("keyPath", obj.getKeyPath());
if (obj.getKeyPaths() != null) {
JsonArray array = new JsonArray();
obj.getKeyPaths().forEach(item -> array.add(item));
json.put("keyPaths", array);
}
if (obj.getKeyValue() != null) {
json.put("keyValue", obj.getKeyValue().getBytes());
if (obj.getKeyValues() != null) {
JsonArray array = new JsonArray();
obj.getKeyValues().forEach(item -> array.add(item.getBytes()));
json.put("keyValues", array);
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/examples/HTTPExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.vertx.core.buffer.Buffer;
import io.vertx.core.file.AsyncFile;
import io.vertx.core.http.*;
import io.vertx.core.net.JksOptions;
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.net.ProxyType;
import io.vertx.core.streams.Pump;
Expand Down Expand Up @@ -789,5 +790,4 @@ public void setSSLPerRequest(HttpClient client) {
System.out.println("Received response with status code " + response.statusCode());
});
}

}
42 changes: 42 additions & 0 deletions src/main/java/examples/NetExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.vertx.core.net.*;

import java.security.cert.CertificateException;
import java.util.Arrays;

/**
* Created by tim on 19/01/15.
Expand Down Expand Up @@ -598,4 +599,45 @@ public void example50(Vertx vertx) throws CertificateException {
.requestHandler(req -> req.response().end("Hello!"))
.listen(8080);
}

public void configureSNIServer(Vertx vertx) {
JksOptions keyCertOptions = new JksOptions().setPath("keystore.jks").setPassword("wibble");

NetServer netServer = vertx.createNetServer(new NetServerOptions()
.setKeyStoreOptions(keyCertOptions)
.setSsl(true)
.setSni(true)
);
}

public void configureSNIServerWithPems(Vertx vertx) {
PemKeyCertOptions keyCertOptions = new PemKeyCertOptions()
.setKeyPaths(Arrays.asList("default-key.pem", "host1-key.pem", "etc..."))
.setCertPaths(Arrays.asList("default-cert.pem", "host2-key.pem", "etc...")
);

NetServer netServer = vertx.createNetServer(new NetServerOptions()
.setPemKeyCertOptions(keyCertOptions)
.setSsl(true)
.setSni(true)
);
}

public void useSNIInClient(Vertx vertx, JksOptions trustOptions) {

NetClient client = vertx.createNetClient(new NetClientOptions()
.setTrustStoreOptions(trustOptions)
.setSsl(true)
);

// Connect to 'localhost' and present 'server.name' server name
client.connect(1234, "localhost", "server.name", res -> {
if (res.succeeded()) {
System.out.println("Connected!");
NetSocket socket = res.result();
} else {
System.out.println("Failed to connect: " + res.cause().getMessage());
}
});
}
}
Loading