Does OkHttp support accepting self-signed SSL certs? - java

I'm working for a customer who has a server with self-signed SSL cert.
I'm using Retrofit + CustomClient using wrapped OkHttp client:
RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(Config.BASE_URL + Config.API_VERSION)
.setClient(new CustomClient(new OkClient(), context))
.build();
Does OkHttp support calling Self-Signed SSL cert server by default?
By the way. Which client is using Retrofit by default? I thought it was OkHttp but when I researched a bit more I realized I needed to import OkHttp dependencies

Yes, It does.
Retrofit allows you to set your custom HTTP client, that is configured to your needs.
As for self-signed SSL certs there is a discussion here. The link contains code samples to add self-signed SSL to Android's DefaultHttpClient and to load this client to Retrofit.
If you need OkHttpClient to accept self signed SSL, you need to pass it custom javax.net.ssl.SSLSocketFactory instance via setSslSocketFactory(SSLSocketFactory sslSocketFactory) method.
The easiest method to get a socket factory is to get one from javax.net.ssl.SSLContext as discussed here.
Here is a sample for configuring OkHttpClient:
OkHttpClient client = new OkHttpClient();
KeyStore keyStore = readKeyStore(); //your method to obtain KeyStore
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "keystore_pass".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(),trustManagerFactory.getTrustManagers(), new SecureRandom());
client.setSslSocketFactory(sslContext.getSocketFactory());
Updated code for okhttp3 (using builder):
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory())
.build();
the client here is now configured to use certificates from your KeyStore. However it will only trust the certificates in your KeyStore and will not trust anything else, even if your system trust them by default. (If you have only self signed certs in your KeyStore and try to connect to Google main page via HTTPS you will get SSLHandshakeException).
You can obtain KeyStore instance from file as seen in docs:
KeyStore readKeyStore() {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
// get user password and file input stream
char[] password = getPassword();
java.io.FileInputStream fis = null;
try {
fis = new java.io.FileInputStream("keyStoreName");
ks.load(fis, password);
} finally {
if (fis != null) {
fis.close();
}
}
return ks;
}
If you are on android you can put it in res/raw folder and get it from a Context instance using
fis = context.getResources().openRawResource(R.raw.your_keystore_filename);
There are several discussions on how to create your keystore. For example here

Another thing to note, if you pre-install the CA on the device, you can make regular https calls with OKHttp, and no special ssl hoops. The key is to add the network security configs to your manifest.
The key for me to know to do this was that I was getting the following exception.
"Trust anchor for certification path not found."
Here is a good article from Google about how to configure it.
https://developer.android.com/training/articles/security-config
Here is an example of my network_security_config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="user"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>

For okhttp3.OkHttpClient Version com.squareup.okhttp3:okhttp:3.2.0 you have to use the code below :
import okhttp3.Call;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
......
OkHttpClient.Builder clientBuilder = client.newBuilder().readTimeout(LOGIN_TIMEOUT_SEC, TimeUnit.SECONDS);
boolean allowUntrusted = true;
if ( allowUntrusted) {
Log.w(TAG,"**** Allow untrusted SSL connection ****");
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
#Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] cArrr = new X509Certificate[0];
return cArrr;
}
#Override
public void checkServerTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
#Override
public void checkClientTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
}};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
clientBuilder.sslSocketFactory(sslContext.getSocketFactory());
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
Log.d(TAG, "Trust Host :" + hostname);
return true;
}
};
clientBuilder.hostnameVerifier( hostnameVerifier);
}
final Call call = clientBuilder.build().newCall(request);

Two methods from our app to get OkHttpClient 3.0 instance that recognizes your self-signed certificates from your keystore (uses prepared pkcs12 certificate file in your Android project "raw" resources folder):
private static OkHttpClient getSSLClient(Context context) throws
NoSuchAlgorithmException,
KeyStoreException,
KeyManagementException,
CertificateException,
IOException {
OkHttpClient client;
SSLContext sslContext;
SSLSocketFactory sslSocketFactory;
TrustManager[] trustManagers;
TrustManagerFactory trustManagerFactory;
X509TrustManager trustManager;
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(readKeyStore(context));
trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
trustManager = (X509TrustManager) trustManagers[0];
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
sslSocketFactory = sslContext.getSocketFactory();
client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManager)
.build();
return client;
}
/**
* Get keys store. Key file should be encrypted with pkcs12 standard. It can be done with standalone encrypting java applications like "keytool". File password is also required.
*
* #param context Activity or some other context.
* #return Keys store.
* #throws KeyStoreException
* #throws CertificateException
* #throws NoSuchAlgorithmException
* #throws IOException
*/
private static KeyStore readKeyStore(Context context) throws
KeyStoreException,
CertificateException,
NoSuchAlgorithmException,
IOException {
KeyStore keyStore;
char[] PASSWORD = "12345678".toCharArray();
ArrayList<InputStream> certificates;
int certificateIndex;
InputStream certificate;
certificates = new ArrayList<>();
certificates.add(context.getResources().openRawResource(R.raw.ssl_pkcs12));
keyStore = KeyStore.getInstance("pkcs12");
for (Certificate certificate : certificates) {
try {
keyStore.load(certificate, PASSWORD);
} finally {
if (certificate != null) {
certificate.close();
}
}
}
return keyStore;
}

