I'm developing an hybrid cordova app which might connect to different servers. Some of them do require a client certificate.
On an Android mobile the corresponding root cert + client certificate is installed.
On Chrome browser I get the following dialog to choose the corresponding client certificate for the Web connection.
With the cordova plugin cordova-client-cert-authentication the same dialog pops up for Http(s) requests within the WebView.
My question is how to achieve a automatic certificate selection on Http(s) requests on the native Android platform without explicitly declaring the corresponding client certificate. Or is there something similiar to the user selection of certificate like implemented on Chrome?
This is the current implementation, which throws a handshake exception:
try {
URL url = new URL( versionUrl );
HttpsURLConnection urlConnection = ( HttpsURLConnection ) url.openConnection();
urlConnection.setConnectTimeout( 10000 );
InputStream in = urlConnection.getInputStream();
}
catch(Exception e)
{
//javax.net.ssl.SSLHandshakeException: Handshake failed
}
You can use a certificate previously installed in Android KeyChain (the system key store) extending X509ExtendedKeyManager to configure the SSLContext used by URLConnection
The certificate is referenced by an alias that you need. To prompt user for selection with a dialog similar to chrome use:
KeyChain.choosePrivateKeyAlias(this, this, // Callback
new String[] {"RSA", "DSA"}, // Any key types.
null, // Any issuers.
null, // Any host
-1, // Any port
DEFAULT_ALIAS);
This is the code to configure the SSL connection using a custom KeyManager. It uses the default TrustManager and HostnameVerifier. You will need to configure them if the server is using a self signed certificate not present in Android default truststore (trusting all certificates is not recommended)
//Configure trustManager if needed
TrustManager[] trustManagers = null;
//Configure keyManager to select the private key and the certificate chain from KeyChain
KeyManager keyManager = KeyChainKeyManager.fromAlias(
context, mClientCertAlias);
//Configure SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(new KeyManager[] {keyManager}, trustManagers, null);
//Perform the connection
URL url = new URL( versionUrl );
HttpsURLConnection urlConnection = ( HttpsURLConnection ) url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
//urlConnection.setHostnameVerifier(hostnameVerifier); //Configure hostnameVerifier if needed
urlConnection.setConnectTimeout( 10000 );
InputStream in = urlConnection.getInputStream();
Finally here you have and a full implementation of the custom X509ExtendedKeyManager extracted from here and here that is in charge of selecting the client certificate. I have extracted the required code.
public static class KeyChainKeyManager extends X509ExtendedKeyManager {
private final String mClientAlias;
private final X509Certificate[] mCertificateChain;
private final PrivateKey mPrivateKey;
/**
* Builds an instance of a KeyChainKeyManager using the given certificate alias.
* If for any reason retrieval of the credentials from the system {#link android.security.KeyChain} fails,
* a {#code null} value will be returned.
*/
public static KeyChainKeyManager fromAlias(Context context, String alias)
throws CertificateException {
X509Certificate[] certificateChain;
try {
certificateChain = KeyChain.getCertificateChain(context, alias);
} catch (KeyChainException e) {
throw new CertificateException(e);
} catch (InterruptedException e) {
throw new CertificateException(e);
}
PrivateKey privateKey;
try {
privateKey = KeyChain.getPrivateKey(context, alias);
} catch (KeyChainException e) {
throw new CertificateException(e);
} catch (InterruptedException e) {
throw new CertificateException(e);
}
if (certificateChain == null || privateKey == null) {
throw new CertificateException("Can't access certificate from keystore");
}
return new KeyChainKeyManager(alias, certificateChain, privateKey);
}
private KeyChainKeyManager(
String clientAlias, X509Certificate[] certificateChain, PrivateKey privateKey) {
mClientAlias = clientAlias;
mCertificateChain = certificateChain;
mPrivateKey = privateKey;
}
#Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
return mClientAlias;
}
#Override
public X509Certificate[] getCertificateChain(String alias) {
return mCertificateChain;
}
#Override
public PrivateKey getPrivateKey(String alias) {
return mPrivateKey;
}
#Override
public final String chooseServerAlias( String keyType, Principal[] issuers, Socket socket) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
#Override
public final String[] getClientAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
#Override
public final String[] getServerAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
}
}
I did not test it. Report any error!
If your URLs are still in development stage (not production version), you can skip those SSL/NON-SSL certificates installing to access the URLs.
Here is how to skip SSL validation :
Call when activity onCreate() or where your need before accessing URL.
public static void skipSSLValidation() {
try {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
/* Create a new array with room for an additional trusted certificate. */
return new X509Certificate[0];
}
#Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
#Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
} catch (Exception e) {
// pass
}
}
Note : If your HTTPS URLs are valid, you will no require to use server-generated certificates. You should using this method for testing/development only. For release/production you don't have to use this method.
Related
I have a self-signed certificate that I use to communicate with my server. As per the this article I can create a Keystore instance with my certificate. I did the same and the code is working just fine, I am able to make server calls over HTTPS connection.
When I print all the certificates that are present in the Keystore it is printing only the certificates that I have inserted into it. I thought that this implementation will instruct android to trust all the in-built certificates in the AndroidCAStore and the new self-signed certificate from my server.
When creating an instance I used AndroidCAStore and AndroidKeyStore but the problem is I am not able to add my self-signed certificate to the keystore. Whenever I call setCertificateEntry I am getting UnsupportedMethodException.
I want to create a KeyStore that has all the default certificate from the Android default keystore and the Self-Signed certificate from my server. How to do that?
public static class CustomTrustManager implements X509TrustManager{
private X509TrustManager defaultTrustManager;
private X509TrustManager localTrustManager;
public CustomTrustManager(KeyStore keyStore){
try {
defaultTrustManager = createTrustManager(null);
localTrustManager = createTrustManager(keyStore);
}catch (NoSuchAlgorithmException e){
Log.e("CustomTrustManager"," Cannot create trust manager : NoSuchAlgorithm found "+e.toString());
}catch (KeyStoreException exp){
Log.e("CustomTrustManager"," Cannot create trust manager : Keystore exception"+e.toString());
}
}
#Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
try {
localTrustManager.checkClientTrusted(x509Certificates, s);
} catch (CertificateException ce) {
defaultTrustManager.checkClientTrusted(x509Certificates, s);
}
}
#Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
Log.e("CustomTrustManager","Checking server trust");
try {
localTrustManager.checkServerTrusted(x509Certificates, s);
} catch (CertificateException ce) {
defaultTrustManager.checkServerTrusted(x509Certificates, s);
}
}
#Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] first = defaultTrustManager.getAcceptedIssuers();
X509Certificate[] second = localTrustManager.getAcceptedIssuers();
X509Certificate[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
private X509TrustManager createTrustManager(KeyStore store) throws NoSuchAlgorithmException, KeyStoreException {
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init((KeyStore) store);
TrustManager[] trustManagers = tmf.getTrustManagers();
return (X509TrustManager) trustManagers[0];
}
}
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);`
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());
}
Hell everyone,
I have set up the java webservices on tomcat + ssl Connection by the link below
http://www.mkyong.com/webservices/jax-ws/deploy-jax-ws-web-services-on-tomcat-ssl-connection/ . It works fine.
My question now is here in this code the client part does not authencticate certificate or ssl connection, I have part only to check the hostname,by hostname verifier,but now I have a self-signed certificate, and not sure what should I do.
how to extend this class. I find few codes from forum but i do not get an entire idea, where the keystore or truststore come from. Reference to any blog or link that guide me is much appreciated.
My Client code is below
public IExample create() throws MalformedURLException{
try{
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
// Trust always
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
// Trust always
}
} };
// Install the all-trusting trust manager
sc = SSLContext.getInstance("SSL");
// Create empty HostnameVerifier
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession arg1) {
if(hostname.equals(arg1.getPeerHost()) && hostname.equals("example.com"))
{
return true;
}else{
return false;
}
}
};
sc.init(null,trustAllCerts , new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
catch(Exception e){
e.printStackTrace();
}
try{
URL url = new URL( urlString );
//1st argument service URI, refer to wsdl document above
//2nd argument is service name, refer to wsdl document above
QName qname = new QName("http://synchronization.ws/", "ExampleImplclass");
Service service = Service.create(url, qname);
IExample iExample = service.getPort(IExample.class);
return iExample;
}catch(Exception e)
{
e.printStackTrace();
return null;
}
}
I'd like to import a new certificate into the keystore without restarting a running service. Is that possible?
Alternatively, is it possible to specify a certificate to use that's not in the keystore for a specific URL connection?
Turns out you can specify specific certificates to use for specific URL fetches; essentially, you need to create your own TrustManager and swap it in, like so:
public String fetchFromUrl(String urlString) throws IOException {
URL url = new URL(urlString);
URLConnection conn = url.openConnection();
if (conn instanceof HttpsURLConnection && shouldSubstituteCert(url)) {
HttpsURLConnection sslConn = (HttpsURLConnection) conn;
try {
SSLContext context = SSLContext.getInstance("SSL");
context.init(null, new TrustManager[] {new MyTrustManager()}, null);
sslConn.setSSLSocketFactory(context.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
throw new IOException("Error creating custom keystore", e);
}
}
return readAll(conn.getInputStream());
}
private static class MyTrustManager implements X509TrustManager {
private final X509TrustManager trustManager;
public MyTrustManager() throws
KeyStoreException, NoSuchAlgorithmException,
CertificateException, IOException {
// Load a KeyStore with only our certificate
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
store.load(null, null);
Certificate cert = loadPemCert();
store.setCertificateEntry("me.com", cert);
// create a TrustManager using our KeyStore
TrustManagerFactory factory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
factory.init(store);
this.trustManager = getX509TrustManager(factory.getTrustManagers());
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
trustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
trustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
return trustManager.getAcceptedIssuers();
}
private static X509TrustManager getX509TrustManager(TrustManager[] managers) {
for (TrustManager tm : managers) {
if (tm instanceof X509TrustManager) {
return (X509TrustManager) tm;
}
}
return null;
}
private Certificate loadPemCert()
throws CertificateException, IOException {
InputStream stream =
this.getClass().getClassLoader().getResourceAsStream("cert.pem");
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return factory.generateCertificate(stream);
}
}