Android SSLHandshake failed - CA certificate - java

I want to secure my SSL connection to the socket. But unfortunately, I have a problem with CA certificate.
javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.OpenSSLEngineImpl.unwrap(OpenSSLEngineImpl.java:441)
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:1014)
at com.koushikdutta.async.AsyncSSLSocketWrapper$5.onDataAvailable(AsyncSSLSocketWrapper.java:194)
at com.koushikdutta.async.Util.emitAllData(Util.java:23)
at com.koushikdutta.async.AsyncNetworkSocket.onReadable(AsyncNetworkSocket.java:152)
at com.koushikdutta.async.AsyncServer.runLoop(AsyncServer.java:789)
at com.koushikdutta.async.AsyncServer.run(AsyncServer.java:627)
at com.koushikdutta.async.AsyncServer.access$700(AsyncServer.java:41)
at com.koushikdutta.async.AsyncServer$13.run(AsyncServer.java:569)
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:337)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:231)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:115)
at com.android.org.conscrypt.OpenSSLEngineImpl.verifyCertificateChain(OpenSSLEngineImpl.java:666)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake_bio(Native Method)
at com.android.org.conscrypt.OpenSSLEngineImpl.unwrap(OpenSSLEngineImpl.java:426)
... 8 more
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
My code:
String type = KeyStore.getDefaultType();
KeyStore trustStore = KeyStore.getInstance(type);
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", new CA().getCert());
String tmfAlg = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlg);
tmf.init(trustStore);
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
SSLEngine engine = context.createSSLEngine();
AsyncSSLSocketWrapper.handshake(socketNormal, url, port, engine, tmf.getTrustManagers(), new NoopHostnameVerifier(), true, (e, socket1) -> {
// ... more
My cert is in my assets catalog:
public X509Certificate getCert() throws CertificateException, IOException {
CertificateFactory certFactory;
certFactory = CertificateFactory.getInstance("X.509");
InputStream inputStream = new BufferedInputStream(context.getAssets().open("pem.pem"));
return (X509Certificate) certFactory.generateCertificate(inputStream);
}
My certificate is signed using a private key and it is self-signed.
Obviously, it is working without cert... but it's not secure.
EDIT:
I have tried to put existing BKS Keystore instead of dynamically adding at run-time:
KeyStore trustStore = KeyStore.getInstance("BKS");
BufferedInputStream is = new BufferedInputStream(c.getAssets().open("key.bks"));
trustStore.load(is, "12345".toCharArray());
Log.i("Cert", "ca " + (new CA().getCert()).getSubjectDN());
String tmfAlg = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlg);
tmf.init(trustStore);
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
SSLEngine engine = context.createSSLEngine();
AsyncSSLSocketWrapper.handshake(socketNormal, url, port,
engine, tmf.getTrustManagers(), new NoopHostnameVerifier(), true, (e, socket1) -> {
But unfortunetly the error is the same.
EDIT 2:
More info:
Server PHP generates this cert using:
function createSSLCert($pem_file, $pem_passphrase, $pem_dn) {
// //create ssl cert for this scripts life.
//Create private key
$privkey = openssl_pkey_new();
//Create and sign CSR
$cert = openssl_csr_new($pem_dn, $privkey);
$cert = openssl_csr_sign($cert, null, $privkey, 365 * 99);//365
//Generate PEM file
$pem = array();
openssl_x509_export($cert, $pem[0]);
openssl_pkey_export($privkey, $pem[1], $pem_passphrase);
$pem = implode($pem);
//Save PEM file
//echo $pem;
file_put_contents($pem_file, $pem);
//chmod($pem_file, 0600);
}
$pem_passphrase = "XXXXX"; //Set a password here
$pem_file = "cert.pem"; //Set a path/filename for the PEM SSL Certificate which will be created.
//The following array of data is needed to generate the SSL Cert
$pem_dn = array(
"countryName" => "PL", //Set your country name
"localityName" => "City", //Ser your city name
"organizationName" => "Firm name", //Set your company name
"commonName" => "CN", //Set your full hostname.
"emailAddress" => "admin#email.pl" //Set your email address
);
//create ssl cert for this scripts life.
$this->createSSLCert($pem_file, $pem_passphrase, $pem_dn);
Keystore was created using Portecle tool like:
New -> BKS -> Import Trusted Certificate -> My *pem file -> save
openssl s_client -debug -connect 10.100.0.24:5678 return:
No client certificate CA names sent
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 1630 bytes and written 451 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Verify return code: 18 (self signed certificate)
plus some private informations about cert

Related

JAVA Certificates - Programmatically SSL handshake fails and import fails

I wrote a routine for the programmatic retrieval of certificates for SSL connections (to be used for example with restTemplate) and the programmatic import of these certificates to import them into cacerts of the currently used jre.
What I would like to replicate programmatically is the following manual procedure:
keytool -printcert -sslserver {host}:{port} -rfc >> {host}.crt
keytool -importcert -alias {host} -keystore /usr/local/openjdk-8/jre/lib/security/cacerts -file {host} -storepass changeit -noprompt
This is the code I use:
InputStream in = new FileInputStream(keystore);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(in, passphrase);
in.close();
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
X509TrustManager defaultTrustManager = null; //(X509TrustManager) tmf.getTrustManagers()[0];
TrustManager[] tms = tmf.getTrustManagers();
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
defaultTrustManager = (X509TrustManager) tm;
break;
}
}
if (defaultTrustManager == null) {
if (isLog) log.error("No Trust Manager found!");
return false;
}
SaveTrustManager tm = new SaveTrustManager(defaultTrustManager);
context.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory factory = context.getSocketFactory();
if (isLog) log.info("Opening connection -> {}:{} ...", host, port);
// Initiate socket
SSLSocket socket = null;
Socket proxiedSocket = null;
if (proxyHost != null) {
if (isLog) log.info("Using Proxy -> {}:{} ...", proxyHost, proxyPort);
try {
if (proxyUser == null || proxyPass == null) {
Proxy prx = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
proxiedSocket = new Socket(prx);
proxiedSocket.connect(new InetSocketAddress(host, port));
socket = (SSLSocket) factory.createSocket(proxiedSocket, host, port, true);
} else {
String proxyUserPass = String.format("%s:%s", proxyUser, proxyPass);
String proxyConnect = "CONNECT " + host + ":" + port + " HTTP/1.0\r\n"
+ "Proxy-Authorization: Basic "
+ Base64.getEncoder().encodeToString(proxyUserPass.getBytes(ASCII7)).replace("\r\n", "") + "\r\n"
+ "Connection: close\r\n"
+ "\r\n";
if (isLog) log.info("Socket: {}", proxyConnect);
proxiedSocket = new Socket(proxyHost, proxyPort);
proxiedSocket.getOutputStream().write(proxyConnect.getBytes(ASCII7));
proxiedSocket.getOutputStream().flush();
this.readSocketConnection(proxiedSocket);
proxiedSocket.getOutputStream().close();
socket = (SSLSocket) factory.createSocket(proxiedSocket, host, port, true);
}
} catch (Throwable t) {
t.printStackTrace();
if (isLog) log.info("Proxy Error: {}", t.getMessage());
}
} else {
socket = (SSLSocket) factory.createSocket(host, port);
}
socket.setSoTimeout(10000);
try {
if (isLog) log.info("Initiating SSL handshake...");
socket.startHandshake();
socket.close();
if (proxiedSocket != null) proxiedSocket.close();
if (isLog) log.info("Certificate is already trusted...");
} catch (SSLException e) {
//e.printStackTrace(System.out);
success = false;
if (isLog) log.warn("Socket Warning: {}", e.getMessage());
}
X509Certificate[] chain = tm.chain;
if (chain == null) {
log.error("Could not obtain server certificate chain");
return false;
}
if (isLog) log.info("Server sent " + chain.length + " certificate(s):");
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
MessageDigest md5 = MessageDigest.getInstance("MD5");
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
try {
cert.checkValidity();
if (isLog) log.info("\tValid certificate...");
} catch (Throwable t) {
log.info("\tCertificate invalid!");
continue;
}
sha1.update(cert.getEncoded());
md5.update(cert.getEncoded());
if (isLog) {
log.info("\tIndex: {} Subject: {}", (i + 1), cert.getSubjectDN());
log.info("\tIssuer: {}", cert.getIssuerDN());
log.info("\tSHA-1: {}", toHexString(sha1.digest()));
log.info("\tMD5: {}", toHexString(md5.digest()));
}
String alias = host + "-" + (i + 1);
ks.setCertificateEntry(alias, cert);
if (isLog) {
log.info("{}", cert);
log.info("Added certificate to keystore '{}' using alias '{}'", keystore, alias);
}
}
OutputStream out = new FileOutputStream(keystore);
ks.store(out, passphrase);
out.close();
On one particular VM I get this error:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
The weird thing is that locally it works and it works on other VMs too.
Also the other strange thing is that if I convert the downloaded certificate (although the handshake fails) in PEM format (base64) and I compare it with the one exported by keytool it's identical!
Also even if I try to load the downloaded PEM file with keytool using the Keystore programmatic procedure in java, I don't get any errors, but when I go to use a restTemplate to make that call.., I get the same error:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
In particular this VM does not run behind a proxy...
I did a lot of tests and I tried with -Djavax.net.debug=ssl,handshake to get some more information, but unfortunately I didn't come up with it.
I tried importing the PEM programmatically from a keytool downloaded file manually and got the same error.
I tried to verify to reach the portal in https with wget/curl/openssl and I reach it correctly.
There are no proxies behind the VM.
I tried giving full access (chmod) to cacerts file.
The portal only downloads 1 certificate from the chain of the 3 certificates but it is the same one that it downloads with keytool (the PEMs downloaded with keytool and the one downloaded programmatically are identical).
Everything is dockerized and runs under tomcat:9.0.35-jdk8-openjdk.
On other machines and locally it works correctly even behind proxies.
The VM it doesn't work on is an Ubuntu 20 (Debian).
I think I solved the issue.
I have a routine that programmatically downloads certificates from SSL protected servers through a socket that handshake and retrieve certificates.
Currently the certificate SSL server has some problems in returning the whole chain of certificates (including the intermediary certificates of the CAs that signed the certificate).
It is likely that they are problems related to a misconfiguration.
This problem can be verified with the command:
openssl s_client -showcerts -connect host:port
These are the intermediary CAs irrecoverable certs:
certs
certs
These are the intermediary certificates of the CAs:
Intermediate cert CAs chain
On a programmatic level, when JAVA persists the trusted certificates in the Trust-Store, it writes information related to the fact that it was not possible to recover the whole CA chain.
It is as if the certificate is self-signed for the trust-store when it is not. In fact, the certificate is not actually trusted (unless you enter it "manually" in the trust store).
When making an external call to an application-side https server, the client is not fully aware of the certificate.
The error was: "PKIX path building failed".
It was necessary to use a custom built HTTP client to accept single certificates of which it was not possible to download the whole CA chain.
Obviously it is not a client that can accept self-signed certificates.
This client is "satisfied" with the single certificate but it is actually a necessary and sufficient condition to perform a secure SSL handshake.
In other words to make an external call with RestTemplate what you have to do is change it from:
restTemplate = new RestTemplate();
to:
restTemplate = new RestTemplate();
HttpClient httpClient = HttpClients.custom().build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
I think I have given an explanation. What do you think about it?

Java SSL exception with default SSLContext

Diving right into the code, here is a method I am using to create an SSLContext
private static SSLContext createContext(String keystore_path, String truststore_path, String password) throws Exception {
final KeyStore key_store = KeyStore.getInstance("JKS");
final KeyStore trust_store = KeyStore.getInstance("JKS");
final char[] passphrase = password.toCharArray();
key_store.load(new FileInputStream(keystore_path), passphrase);
trust_store.load(new FileInputStream(truststore_path), passphrase);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(key_store, passphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trust_store);
SSLContext ssl_ctx = SSLContext.getInstance("TLSv1");
ssl_ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return ssl_ctx;
}
The resulting SSLContext is then used to create SSLEngine instances which I use to encrypt network communications.
Now that this is out of the way, I have tried 3 different ways to create an SSLContext, and only one works. I would like to understand why the 2 others fail, and how to make them work (if possible). Both the failing ones fail during the SSL handshake.
Method #1 (works)
My SSL handshakes work when using my own truststore and keystore like so:
ssl_ctx = createContext(my_keystore_file, my_truststore_filepath, my_password);
Method #2 (does NOT work)
final String default_store_path = System.getProperties().getProperty("java.home") + File.separator + "lib"
+ File.separator + "security" + File.separator + "cacerts";
final String default_password = "changeit";
ssl_ctx = createContext(default_store_path, default_store_path, default_password);
I get the following exception in this case:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: no cipher suites in common
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:307)
...
Method #3 (does NOT work)
ssl_ctx = SSLContext.getDefault();
But this time I am getting the following exception:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: No available authentication scheme
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:307)
On a side note, I am not interested in using a third party library such as Grizzly. Thank you!