I had the same problem and I fixed it with the okhttp client as follow:
1.) Add the certificate file to src/main/res/raw/, which includes this content:
-----BEGIN CERTIFICATE-----
...=
-----END CERTIFICATE-----
2.) Instanciate the okHttpClient:
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getSslContext(context).getSocketFactory())
.build();
3.) Here is the used getSslContext(Context context) method:
SSLContext getSslContext(Context context) throws Exception {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // "BKS"
ks.load(null, null);
InputStream is = context.getResources().openRawResource(R.raw.certificate);
String certificate = Converter.convertStreamToString(is);
// generate input stream for certificate factory
InputStream stream = IOUtils.toInputStream(certificate);
// CertificateFactory
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// certificate
Certificate ca;
try {
ca = cf.generateCertificate(stream);
} finally {
is.close();
}
ks.setCertificateEntry("my-ca", ca);
// TrustManagerFactory
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
// Create a TrustManager that trusts the CAs in our KeyStore
tmf.init(ks);
// Create a SSLContext with the certificate that uses tmf (TrustManager)
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
If there is the need to add multiple certificates to the SslContext, here is the solution.

Against Retrofit 1.9 I was able to accept any certificate with the following strategy: use at your own risk! Accepting any certificate is dangerous and you should understand the consequences. Some relevant parts come from org.apache.http.ssl, so you may require some imports here.
// ...
Client httpClient = getHttpClient();
RestAdapter adapter = new RestAdapter.Builder()
.setClient(httpClient)
// ... the rest of your builder setup
.build();
// ...
private Client getHttpClient() {
try {
// Allow self-signed (and actually any) SSL certificate to be trusted in this context
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
sslContext.getSocketFactory();
SSLSocketFactory sf = sslContext.getSocketFactory();
OkHttpClient client = new OkHttpClient();
client.setSslSocketFactory(sf);
return new OkClient(client);
} catch (Exception e) {
throw new RuntimeException("Failed to create new HTTP client", e);
}
}

I know that this post is quite old, bui i want to share the solution that worked for me with the latest update of OkHttp, the 3.12.1 version in the time i'm writing.
First of all you need to obtain the KeyStore object that will be then added to the TrustManager:
/**
* #param context The Android context to be used for retrieving the keystore from raw resource
* #return the KeyStore read or null on error
*/
private static KeyStore readKeyStore(Context context) {
char[] password = "keystore_password".toCharArray();
// for non-android usage:
// try(FileInputStream is = new FileInputStream(keystoreName)) {
try(InputStream is = context.getResources().openRawResource(R.raw.keystore)) {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(is, password);
return ks;
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return null;
}
Now you can get the builded OkHttpClient with the self-signed certificate in your keystore:
/**
* #param context The Android context used to obtain the KeyStore
* #return the builded OkHttpClient or null on error
*/
public static OkHttpClient getOkHttpClient(Context context) {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(readKeyStore(context));
X509TrustManager trustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
return new OkHttpClient.Builder()
.hostnameVerifier((hostname, session) -> {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
/* Never return true without verifying the hostname, otherwise you will be vulnerable
to man in the middle attacks. */
return hv.verify("your_hostname_here", session);
})
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Remember that it is highly discouraged to return always true in the hostnameVerifier to avoid risk of man in the middle attacks.

I find answer from :
https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java
It uses HandshakeCertificates to add certificates.
HandshakeCertificates certificates = new HandshakeCertificates.Builder()
.addTrustedCertificate(letsEncryptCertificateAuthority)
.addTrustedCertificate(entrustRootCertificateAuthority)
.addTrustedCertificate(comodoRsaCertificationAuthority)
// Uncomment if standard certificates are also required.
//.addPlatformTrustedCertificates()
.build();
client = new OkHttpClient.Builder()
.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager())
.build();
If you have trust certificates in store, you can use it as below:
.......
List<X509Certificate> certificates = getCertificatesFromTrustStore();
Builder certificateBuilder = new HandshakeCertificates.Builder();
for (X509Certificate x509Certificate : certificates) {
certificateBuilder.addTrustedCertificate(x509Certificate);
}
HandshakeCertificates handshakeCertificates = certificateBuilder.build();
.......
//To get certificates from a keystore
private List<X509Certificate> getCertificatesFromTrustStore() throws Exception {
KeyStore truststore = KeyStore.getInstance("JKS");
truststore.load(new FileInputStream("d:\certs.jsk"), "mypassword".toCharArray());
PKIXParameters params = new PKIXParameters(truststore);
Set<TrustAnchor> trustAnchors = params.getTrustAnchors();
LOG.debug("{} certificates found in {} which will be used", trustAnchors.size(), trustStorePath);
List<X509Certificate> certificates = trustAnchors.stream()
.map(TrustAnchor::getTrustedCert)
.collect(Collectors.toList());
return certificates;
}

If you need to provide your own certificate, you can pass it like this:
Manifest:
<application android:networkSecurityConfig="#xml/network_security_config"
... >
res/xml/network_security_config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="#raw/your_PEM_formatted_cert" />
<certificates src="user" />
<certificates src="system" />
</trust-anchors>
</base-config>

The following piece of code allows you to create an OkHttp client that can be used with Retrofit. Mailmustdie's answer is "better" in the sense that it is more secure, but the code snippet below is faster to implement
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.ResponseBody;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okio.BufferedSink;
import retrofit.client.Header;
import retrofit.client.OkClient;
import retrofit.client.Request;
import retrofit.client.Response;
import retrofit.mime.TypedInput;
import retrofit.mime.TypedOutput;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class TrustingOkClient extends OkClient {
static final int CONNECT_TIMEOUT_MILLIS = 15 * 1000; // 15s
static final int READ_TIMEOUT_MILLIS = 20 * 1000; // 20s
private static OkHttpClient generateDefaultOkHttp() {
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
final TrustManager[] certs = new TrustManager[]{new X509TrustManager() {
#Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkServerTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
#Override
public void checkClientTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
}};
SSLContext ctx = null;
try {
ctx = SSLContext.getInstance("TLS");
ctx.init(null, certs, new SecureRandom());
} catch (final java.security.GeneralSecurityException ex) {
}
try {
final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
#Override
public boolean verify(final String hostname,
final SSLSession session) {
return true;
}
};
client.setHostnameVerifier(hostnameVerifier);
client.setSslSocketFactory(ctx.getSocketFactory());
} catch (final Exception e) {
}
return client;
}
private final OkHttpClient client;
public TrustingOkClient() {
this(generateDefaultOkHttp());
}
public TrustingOkClient(OkHttpClient client) {
if (client == null) throw new NullPointerException("client == null");
this.client = client;
}
#Override public Response execute(Request request) throws IOException {
return parseResponse(client.newCall(createRequest(request)).execute());
}
static com.squareup.okhttp.Request createRequest(Request request) {
com.squareup.okhttp.Request.Builder builder = new com.squareup.okhttp.Request.Builder()
.url(request.getUrl())
.method(request.getMethod(), createRequestBody(request.getBody()));
List<Header> headers = request.getHeaders();
for (int i = 0, size = headers.size(); i < size; i++) {
Header header = headers.get(i);
String value = header.getValue();
if (value == null) value = "";
builder.addHeader(header.getName(), value);
}
return builder.build();
}
static Response parseResponse(com.squareup.okhttp.Response response) {
return new Response(response.request().urlString(), response.code(), response.message(),
createHeaders(response.headers()), createResponseBody(response.body()));
}
private static RequestBody createRequestBody(final TypedOutput body) {
if (body == null) {
return null;
}
final MediaType mediaType = MediaType.parse(body.mimeType());
return new RequestBody() {
#Override public MediaType contentType() {
return mediaType;
}
#Override public void writeTo(BufferedSink sink) throws IOException {
body.writeTo(sink.outputStream());
}
#Override public long contentLength() {
return body.length();
}
};
}
private static TypedInput createResponseBody(final ResponseBody body) {
try {
if (body.contentLength() == 0) {
return null;
}
return new TypedInput() {
#Override public String mimeType() {
MediaType mediaType = body.contentType();
return mediaType == null ? null : mediaType.toString();
}
#Override public long length() {
try {
return body.contentLength();
} catch (Exception exception) {
System.out.println(exception.toString());
}
throw new Error("createResponseBody has invalid length for its response");
}
#Override public InputStream in() throws IOException {
return body.byteStream();
}
};
} catch (Exception exception) {
System.out.println(exception.toString());
}
throw new Error("createResponseBody has invalid content length for its response");
}
private static List<Header> createHeaders(Headers headers) {
int size = headers.size();
List<Header> headerList = new ArrayList<Header>(size);
for (int i = 0; i < size; i++) {
headerList.add(new Header(headers.name(i), headers.value(i)));
}
return headerList;
}
}

