Skip to content

Commit

Permalink
Add alias property in JksOptions for net/http servers
Browse files Browse the repository at this point in the history
Fixes eclipse-vertx#3926

This can be useful if users don't control the JKS file and it contains
multiple entries.

In this case, the SSL engine chooses the first one by default but it
might not what the user expects.

Signed-off-by: Thomas Segismont <[email protected]>
  • Loading branch information
tsegismont committed May 18, 2021
1 parent c2c5970 commit 5480d62
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 23 deletions.
8 changes: 8 additions & 0 deletions src/main/generated/io/vertx/core/net/JksOptionsConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public class JksOptionsConverter {
public static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, JksOptions obj) {
for (java.util.Map.Entry<String, Object> member : json) {
switch (member.getKey()) {
case "alias":
if (member.getValue() instanceof String) {
obj.setAlias((String)member.getValue());
}
break;
case "password":
if (member.getValue() instanceof String) {
obj.setPassword((String)member.getValue());
Expand All @@ -40,6 +45,9 @@ public static void toJson(JksOptions obj, JsonObject json) {
}

public static void toJson(JksOptions obj, java.util.Map<String, Object> json) {
if (obj.getAlias() != null) {
json.put("alias", obj.getAlias());
}
if (obj.getPassword() != null) {
json.put("password", obj.getPassword());
}
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/io/vertx/core/net/JksOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
@DataObject(generateConverter = true)
public class JksOptions extends KeyStoreOptionsBase {

private String alias;

/**
* Default constructor
*/
Expand All @@ -39,6 +41,7 @@ public JksOptions() {
*/
public JksOptions(JksOptions other) {
super(other);
alias = other.alias;
}

/**
Expand All @@ -51,10 +54,12 @@ public JksOptions(JsonObject json) {
JksOptionsConverter.fromJson(json, this);
}

@Override
public JksOptions setPassword(String password) {
return (JksOptions) super.setPassword(password);
}

@Override
public JksOptions setPath(String path) {
return (JksOptions) super.setPath(path);
}
Expand All @@ -65,10 +70,12 @@ public JksOptions setPath(String path) {
* @param value the key store as a buffer
* @return a reference to this, so the API can be used fluently
*/
@Override
public JksOptions setValue(Buffer value) {
return (JksOptions) super.setValue(value);
}

@Override
public JksOptions copy() {
return new JksOptions(this);
}
Expand All @@ -83,4 +90,21 @@ public JsonObject toJson() {
JksOptionsConverter.toJson(this, json);
return json;
}

/**
* @return the alias for a server certificate when the keystore has more than one, or {@code null}
*/
public String getAlias() {
return alias;
}

/**
* Set the alias for a server certificate when the keystore has more than one.
*
* @return a reference to this, so the API can be used fluently
*/
public JksOptions setAlias(String alias) {
this.alias = alias;
return this;
}
}
119 changes: 104 additions & 15 deletions src/main/java/io/vertx/core/net/impl/SSLHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,14 @@
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.net.ClientOptionsBase;
import io.vertx.core.net.JdkSSLEngineOptions;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetServerOptions;
import io.vertx.core.net.OpenSSLEngineOptions;
import io.vertx.core.net.SSLEngineOptions;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.net.TCPSSLOptions;
import io.vertx.core.net.TrustOptions;
import io.vertx.core.net.*;

import javax.net.ssl.*;
import java.io.ByteArrayInputStream;
import java.security.cert.CRL;
import java.net.Socket;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.cert.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -291,7 +281,18 @@ private SslContext createContext(VertxInternal vertx, boolean useAlpn, X509KeyMa
}

private KeyManagerFactory getKeyMgrFactory(VertxInternal vertx) throws Exception {
return keyCertOptions == null ? null : keyCertOptions.getKeyManagerFactory(vertx);
if (keyCertOptions == null) {
return null;
}
KeyManagerFactory kmf = keyCertOptions.getKeyManagerFactory(vertx);
if (!client && keyCertOptions instanceof JksOptions) {
JksOptions jksOptions = (JksOptions) keyCertOptions;
String alias = jksOptions.getAlias();
if (alias != null) {
return new ChosenAliasKeyManagerFactory(alias, kmf);
}
}
return kmf;
}

private TrustManagerFactory getTrustMgrFactory(VertxInternal vertx, String serverName) throws Exception {
Expand Down Expand Up @@ -530,4 +531,92 @@ public SSLEngine createEngine(VertxInternal vertx) {
configureEngine(engine, null);
return engine;
}

private static class ChosenAliasKeyManagerFactory extends KeyManagerFactory {
ChosenAliasKeyManagerFactory(String alias, KeyManagerFactory delegate) {
super(new ChosenAliasKeyManagerFactorySpi(alias, delegate), delegate.getProvider(), KeyManagerFactory.getDefaultAlgorithm());
}
}

private static class ChosenAliasKeyManagerFactorySpi extends KeyManagerFactorySpi {
private final String alias;
private final KeyManagerFactory delegate;

ChosenAliasKeyManagerFactorySpi(String alias, KeyManagerFactory delegate) {
this.alias = alias;
this.delegate = delegate;
}

@Override
protected void engineInit(KeyStore ks, char[] password) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
delegate.init(ks, password);
}

@Override
protected void engineInit(ManagerFactoryParameters spec) throws InvalidAlgorithmParameterException {
delegate.init(spec);
}

@Override
protected KeyManager[] engineGetKeyManagers() {
KeyManager[] keyManagers = this.delegate.getKeyManagers();
for (int i = 0; i < keyManagers.length; i++) {
KeyManager keyManager = keyManagers[i];
if (keyManager instanceof X509KeyManager) {
keyManagers[i] = new ChosenAliasKeyManager(alias, (X509KeyManager) keyManager);
}
}
return keyManagers;
}
}

private static class ChosenAliasKeyManager extends X509ExtendedKeyManager {
private final String alias;
private final X509KeyManager delegate;

ChosenAliasKeyManager(String alias, X509KeyManager delegate) {
this.alias = alias;
this.delegate = delegate;
}

@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
return alias;
}

@Override
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
throw new UnsupportedOperationException();
}

@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return delegate.getClientAliases(keyType, issuers);
}

@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return delegate.chooseClientAlias(keyType, issuers, socket);
}

@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return delegate.getServerAliases(keyType, issuers);
}

@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return delegate.chooseServerAlias(keyType, issuers, socket);
}

@Override
public X509Certificate[] getCertificateChain(String alias) {
return delegate.getCertificateChain(alias);
}

@Override
public PrivateKey getPrivateKey(String alias) {
return delegate.getPrivateKey(alias);
}
}
}
31 changes: 23 additions & 8 deletions src/test/java/io/vertx/core/net/NetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,32 @@
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.*;
import io.vertx.core.*;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.Message;
import io.vertx.core.eventbus.MessageConsumer;
import io.vertx.core.http.*;
import io.vertx.core.impl.ConcurrentHashSet;
import io.vertx.core.net.impl.HAProxyMessageCompletionHandler;
import io.vertx.core.net.impl.NetSocketInternal;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.impl.HAProxyMessageCompletionHandler;
import io.vertx.core.net.impl.NetServerImpl;
import io.vertx.core.net.impl.NetSocketInternal;
import io.vertx.core.net.impl.VertxHandler;
import io.vertx.core.streams.ReadStream;
import io.vertx.test.core.*;
import io.vertx.test.core.CheckingSender;
import io.vertx.test.core.TestUtils;
import io.vertx.test.core.VertxTestBase;
import io.vertx.test.netty.TestLoggerFactory;
import io.vertx.test.proxy.*;
import io.vertx.test.tls.Cert;
import io.vertx.test.tls.Trust;
import io.vertx.test.netty.TestLoggerFactory;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
Expand All @@ -49,7 +51,10 @@
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.security.cert.X509Certificate;
import java.io.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.*;
Expand Down Expand Up @@ -1470,6 +1475,16 @@ public void testSniWithTrailingDotHost() throws Exception {
assertEquals("host2.com", test.indicatedServerName);
}

@Test
public void testServerCertificateMultiple() throws Exception {
TLSTest test = new TLSTest()
.serverCert(Cert.MULTIPLE_JKS)
.clientTrustAll(true);
test.run(true);
await();
assertEquals("precious", cnOf(test.clientPeerCert()));
}

void testTLS(Cert<?> clientCert, Trust<?> clientTrust,
Cert<?> serverCert, Trust<?> serverTrust,
boolean requireClientAuth, boolean clientTrustAll,
Expand Down
1 change: 1 addition & 0 deletions src/test/java/io/vertx/test/tls/Cert.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ public interface Cert<K extends KeyCertOptions> extends Supplier<K> {
.addKeyPath("tls/host3-key.pem").addCertPath("tls/host3-cert.pem")
.addKeyPath("tls/host4-key.pem").addCertPath("tls/host4-cert.pem")
.addKeyPath("tls/host5-key.pem").addCertPath("tls/host5-cert.pem");
Cert<JksOptions> MULTIPLE_JKS = () -> new JksOptions().setPath("tls/multiple.jks").setPassword("wibble").setAlias("precious");

}
Binary file added src/test/resources/tls/multiple.jks
Binary file not shown.

0 comments on commit 5480d62

Please sign in to comment.