Trouble using Positive SSL Multi-Site Cert with Java HttpsServer

I'm trying to load a comodo Positive SSL Multi-Site cert into Java's HttpsServer. I'm not getting any errors from the code, but when I try and access the URL in a browser it tells me there is an SSL error. Neither Chrome nor FireFox give any additional information. This cert is working fine in Apache.
Below is the code I am using. I've made it fairly verbose. Does anything stand out as incorrect? I've converted the private key to pkcs8 for importing. The certificate and bundle I'm loading are PEM encoded.
serverHttps = HttpsServer.create(new InetSocketAddress(ports[port_selector]), 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
String alias = "alias";
// Load Certificates
InputStream stream = MyClass.class.getResourceAsStream("/certs/mycert.crt");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(stream);
stream.close();
stream = MyClass.class.getResourceAsStream("/certs/bundle.crt");
cf = CertificateFactory.getInstance("X.509");
Collection bundle = cf.generateCertificates(stream);
stream.close();
// Build cert chain
java.security.cert.Certificate[] chain = new Certificate[bundle.size()+1];
Iterator i = bundle.iterator();
int pos = 0;
while (i.hasNext()) {
chain[pos] = (Certificate)i.next();
pos++;
}
chain[chain.length-1] = cert;
// Load private key
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
stream = MyClass.class.getResourceAsStream("/certs/pkcs8_my_key");
PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(IOUtils.toByteArray(stream));
RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8);
stream.close();
stream = null;
KeyStore ks = KeyStore.getInstance("JKS");
char[] ksPassword = "mypass".toCharArray();
ks.load(null, ksPassword);
ks.setKeyEntry(alias, privKey, ksPassword, chain);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, ksPassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
// serverHttps.setHttpsConfigurator(new HttpsConfigurator(sslContext));
serverHttps.setHttpsConfigurator ( new HttpsConfigurator( sslContext )
{
#Override
public void configure ( HttpsParameters params )
{
try
{
// initialise the SSL context
SSLContext c = SSLContext.getDefault ();
SSLEngine engine = c.createSSLEngine ();
params.setNeedClientAuth ( false );
params.setCipherSuites ( engine.getEnabledCipherSuites () );
params.setProtocols ( engine.getEnabledProtocols () );
// get the default parameters
SSLParameters defaultSSLParameters = c.getDefaultSSLParameters ();
params.setSSLParameters ( defaultSSLParameters );
}
catch ( Exception ex )
{
System.out.println( "Failed to configure HTTPS server: "+ex.getMessage() );
System.exit(100);
}
}
} );
Your server cert must be chain[0] in the keystore entry.
The remaining certs should be in upward order i.e. root last -- and when you use keytool it puts them in that order -- because JSSE server sends them in the keystore order and SSL/TLS protocol says they should be sent in upward order. However, in my experience (most?) browsers/clients will tolerate the rest of the chain being out of order as long as the server cert is first.
PS: I think everything in your configure overrride is unnecessary. You haven't done anything to make the parameters of your SSLContext different from the default one, and the SSLParameters of the default context are (and override) the CipherSuites and Protocols you just set individually. But I can't easily test.

