HttpUrlConnection ignores proxy on Android < KitKat - xamarin

I'm using a library that uses a Proxy on http requests to add security. The problem is that it works perfectly on Android >= KitKat, but in JellyBean it does not go through the proxy.
Do you know if proxy is supported using AndroidClientHandler in that version? I know that TLS 1.2+ is not supported using AndroidClientHandler pre Lollipop, but I need that handler (in fact I inherit from AndroidClientHandler to bypass SSL verification because the proxy takes care of that and it is needed by the library) to configure some things of how the proxy works.
MyCustomMessageHandler implementation:
public class MyCustomMessageHandler : AndroidClientHandler
{
public override bool SupportsProxy => true;
protected override SSLSocketFactory ConfigureCustomSSLSocketFactory(HttpsURLConnection connection)
{
return this.GetBypassVerificationSSLSocketFactory();
}
protected override IHostnameVerifier GetSSLHostnameVerifier(HttpsURLConnection connection)
{
return new CustomHostnameVerifier();
}
protected override async Task SetupRequest(HttpRequestMessage request, HttpURLConnection conn)
{
this.HandleCustomPreAuthentication(conn);
await base.SetupRequest(request, conn);
}
private void HandleCustomPreAuthentication(HttpURLConnection conn)
{
var proxyAuth = "MyUsername" + ":" + "MyPassword";
var encodedProxyAuth = Base64.EncodeToString(Encoding.UTF8.GetBytes(proxyAuth), Base64Flags.Default);
conn.SetRequestProperty("Proxy-Authorization", encodedProxyAuth);
}
private SSLSocketFactory GetBypassVerificationSSLSocketFactory()
{
SSLContext sslContext;
try
{
sslContext = SSLContext.GetInstance("SSL");
sslContext.Init(new IKeyManager[0], new ITrustManager[] { new X509TrustManager() }, new SecureRandom());
return sslContext.SocketFactory;
}
catch (GeneralSecurityException e)
{
throw new RuntimeException(e);
}
}
}
public class CustomHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
public bool Verify(string hostname, ISSLSession session) => true;
}
public class X509TrustManager : Java.Lang.Object, IX509TrustManager
{
public void CheckClientTrusted(X509Certificate[] chain, string authType)
{
}
public void CheckServerTrusted(X509Certificate[] chain, string authType)
{
}
public X509Certificate[] GetAcceptedIssuers()
{
return new X509Certificate[0];
}
}
I've already tried configuring the proxy using Managed client but it does not work as expected; that's why I chose AndroidClientHandler
I infer it is not going through the proxy because capturing packets I realized that < KitKat it sends the requests over Http while >= KitKat they are all over TCP or TLSv1.2 and the proxy is an https url.

Related

Decent approach to time OkHttpClient events for metrics?

I'd like to gather connection and request timing metrics for an OkHttpClient instance that calls a particular service. I'm wondering if this approach is correct, and whether my interpretation of the event types makes sense?
Timer callTimer = <new codahale timer>;
Timer connectTimer = <new codahale timer>;
Timer secureConnectTimer = <new codahale timer>;
Timer requestTimer = <new codahale timer>;
# this gets registered with my client
new EventListener() {
// see https://square.github.io/okhttp/events/#eventlistener for info on the ordering of these events
private final Map<Call, Timer.Context> secureConnectTimerContexts = Maps.newConcurrentMap();
private final Map<Call, Timer.Context> connectTimerContexts = Maps.newConcurrentMap();
private final Map<Call, Timer.Context> callTimerContexts = Maps.newConcurrentMap();
private final Map<Call, Timer.Context> requestTimerContexts = Maps.newConcurrentMap();
#Override
public void secureConnectStart(Call call) {
secureConnectTimerContexts.put(call, secureConnectTimer.time());
}
#Override
public void secureConnectEnd(Call call, #Nullable Handshake handshake) {
Timer.Context context = secureConnectTimerContexts.remove(call);
if (Objects.nonNull(context)) {
context.stop();
}
}
#Override
public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
connectTimerContexts.put(call, connectTimer.time());
}
#Override
public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, #Nullable Protocol protocol) {
Timer.Context context = connectTimerContexts.remove(call);
if (Objects.nonNull(context)) {
context.stop();
}
}
#Override
public void connectionAcquired(Call call, Connection connection) {
requestTimerContexts.put(call, requestTimer.time());
}
#Override
public void connectionReleased(Call call, Connection connection) {
Timer.Context context = requestTimerContexts.remove(call);
if (context != null) {
context.stop();
}
}
#Override
public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy,
#Nullable Protocol protocol, IOException ioe) {
Timer.Context context = connectTimerContexts.remove(call);
if (Objects.nonNull(context)) {
context.stop();
}
}
#Override
public void callStart(Call call) {
callTimerContexts.put(call, callTimer.time());
}
#Override
public void callEnd(Call call) {
callFinishedForMetrics(call);
}
#Override
public void callFailed(Call call, IOException ioe) {
callFinishedForMetrics(call);
}
private void callFinishedForMetrics(Call call) {
Timer.Context callTimerContext = callTimerContexts.remove(call);
if (callTimerContext != null) {
callTimerContext.stop();
}
requestTimerContexts.remove(call);
secureConnectTimerContexts.remove(call);
connectTimerContexts.remove(call);
}
}
You can use EventListener.Factory to create a unique listener instance for each Call. That way you don't need all the maps; the Timer.Context objects can just be instance fields of the call-bound EventListener.