Related

Call API on server that is signed using Self-Signed Certificate

I'm working on an Android application that call some APIs over https. using retrofit2 and okhttp3. During my development I use a self-signed certificate that I generate in server. I face a lot of problems in calling APIs as I'm using a self-signed certificate, I solve them all but stuck on this exception SSLPeerUnverifiedException.
Currently I had copy my certificate ServerCertificate.cer to Download directory in order to load it and add it to allowed KeyStore.
I try a lot of solutions from different websites. I try okhttp CustomTrust and from Android developer website
I write below code depending on Android developer example:
X509TrustManager mTrustManager = null;
private Retrofit getRetrofit(String identity, String serverBaseUrl) {
Retrofit retrofit = null;
try {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(getSSLConfig().getSocketFactory(), mTrustManager)
.addInterceptor(new CustomInterceptor(identity))
.addInterceptor((new okhttp3.logging.HttpLoggingInterceptor())
.setLevel(okhttp3.logging.HttpLoggingInterceptor.Level.BODY))
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(serverBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
} catch (Exception ex) {
}
return retrofit;
}
private SSLContext getSSLConfig() throws Exception {
FileHelper fileHelper = FileHelper.getInstance();
String cerFilePath = "/storage/emulated/0/Download/ServerCertificate.cer";
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new FileInputStream(cerFilePath);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
//Below line print: ca=CN=SS_CEM_5_4
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
mTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
return context;
}
Currently when I call any API I get following exception:
Exception occurred while calling heartbeat
javax.net.ssl.SSLPeerUnverifiedException: Hostname ss_cem_5_4 not verified:
certificate: sha256/OUxkHCacC0q0+ZQpL/3V1jFgV57CXweub/lSSUXsAZw=
DN: CN=\00S\00S\00_\00C\00E\00M\00_\005\00_\004
subjectAltNames: []
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:330)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:283)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:168)
at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:257)
at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:135)
at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:114)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:126)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at co.sedco.sevicesbase.managementproxy.webproxy.CustomInterceptor.intercept(CustomInterceptor.java:39)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:254)
at okhttp3.RealCall.execute(RealCall.java:92)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:186)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:92)
at co.sedco.sevicesbase.managementproxy.webproxy.ManagementWebProxy.callHeartbeat(ManagementWebProxy.java:271)
at co.sedco.sevicesbase.heartbeat.HeartbeatManager$CallHeartbeatTimerTask.run(HeartbeatManager.java:91)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
I only managed to make self-signed certificate work by adding HostnameVerifier to OkHttpClient and override Verify function to always return true, but this solution is not acceptable, I believe that I will encounter a situations where I have to use a self-signed certificate in customer server (Although it is not recommended).
I'm calling Server using Url: https://ss_cem_5_4/Portal/api/GetHeartbeat
I also should mention that I was unable to call server through server name so I modified hosts file in path '/system/etc/' to add mapping for my server. (I'm working on a rooted device)
After many hours searching for a solution for the problem finally it has been solved. I will write here my solution in case anyone face same problem.
First there was a problem in Self-Signed certificate, as Patrick Mevzek mention in the comments subjectAltNames was empty, and after checking how OkHostnameVerifier verify the server you are calling is trusted or not, it check subjectAltNames that server name in the Url match any name in certificate subjectAltNames.
Previously I was generating my self-signed certificate using IIS and it appear that it only fill certificate Common Name CN and keep subjectAltNames empty. I had a hint regarding this problem from following stackoverflow answer. In order to solve this problem I generate a new license by running below command on server PowerShell.
New-SelfSignedCertificate -DnsName "ss_cem_5_4" -CertStoreLocation "cert:\LocalMachine\My"
Please make sure to run PowerShell as administrator, and for more information regarding New-SelfSignedCertificate command parameters you can check this Microsoft website.
My second problem (or requirement) was to trust my self-signed certificate, I try some solutions but they all led to only trust my certificate, I was unable to call websites which have a certificate that is trusted by Android by default, so I search for a solution to extend Android trusted certificates, and with the help of this stackoverflow answer I manage to accomplish that, and below is my full code.
Below class is used to extend Android trusted certificates:
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class AdditionalKeyStoresSSLSocketFactory extends SSLSocketFactory {
protected SSLContext sslContext = SSLContext.getInstance("TLS");
public TrustManager[] TrustManager;
public AdditionalKeyStoresSSLSocketFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
//super(null, null, null, null, null, null);
super();
TrustManager = new TrustManager[]{new AdditionalKeyStoresTrustManager(keyStore)};
sslContext.init(null, TrustManager, null);
}
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return sslContext.getSocketFactory().createSocket(host, port, localHost, localPort);
}
#Override
public Socket createSocket(String host, int port) throws IOException {
return sslContext.getSocketFactory().createSocket(host, port);
}
#Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return sslContext.getSocketFactory().createSocket(host, port);
}
#Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return sslContext.getSocketFactory().createSocket(address, port, localAddress, localPort);
}
#Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
#Override
public String[] getDefaultCipherSuites() {
return sslContext.getSocketFactory().getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return sslContext.getSocketFactory().getSupportedCipherSuites();
}
/**
* Based on http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager
*/
public static class AdditionalKeyStoresTrustManager implements X509TrustManager {
protected ArrayList<X509TrustManager> x509TrustManagers = new ArrayList<X509TrustManager>();
protected AdditionalKeyStoresTrustManager(KeyStore... additionalkeyStores) {
final ArrayList<TrustManagerFactory> factories = new ArrayList<TrustManagerFactory>();
try {
// The default Trustmanager with default keystore
final TrustManagerFactory original = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
original.init((KeyStore) null);
factories.add(original);
for (KeyStore keyStore : additionalkeyStores) {
final TrustManagerFactory additionalCerts = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
additionalCerts.init(keyStore);
factories.add(additionalCerts);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
/*
* Iterate over the returned trustmanagers, and hold on
* to any that are X509TrustManagers
*/
for (TrustManagerFactory tmf : factories)
for (TrustManager tm : tmf.getTrustManagers())
if (tm instanceof X509TrustManager)
x509TrustManagers.add((X509TrustManager) tm);
if (x509TrustManagers.size() == 0)
throw new RuntimeException("Couldn't find any X509TrustManagers");
}
/*
* Delegate to the default trust manager.
*/
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
final X509TrustManager defaultX509TrustManager = x509TrustManagers.get(0);
defaultX509TrustManager.checkClientTrusted(chain, authType);
}
/*
* Loop over the trustmanagers until we find one that accepts our server
*/
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509TrustManager tm : x509TrustManagers) {
try {
tm.checkServerTrusted(chain, authType);
return;
} catch (CertificateException e) {
// ignore
}
}
throw new CertificateException();
}
public X509Certificate[] getAcceptedIssuers() {
final ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
for (X509TrustManager tm : x509TrustManagers)
list.addAll(Arrays.asList(tm.getAcceptedIssuers()));
return list.toArray(new X509Certificate[list.size()]);
}
}
}
Below code is to create my AdditionalKeyStoresSSLSocketFactory
private Certificate getCertificate(String cerFilePath) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new FileInputStream(cerFilePath);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
return ca;
}
private SSLSocketFactory getSSLConfig() throws Exception {
FileHelper fileHelper = FileHelper.getInstance();
String downloadPath = fileHelper.getDeviceDownloadPath() + File.separator;
String[] cerFilePath = new String[]{downloadPath + "ServerCertificate.cer", downloadPath + "ServerCertificate2.cer", downloadPath + "ServerCertificate3.cer", downloadPath + "ServerCertificate4.cer"};
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < cerFilePath.length; i++) {
Certificate ca = getCertificate(cerFilePath[i]);
keyStore.setCertificateEntry("ca" + i, ca);
}
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
mTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
AdditionalKeyStoresSSLSocketFactory factory = new AdditionalKeyStoresSSLSocketFactory(keyStore);
return factory;
}
and adding my custom SSLSocketFactory using below code:
SSLSocketFactory factory = getSSLConfig();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(factory, (X509TrustManager) ((AdditionalKeyStoresSSLSocketFactory) factory).TrustManager[0])
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(serverBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
Disclaimer: I copy my certificates to the download directory, I do not think this is a good idea because it has a lot of security risks, my code is just for demonstration

error:0c0890ba:ASN.1 encoding routines:asn1_check_tlen:WRONG_TAG

I am trying to implement ssl support in my volley request (also I saw answers in SO with similar issues, but it does not help me)
With help of this article I converted my certificate extension from .cer to .bks
That according to this SO answer I do next
mRequestQueue = Volley.newRequestQueue(this, hurlStack);
private HurlStack hurlStack = new HurlStack()
{
#Override
protected HttpURLConnection createConnection(URL url) throws IOException
{
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) super.createConnection(url);
try
{
httpsURLConnection.setSSLSocketFactory(getSSLSocketFactory());
httpsURLConnection.setHostnameVerifier(getHostnameVerifier());
}
catch (Exception e)
{
AppUtils.printLog(Log.ERROR, TAG, e.getMessage());
}
return httpsURLConnection;
}
};
private SSLSocketFactory getSSLSocketFactory() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException
{
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = getResources().openRawResource(R.raw.keystore); // this cert file stored in \app\src\main\res\raw folder path
Certificate ca = cf.generateCertificate(caInput);
caInput.close();
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
TrustManager[] wrappedTrustManagers = getWrappedTrustManagers(tmf.getTrustManagers());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, wrappedTrustManagers, null);
return sslContext.getSocketFactory();
}
// Let's assume your server app is hosting inside a server machine
// which has a server certificate in which "Issued to" is "localhost",for example.
// Then, inside verify method you can verify "localhost".
// If not, you can temporarily return true
private HostnameVerifier getHostnameVerifier()
{
return new HostnameVerifier()
{
#Override
public boolean verify(String hostname, SSLSession session)
{
//return true; // verify always returns true, which could cause insecure network traffic due to trusting TLS/SSL server certificates for wrong hostnames
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify("localhost", session);
}
};
}
private TrustManager[] getWrappedTrustManagers(TrustManager[] trustManagers)
{
final X509TrustManager originalTrustManager = (X509TrustManager) trustManagers[0];
return new TrustManager[] {new X509TrustManager()
{
public X509Certificate[] getAcceptedIssuers()
{
return originalTrustManager.getAcceptedIssuers();
}
public void checkClientTrusted(X509Certificate[] certs, String authType)
{
try
{
if (certs != null && certs.length > 0)
{
certs[0].checkValidity();
}
else
{
originalTrustManager.checkClientTrusted(certs, authType);
}
}
catch (CertificateException e)
{
Log.w("checkClientTrusted", e.toString());
}
}
public void checkServerTrusted(X509Certificate[] certs, String authType)
{
try
{
if (certs != null && certs.length > 0)
{
certs[0].checkValidity();
}
else
{
originalTrustManager.checkServerTrusted(certs, authType);
}
}
catch (CertificateException e)
{
Log.w("checkServerTrusted", e.toString());
}
}
}};
}
And I get next error
com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: java.lang.RuntimeException: error:0c0890ba:ASN.1 encoding routines:asn1_check_tlen:WRONG_TAG
And because of this I get such respond
Bad Request
Bad Request - Invalid Header
HTTP Error 400. The request has an invalid header name.
What am I doing wrong?
Feel free to ask
EDIT 1
so now my getSSLSocketFactory() method look like this
private SSLSocketFactory getSSLSocketFactory() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException
{
InputStream ksInStream = getResources().openRawResource(R.raw.keystore);
KeyStore ks = KeyStore.getInstance("BKS");
ks.load(ksInStream, SslUtils.KEYSTORE_PASSWORD_SSL.toCharArray());
// Certificate cert = ks.getCertificate("alias");
// ks.setCertificateEntry("ca", cert);
ksInStream.close();
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(ks);
TrustManager[] wrappedTrustManagers = getWrappedTrustManagers(tmf.getTrustManagers());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, wrappedTrustManagers, null);
return sslContext.getSocketFactory();
}
Now I did not get message about wrong TAG , but I still get bad respond
ResponseJsonString =
Bad Request
Bad Request - Invalid Header
HTTP Error 400. The request has an invalid header name.
In this code you seem to load keystore in BKS format as it would be X.509 encoded certificate, which is bound to fail
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = getResources().openRawResource(R.raw.elalkeystore);
Certificate ca = cf.generateCertificate(caInput);
caInput.close();
You can load keystore like this:
InputStream ksInStream = getResources().openRawResource(R.raw.elalkeystore);
KeyStore ks = KeyStore.getInstance("BKS");
ks.load(ksInStream, keystorePasswordCharArray);
Certificate cert = ks.getCertificate("entryAlias");
ksInStream.close();
Eventually I did not find solution for the issue, I found another approach for implementation
So follow this article
http://ogrelab.ikratko.com/using-android-volley-with-self-signed-certificate/
also if there is any issue about converting .cer to .bks here my SO question and answer
Extension of certificate .cer convert to .bks

