How to configure sslContext with p12 store (and password) for client authentication with https endpoint in Spring/Kotlin - spring

I want to call a https protected endpoint using a certificate and private key. I received a .p12 keystore which is protected with a password.
For testing purposes I extracted the .cer file and private key using openssl.
I could verify locally that communication works by setting the ssl context like this:
fun test(): WebClient.RequestHeadersSpec<*> {
val sslContext = SslContextBuilder
.forClient()
.keyManager(ClassPathResource("cert").inputStream, ClassPathResource("key").inputStream)
.build()
val httpClient: HttpClient = HttpClient.create().secure { sslSpec -> sslSpec.sslContext(sslContext) }
val webClient = WebClient
.builder()
.baseUrl("baseUrl")
.clientConnector(ReactorClientHttpConnector(httpClient))
.build()
return webClient
.post()
.uri("uri")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("test")
}
However, I do not want to version the cert and private key in my repository. How would I set the sslContext with the .p12 keystore and the password? I did not find any examples for this scenario.

load file from disc
this is java but it should be easy to adapt to kotlin
KeyStore keyStore = KeyStore.getInstance("pkcs12");
keyStore.load(new FileInputStream(keyStoreLocation), keyStorePassword.toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
.build(),
NoopHostnameVerifier.INSTANCE);
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
where file location and password can provided from configuration
#Value("${keyStore.location}")
private String keyStoreLocation;
#Value("${keyStore.password}")
private String keyStorePassword;

Related

Unable to get access_token, Refresh token using client_assertion in Spring oauth2 Server

generated client assertion like below using PKCS12, still I get invalid client error. Need your help to resolve this issue. Looks like authorization server is not compatible with certificate generated RSAkey. I am badly stuck here. base branch used to create project is https://github.com/jgrandja/spring-authorization-server/tree/jwt-client-authn/samples/default-authorizationserver
InputStream fm = new FileInputStream(pathPKCS12);
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(fm, pwdPKCS12.toCharArray());
Key key = keystore.getKey(keystore.aliases().nextElement(), pwdPKCS12.toCharArray());
Certificate cert = keystore.getCertificate(keystore.aliases().nextElement());
PublicKey publicKey = cert.getPublicKey();
KeyPair keyPair = new KeyPair(publicKey, (PrivateKey) key);
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
Instant now = Instant.now();
//The JWT signature algorithm we will be using to sign the token
//SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256;
String jwt= Jwts.builder()
.setAudience("https://localhost:9000")
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(now.plus(5L, ChronoUnit.DAYS)))
.setIssuer("profinch")
.setSubject("profinch")
.setId(UUID.randomUUID().toString())
.signWith(rsaPrivateKey)
.compact();
System.out.println(jwt);
error is below -
postman printscreen where error is displayed
post man request x-www-form-URL encoded is -
client_assertion_type:urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion:eyJraWQiOiJKWHYwMXhuNW83eDFuTXB1SVV1Q1gyNDdPbnhMMGdMeGV2VUFnLW0xbjdvIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo5MDAwL29hdXRoMi90b2tlbiIsImlhdCI6MTY3NTY3NDU4NywiZXhwIjoxNjc2MTA2NTg3LCJpc3MiOiJwcm9maW5jaCIsInN1YiI6InByb2ZpbmNoIiwianRpIjoiYTFiYzdiZTktZGY3Zi00NjhkLTkwMzktNTZjOTE4NmMwNDkwIn0.LjyF122kDSTlkUzVomOpnqwpHxNEdeq73fVx4Zzcu1dEmChWpk2LB1NbDhOPTIDNpGPF4aB8RkHkZfTmbA9TFYrm20MdHYFt5fRliBILUdBuNjRok3EEmNmWnjFTyfwoufMGT-sX6orLWkNUmsOkaywGIKCgHgR-MfEpTZ_v0cNL3uSuWnDQAy6yZgm9kWt_jNZdPGouBFHGsoZxhr2XAFrwhlQVq0VDOTELX7ylDcqOR5phHxAfubx_mW79ns1Que3jO-o_mvYtXTamoOq_DiJD-xpRnAQEq36czoSHvPYVrjlLhCloBtuedzD0gODmypVc_0Dq2dPp4-y9pCiV-g
Auth-server client registration as below-
RegisteredClient registeredClient2 = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("profinch") .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri(clientModel.getRedirectUri()) .scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).scope("message.read").scope("message.write") .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(false) .jwkSetUrl("http://localhost:8080/jwks") .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256) .build()) .build();
Should be able to get acces_token, refresh_oken using client_assertion in postman as per printscreen shared.