Overwrite Zuul's connection header Keep-Alive

I have following applications:
Gateway-Service
Api-Service
Gateway-Serivce is Zuul proxy and Api-Service is basically rest + broker + websocket + other thechnologies.
I'm using sockjs + stomp to communicate with broker through zuul. For doing protocol upgrade socket handler need to have connection header set to Upgrade. When I request this on gateway this connection header is set to Upgrade but after the zuul processing header is set to Keep-Alive.
So first question is if it's ok to replace connection header with original request header and if it's ok then how to do that? Using some Zuul filter?
Thanks
for someone interested this could be the solution...all credits goes to #spencergibb
#Component
public static class WebSocketHeaderFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
RequestWrapper wrapper = new RequestWrapper(ctx.getRequest());
String upgradeHeader = wrapper.getHeader("Upgrade");
if (null == upgradeHeader) {
upgradeHeader = wrapper.getHeader("upgrade");
}
if (null != upgradeHeader && "websocket".equalsIgnoreCase(upgradeHeader)) {
wrapper.addHeader("connection", "Upgrade");
ctx.addZuulRequestHeader("connection", "Upgrade");
ctx.setRequest(wrapper);
}
return null;
}
}

Jersey with self signed certificate

I am currently working in a dev environment where we are using self signed certificates. I've been trying for a little over a day now to get jersey to ignore the self signed certificate. This is purely for a POC environment, I wouldn't dream of doing thi sin production. While I've found many answers on the interweb about how to make this work, something is still off.
Here's my current test class:
public class JerseyTestClient {
private static final Logger LOG = Logger.getLogger(JerseyTestClient.class.getName());
public static void sendTestRequest() {
try {
Client client = Client.create(configureClient());
WebResource webResource = client.resource("https://server/endpoint/");
ClientResponse response = webResource.accept("application/json").get(ClientResponse.class);
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
}
List<Hardware> output = response.getEntity(new GenericType<List<Hardware>>() {});
LOG.severe("Output from Server .... \n");
LOG.severe("Nr of entries: " + output.size());
} catch (Exception e) {
LOG.log(Level.SEVERE, " test request failed", e);
}
}
public static ClientConfig configureClient() {
TrustManager[ ] certs = new TrustManager[ ] {
new X509TrustManager() {
#Override
public X509Certificate[] getAcceptedIssuers() {
LOG.severe("getAcceptedIssuers");
return null;
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
LOG.severe("checkServerTrusted");
}
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
LOG.severe("checkClientTrusted");
}
}
};
SSLContext ctx = null;
try {
ctx = SSLContext.getInstance("TLS");
ctx.init(null, certs, null);
} catch (java.security.GeneralSecurityException e) {
LOG.log(Level.SEVERE, "Error", e);
}
HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
LOG.severe("verify");
return true;
}
});
ClientConfig config = new DefaultClientConfig();
try {
config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(
new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
LOG.severe("verify");
return true;
}
},
ctx
));
} catch(Exception e) {
LOG.log(Level.SEVERE, "Error", e);
}
return config;
}
}
While everything seems right, the log lines in the TrustManager and in the HostnameVerifier never show up in the logs, and the connection still failes with an SSL Handshaking Exception on the get(ClientResponse.class).
I've been going over this for a while now and when I compare this to all the tutorials and people saying they fixed it, I can't find a difference.
If someone could point out the flaw that should be in there somehwere....
My company BITPlan has published an open source project to simplify handling of Jersey 1.19 see
https://github.com/BITPlan/com.bitplan.simplerest
The test case:
https://github.com/BITPlan/com.bitplan.simplerest/blob/master/src/test/java/com/bitplan/rest/test/TestBasicAuth.java
creates an SSL connection with no client certificate being used.
This leads to:
Schwerwiegend: SSL Client certificate is missing for /hello/hello
but the testcase nevertheless runs
The handling on the Clientside is done with:
https://github.com/BITPlan/com.bitplan.simplerest/blob/master/src/main/java/com/bitplan/rest/SSLClientHelper.java
if you should have any trouble with this you might want to file an issue
via https://github.com/BITPlan/com.bitplan.simplerest/issues