Consuming Java / Spring rest service using HTTPs(and certificate)

I have to consume a rest service which is running on HTTPs. The producer has given me the certificate and method arguments. Could you please let me know how to consume the service and how to use the certificate in code. I am using Spring 4, Java 8. Please share the code snippet.
If it is just an one way SSL where consumer validates the identity of the service, you simply need to import the certificate provided by the service(producers certificate) to you trust store (CACerts file) or write your own trust manager.
For 2 Way SSL where service also authenticate the client's identity, you not only need to validate the identity of the service, you also need to send your certificate to the service so that service can take decision on it.
Following snippet is for 2 way SSL, but you can easily adopt it to 1 way SSL by commenting out the portion which sends client certicate to the server.
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager(){
public X509Certificate[] getAcceptedIssuers(){return null;}
public void checkClientTrusted(X509Certificate[] certs, String authType){}
public void checkServerTrusted(X509Certificate[] certs, String authType){}
}};
// Install the all-trusting trust manager
RestTemplate restTemplate = new RestTemplate();
try {
String keyPassphrase = "changeit";
KeyStore keyStore = KeyStore.getInstance("jks");
keyStore.load(new FileInputStream("c:\\jks\\client.jks"), keyPassphrase.toCharArray());
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmfactory.init(keyStore, keyPassphrase.toCharArray());
KeyManager[] keyManager = kmfactory.getKeyManagers();
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(keyManager, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
if (hostname.equals("localhost")) {
return true;
}
return false;
}
});
} catch (Exception e) {
;
}
System.out.println("Testing listAllUsers API-----------");
restTemplate.setErrorHandler(new ResponseErrorHandler(){
#Override
public void handleError(ClientHttpResponse rs) throws IOException {
InputStream in = rs.getBody();
String str = new String("");
int len =0;
while((len = in.available()) > 0){
byte[] bytes = new byte[len];
in.read(bytes);
str = str + new String (bytes, "UTF-8");
}
System.out.println(str);
}
#Override
public boolean hasError(ClientHttpResponse rs) throws IOException {
return false;
}
});
try{
String usersMap = restTemplate.getForObject(REST_SERVICE_URI+"/user/shailendra/", String.class);`

Does X509TrustManagerImpl.checkServerTrusted() handle OCSP by itself if the appropriate properties are set?

public class CustomTrustManager implements X509TrustManager {
private X509TrustManager trustManager;
// If a connection was previously attempted and failed the certificate check, that certificate chain will be saved here.
private Certificate[] rejectedCertificates = null;
private Certificate[] encounteredCertificates = null;
private KeyStore keyStore = null;
private Logger logger;
/**
* Constructor
*
* #param loggerFactory
* see {#link InstanceLoggerFactory}
*/
public CustomTrustManager(InstanceLoggerFactory loggerFactory) {
try {
this.logger = loggerFactory.getLogger(CustomTrustManager.class);
keyStore = KeyStore.getInstance("JKS");
// a keyStore must be initialized with load, even if certificate trust is not file based.
keyStore.load(null, null);
System.setProperty("com.sun.net.ssl.checkRevocation", "true");
Security.setProperty("ocsp.enable", "true");
} catch (Exception ex) {
logger.error("Problem initializing keyStore", ex);
}
}
/**
* Returns the rejected certificate based on the last usage
*/
public Certificate[] getRejectedCertificateChain() {
return rejectedCertificates;
}
/**
* Returns the encountered certificates based on the last usage
*/
public Certificate[] getEncounteredCertificates() {
return encounteredCertificates;
}
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if (trustManager != null) {
trustManager.checkClientTrusted(chain, authType);
}
}
/**
* Checks if a server is trusted, based on the wrapped keyStore's trust
* anchors. This will also capture the encountered certificate chain and, if
* trust fails, the rejected certificate chain.
*/
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CustomCertificateException {
// Capture the certificate if it fails
try {
encounteredCertificates = chain;
if (trustManager != null) {
trustManager.checkServerTrusted(chain, authType);
} else {
throw new RuntimeException("Trust manager is null");
}
} catch (CertificateException ex) {
rejectedCertificates = chain;
throw new CustomCertificateException(ex, rejectedCertificates);
} catch (Exception ex) {
rejectedCertificates = chain;
throw new CustomCertificateException(new CertificateException(ex), rejectedCertificates);
}
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return trustManager == null ? new X509Certificate[0] : trustManager.getAcceptedIssuers();
}
/**
* initializes the internal trust manager with all known certificates
* certificates are stored in the keyStore object
*/
private void initTrustManager() {
try {
// initialize a new TMF with our keyStore
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
// keyStore must not be empty
CertPathParameters pkixParams = new PKIXBuilderParameters(keyStore, new X509CertSelector());
((PKIXBuilderParameters) pkixParams).setRevocationEnabled(true);
tmf.init(new CertPathTrustManagerParameters(pkixParams));
// acquire X509 trust manager from factory
TrustManager tms[] = tmf.getTrustManagers();
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
trustManager = (X509TrustManager) tm;
break;
}
}
} catch (Exception ex) {
logger.error("Problem initializing trust manager", ex);
}
}
...
}
Here I've implemented X509TrustManager trust manager and tried to delegate the appropriate checking calls to the x509 trust manager found at run time.
My question is are the properties I've set regarding to OCSP enough to be sure that Java will also do OCSP while validating the certificate chain? In other words will checkServerTrusted() method handle that by itself if the properties are set?
It does not look like you're checking the revocation via OCSP. Here is an example of how to do this. You will need the target certificate and the responder URL. I extracted this from a working example and modified it to be as generic as possible. Have not tested it, but it should work or be very close to working. You might have to tailor it to your needs, but not by much.
private void validateCertPath(X509Certificate targetCertificate, X509Certificate issuerCertificate, String responderURL, String trustAnchorDirectory)
throws CertPathValidatorException,
InvalidAlgorithmParameterException,
FileNotFoundException,
CertificateException,
NoSuchAlgorithmException {
List<X509Certificate> certList = new Vector<X509Certificate>();
certList.add(targetCertificate);
certList.add(issuerCertificate);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath cp = cf.generateCertPath(certList);
CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
Set<TrustAnchor> trustStore = new HashSet<TrustAnchor>();
TrustAnchor anchor = null;
X509Certificate cacert = null;
File directory = new File(trustAnchorDirectory);
String certFileNames[] = directory.list();
for (String certFile : certFileNames) {
cacert = readCert(trustAnchorDirectory +"/" + certFile);
anchor = new TrustAnchor(cacert, null);
trustStore.add(anchor);
}
PKIXParameters params = new PKIXParameters(trustStore);
params.setRevocationEnabled(true);
Security.setProperty("ocsp.enable", "true");
Security.setProperty("ocsp.responderURL", responderUrl);
PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
System.out.println("Certificate validated");
System.out.println("Policy Tree:\n" + result.getPolicyTree());
}

Https Connection Android

I am doing a https post and I'm getting an exception of ssl exception Not trusted server certificate. If i do normal http it is working perfectly fine. Do I have to accept the server certificate somehow?
This is what I am doing. It simply doesn't check the certificate anymore.
// always verify the host - dont check for certificate
final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
/**
* Trust every server - dont check for any certificate
*/
private static void trustAllHosts() {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
} };
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection
.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
}
and
HttpURLConnection http = null;
if (url.getProtocol().toLowerCase().equals("https")) {
trustAllHosts();
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
https.setHostnameVerifier(DO_NOT_VERIFY);
http = https;
} else {
http = (HttpURLConnection) url.openConnection();
}
I'm making a guess, but if you want an actual handshake to occur, you have to let android know of your certificate. If you want to just accept no matter what, then use this pseudo-code to get what you need with the Apache HTTP Client:
SchemeRegistry schemeRegistry = new SchemeRegistry ();
schemeRegistry.register (new Scheme ("http",
PlainSocketFactory.getSocketFactory (), 80));
schemeRegistry.register (new Scheme ("https",
new CustomSSLSocketFactory (), 443));
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager (
params, schemeRegistry);
return new DefaultHttpClient (cm, params);
CustomSSLSocketFactory:
public class CustomSSLSocketFactory extends org.apache.http.conn.ssl.SSLSocketFactory
{
private SSLSocketFactory FACTORY = HttpsURLConnection.getDefaultSSLSocketFactory ();
public CustomSSLSocketFactory ()
{
super(null);
try
{
SSLContext context = SSLContext.getInstance ("TLS");
TrustManager[] tm = new TrustManager[] { new FullX509TrustManager () };
context.init (null, tm, new SecureRandom ());
FACTORY = context.getSocketFactory ();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public Socket createSocket() throws IOException
{
return FACTORY.createSocket();
}
// TODO: add other methods like createSocket() and getDefaultCipherSuites().
// Hint: they all just make a call to member FACTORY
}
FullX509TrustManager is a class that implements javax.net.ssl.X509TrustManager, yet none of the methods actually perform any work, get a sample here.
Good Luck!
http://madurangasblogs.blogspot.in/2013/08/avoiding-javaxnetsslsslpeerunverifiedex.html
Courtesy Maduranga
When developing an application that uses https, your test server doesn't have a valid SSL certificate. Or sometimes the web site is using a self-signed certificate or the web site is using free SSL certificate. So if you try to connect to the server using Apache HttpClient, you will get a exception telling that the "peer not authenticated". Though it is not a good practice to trust all the certificates in a production software, you may have to do so according to the situation.
This solution resolves the exception caused by "peer not authenticated".
But before we go to the solution, I must warn you that this is not a good idea for a production application. This will violate the purpose of using a security certificate. So unless you have a good reason or if you are sure that this will not cause any problem, don't use this solution.
Normally you create a HttpClient like this.
HttpClient httpclient = new DefaultHttpClient();
But you have to change the way you create the HttpClient.
First you have to create a class extending org.apache.http.conn.ssl.SSLSocketFactory.
import org.apache.http.conn.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
Then create a method like this.
public HttpClient getNewHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}
Then you can create the HttpClient.
HttpClient httpclient = getNewHttpClient();
If you are trying to send a post request to a login page the rest of the code would be like this.
private URI url = new URI("url of the action of the form");
HttpPost httppost = new HttpPost(url);
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("username", "user"));
nameValuePairs.add(new BasicNameValuePair("password", "password"));
try {
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
You get the html page to the InputStream. Then you can do whatever you want with the returned html page.
But here you will face a problem. If you want to manage a session using cookies, you will not be able to do it with this method. If you want to get the cookies, you will have to do it via a browser. Then only you will receive cookies.
If you are using a StartSSL or Thawte certificate, it will fail for Froyo and older versions. You can use a newer version's CAcert repository instead of trusting every certificate.
Any of this answers didn't work for me so here is code which trust any certificates.
import java.io.IOException;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
public class HttpsClientBuilder {
public static DefaultHttpClient getBelieverHttpsClient() {
DefaultHttpClient client = null;
SchemeRegistry Current_Scheme = new SchemeRegistry();
Current_Scheme.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
try {
Current_Scheme.register(new Scheme("https", new Naive_SSLSocketFactory(), 8443));
} catch (KeyManagementException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyStoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
HttpParams Current_Params = new BasicHttpParams();
int timeoutConnection = 8000;
HttpConnectionParams.setConnectionTimeout(Current_Params, timeoutConnection);
int timeoutSocket = 10000;
HttpConnectionParams.setSoTimeout(Current_Params, timeoutSocket);
ThreadSafeClientConnManager Current_Manager = new ThreadSafeClientConnManager(Current_Params, Current_Scheme);
client = new DefaultHttpClient(Current_Manager, Current_Params);
//HttpPost httpPost = new HttpPost(url);
//client.execute(httpPost);
return client;
}
public static class Naive_SSLSocketFactory extends SSLSocketFactory
{
protected SSLContext Cur_SSL_Context = SSLContext.getInstance("TLS");
public Naive_SSLSocketFactory ()
throws NoSuchAlgorithmException, KeyManagementException,
KeyStoreException, UnrecoverableKeyException
{
super(null, null, null, null, null, (X509HostnameVerifier)null);
Cur_SSL_Context.init(null, new TrustManager[] { new X509_Trust_Manager() }, null);
}
#Override
public Socket createSocket(Socket socket, String host, int port,
boolean autoClose) throws IOException
{
return Cur_SSL_Context.getSocketFactory().createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket() throws IOException
{
return Cur_SSL_Context.getSocketFactory().createSocket();
}
}
private static class X509_Trust_Manager implements X509TrustManager
{
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// TODO Auto-generated method stub
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// TODO Auto-generated method stub
}
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
};
}
This is a known problem with Android 2.x. I was struggling with this problem for a week until I came across the following question, which not only gives a good background of the problem but also provides a working and effective solution devoid of any security holes.
'No peer certificate' error in Android 2.3 but NOT in 4
For some reason the solution mentioned for httpClient above didn't worked for me. At the end I was able to make it work by correctly overriding the method when implementing the custom SSLSocketFactory class.
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
throws IOException, UnknownHostException
{
return sslFactory.createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket() throws IOException {
return sslFactory.createSocket();
}
This is how it worked perfectly for me. You can see the full custom class and implementing on the following thread:
http://blog.syedgakbar.com/2012/07/21/android-https-and-not-trusted-server-certificate-error/
I make this class and found
package com.example.fakessl;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import android.util.Log;
public class CertificadoAceptar {
private static TrustManager[] trustManagers;
public static class _FakeX509TrustManager implements
javax.net.ssl.X509TrustManager {
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {};
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return (true);
}
public boolean isServerTrusted(X509Certificate[] chain) {
return (true);
}
public X509Certificate[] getAcceptedIssuers() {
return (_AcceptedIssuers);
}
}
public static void allowAllSSL() {
javax.net.ssl.HttpsURLConnection
.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
javax.net.ssl.SSLContext context = null;
if (trustManagers == null) {
trustManagers = new javax.net.ssl.TrustManager[] { new _FakeX509TrustManager() };
}
try {
context = javax.net.ssl.SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
Log.e("allowAllSSL", e.toString());
} catch (KeyManagementException e) {
Log.e("allowAllSSL", e.toString());
}
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
}
}
in you code white this
CertificadoAceptar ca = new CertificadoAceptar();
ca.allowAllSSL();
HttpsTransportSE Transport = new HttpsTransportSE("iphost or host name", 8080, "/WS/wsexample.asmx?WSDL", 30000);
Sources that helped me get to work with my self signed certificate on my AWS Apache server and connect with HttpsURLConnection from android device:
SSL on aws instance - amazon tutorial on ssl
Android Security with HTTPS and SSL - creating your own trust manager on client for accepting your certificate
Creating self signed certificate - easy script for creating your certificates
Then I did the following:
Made sure the server supports https (sudo yum install -y mod24_ssl)
Put this script in a file create_my_certs.sh:
#!/bin/bash
FQDN=$1
# make directories to work from
mkdir -p server/ client/ all/
# Create your very own Root Certificate Authority
openssl genrsa \
-out all/my-private-root-ca.privkey.pem \
2048
# Self-sign your Root Certificate Authority
# Since this is private, the details can be as bogus as you like
openssl req \
-x509 \
-new \
-nodes \
-key all/my-private-root-ca.privkey.pem \
-days 1024 \
-out all/my-private-root-ca.cert.pem \
-subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.com"
# Create a Device Certificate for each domain,
# such as example.com, *.example.com, awesome.example.com
# NOTE: You MUST match CN to the domain name or ip address you want to use
openssl genrsa \
-out all/privkey.pem \
2048
# Create a request from your Device, which your Root CA will sign
openssl req -new \
-key all/privkey.pem \
-out all/csr.pem \
-subj "/C=US/ST=Utah/L=Provo/O=ACME Tech Inc/CN=${FQDN}"
# Sign the request from Device with your Root CA
openssl x509 \
-req -in all/csr.pem \
-CA all/my-private-root-ca.cert.pem \
-CAkey all/my-private-root-ca.privkey.pem \
-CAcreateserial \
-out all/cert.pem \
-days 500
# Put things in their proper place
rsync -a all/{privkey,cert}.pem server/
cat all/cert.pem > server/fullchain.pem # we have no intermediates in this case
rsync -a all/my-private-root-ca.cert.pem server/
rsync -a all/my-private-root-ca.cert.pem client/
Run bash create_my_certs.sh yourdomain.com
Place the certificates in their proper place on the server (you can find configuration in /etc/httpd/conf.d/ssl.conf). All these should be set:
SSLCertificateFile
SSLCertificateKeyFile
SSLCertificateChainFile
SSLCACertificateFile
Restart httpd using sudo service httpd restart and make sure httpd started:
Stopping httpd: [ OK ]
Starting httpd: [ OK ]
Copy my-private-root-ca.cert to your android project assets folder
Create your trust manager:
SSLContext SSLContext;
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = context.getAssets().open("my-private-root-ca.cert.pem");
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext = SSLContext.getInstance("TLS");
SSSLContext.init(null, tmf.getTrustManagers(), null);
And make the connection using HttpsURLConnection:
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(SSLContext.getSocketFactory());
Thats it, try your https connection.
Probably you can try something like this. This helped me
SslContextFactory sec = new SslContextFactory();
sec.setValidateCerts(false);
sec.setTrustAll(true);
org.eclipse.jetty.websocket.client.WebSocketClient client = new WebSocketClient(sec);
Just use this method as your HTTPClient:
public static HttpClient getNewHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}

Categories

Resources