HttpClient have both SSL and Proxy authentication configured?

I have two pieces of code using HttpClient,
First part in case that the end point requires SSL
Second is proxy connection with basic authentication
My question Is how can I make this code conditional so in cases i have SSL + Proxy or SSL only
I have hard time figuring out how to set the default credentials for example after I created the client using the client in the SSL part
.setDefaultCredentialsProvider(credsProvider)
This part is how I create the Client when I need SSL
CloseableHttpClient client = null;
if(conf.isUseSslConfig()) {
SSLContext sslcontext = SSLContexts.custom()
.loadTrustMaterial(new File(conf.getTrustStoreLocation()), conf.getTrustStorePassword().toCharArray(), new TrustSelfSignedStrategy()).build();
// Allow protocols
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,conf.getTlsVersions(), null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
client = HttpClients.custom().setSSLSocketFactory(sslsf).build();
}else {
client= HttpClients.createDefault();
}
And this part is how I create the Client when I need Proxy authentication:
if(conf.isUseProxyConfig()){
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope("fakeProxy.xerox.com", 80),
new UsernamePasswordCredentials("xeroxUser","fakePassword123"));
HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider).build();
}
So the bottom line is how to make the two sections work together so in case
Call with SSL + Proxy and authentication
Call with only SSL
Call with only Proxy and authentication
You can write code this way to get multiple conditions resolved :
CloseableHttpClient client = null;
if(conf.isUseSslConfig() && conf.isUseProxyConfig()) {
setSSLSetting(client);
setProxy()
}else if(conf.isUseSslConfig()) {
setSSLSetting(client);
}else {
client= HttpClients.createDefault();
}
private void setProxy(){
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope("fakeProxy.xerox.com", 80),new UsernamePasswordCredentials("xeroxUser","fakePassword123"));
}
private void setSSLSetting(CloseableHttpClient client){
SSLContext sslcontext = SSLContexts.custom()
.loadTrustMaterial(new File(conf.getTrustStoreLocation()), conf.getTrustStorePassword().toCharArray(), new TrustSelfSignedStrategy()).build();
// Allow protocols
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,conf.getTlsVersions(), null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
client = HttpClients.custom().setSSLSocketFactory(sslsf).build();
}
or you can create methods that return client with different settings and configs like this :
final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", new PlainConnectionSocketFactory()).register("https", sslsf).build();
final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
private CloseableHttpClient createHttpClient(String headerName, String value) throws NoSuchAlgorithmException, KeyManagementException,KeyStoreException {
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
Header header = new BasicHeader(headerName,value);
List<Header> headers = new ArrayList<>();
headers.add(header);
RequestConfig reqConfig = RequestConfig.custom().setConnectionRequestTimeout(long milli seconds).build();
CloseableHttpClient httpclient = HttpClients.custom().
setDefaultHeaders(headers).
setDefaultRequestConfig(reqConfig).
setConnectionManager(cm).
build();
return httpclient;
}

SSL context in mongo:client attribute using spring xml config

How to add SSL key store and trust store file path and password in mongo:client options using spring xml to connect mongo db on TLS . Also need to know how to add ssl invalid host name allowed in mongo:client attribute in xml. I am using spring data mongo db 2.2.3 .
This is not XML solution, but via Bean.
This is what I did for 2.2.5.RELEASE. Note that for 2.3.0, there is no MongoClientOptions
#Value("classpath:truststore/mongoserver-truststore.p12")
private Resource trustStore;
#Value("${ssl.truststore.mongodb.password}")
private String mongoTrustStorePassword;
#Bean
public MongoClientOptions mongoClientOptions() throws Exception {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(trustStore.getInputStream(), mongoTrustStorePassword.toCharArray());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return MongoClientOptions.builder()
.sslEnabled(true)
.sslContext(sslContext)
.sslInvalidHostNameAllowed(true)
.build();
}

How to set cipher suites with javax.net.ssl.SSLContext

In a spring boot application using java8, I am setting the underlying SSLConext of an httpClient connection as follows:
import javax.net.ssl.SSLContext;
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
CloseableHttpClient httpClient = HttpClientBuilder
.create()
.setConnectionManager(myConnectionManager)
.setDefaultRequestConfig(rqConfig)
.setSSLContext(sslContext)
.build();
I need to set the cipher suites for the underlying TLS1.2 secured connection to something stronger of my choice. I don't see a way to do this with the way I am creation the sslContext in my code.
Can someone help me set up the cipher suites with my sslContext ?
================UPDATE=================
This is how I have now created my HttpClient
CloseableHttpClient httpClient = HttpClientBuilder
.create()
.setConnectionManager(myConnectionManager)
.setDefaultRequestConfig(rqConfig)
.setSSLSocketFactory(new SSLConnectionSocketFactory(
SSLContexts.createSystemDefault(),
new String[]{"TLSv1.2"},
new String[] {"some-gibberish-cipher-suite"},
SSLConnectionSocketFactory.getDefaultHostnameVerifier()))
.build();
Preferred TLS protocol versions and custom ciphers can be specified when creating a custom SSLConnectionSocketFactory instance
CloseableHttpClient client = HttpClients.custom()
.setSSLSocketFactory(new SSLConnectionSocketFactory(
SSLContexts.createSystemDefault(),
new String[]{"TLSv1.2"},
new String[] {"TLS_RSA_WITH_AES_256_CBC_SHA256"},
SSLConnectionSocketFactory.getDefaultHostnameVerifier()))
.build();
try (CloseableHttpResponse response = client.execute(new HttpGet("https://httpbin.org/"))) {
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
}
Alternatively, one can create a custom PoolingHttpClientConnectionManager instance with the desired SSL configuration.
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", new SSLConnectionSocketFactory(
SSLContexts.createSystemDefault(),
new String[]{"TLSv1.2"},
new String[]{"TLS_RSA_WITH_AES_256_CBC_SHA256"},
SSLConnectionSocketFactory.getDefaultHostnameVerifier()))
.build());
CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(cm)
.build();
try (CloseableHttpResponse response = client.execute(new HttpGet("https://httpbin.org/"))) {
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
}

resttemplate with 2 way ssl certificate

I am using rettemplate to hit a third party url which is using 2 way ssl certificate. i have set httpclient with truststore and keystore still getting ssl handshake exception. I have also tried after importing certificate in my cacerts but still no luck. Any one who has used 2 way ssl with resttemplate
private HttpClient createHttpClient(final String keyAlias) {
logger.info("Creating HTTP client using keystore={} and alias={}", keyStorePath, keyAlias);
final KeyStore trustStore = new KeyStoreFactoryBean(makeResource(keyStorePath), keyStoreType, keyStorePassword)
.newInstance();
KeyStore keyStore =
new KeyStoreFactoryBean(makeResource(keyStorePath), keyStoreType, keyStorePassword)
.newInstance();
final SSLContext sslContext;
HttpHost proxyNew = null;
proxyNew = new HttpHost(proxyURL, proxyPort);
HttpClient httpClient=null;
try {
sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray(), (aliases, socket) -> keyAlias)
.loadTrustMaterial(trustStore, (x509Certificates, s) -> false).build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build());
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) {
throw new IllegalStateException("Error loading key or trust material", e);
}
final SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[] { "TLSv1.2", "TLSv1.1" }, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslSocketFactory)
.build();
final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
connectionManager.setMaxTotal(httpClientPoolSize);
connectionManager.setDefaultMaxPerRoute(httpClientPoolSize);
HttpHost proxy = null;
proxy = new HttpHost(proxyURL, proxyPort);
if(isProxy)
return HttpClients.custom().setSSLSocketFactory(sslSocketFactory).setConnectionManager(connectionManager).setProxy(proxy).build();
return HttpClients.custom().setSSLSocketFactory(sslSocketFactory).setConnectionManager(connectionManager).build();
}

Resources