Created
June 16, 2021 13:32
-
-
Save samiraghayarov/fc400e18b4abf19caa4bb80ce771100c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.tarabutgateway.obc.bobf.accounts.repository; | |
import feign.Client; | |
import feign.codec.Encoder; | |
import feign.form.spring.SpringFormEncoder; | |
import feign.httpclient.ApacheHttpClient; | |
import net.tarabutgateway.obc.bobf.accounts.config.MTLSConfiguration; | |
import org.apache.commons.codec.binary.Base64; | |
import org.apache.http.client.config.RequestConfig; | |
import org.apache.http.conn.ssl.NoopHostnameVerifier; | |
import org.apache.http.impl.client.HttpClientBuilder; | |
import org.apache.http.impl.client.HttpClients; | |
import org.apache.http.ssl.SSLContextBuilder; | |
import org.apache.http.ssl.SSLContexts; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.beans.factory.ObjectFactory; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.boot.autoconfigure.http.HttpMessageConverters; | |
import org.springframework.cloud.openfeign.support.SpringEncoder; | |
import org.springframework.context.annotation.Bean; | |
import javax.net.ssl.SSLContext; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.IOException; | |
import java.nio.charset.StandardCharsets; | |
import java.security.GeneralSecurityException; | |
import java.security.KeyFactory; | |
import java.security.KeyStore; | |
import java.security.KeyStoreException; | |
import java.security.NoSuchAlgorithmException; | |
import java.security.cert.Certificate; | |
import java.security.cert.CertificateException; | |
import java.security.cert.CertificateFactory; | |
import java.security.cert.X509Certificate; | |
import java.security.interfaces.RSAPrivateKey; | |
import java.security.spec.PKCS8EncodedKeySpec; | |
import java.util.Enumeration; | |
import java.util.Optional; | |
import java.util.Set; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
import static java.util.Objects.requireNonNull; | |
public class BobfFeignTLSConfiguration { | |
@Value("${feign.client.config.default.connectTimeout}") | |
private int connectTimeout; | |
@Value("${feign.client.config.default.readTimeout}") | |
private int readTimeout; | |
private static final Logger LOGGER = LoggerFactory.getLogger(BobfFeignTLSConfiguration.class); | |
@Bean | |
Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> converters) { | |
return new SpringFormEncoder(new SpringEncoder(converters)); | |
} | |
@Bean | |
Client getFeignClient(MTLSConfiguration mtlsConfiguration) { | |
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout) | |
.setConnectionRequestTimeout(readTimeout).setSocketTimeout(mtlsConfiguration.getReadTimeout()).build(); | |
HttpClientBuilder httpClientBuilder = HttpClients.custom().setDefaultRequestConfig(requestConfig); | |
if ("true".equalsIgnoreCase(mtlsConfiguration.getNoopHostnameVerifier())) { | |
httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); // **THIS IS VERY BAD** | |
} | |
return new ApacheHttpClient(httpClientBuilder.setSSLContext(getSSLContext(mtlsConfiguration)).build()); | |
} | |
private SSLContext getSSLContext(MTLSConfiguration mtlsConfiguration) { | |
// this is an in-memory key store, use password if there is one configured | |
String password = Optional.ofNullable(mtlsConfiguration.getKeyStorePassword()).orElse(""); | |
CertificateFactory certificateFactory; | |
try { | |
certificateFactory = CertificateFactory.getInstance("X.509"); | |
} catch (CertificateException e) { | |
throw new IllegalArgumentException("Error initialising MTLS certificate factory", e); | |
} | |
try { | |
SSLContextBuilder sslContextBuilder = SSLContexts.custom(); | |
if (new File(mtlsConfiguration.getKeyStore()).exists()) | |
sslContextBuilder = sslContextBuilder.loadKeyMaterial( | |
getIdKeyStore(certificateFactory, mtlsConfiguration.getKeyStore(), password), | |
password.toCharArray(), (aliases, socket) -> mtlsConfiguration.getAlias()); | |
File ts = new File(mtlsConfiguration.getTrustStore()); | |
if (ts.exists()) { | |
KeyStore trustStore = getTrustStore(mtlsConfiguration.getTrustStore(), | |
mtlsConfiguration.getTrustStorePassword()); | |
sslContextBuilder = sslContextBuilder.loadTrustMaterial(trustStore, null); | |
} | |
return sslContextBuilder.build(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
throw new IllegalArgumentException("Error initialising MTLS SSL Context", e); | |
} | |
} | |
private KeyStore getIdKeyStore(CertificateFactory certificateFactory, String keyStore, String password) | |
throws IOException, GeneralSecurityException { | |
// prepare the key store for mutual-tls | |
KeyStore identityKeyStore = KeyStore.getInstance("jks"); | |
identityKeyStore.load(null); | |
Set<String> privateKeys = Stream.of(requireNonNull(new File(keyStore).listFiles())) | |
.filter(file -> !file.isDirectory()) | |
.map(File::getName) | |
.filter(f -> f.endsWith(".pem")) | |
.collect(Collectors.toSet()); | |
privateKeys.forEach(pemKey -> { | |
String alias = pemKey.split("\\.")[0]; | |
String keyFile = keyStore + pemKey; | |
String certFile = keyStore + alias + ".crt"; | |
if (new File(certFile).exists()) { | |
try (FileInputStream input = new FileInputStream(certFile)) { | |
X509Certificate kcert = (X509Certificate) certificateFactory.generateCertificate(input); | |
identityKeyStore.setKeyEntry(alias, getPrivateKey(keyFile), password.toCharArray(), | |
new Certificate[]{kcert}); | |
LOGGER.info("added to keystore : {} with alias", alias); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
throw new IllegalArgumentException("Error initialising the key store for mtls", e); | |
} | |
} else { | |
LOGGER.warn("ignoring the Key {}, cannot add to keystore without the crt", keyFile); | |
} | |
}); | |
return identityKeyStore; | |
} | |
private KeyStore getTrustStore(String trustStore, String trustStorePassword) | |
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { | |
// load the truststore with the default jdk cacerts | |
KeyStore trustKeyStore = KeyStore.getInstance("jks"); | |
try (FileInputStream in = new FileInputStream(System.getProperty("java.home") + "/lib/security/cacerts")) { | |
trustKeyStore.load(in, null); | |
} | |
// append tg ca certs | |
try { | |
try (FileInputStream stream = new FileInputStream(trustStore)) { | |
trustKeyStore.load(stream, trustStorePassword.toCharArray()); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
LOGGER.warn("truststore load failed"); | |
} | |
Enumeration<String> enumeration = trustKeyStore.aliases(); | |
while (enumeration.hasMoreElements()) | |
LOGGER.info("added to truststore: {}", enumeration.nextElement()); | |
return trustKeyStore; | |
} | |
private RSAPrivateKey getPrivateKey(String filename) throws IOException, GeneralSecurityException { | |
String privateKeyPEM = getKey(filename); | |
privateKeyPEM = privateKeyPEM.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "") | |
.replace("-----END PRIVATE KEY-----", ""); | |
PKCS8EncodedKeySpec spec1 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyPEM)); | |
KeyFactory kf = KeyFactory.getInstance("RSA"); | |
return (RSAPrivateKey) kf.generatePrivate(spec1); | |
} | |
private String getKey(String fileName) throws IOException { | |
File file = new File(fileName); | |
return com.google.common.io.Files.asCharSource(file, StandardCharsets.UTF_8).read(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment