Related
I have a requirement to convert certain bash scripts to java and one such script connects to a server using openssl with a vanity-url as a parameter to check if that is connectable using that vanity-url. See command below
/usr/bin/openssl s_client -connect api.sys.found1.cf.company.com:443 -servername www.app.company.com 2>/dev/null
I wanted to do the similar activity in java and test the connectivity. Any ideas on how to make a open-ssl connection using Java .. Is this something that I need to use external Library ?
I was able to achieve this by referring the document over here
Basically, a SSLEngine needs to be created and make a successful handshake along with SNI
private SocketChannel createSocketChannel() throws IOException {
InetSocketAddress socketAddress = new InetSocketAddress(PROXY_ADDRESS, PROXY_PORT);
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(socketAddress);
socketChannel.configureBlocking(false);
return socketChannel;
}
private SSLContext createSSLContext() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext sslContext = SSLContext.getInstance(TLS_VERSION);
sslContext.init(null,null,null);
return sslContext;
}
private SSLEngine createSSLEngine() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext sslContext = createSSLContext();
SSLEngine sslEngine = sslContext.createSSLEngine(PROXY_ADDRESS, PROXY_PORT);
sslEngine.setUseClientMode(true);
List<SNIServerName> serverNameList = new ArrayList<>();
serverNameList.add(new SNIHostName(SNI_HOST_NAME));
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setServerNames(serverNameList);
sslEngine.setSSLParameters(sslParameters);
return sslEngine;
}
After creating SSLEngine, the handShake has to begin
SocketChannel channel = createSocketChannel();
SSLEngine sslEngine = createSSLEngine();
doHandShake(sslEngine,channel);
private void doHandShake(SSLEngine sslEngine, SocketChannel socketChannel) throws Exception {
System.out.println("Going to do Handshake");
SSLSession session = sslEngine.getSession();
ByteBuffer myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
ByteBuffer peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
ByteBuffer myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
ByteBuffer peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
sslEngine.beginHandshake();
SSLEngineResult result;
handshakeStatus = sslEngine.getHandshakeStatus();
while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED &&
handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
switch (handshakeStatus) {
case NEED_UNWRAP:
if (! (socketChannel.read(peerNetData) <0)) {
peerNetData.flip();
result = sslEngine.unwrap(peerNetData,peerAppData);
peerNetData.compact();
handshakeStatus = result.getHandshakeStatus();
switch (result.getStatus()) {
case OK:
break;
}
}
break;
case NEED_WRAP:
myNetData.clear() ;// Empty the local network packet buffer
result = sslEngine.wrap(myAppData,myNetData);
handshakeStatus = result.getHandshakeStatus();
switch (result.getStatus()) {
case OK:
myNetData.flip();
while (myNetData.hasRemaining()) {
socketChannel.write(myNetData);
}
}
break;
case NEED_TASK:
Runnable task = sslEngine.getDelegatedTask();
if (null!=task) {
task.run();
}
handshakeStatus = sslEngine.getHandshakeStatus();
break;
}
}
Once the handShake is done. you can get the Principal object
Principal principal = sslEngine.getSession().getPeerPrincipal();
if (principal.getName().contains(SNI_HOST_NAME)) {
System.out.println("available ... ");
}else {
System.out.println("Not available");
}
call isAliasExists with your values ,
isAliasExists("api.sys.found1.cf.company.com","www.app.company.com");
Returns true if your alias (servername) is part of the cert,
private static boolean isAliasExists(String hostName, String alias) throws Exception {
String host;
int port;
String[] parts = hostName.split(":");
host = parts[0];
port = (parts.length == 1) ? 443 : Integer.parseInt(parts[1]);
// key store password
char[] passphrase = "changeit".toCharArray();
File file = new File("jssecacerts");
if (file.isFile() == false) {
char SEP = File.separatorChar;
File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security");
file = new File(dir, "jssecacerts");
if (file.isFile() == false) {
file = new File(dir, "cacerts");
}
}
InputStream in = new FileInputStream(file);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(in, passphrase);
in.close();
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
context.init(null, new TrustManager[] { tm }, null);
SSLSocketFactory factory = context.getSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
socket.setSoTimeout(10000);
try {
System.out.println("Starting SSL handshake...");
socket.startHandshake();
socket.close();
System.out.println("Certificate is already trusted");
} catch (SSLException e) {
e.printStackTrace();
}
X509Certificate[] chain = tm.chain;
List<String> altNames=new ArrayList<String>();
for (X509Certificate cert: chain)
{
altNames.addAll(getSubjectAltNames(cert));
}
for(String altName: altNames) {
if(altName.trim().contains(alias))
return true;
}
if (chain == null) {
System.out.println("Could not obtain server certificate chain");
return false;
}
return false;
}
Returns list of alternative names from cert,
private static List<String> getSubjectAltNames(X509Certificate certificate) throws CertificateParsingException {
List<String> result = new ArrayList<>();
try {
Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames();
if (subjectAltNames == null) {
return Collections.emptyList();
}
for (Object subjectAltName : subjectAltNames) {
List<?> entry = (List<?>) subjectAltName;
if (entry == null || entry.size() < 2) {
continue;
}
Integer altNameType = (Integer) entry.get(0);
if (altNameType == null) {
continue;
}
String altName = (String) entry.get(1);
if (altName != null) {
result.add(altName);
}
}
return result;
} catch (CertificateParsingException e) {
return Collections.emptyList();
}
}
custom trust manager,
private static class SavingTrustManager implements X509TrustManager {
private final X509TrustManager tm;
private X509Certificate[] chain;
SavingTrustManager(X509TrustManager tm) {
this.tm = tm;
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
// throw new UnsupportedOperationException();
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
throw new UnsupportedOperationException();
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
this.chain = chain;
tm.checkServerTrusted(chain, authType);
}
}
Without really knowing what SNI was I tried to get some insight with the test-program shown below.
I don't know the output from the openssl s_client command, but the test-program might prove to be a starting point. When the javax.net.debug output is turned on a lot of output is dumped of which only a few lines are relevant (see also the comments). That is a bit annoying and I do not have an easy solution for that. The TrustAllServers class can be reworked to inspect the certificates you expect to receive from the server (a.ka. host) for a particular domain. There might be other options (e.g. the socket's handshake methods) but this is as far as I got.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
// https://stackoverflow.com/questions/56005883/java-equivalent-to-openssl-s-client-command
// Please use latest Java 8 version, bugs are around in earlier versions.
public class ServerNameTest {
public static void main(String[] args) {
// SSL debug options, see https://stackoverflow.com/q/23659564/3080094 and https://access.redhat.com/solutions/973783
// System.setProperty("javax.net.debug", "all");
// System.setProperty("javax.net.debug", "ssl:handshake");
// System.setProperty("jsse.enableSNIExtension", "true"); // "true" is the default
try {
ServerNameTest sn = new ServerNameTest();
// This will show 2 different server certificate chains.
// Note this is a random server - please pick your own one.
sn.test("major.io", "rackerhacker.com");
sn.test("major.io", "major.io");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Done");
}
/*
* With javax.net.debug output you should see something like:
* <pre>
* *** ClientHello
* ...
* Extension server_name, server_name: [type=host_name (0), value=DOMAIN;]
* ...
* *** ServerHello
* ...
* Extension server_name, server_name:
* ...
* </pre>
* Note that if the server does not provide a value for server_name,
* it does not actually mean the server does not support SNI/server_name (see https://serverfault.com/a/506303)
*/
void test(String host, String domain) throws Exception {
SSLParameters sslParams = new SSLParameters();
if (domain != null && !domain.isEmpty()) {
sslParams.setServerNames(Arrays.asList(new SNIHostName(domain)));
}
// Only for webservers: set endpoint algorithm to HTTPS
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
SSLSocketFactory sslsf = serverTrustingSSLFactory();
try (SSLSocket socket = (SSLSocket) sslsf.createSocket()) {
socket.setSSLParameters(sslParams);
socket.setSoTimeout(3_000);
System.out.println("Connecting to " + host + " for domain " + domain);
socket.connect(new InetSocketAddress(host, 443), 3_000);
// Trigger actual connection by getting the session.
socket.getSession();
System.out.println("Connected to remote " + socket.getRemoteSocketAddress());
try (BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
try (OutputStream out = socket.getOutputStream()) {
System.out.println(">> OPTIONS");
out.write("OPTIONS * HTTP/1.1\r\n\r\n".getBytes(StandardCharsets.UTF_8));
System.out.println("<< " + input.readLine());
}
} catch (Exception e) {
System.err.println("No line read: " + e);
}
}
}
SSLSocketFactory serverTrustingSSLFactory() throws Exception {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, trustManager(), null);
return ctx.getSocketFactory();
}
TrustManager[] trustManager() throws Exception {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init( (KeyStore) null);
// Must use "extended" type versus the default javax.net.ssl.X509TrustManager,
// otherwise the error "No subject alternative DNS name matching" keeps showing up.
X509ExtendedTrustManager defaultManager = null;
for (TrustManager trustManager : tmf.getTrustManagers()) {
if (trustManager instanceof X509ExtendedTrustManager) {
defaultManager = (X509ExtendedTrustManager) trustManager;
break;
}
}
if (defaultManager == null) {
throw new RuntimeException("Cannot find default X509ExtendedTrustManager");
}
return new TrustManager[] { new TrustAllServers(defaultManager) };
}
static void printChain(X509Certificate[] chain) {
try {
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
System.out.println("Cert[" + i + "] " + cert.getSubjectX500Principal() + " :alt: " + cert.getSubjectAlternativeNames());
}
} catch (Exception e) {
e.printStackTrace();
}
}
static class TrustAllServers extends X509ExtendedTrustManager {
final X509ExtendedTrustManager defaultManager;
public TrustAllServers(X509ExtendedTrustManager defaultManager) {
this.defaultManager = defaultManager;
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultManager.checkServerTrusted(chain, authType);
} catch (Exception e) {
System.err.println("Untrusted server: " + e);
}
printChain(chain);
}
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
try {
defaultManager.checkServerTrusted(chain, authType, socket);
} catch (Exception e) {
System.err.println("Untrusted server for socket: " + e);
}
printChain(chain);
}
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
try {
defaultManager.checkServerTrusted(chain, authType, engine);
} catch (Exception e) {
System.err.println("Untrusted server for engine: " + e);
}
printChain(chain);
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
defaultManager.checkClientTrusted(chain, authType);
}
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
defaultManager.checkClientTrusted(chain, authType, socket);
}
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
defaultManager.checkClientTrusted(chain, authType, engine);
}
public X509Certificate[] getAcceptedIssuers() {
return defaultManager.getAcceptedIssuers();
}
}
}
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);`
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.
I'm trying to make an https server on Android with a programmatically generated self signed certificate. I feel like I'm pretty close but I still can't connect to the https server. When I attempt to connect to the server with openssl I get the following:
openssl s_client -connect 192.168.1.97:8888
CONNECTED(00000003)
2895:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:/SourceCache/OpenSSL098/OpenSSL098-50/src/ssl/s23_clnt.c:602:
The code is the following:
public class HttpsHello {
private static String domainName = "localhost";
static {
Security.addProvider(new BouncyCastleProvider());
}
public static void test(String[] args) {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair KPair = keyPairGenerator.generateKeyPair();
X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
int ran = new SecureRandom().nextInt();
if (ran < 0) ran = ran * -1;
BigInteger serialNumber = BigInteger.valueOf(ran);
v3CertGen.setSerialNumber(serialNumber);
v3CertGen.setIssuerDN(new X509Principal("CN=" + domainName + ", OU=None, O=None L=None, C=None"));
v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30));
v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365 * 10)));
v3CertGen.setSubjectDN(new X509Principal("CN=" + domainName + ", OU=None, O=None L=None, C=None"));
v3CertGen.setPublicKey(KPair.getPublic());
// v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");
v3CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");
X509Certificate pkcert = v3CertGen.generateX509Certificate(KPair.getPrivate());
// FileOutputStream fos = new FileOutputStream("/path/to/testCert.cert");
// fos.write(pkcert.getEncoded());
// fos.close();
ByteArrayInputStream cert = new ByteArrayInputStream(pkcert.getEncoded());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null);
ks.setCertificateEntry("localhost", pkcert);
// ks.load(cert,null);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance("X509");
kmf.init(ks, null);
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, null);
SSLServerSocketFactory ssf = sc.getServerSocketFactory();
SSLServerSocket s
= (SSLServerSocket) ssf.createServerSocket(8888);
s.setEnabledCipherSuites(s.getSupportedCipherSuites());
// s.setEnabledCipherSuites(new String[]{"SSL_DH_anon_WITH_RC4_128_MD5"});
// s.setEnabledCipherSuites(new String[]{"SHA1WithRSAEncryption"});
System.out.println("Server started:");
printServerSocketInfo(s);
// Listening to the port
SSLSocket c = (SSLSocket) s.accept();
printSocketInfo(c);
BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(c.getOutputStream()));
BufferedReader r = new BufferedReader(
new InputStreamReader(c.getInputStream()));
String m = r.readLine();
w.write("HTTP/1.0 200 OK");
w.newLine();
w.write("Content-Type: text/html");
w.newLine();
w.newLine();
w.write("<html><body>Hello world!</body></html>");
w.newLine();
w.flush();
w.close();
r.close();
c.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void printSocketInfo(SSLSocket s) {
System.out.println("Socket class: " + s.getClass());
System.out.println(" Remote address = "
+ s.getInetAddress().toString());
System.out.println(" Remote port = " + s.getPort());
System.out.println(" Local socket address = "
+ s.getLocalSocketAddress().toString());
System.out.println(" Local address = "
+ s.getLocalAddress().toString());
System.out.println(" Local port = " + s.getLocalPort());
System.out.println(" Need client authentication = "
+ s.getNeedClientAuth());
SSLSession ss = s.getSession();
System.out.println(" Cipher suite = " + ss.getCipherSuite());
System.out.println(" Protocol = " + ss.getProtocol());
}
private static void printServerSocketInfo(SSLServerSocket s) {
System.out.println("Server socket class: " + s.getClass());
System.out.println(" Socker address = "
+ s.getInetAddress().toString());
System.out.println(" Socker port = "
+ s.getLocalPort());
System.out.println(" Need client authentication = "
+ s.getNeedClientAuth());
System.out.println(" Want client authentication = "
+ s.getWantClientAuth());
System.out.println(" Use client mode = "
+ s.getUseClientMode());
}
}
Thank you.
EDIT: I looked at two keytool generated keystores, one which worked and one that didn't. The one keystore which works has an entry in there for a PrivateKeyEntry where as the one which doesn't work has a trustedCertEntry. I then changed this code to print out the entry for the "localhost" alias and below is what I got, I'm guessing the issue is that it is a Trusted certificate entry and not a private key entry. How do I change that?
Trusted certificate entry:
[0] Version: 3
SerialNumber: 752445443
IssuerDN: CN=localhost,OU=None,O=None L,C=None
Start Date: Mon May 26 09:17:01 CDT 2014
Final Date: Sat Jun 22 09:17:01 CDT 2024
SubjectDN: CN=localhost,OU=None,O=None L,C=None
Public Key: RSA Public Key
modulus: b75870cd29db79f8c015d440a27cc1e81c9dd829268efa2ce48efc596b33e9c60e1d1621e10aba34472b6f7890b16392db021c0358e665b1bf58a426fbc47e7c135da583e4cd6bb9c69668ee4ff1e05b1de8e7f5fb5604044a1087ac0181ba09f61ab5345d9be5d930889b7c328329d0d18cf53f4c5af6bff1f0e488744ea1fb
public exponent: 10001
Signature Algorithm: SHA1WITHRSA
Signature: 83df0e761e9df2e61d5354ca58379975e0d97fcd
5201f8904b695d7bdbe08c5dfdfb8bcd6447657c
19740797a66314b2547a45985166c11ebadc16c6
c24b8e1d3c5de83ec1ac2c1c1092c3d06ed33408
4cf2811c5f9dba8a9d3ef0dcb8fef760e4d1d704
8fbb60eaa83eec23426fb9d8589e859a21a5ecce
951901f8e16ab6cd
s.setEnabledCipherSuites(s.getSupportedCipherSuites());
Remove this line.
The handshake failure usually means there's no shared cipher suite:
2895:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
SSL_NULL_WITH_NULL_NULL looks like its both eNULL and aNULL. Did BouncyCastle not load any ciphers?
Below is the code I use for a hardened SSLSocketFactoryEx. It only provides TLS (getInstance("TLS")will still return a SSLv3 socket), and it only provides approved cipher suites (approved by me). Its not enough to provide approved ciphers - the list must intersect with what's available else there's an exception. There are a few fallback cipher suites to ensure a shared cipher suite between old servers like those provided by Microsoft.
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.io.IOException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.net.Socket;
import java.net.InetAddress;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
class SSLSocketFactoryEx extends SSLSocketFactory
{
public SSLSocketFactoryEx() throws NoSuchAlgorithmException, KeyManagementException
{
initSSLSocketFactoryEx(null,null,null);
}
public SSLSocketFactoryEx(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws NoSuchAlgorithmException, KeyManagementException
{
initSSLSocketFactoryEx(km, tm, random);
}
public SSLSocketFactoryEx(SSLContext ctx) throws NoSuchAlgorithmException, KeyManagementException
{
initSSLSocketFactoryEx(ctx);
}
public String[] getDefaultCipherSuites()
{
return m_ciphers;
}
public String[] getSupportedCipherSuites()
{
return m_ciphers;
}
public String[] getDefaultProtocols()
{
return m_protocols;
}
public String[] getSupportedProtocols()
{
return m_protocols;
}
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException
{
SSLSocketFactory factory = m_ctx.getSocketFactory();
SSLSocket ss = (SSLSocket)factory.createSocket(s, host, port, autoClose);
ss.setEnabledProtocols(m_protocols);
ss.setEnabledCipherSuites(m_ciphers);
return ss;
}
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
{
SSLSocketFactory factory = m_ctx.getSocketFactory();
SSLSocket ss = (SSLSocket)factory.createSocket(address, port, localAddress, localPort);
ss.setEnabledProtocols(m_protocols);
ss.setEnabledCipherSuites(m_ciphers);
return ss;
}
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException
{
SSLSocketFactory factory = m_ctx.getSocketFactory();
SSLSocket ss = (SSLSocket)factory.createSocket(host, port, localHost, localPort);
ss.setEnabledProtocols(m_protocols);
ss.setEnabledCipherSuites(m_ciphers);
return ss;
}
public Socket createSocket(InetAddress host, int port) throws IOException
{
SSLSocketFactory factory = m_ctx.getSocketFactory();
SSLSocket ss = (SSLSocket)factory.createSocket(host, port);
ss.setEnabledProtocols(m_protocols);
ss.setEnabledCipherSuites(m_ciphers);
return ss;
}
public Socket createSocket(String host, int port) throws IOException
{
SSLSocketFactory factory = m_ctx.getSocketFactory();
SSLSocket ss = (SSLSocket)factory.createSocket(host, port);
ss.setEnabledProtocols(m_protocols);
ss.setEnabledCipherSuites(m_ciphers);
return ss;
}
private void initSSLSocketFactoryEx(KeyManager[] km, TrustManager[] tm, SecureRandom random)
throws NoSuchAlgorithmException, KeyManagementException
{
m_ctx = SSLContext.getInstance("TLS");
m_ctx.init(km, tm, random);
m_protocols = GetProtocolList();
m_ciphers = GetCipherList();
}
private void initSSLSocketFactoryEx(SSLContext ctx)
throws NoSuchAlgorithmException, KeyManagementException
{
m_ctx = ctx;
m_protocols = GetProtocolList();
m_ciphers = GetCipherList();
}
protected String[] GetProtocolList()
{
String[] preferredProtocols = { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };
String[] availableProtocols = null;
SSLSocket socket = null;
try
{
SSLSocketFactory factory = m_ctx.getSocketFactory();
socket = (SSLSocket)factory.createSocket();
availableProtocols = socket.getSupportedProtocols();
Arrays.sort(availableProtocols);
}
catch(Exception e)
{
return new String[]{ "TLSv1" };
}
finally
{
if(socket != null)
socket.close();
}
List<String> aa = new ArrayList<String>();
for(int i = 0; i < preferredProtocols.length; i++)
{
int idx = Arrays.binarySearch(availableProtocols, preferredProtocols[i]);
if(idx >= 0)
aa.add(preferredProtocols[i]);
}
return aa.toArray(new String[0]);
}
protected String[] GetCipherList()
{
String[] preferredCiphers = {
// *_CHACHA20_POLY1305 are 3x to 4x faster than existing cipher suites.
// http://googleonlinesecurity.blogspot.com/2014/04/speeding-up-and-strengthening-https.html
// Use them if available. Normative names can be found at (TLS spec depends on IPSec spec):
// http://tools.ietf.org/html/draft-nir-ipsecme-chacha20-poly1305-01
// http://tools.ietf.org/html/draft-mavrogiannopoulos-chacha-tls-02
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_SHA",
"TLS_ECDHE_RSA_WITH_CHACHA20_SHA",
"TLS_DHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_RSA_WITH_CHACHA20_POLY1305",
"TLS_DHE_RSA_WITH_CHACHA20_SHA",
"TLS_RSA_WITH_CHACHA20_SHA",
// Done with bleeding edge, back to TLS v1.2 and below
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
// TLS v1.0 (with some SSLv3 interop)
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA",
// RSA key transport sucks, but they are needed as a fallback.
// For example, microsoft.com fails under all versions of TLS
// if they are not included. If only TLS 1.0 is available at
// the client, then google.com will fail too. TLS v1.3 is
// trying to deprecate them, so it will be interesteng to see
// what happens.
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA"
};
String[] availableCiphers = null;
try
{
SSLSocketFactory factory = m_ctx.getSocketFactory();
availableCiphers = factory.getSupportedCipherSuites();
Arrays.sort(availableCiphers);
}
catch(Exception e)
{
return new String[] {
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV"
};
}
List<String> aa = new ArrayList<String>();
for(int i = 0; i < preferredCiphers.length; i++)
{
int idx = Arrays.binarySearch(availableCiphers, preferredCiphers[i]);
if(idx >= 0)
aa.add(preferredCiphers[i]);
}
aa.add("TLS_EMPTY_RENEGOTIATION_INFO_SCSV");
return aa.toArray(new String[0]);
}
private SSLContext m_ctx;
private String[] m_ciphers;
private String[] m_protocols;
}