Spring websocket timeout settings

I'm using the Spring websocket support. My question is how to set the websocket connection timeout. Now the connection is closed automatically after several minutes. I want the connection never to be closed.
Here is my websocket handler:
public class MyHandler implements WebSocketHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
class MyTimerTask extends TimerTask {
private WebSocketSession session;
public MyTimerTask(WebSocketSession session) {
this.session = session;
}
#Override
public void run() {
try {
String msg = ((int)(Math.random()*50)) + "";
this.session.sendMessage(new TextMessage(msg.toString()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Autowired
private UserDao userDao;
#Autowired
private JdbcDaoImpl jdbcDaoImpl;
private Timer timer;
#Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
System.out.println("websocket????");
timer = new Timer();
timer.schedule(new MyTimerTask(session), 0, 1000);
logger.info("logger connection");
}
#Override
public void handleMessage(WebSocketSession session,
WebSocketMessage<?> message) throws Exception { }
#Override
public void handleTransportError(WebSocketSession session,
Throwable exception) throws Exception { }
#Override
public void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatus) throws Exception {
System.out.println("websocket????");
timer.cancel();
}
#Override
public boolean supportsPartialMessages() {
return false;
}
}
my websocket config:
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
</websocket:handlers>
<bean id="myHandler" class="com.sdp.websocket.MyHandler"/>
and javascript client:
var webserver = 'ws://localhost:8080/authtest/myHandler';
var websocket = new WebSocket(webserver);
websocket.onopen = function (evt) { onOpen(evt) };
websocket.onclose = function (evt) { onClose(evt) };
websocket.onmessage = function (evt) { onMessage(evt) };
websocket.onerror = function (evt) { onError(evt) };
function onOpen(evt) {
console.log("Connected to WebSocket server.");
}
function onClose(evt) {
console.log("Disconnected");
}
function onMessage(evt) {
console.log('Retrieved data from server: ' + evt.data);
}
function onError(evt) {
console.log('Error occured: ' + evt.data);
}
debugger;
function sendMsg(){
websocket.send("{msg:'hello'}");
}
The websocket stays opened until either the server or the client decide to close it. However, websockets are affected by two timeouts:
HTTP session timeout;
proxy connection timeouts;
If all you have between your client and your server is a websocket connection, and you don't interact over HTTP with AJAX or requesting other pages, the HTTP session expires and some servers decide to invalidate it along with the websocket (Tomcat7 had a bug that did just that). Some other servers don't do that because they see there is activity on the websocket. See here for an extended discussion: Need some resolution or clarification for how and when HttpSession last access time gets updated.
The other timeout is with proxies. They see the connection and if there is no activity on the wire for a longer period of time, they just cut it because they think it hanged. To address this, while you don't send actual application data, you need to have a heartbeat or a ping/pong of messages from time to time to let the proxy know that the connection is still OK.
Other issues might also intervene, like a buggy browser support for websocket, how your network is configured, firewall rules, etc.
For available timeout options in Spring see the websocket documentation: Configuring the WebSocket Engine.
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
var container = new ServletServerContainerFactoryBean();
container.setMaxSessionIdleTimeout(...);
return container;
}
}

Access Https Rest Service using Spring RestTemplate

