Skip to content

Commit

Permalink
Add support for cert / key in REST Client
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Feb 16, 2024
1 parent 628cd3e commit 7414953
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ public class RestClientConfig {
@ConfigItem
public Optional<String> keyStoreType;

/**
* The classpath path or file path to a server certificate or certificate chain in PEM format.
*/
@ConfigItem
public Optional<String> certificate;

/**
* The classpath path or file path to the corresponding certificate private key file in PEM format.
*/
@ConfigItem
public Optional<String> key;

/**
* The class name of the host name verifier. The class must have a public no-argument constructor.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,22 @@ public class RestClientsConfig {
@ConfigItem
public Optional<String> keyStoreType;

/**
* The classpath path or file path to a server certificate or certificate chain in PEM format.
* <p>
* Can be overwritten by client-specific settings.
*/
@ConfigItem
public Optional<String> certificate;

/**
* The classpath path or file path to the corresponding certificate private key file in PEM format.
* <p>
* Can be overwritten by client-specific settings.
*/
@ConfigItem
public Optional<String> key;

/**
* If this is true then HTTP/2 will be enabled.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.security.KeyStore;
import java.util.ServiceLoader;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -151,6 +152,16 @@ static QuarkusRestClientBuilder newBuilder() {
*/
QuarkusRestClientBuilder trustStore(KeyStore trustStore, String trustStorePassword);

/**
* The classpath path or file path to a server certificate or certificate chain in PEM format.
*/
QuarkusRestClientBuilder certificate(Path certificate);

/**
* The classpath path or file path to the corresponding certificate private key file in PEM format.
*/
QuarkusRestClientBuilder key(Path key);

/**
* Set the client-side key store.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.security.KeyStore;
import java.util.Map;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -84,6 +85,18 @@ public QuarkusRestClientBuilder keyStore(KeyStore keyStore, String keystorePassw
return this;
}

@Override
public QuarkusRestClientBuilder certificate(Path certificate) {
proxy.certificate(certificate);
return this;
}

@Override
public QuarkusRestClientBuilder key(Path key) {
proxy.key(key);
return this;
}

@Override
public QuarkusRestClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) {
proxy.hostnameVerifier(hostnameVerifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Comparator;
Expand Down Expand Up @@ -75,6 +76,8 @@ public class RestClientBuilderImpl implements RestClientBuilder {

private Boolean trustAll;
private String userAgent;
private Path certificate;
private Path key;

@Override
public RestClientBuilderImpl baseUrl(URL url) {
Expand Down Expand Up @@ -126,6 +129,16 @@ public RestClientBuilderImpl keyStore(KeyStore keyStore, String keystorePassword
return this;
}

public RestClientBuilderImpl certificate(Path certificate) {
this.certificate = certificate;
return this;
}

public RestClientBuilderImpl key(Path key) {
this.key = key;
return this;
}

@Override
public RestClientBuilderImpl hostnameVerifier(HostnameVerifier hostnameVerifier) {
clientBuilder.hostnameVerifier(hostnameVerifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
Expand Down Expand Up @@ -229,6 +230,18 @@ private void configureSsl(QuarkusRestClientBuilder builder) {
registerKeyStore(maybeKeyStore.get(), builder);
}

Optional<String> maybeCertificate = oneOf(clientConfigByClassName().certificate, clientConfigByConfigKey().certificate,
configRoot.certificate);
if (maybeCertificate.isPresent()) {
builder.certificate(Paths.get(maybeCertificate.get()));
}

Optional<String> maybeKey = oneOf(clientConfigByClassName().key, clientConfigByConfigKey().key,
configRoot.key);
if (maybeKey.isPresent()) {
builder.key(Paths.get(maybeKey.get()));
}

Optional<String> maybeHostnameVerifier = oneOf(clientConfigByClassName().hostnameVerifier,
clientConfigByConfigKey().hostnameVerifier, configRoot.hostnameVerifier);
if (maybeHostnameVerifier.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
Expand Down Expand Up @@ -39,6 +43,7 @@
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.net.JksOptions;
import io.vertx.core.net.PemKeyCertOptions;
import io.vertx.core.net.PfxOptions;
import io.vertx.core.net.ProxyOptions;

Expand All @@ -57,6 +62,9 @@ public class ClientBuilderImpl extends ClientBuilder {
private SSLContext sslContext;
private KeyStore trustStore;
private char[] trustStorePassword;

private Path certificate;
private Path key;
private boolean http2;
private boolean alpn;

Expand Down Expand Up @@ -114,6 +122,16 @@ public ClientBuilder trustStore(KeyStore trustStore, char[] password) {
return this;
}

public ClientBuilder certificate(Path certificate) {
this.certificate = certificate;
return this;
}

public ClientBuilder key(Path key) {
this.key = key;
return this;
}

@Override
public ClientBuilder hostnameVerifier(HostnameVerifier verifier) {
// TODO
Expand Down Expand Up @@ -222,43 +240,58 @@ public ClientImpl build() {
options.setVerifyHost(false);
}

char[] effectiveTrustStorePassword = trustStorePassword == null ? EMPTY_CHAR_ARARAY : trustStorePassword;
Buffer keyStore = asBuffer(this.keyStore, keystorePassword);
Buffer trustStore = asBuffer(this.trustStore, effectiveTrustStorePassword);
if (keyStore != null || trustStore != null) {
options = options.setSsl(true);
if (keyStore != null) {
String keyStoreType = this.keyStore.getType();
if ("PKCS12".equals(keyStoreType)) {
PfxOptions pks = new PfxOptions();
pks.setValue(keyStore);
pks.setPassword(new String(keystorePassword));
options = options.setKeyCertOptions(pks);
} else if ("JKS".equals(keyStoreType)) {
JksOptions jks = new JksOptions();
jks.setValue(keyStore);
jks.setPassword(new String(keystorePassword));
options = options.setKeyCertOptions(jks);
} else {
throw new IllegalStateException("Unsupported key store type " + keyStoreType);
}

if (certificate != null && key != null) {
try {
byte[] certBytes = getFileContent(certificate);
byte[] keyBytes = getFileContent(key);

PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
.setCertValue(Buffer.buffer(certBytes))
.setKeyValue(Buffer.buffer(keyBytes));
options.setKeyCertOptions(pemKeyCertOptions);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
if (trustStore != null) {
String trustStoreType = this.keyStore.getType();
if ("PKCS12".equals(trustStoreType)) {
PfxOptions pks = new PfxOptions();
pks.setValue(trustStore);
pks.setPassword(new String(effectiveTrustStorePassword));
} else if ("JKS".equals(trustStoreType)) {
JksOptions jks = new JksOptions();
jks.setValue(trustStore);
jks.setPassword(new String(effectiveTrustStorePassword));
options.setTrustOptions(jks);
} else {
throw new IllegalStateException("Unsupported trust store type " + trustStoreType);

} else {
char[] effectiveTrustStorePassword = trustStorePassword == null ? EMPTY_CHAR_ARARAY : trustStorePassword;
Buffer keyStore = asBuffer(this.keyStore, keystorePassword);
Buffer trustStore = asBuffer(this.trustStore, effectiveTrustStorePassword);
if (keyStore != null || trustStore != null) {
options = options.setSsl(true);
if (keyStore != null) {
String keyStoreType = this.keyStore.getType();
if ("PKCS12".equals(keyStoreType)) {
PfxOptions pks = new PfxOptions();
pks.setValue(keyStore);
pks.setPassword(new String(keystorePassword));
options = options.setKeyCertOptions(pks);
} else if ("JKS".equals(keyStoreType)) {
JksOptions jks = new JksOptions();
jks.setValue(keyStore);
jks.setPassword(new String(keystorePassword));
options = options.setKeyCertOptions(jks);
} else {
throw new IllegalStateException("Unsupported key store type " + keyStoreType);
}

}
if (trustStore != null) {
String trustStoreType = this.keyStore.getType();
if ("PKCS12".equals(trustStoreType)) {
PfxOptions pks = new PfxOptions();
pks.setValue(trustStore);
pks.setPassword(new String(effectiveTrustStorePassword));
} else if ("JKS".equals(trustStoreType)) {
JksOptions jks = new JksOptions();
jks.setValue(trustStore);
jks.setPassword(new String(effectiveTrustStorePassword));
options.setTrustOptions(jks);
} else {
throw new IllegalStateException("Unsupported trust store type " + trustStoreType);
}

}
}
}

Expand Down Expand Up @@ -446,4 +479,41 @@ public ClientBuilderImpl nonProxyHosts(String nonProxyHosts) {
this.nonProxyHosts = nonProxyHosts;
return this;
}

private static byte[] getFileContent(Path path) throws IOException {
byte[] data;
final InputStream resource = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(toResourceName(path));
if (resource != null) {
try (InputStream is = resource) {
data = doRead(is);
}
} else {
try (InputStream is = Files.newInputStream(path)) {
data = doRead(is);
}
}
return data;
}

/**
* Translates a file system-specific path to a Java classpath resource name
* that uses '/' as a separator.
*
* @param path file system path
* @return Java classpath resource name
*/
public static String toResourceName(Path path) {
if (path == null) {
return null;
}
if (path.getFileSystem().getSeparator().equals("/")) {
return path.toString();
}
return path.toString().replace(path.getFileSystem().getSeparator(), "/");
}

private static byte[] doRead(InputStream is) throws IOException {
return is.readAllBytes();
}
}

0 comments on commit 7414953

Please sign in to comment.