Validating certificate chain in Java from truststore

I have a certificate chain as der encoded byte[][] array to verify. I also have a truststore file.
After I create X509Certificate[] from that byte array[][] and initializing trustmanager, how will I tell to TrustManager to verify that X509Certificate[]? What is the proper way to do it?
Thanks.
Sample code:
int certVerify(byte certChain[][])
{
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate certx[] = new X509Certificate[10];
for(int i=0;i<certChain.length;i++)
{
certx[i] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certChain[i]));
}
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load( new FileInputStream("cacerts.jks"),"123456".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
}
You'll need to enable OCSP with the necessary system properties, or obtain CRLs for each certificate in the chain, in order to check the revocation status. (Alternatively, you can disable revocation checking, with the attendant risks.)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<Certificate> certx = new ArrayList<>(certChain.length);
for (byte[] c : certChain)
certx.add(cf.generateCertificate(new ByteArrayInputStream(c)));
CertPath path = cf.generateCertPath(certx);
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
KeyStore keystore = KeyStore.getInstance("JKS");
try (InputStream is = Files.newInputStream(Paths.get("cacerts.jks"))) {
keystore.load(is, "changeit".toCharArray());
}
Collection<? extends CRL> crls;
try (InputStream is = Files.newInputStream(Paths.get("crls.p7c"))) {
crls = cf.generateCRLs(is);
}
PKIXParameters params = new PKIXParameters(keystore);
CertStore store = CertStore.getInstance("Collection", new CollectionCertStoreParameters(crls));
/* If necessary, specify the certificate policy or other requirements
* with the appropriate params.setXXX() method. */
params.addCertStore(store);
/* Validate will throw an exception on invalid chains. */
PKIXCertPathValidatorResult r = (PKIXCertPathValidatorResult) validator.validate(path, params);
There is some good information on how to implement one here
Or you could use the BouncyCastle APIs as explained here