Can anybody provide me with a code sample to access the rest service URL secured with HTTPS using the Spring Rest template?
I have the certificate, username and password. Basic Authentication is used on the server-side and I want to create a client that can connect to that server using a provided certificate, username and password (if needed).
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File(keyStoreFile)),
keyStorePassword.toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
.build(),
NoopHostnameVerifier.INSTANCE);
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(
socketFactory).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
MyRecord record = restTemplate.getForObject(uri, MyRecord.class);
LOG.debug(record.toString());
Here is some code that will give you the general idea.
You need to create a custom ClientHttpRequestFactory in order to trust the certificate.
It looks like this:
final ClientHttpRequestFactory clientHttpRequestFactory =
new MyCustomClientHttpRequestFactory(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER, serverInfo);
restTemplate.setRequestFactory(clientHttpRequestFactory);
This is the implementation for MyCustomClientHttpRequestFactory:
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
private final HostnameVerifier hostNameVerifier;
private final ServerInfo serverInfo;
public MyCustomClientHttpRequestFactory (final HostnameVerifier hostNameVerifier,
final ServerInfo serverInfo) {
this.hostNameVerifier = hostNameVerifier;
this.serverInfo = serverInfo;
}
#Override
protected void prepareConnection(final HttpURLConnection connection, final String httpMethod)
throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(hostNameVerifier);
((HttpsURLConnection) connection).setSSLSocketFactory(initSSLContext()
.getSocketFactory());
}
super.prepareConnection(connection, httpMethod);
}
private SSLContext initSSLContext() {
try {
System.setProperty("https.protocols", "TLSv1");
// Set ssl trust manager. Verify against our server thumbprint
final SSLContext ctx = SSLContext.getInstance("TLSv1");
final SslThumbprintVerifier verifier = new SslThumbprintVerifier(serverInfo);
final ThumbprintTrustManager thumbPrintTrustManager =
new ThumbprintTrustManager(null, verifier);
ctx.init(null, new TrustManager[] { thumbPrintTrustManager }, null);
return ctx;
} catch (final Exception ex) {
LOGGER.error(
"An exception was thrown while trying to initialize HTTP security manager.", ex);
return null;
}
}
In this case my serverInfo object contains the thumbprint of the server.
You need to implement the TrustManager interface to get
the SslThumbprintVerifier or any other method you want to verify your certificate (you can also decide to also always return true).
The value org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER allows all host names.
If you need to verify the host name,
you will need to implement it differently.
I'm not sure about the user and password and how you implemented it.
Often,
you need to add a header to the restTemplate named Authorization
with a value that looks like this: Base: <encoded user+password>.
The user+password must be Base64 encoded.
This is a solution with no deprecated class or method :
(Java 8 approved)
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
Important information : Using NoopHostnameVerifier is a security risk
One point from me. I used a mutual cert authentication with spring-boot microservices. The following is working for me, key points here are
keyManagerFactory.init(...) and sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()) lines of code without them, at least for me, things did not work. Certificates are packaged by PKCS12.
#Value("${server.ssl.key-store-password}")
private String keyStorePassword;
#Value("${server.ssl.key-store-type}")
private String keyStoreType;
#Value("${server.ssl.key-store}")
private Resource resource;
private RestTemplate getRestTemplate() throws Exception {
return new RestTemplate(clientHttpRequestFactory());
}
private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
private HttpClient httpClient() throws Exception {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore trustStore = KeyStore.getInstance(keyStoreType);
if (resource.exists()) {
InputStream inputStream = resource.getInputStream();
try {
if (inputStream != null) {
trustStore.load(inputStream, keyStorePassword.toCharArray());
keyManagerFactory.init(trustStore, keyStorePassword.toCharArray());
}
} finally {
if (inputStream != null) {
inputStream.close();
}
}
} else {
throw new RuntimeException("Cannot find resource: " + resource.getFilename());
}
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory =
new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null, getDefaultHostnameVerifier());
return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
}
Here is what I ended up with for the similar problem. The idea is the same as in #Avi's answer, but I also wanted to avoid the static "System.setProperty("https.protocols", "TLSv1");", so that any adjustments won't affect the system. Inspired by an answer from here http://www.coderanch.com/t/637177/Security/Disabling-handshake-message-Java
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier((hostname, session) -> true);
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
* see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
*/
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
#Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
#Override
public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[] {"SSLv3"});
return socket;
}
}
}
You need to configure a raw HttpClient with SSL support, something like this:
#Test
public void givenAcceptingAllCertificatesUsing4_4_whenUsingRestTemplate_thenCorrect()
throws ClientProtocolException, IOException {
CloseableHttpClient httpClient
= HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
ResponseEntity<String> response
= new RestTemplate(requestFactory).exchange(
urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode().value(), equalTo(200));
}
from: Baeldung

Resources