I am running Java 8 and using com.sun.net.httpserver.HttpsServer to create a HTTPS server. I have a working implementation using a trusted CA signed EC certificate in a Java KeyStore.
I have been looking at how I could restrict what Ciphers and Protocols the server could support (similar to Apache using "SSLProtocol" and "SSLCipherSuite" in the configuration) - mainly to enforce a higher level of security by disabling the use of SHA-1.
No one should really be forcing TLSv1.1 over 1.2, but I am just doing this to prove a point that the following configuration works:
KeyStore ks = KeyStore.getInstance("JKS");
// Load KeyStore into "ks"
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, jksPassword.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
HttpsServer hsS = HttpsServer.create(sBind, 0);
hsS.setHttpsConfigurator(new HttpsConfigurator(sc) {
#Override
public void configure(HttpsParameters p) {
try {
SSLContext c = getSSLContext();
SSLEngine e = c.createSSLEngine();
p.setNeedClientAuth(false);
p.setProtocols(new String[] { "TLSv1.1" });
p.setCipherSuites(e.getEnabledCipherSuites());
p.setSSLParameters(c.getDefaultSSLParameters());
}
catch (Exception e) {
e.printStackTrace();
}
}
});
Even though I use setProtocols() to only accept "TLSv1.1", this doesn't seem to stop "TLSv1.2" connections and everything from Chrome to IE still uses "TLSv1.2". If I disable the use of "TLSv1.2" and "TLSv1.1" in IE and just leave "TLSv1.0" enabled then it will still work and negotiate "TLSv1.0". The setProtocols() method doesn't seem to do anything!?!
If I got this working then I was going to modify the list of Ciphers using setCipherSuites().
You can tweak it by updating jre/lib/security/java.security file. Look for an entry like below and tweak.
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.2, RC4, MD5withRSA, DH keySize < 768, \
EC keySize < 224
Related
I am trying to setup a SSL Socket connection (and am doing the following on the client)
I generate a Certificte Signing Request to obtain a signed client certificate
Now I have a private key (used during the CSR), a signed client certificate and root certificate (obtained out of band).
I add the private key and signed client certificate to a cert chain and add that to the key manager. and the root cert to the trust manager.
But I get a bad certificate error.
I am pretty sure I am using the right certs. Should I add the signed client cert to the trust manager as well? Tried that, no luck still.
//I add the private key and the client cert to KeyStore ks
FileInputStream certificateStream = new FileInputStream(clientCertFile);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
java.security.cert.Certificate[] chain = {};
chain = certificateFactory.generateCertificates(certificateStream).toArray(chain);
certificateStream.close();
String privateKeyEntryPassword = "123";
ks.setEntry("abc", new KeyStore.PrivateKeyEntry(privateKey, chain),
new KeyStore.PasswordProtection(privateKeyEntryPassword.toCharArray()));
//Add the root certificate to keystore jks
FileInputStream is = new FileInputStream(new File(filename));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
java.security.cert.X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
System.out.println("Certificate Information: ");
System.out.println(cert.getSubjectDN().toString());
jks.setCertificateEntry(cert.getSubjectDN().toString(), cert);
//Initialize the keymanager and trustmanager and add them to the SSL context
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "123".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(jks);
Is there some sort of certificate chain that I need to create here?
I had a p12 with these components as well and upon using pretty similar code, adding the private key to the keymanager and the root cert from p12 to the trust manager I could make it work. But now I need to make it work without the p12.
EDIT: Stack trace was requested. Hope this should suffice. (NOTE: I masked the filenames)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:136)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1720)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:954)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1138)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1165)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1149)
at client.abc2.openSocketConnection(abc2.java:33)
at client.abc1.runClient(abc1.java:63)
at screens.app.abc.validateLogin(abc.java:197)
... 32 more
You need to add the root cert to the keystore as well.
I got this error when I removed these 2 lines. If you know your keystore has the right certs, make sure your code is looking at the right keystore.
System.setProperty("javax.net.ssl.keyStore", <keystorePath>));
System.setProperty("javax.net.ssl.keyStorePassword",<keystorePassword>));
I also needed this VM argument:
-Djavax.net.ssl.trustStore=/app/certs/keystore.jk
See here for more details:
https://stackoverflow.com/a/34311797/1308453
Provided that the server certificate is signed and valid, you only need to open the connection as usual:
import java.net.*;
import java.io.*;
public class URLConnectionReader {
public static void main(String[] args) throws Exception {
URL google = new URL("https://www.google.com/");
URLConnection yc = google.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(
yc.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
}
Note that the URL has the HTTPS schema to indicate the use of SSL.
If the server's certificate is signed but you are accessing using a different IP address/domain name than the one in the certificate, you can bypass hostname verification with this:
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName,SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
If the certificate is not signed then you need to add it to the keystore used by the JVM (useful commands).
I tried to secure the connection to my java server, after downloading a certificate(certificate.crt) and adding it to the keystore (keystore.jks) my server run normally and read the certificate.But if I want to consume a service via https://123.456.88.99:1010/myService from the navigator(firefox) I get a PR_END_OF_FILE_ERROR The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.ps : http://123.456.88.99:1010/myService works and consume the service and retrieve data also by using firefox, I think its a problem of private key that the navigator don't get, I really need help, thank you
ps if I try to use a certificate that I create using keytool it works
private void startHttpsServer(RestFactory factory, int port, int minWorkers, int maxWorkers, int socketTimeoutMS,
boolean keepConnection, boolean ignoreContentLength, boolean debug, Compression compression, boolean useClassicServer, boolean requireCertificate) throws Exception {
String alias = "server-alias";
String pwd = "changeit";
char [] storepass = pwd.toCharArray();
String keystoreName = "c:\\keystore.jks";
FileInputStream in = new FileInputStream(keystoreName);
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(in, storepass);
Certificate cert = keystore.getCertificate(alias);
Log.debug("the certification is here : " + cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
char [] keypass = pwd.toCharArray();
kmf.init(keystore, keypass);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keystore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLEngine engine = sslContext.createSSLEngine();
engine.setEnabledCipherSuites(new String[] {"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"});
SSLParameters defaultSSLParameters = sslContext.getDefaultSSLParameters();
engine.setSSLParameters(defaultSSLParameters);
HttpsRestServer server = new HttpsRestServer(factory, port, minWorkers, maxWorkers, debug, compression, keystore, keypass, false);
server.addCleaner(new CleanupListener() {
#Override
public void cleanup(CleanupEvent event) {
Database.disconnectAllThreadConnections(event.thread, false);
}
});
this.servers.add(server);
log.info("Starting classic HTTPS replication server on port " + port);
server.start();
log.info("Secure XML replication server started on port " + port);
}
This question already has an answer here:
What is SunX509 used for and can it work with parties using IbmX509?
(1 answer)
Closed 4 years ago.
thanks for helping me!
First thing first.
I tried to use my tablet (Android 7.1.1) connecting to my laptop (java server JDK1.8) via wi-fi by using SSLSocket.
Part 1: I created a new Keystore with keytool comes with the Java SDK.
keytool -genkey -alias projectname -keystore /PATH/project.keystore -validity 365
then generated cert base on the keystore.
keytool -export -alias projectname -keystore /PATH/project.keystore -file /PATH/projectcert.cer
created .BKS file for Android client.
keytool -import -alias projectname -file /PATH/projectcert.cer -keystore /PATH/project.bks -storetype BKS -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/bcprov-jdk16-146.jar
now I have 3 files.
project.keystore (I simply put this one into Android Raw folder)
project.bks (I used portecle tool to switch type "bks" to "JKS" then copy to java server)
cprojectcert.cer
Part 2: Android client
public class HTTPSClient{
..............
..............
private SSLContext createSSLContext(){
try{
KeyStore keyStore = KeyStore.getInstance("BKS");
InputStream in = context.getResources().openRawResource(R.raw.project);
keyStore.load(in,"password".toCharArray());
// Create key manager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("sunX509");
keyManagerFactory.init(keyStore, "password".toCharArray());
KeyManager[] km = keyManagerFactory.getKeyManagers();
// Create trust manager
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("sunX509");
trustManagerFactory.init(keyStore);
TrustManager[] tm = trustManagerFactory.getTrustManagers();
// Initialize SSLContext
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(km, tm, null);
return sslContext;
} catch (Exception ex){
ex.printStackTrace();
}
return null;
}
}
Part 3: java server
private SSLContext createSSLContext(){
try{
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("temp"+'/'+"project.jks"),"password".toCharArray());
// Create key manager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("sunX509");
keyManagerFactory.init(keyStore, "password".toCharArray());
KeyManager[] km = keyManagerFactory.getKeyManagers();
// Create trust manager
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("sunX509");
trustManagerFactory.init(keyStore);
TrustManager[] tm = trustManagerFactory.getTrustManagers();
// Initialize SSLContext
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(km, tm, null);
return sslContext;
} catch (Exception ex){
ex.printStackTrace();
}
return null;
}
error:
W/System.err: java.security.NoSuchAlgorithmException: sunX509 KeyManagerFactory not available
I also changed sunX509 to PKIX on both side. However, I got handshake fail.
I don't know where you got "sunx509" from as a KeyManagerFactory or TrustManagerFactory algorithm. The only one listed for both since at least JDK 1.6 is "PKIX". In any case you should just use
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
and
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
I also changed sunX509 to PKIX on both side. However, I got handshake fail.
'Handshake fail' is progress. You got past all this code.
My network code is written in NDK (cURL + OpenSSL) and I'd like to use a certificate from Android's credential storage as a client certificate for a SSL connection. Moreover, I'd like to offer a list of available certificates to the user, so he can choose the certificate for the connection. Unfortunately, I cannot obtain a certificate from the key storage.
I installed a client certificate to "Credential storage" (Settings -> Secutrity -> ...) on my Android device (5.0.2), but I'm not able to access it from Java. I tried to call following code, but the key storage is empy, athough the certificate is installed in the Credential storage:
//KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();
while(aliases.hasMoreElements()) {
String alias = (String)aliases.nextElement();
Log.i("app", "alias name: " + alias);
Certificate certificate = ks.getCertificate(alias);
Log.i("app", certificate.toString());
}
What am I doing wrong?
User credentials installed on the device are available through Android KeyChain, not Android KeyStore
The KeyChain class provides access to private keys and their corresponding certificate chains in credential storage.
Use choosePrivateKeyAlias to prompt the user for selecting the certificate. The system launches an Activity for the user to select the alias and returns it via a callback. Then use getPrivateKey and getCertificate to recover the key and the corresponding certificate chain
KeyChain.choosePrivateKeyAlias(activity, new KeyChainAliasCallback() {
public void alias(String alias) {
//do something with the selected alias
}
},
new String[] { KeyProperties.KEY_ALGORITHM_RSA, "DSA"}, // List of acceptable key types. null for any
null, // issuer, null for any
null, // host name of server requesting the cert, null if unavailable
-1, // port of server requesting the cert, -1 if unavailable
""); // alias to preselect, null if unavailable
PrivateKey privateKey = KeyChain.getPrivateKey(activity, alias);
X509Certificate chain[] = KeyChain.getCertificateChain(activity, alias);
Try something like this:
X509TrustManager manager = null;
FileInputStream fs = null;
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try
{
fs = new FileInputStream(System.getProperty("javax.net.ssl.trustStore"));
keyStore.load(fs, null);
}
finally
{
if (fs != null) { fs.close(); }
}
trustManagerFactory.init(keyStore);
TrustManager[] managers = trustManagerFactory.getTrustManagers();
for (TrustManager tm : managers)
{
if (tm instanceof X509TrustManager)
{
manager = (X509TrustManager) tm;
break;
}
}
I'm currently writing an Android App (Min SDK 16) that queries a HTTPS server for data. The server (Apache 2.4 on Debian 8) uses a certificate signed by our own CA and requires clients to also have a certificate signed by it. This works perfectly with Firefox after importing both the CA and the client certificate in PKCS format.
I am, however, unable to get this to work in Android. I'm using HttpsURLConnections, as the Apache HTTP Client has been deprecated for Android recently. Trusting our custom CA works, but as soon as I require the client certificate, I get the following Exception:
java.lang.reflect.InvocationTargetException
[...]
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:282)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:192)
at eu.olynet.olydorfapp.resources.CustomTrustManager.checkServerTrusted(CustomTrustManager.java:96)
at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:614)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:406)
at com.android.okhttp.Connection.upgradeToTls(Connection.java:146)
at com.android.okhttp.Connection.connect(Connection.java:107)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294)
at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:503)
at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136)
at org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngine.invoke(URLConnectionEngine.java:49)
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:436)
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:102)
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:64)
at $Proxy9.getMetaNews(Native Method)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at eu.olynet.olydorfapp.resources.ResourceManager.fetchMetaItems(ResourceManager.java:372)
at eu.olynet.olydorfapp.resources.ResourceManager.getTreeOfMetaItems(ResourceManager.java:542)
at eu.olynet.olydorfapp.tabs.NewsTab$1.doInBackground(NewsTab.java:51)
at eu.olynet.olydorfapp.tabs.NewsTab$1.doInBackground(NewsTab.java:45)
at android.os.AsyncTask$2.call(AsyncTask.java:288)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:841)
To me this looks like the server certificate cannot be verified, which should not be the case.
This is what the code looks like:
private static final String CA_FILE = "ca.pem";
private static final String CERTIFICATE_FILE = "app_01.pfx";
private static final char[] CERTIFICATE_KEY = "password".toCharArray();
[...]
CertificateFactory cf = CertificateFactory.getInstance("X.509");
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
InputStream ca = this.context.getAssets().open(CA_FILE);
KeyStore trustStore = KeyStore.getInstance("PKCS12");
trustStore.load(null);
Certificate caCert = cf.generateCertificate(ca);
trustStore.setCertificateEntry("CA Name", caCert);
CustomTrustManager tm = new CustomTrustManager(trustStore);
ca.close();
InputStream clientCert = this.context.getAssets().open(CERTIFICATE_FILE);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(clientCert, CERTIFICATE_KEY);
Log.e("KeyStore", "Size: " + keyStore.size());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(keyStore, CERTIFICATE_KEY);
clientCert.close();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), new TrustManager[]{tm}, null);
[...]
((HttpsURLConnection) con).setSSLSocketFactory(sslContext.getSocketFactory());
Relevant function of the CustomTrustManager (where localTrustManager contains just our CA and defaultTrustManager the system's CAs):
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
localTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ce) {
defaultTrustManager.checkServerTrusted(chain, authType);
}
}
I've already tried converting the PKCS file to a BKS file (and adapting the KeyStore, of course) without success. I've also seen the similar questions here but none of the solutions worked for me.
I've found that adding the intermediate CA (the one that signed the server's certificate directly) in addition to the root CA worked. I do not understand why this is necessary as verification works fine with just the root CA if no client certificate is required by the server. To me this seems like some kind of bug in the Android implementation of HttpsURLConnections or a related class. Please educate me if I'm wrong.
Working code:
private static final String CA_FILE = "ca.pem";
private static final String INTERMEDIATE_FILE = "intermediate.pem";
private static final String CERTIFICATE_FILE = "app_01.pfx";
private static final char[] CERTIFICATE_KEY = "password".toCharArray();
[...]
CertificateFactory cf = CertificateFactory.getInstance("X.509");
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
/* trust setup */
InputStream ca = this.context.getAssets().open(CA_FILE);
InputStream intermediate = this.context.getAssets().open(INTERMEDIATE_FILE);
KeyStore trustStore = KeyStore.getInstance("PKCS12");
trustStore.load(null);
Certificate caCert = cf.generateCertificate(ca);
Certificate intermediateCert = cf.generateCertificate(intermediate);
trustStore.setCertificateEntry("CA Name", caCert);
trustStore.setCertificateEntry("Intermediate Name", intermediateCert);
CustomTrustManager tm = new CustomTrustManager(trustStore);
ca.close();
intermediate.close();
/* client certificate setup */
InputStream clientCert = this.context.getAssets().open(CERTIFICATE_FILE);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(clientCert, CERTIFICATE_KEY);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(keyStore, CERTIFICATE_KEY);
clientCert.close();
/* SSLContext setup */
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), new TrustManager[]{tm}, null);
[...]
((HttpsURLConnection) con).setSSLSocketFactory(sslContext.getSocketFactory());