Signing a X509Certificate with another Self Signed x509Certificate [acting as CA]

I have created a self-signed certificate and encoded it successfully. But I want to sign this certificate with another self signed certificate, which will act as a Certification Authority.
The code is below:
X509Certificate caCert;
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
CertAndKeyGen keypair = new CertAndKeyGen("RSA", "SHA1WithRSA", null);
X500Name x500Name = new X500Name(commonName, organizationalUnit, organization, city, state, country);
keypair.generate(keysize);
PrivateKey privKey = keypair.getPrivateKey();
X509Certificate[] chain = new X509Certificate[1];
chain[0] = keypair.getSelfCertificate(x500Name, new Date(), (long) validity * 24 * 60 * 60);
keypair.getCertRequest(x500Name);
keyStore.setKeyEntry(alias, privKey, keyPass, chain);
keyStore.store(new FileOutputStream("test.keystore"), keyPass);
caCert = (X509Certificate) keyStore.getCertificate(alias);
File crtFile = new File("saif.der");
writeCertificate(new FileOutputStream(crtFile), caCert);
Create the user certificate using X509V3CertificateGenerator class of bouncycastle. Then finally use the X509V3CertificateGenerator.generateX509Certificate(privateKey) method to generate the X509Certificate. Here the privateKey will be the self signed certificate's private key from PKCS12. Save the user certificate in PKCS12 format.

Categories

Resources