Skip to content

Commit

Permalink
Merge pull request #81 from medizininformatik-initiative/78-flarewebs…
Browse files Browse the repository at this point in the history
…erviceclient-does-not-use-configured-java-trust-store-for-https

Use existing tls and authentication configuration for FlareWebserviceClient
  • Loading branch information
EmteZogaf authored Nov 22, 2023
2 parents 5773aba + 08c1ab6 commit 9a13c23
Show file tree
Hide file tree
Showing 35 changed files with 1,189 additions and 226 deletions.
2 changes: 1 addition & 1 deletion feasibility-dsf-process/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
<goal>exec</goal>
</goals>
<configuration>
<executable>${basedir}/scripts/create_certs_for_store_client_tests.sh</executable>
<executable>${basedir}/scripts/create_certs_for_client_tests.sh</executable>
</configuration>
</execution>
</executions>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash

BASE_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
TARGET_DIR=$(readlink -f "${BASE_DIR}/../src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/store/certs")
TARGET_DIR=$(readlink -f "${BASE_DIR}/../src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/certs")

mkdir -p "${TARGET_DIR}"

Expand All @@ -19,12 +19,14 @@ openssl pkcs12 -export -out ${TARGET_DIR}/ca.p12 \
# Issue server certificate using said self signed CA
openssl req -nodes -sha256 -new -newkey rsa:2048 -keyout ${TARGET_DIR}/server_cert_key.pem \
-out ${TARGET_DIR}/server_cert_csr.pem \
-subj "/C=DE/ST=Berlin/L=Berlin/O=Bar/CN=localhost"
-subj "/C=DE/ST=Berlin/L=Berlin/O=Bar/CN=localhost" \
-addext "subjectAltName = DNS:localhost, DNS:proxy"

openssl x509 -req -days 7 -sha256 -in ${TARGET_DIR}/server_cert_csr.pem \
-CA ${TARGET_DIR}/ca.pem \
-CAkey ${TARGET_DIR}/ca_key.pem \
-CAcreateserial \
-copy_extensions copyall \
-out ${TARGET_DIR}/server_cert.pem

# Server cert chain
Expand Down Expand Up @@ -56,4 +58,4 @@ rm -f ${TARGET_DIR}/server_cert_csr.pem
rm -f ${TARGET_DIR}/server_cert.pem
rm -f ${TARGET_DIR}/client_cert_csr.pem
rm -f ${TARGET_DIR}/client_cert_key.pem
rm -f ${TARGET_DIR}/client_cert.pem
rm -f ${TARGET_DIR}/client_cert.pem
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package de.medizininformatik_initiative.feasibility_dsf_process.client.flare;

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.message.BasicHeader;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;

import static java.net.http.HttpRequest.BodyPublishers.ofByteArray;
import static java.net.http.HttpResponse.BodyHandlers.ofString;
import static ca.uhn.fhir.rest.api.Constants.HEADER_CONTENT_TYPE;

/**
* Client for communicating with a Flare instance.
*/
public class FlareWebserviceClientImpl implements FlareWebserviceClient {

private final HttpClient httpClient;
private final org.apache.http.client.HttpClient httpClient;
private final URI flareBaseUrl;

public FlareWebserviceClientImpl(HttpClient httpClient, URI flareBaseUrl) {
Expand All @@ -23,13 +26,12 @@ public FlareWebserviceClientImpl(HttpClient httpClient, URI flareBaseUrl) {

@Override
public int requestFeasibility(byte[] structuredQuery) throws IOException, InterruptedException {
var req = HttpRequest.newBuilder()
.POST(ofByteArray(structuredQuery))
.setHeader("Content-Type", "application/sq+json")
.uri(flareBaseUrl.resolve("/query/execute"))
.build();

var res = httpClient.send(req, ofString());
return Integer.parseInt(res.body());
var req = new HttpPost(flareBaseUrl.resolve("/query/execute"));
req.setEntity(new ByteArrayEntity(structuredQuery));
req.setHeader(new BasicHeader(HEADER_CONTENT_TYPE, "application/sq+json"));

var response = httpClient.execute(req, new BasicResponseHandler());

return Integer.parseInt(response);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
package de.medizininformatik_initiative.feasibility_dsf_process.client.flare;

import de.medizininformatik_initiative.feasibility_dsf_process.client.store.TlsClientFactory;
import de.medizininformatik_initiative.feasibility_dsf_process.spring.config.BaseConfig;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.ProxyAuthenticationStrategy;
import org.apache.http.message.BasicHeader;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.time.Duration;

import javax.net.ssl.SSLContext;

import static ca.uhn.fhir.rest.api.Constants.HEADER_AUTHORIZATION;
import static ca.uhn.fhir.rest.api.Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER;
import static com.google.common.base.Strings.isNullOrEmpty;

@Configuration
@Import(BaseConfig.class)
public class FlareWebserviceClientSpringConfig {

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url:}")
Expand All @@ -17,15 +44,91 @@ public class FlareWebserviceClientSpringConfig {
@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.flare.timeout.connect:2000}")
private int connectTimeout;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host:#{null}}")
private String proxyHost;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.port:}")
private Integer proxyPort;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.username:#{null}}")
private String proxyUsername;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.password:#{null}}")
private String proxyPassword;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.username:#{null}}")
private String basicAuthUsername;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.password:#{null}}")
private String basicAuthPassword;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.bearer.token:#{null}}")
private String bearerAuthToken;

@Bean
public FlareWebserviceClient flareWebserviceClient(HttpClient httpClient) {
return new FlareWebserviceClientImpl(httpClient, URI.create(flareBaseUrl));
}

@Bean
public HttpClient flareHttpClient() {
return HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(connectTimeout))
.build();
public HttpClient flareHttpClient(@Qualifier("base-client") SSLContext sslContext) {
HttpClientBuilder builder = new TlsClientFactory(null, sslContext).getNativeHttpClientBuilder();

BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();

if (!isNullOrEmpty(proxyHost) && proxyPort != null) {
HttpHost proxy = new HttpHost(proxyHost, proxyPort);
builder.setProxy(proxy);
if (!isNullOrEmpty(proxyUsername) && !isNullOrEmpty(proxyPassword)) {
builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
credentialsProvider.setCredentials(new AuthScope(proxy),
new UsernamePasswordCredentials(proxyUsername, proxyPassword));
}
}
if (!isNullOrEmpty(basicAuthUsername) && !isNullOrEmpty(basicAuthPassword)) {
URI flareUri = URI.create(flareBaseUrl);
credentialsProvider.setCredentials(new AuthScope(new HttpHost(flareUri.getHost(), flareUri.getPort())),
new UsernamePasswordCredentials(basicAuthUsername, basicAuthPassword));
} else if (!isNullOrEmpty(bearerAuthToken)) {
return new BearerHttpClient(builder.setDefaultCredentialsProvider(credentialsProvider).build());
}
return builder.setDefaultCredentialsProvider(credentialsProvider).build();
}

private final class BearerHttpClient extends CloseableHttpClient {
private CloseableHttpClient client;

public BearerHttpClient(CloseableHttpClient client) {
this.client = client;
}

@Override
public HttpParams getParams() {
return client.getParams();
}

@Override
public ClientConnectionManager getConnectionManager() {
return client.getConnectionManager();
}

@Override
public void close() throws IOException {
client.close();
}

@Override
protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context)
throws IOException, ClientProtocolException {
return client.execute(target, request, context);
}

@Override
public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler)
throws IOException, ClientProtocolException {
request.setHeader(new BasicHeader(HEADER_AUTHORIZATION,
HEADER_AUTHORIZATION_VALPREFIX_BEARER + "1234"));
return super.execute(request, responseHandler);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,17 @@
import ca.uhn.fhir.rest.client.impl.RestfulClientFactory;
import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor;
import org.apache.http.ssl.SSLContexts;
import de.medizininformatik_initiative.feasibility_dsf_process.spring.config.BaseConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.context.annotation.Import;

import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;

@Configuration
@Import(BaseConfig.class)
public class StoreClientSpringConfig {
@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host:#{null}}")
private String proxyHost;
Expand Down Expand Up @@ -51,18 +48,6 @@ public class StoreClientSpringConfig {
@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.timeout.socket:20000}")
private Integer socketTimeout;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_path:#{null}}")
private String trustStorePath;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_password:#{null}}")
private String trustStorePassword;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.key_store_path:#{null}}")
private String keyStorePath;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.key_store_password:#{null}}")
private String keyStorePassword;

@Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.base_url}")
private String storeBaseUrl;

Expand Down Expand Up @@ -108,57 +93,7 @@ FhirContext fhirContext() {
@Bean
@Qualifier("store-client")
RestfulClientFactory clientFactory(@Qualifier("store-client") FhirContext fhirContext,
@Qualifier("store-client") SSLContext sslContext) {
@Qualifier("base-client") SSLContext sslContext) {
return new TlsClientFactory(fhirContext, sslContext);
}

@Bean
@Qualifier("store-client-trust")
KeyStore loadTrustStore() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
if (trustStorePath == null || trustStorePath.isBlank()) {
return DefaultTrustStoreUtils.loadDefaultTrustStore();
}

var trustStoreInputStream = new FileInputStream(trustStorePath);

var trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(trustStoreInputStream, (trustStorePassword == null) ? null : trustStorePassword.toCharArray());
trustStoreInputStream.close();

return trustStore;
}


@Bean
@Qualifier("store-client-key")
@Nullable
KeyStore loadKeyStore() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
if (keyStorePath == null) {
return null;
}

var keyStoreInputStream = new FileInputStream(keyStorePath);

var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(keyStoreInputStream, (keyStorePassword == null) ? null : keyStorePassword.toCharArray());
keyStoreInputStream.close();

return keyStore;
}

@Bean
@Qualifier("store-client")
SSLContext createSslContext(@Qualifier("store-client-trust") KeyStore trustStore,
@Nullable @Qualifier("store-client-key") KeyStore keyStore)
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, UnrecoverableKeyException {
var sslContextBuilder = SSLContexts.custom()
.loadTrustMaterial(trustStore, null);

if (keyStore != null) {
sslContextBuilder.loadKeyMaterial(keyStore, (keyStorePassword == null) ? null :
keyStorePassword.toCharArray());
}

return sslContextBuilder.build();
}
}
Loading

0 comments on commit 9a13c23

Please sign in to comment.