Unable to make ACRA accept a self-signed certificate - java

ACRA set up with standard options:
#ReportsCrashes(
formUri = "https://XXXXXXXXXX.php",
mode = ReportingInteractionMode.TOAST,
resToastText = R.string.str_acra_crash_report_info)
Tried to copy the server certificate to assets and create a custom KeyStore:
try {
KeyStore ksTrust = KeyStore.getInstance("BKS");
InputStream instream = new BufferedInputStream(getAssets().open("keystore.bks"));
ksTrust.load(instream, "ez24get".toCharArray());
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder(this);
configurationBuilder.setKeyStore(ksTrust);
final ACRAConfiguration config = configurationBuilder.build();
ACRA.init(this, config);
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
e.printStackTrace();
}
or another way:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(getAssets().open("ssl-cert-snakeoil.pem"));
Certificate ca = cf.generateCertificate(caInput);
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
Unfortunately after hours of tests, still no luck, still getting an exception:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Any hints?
EDIT: Created another certificate, with CA:TRUE (standard ssl-cert-snakeoil.pem had CA:FALSE), but still no luck.
EDIT 2: Certificates made as they should be: main CA cert. + server cert., but still the same exception.

#Matthew you will need to use the head of the ACRA's master as it has this https://github.com/ACRA/acra/pull/388 pull request added.
We'll probably cut another release within a week or so.

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?

How to save public key to keystore-file knowing only modulus and exponent? [duplicate]

I had created keystore ,i want to store only publickey in keystore , and don't want to store private key.because private key will be in client device and he will share public key using rest API.
Also how should i keep multiple public key in one keystore.with different aliases
If some one has sample code,or sample link it will be great helpful for me.
It can't be done, at least not in the format of a raw java.security.PublicKey. You can only store 3 types of entries in a JKS keystore: PrivateKeyEntry (for asymmetrical private keys), SecretKey (for symmetrical secret keys) and TrustedCertificateEntry (for "public" keys). They all implement the java.security.KeyStore.Entry interface.
The bottom line is: you need to associate your public key with a certificate, which you can create, and then store the certificate in the keystore as a separate entry.
Creating certificates is a bit tricky, but an example can be found here: Creating an X509 Certificate in Java without BouncyCastle?
This may be closer to what you are looking for to call a REST web service with SSL taken from here Importing PEM certificate into Java KeyStore programmatically :
private static SSLContext createSSLContext(String certString) throws IOException {
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
ByteArrayInputStream inStream = new ByteArrayInputStream(certString.getBytes(StandardCharsets.UTF_8));
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(inStream);
KeyStore store = KeyStore.getInstance("JKS");
store.load(null);
store.setCertificateEntry("certificate", cert);
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(store, "".toCharArray())
.build();
return sslContext;
} catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException |
KeyManagementException | UnrecoverableKeyException e) {
throw new IOException(e);
}
}

How to check if X509 certificate is already in the Java Trust Store programatically?

I am developing a client GUI that accepts self signed server certificates and adds them to the trust store just like any browser would do.
The problem is that my client application asks for the certificate every time it is started, in other words it does not remember that the certificate is already in the trust store. How do I implement this?
This is how I am writing my trust store files:
public void WriteTrustStore(String alias, X509Certificate c){
char[] password = "changeit".toCharArray();
char SEP = File.separatorChar;
keystoreFile = new File(System.getProperty("java.home")
+ SEP + "lib" + SEP + "security" + SEP + "cacerts");
try {
setTrustStore(trustStore);
FileInputStream in = new FileInputStream(keystoreFile);
trustStore.load(in, password);
in.close();
trustStore.setCertificateEntry(alias, c);
FileOutputStream out = new FileOutputStream(keystoreFile);
trustStore.store(out, password);
out.close();
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
e.printStackTrace();
}
}
Then I have another method where I am initializing my SSL Context and also creating dynamic alias names by doing something like:
string alias = getHostname() + "-" + getPortname();
In the end I have an alias name like:
"myhost-5001"
And then I call the WriteTrustStore(alias,certificate) method.
But in the next execution run of the program if I try to find the certificate with this alias name, I always end up getting a Null Pointer Exception.
I know that a truststore file has a property like:
trustStore.containsAlias(alias)
I tried,
if(trustStore.containsAlias(alias) == false){
WriteTrustStore(alias, (X509Certificate) cert)
}
else {
System.out.Println("Certificate already in trust store!");
}
But still I get a Null-Pointer exception. And also I know that the certificate with alias name myhost-5001 is in the Java trust store, I crossed checked using keytool and portecle.
Thanks for your help!
I figured it out, there are two ways this can be done.
First Method
I found this here: Check for trusted certificates, where you enumerate over aliases like this:
Enumeration en = keystore.aliases();
String ALIAS = "" ;
X509Certificate signingcert = null;
while (en.hasMoreElements())
{
X509Certificate storecert = null;
String ali = (String)en.nextElement() ;
if(keystore.isCertificateEntry(ali))
{
storecert = (X509Certificate)keystore.getCertificate(ali);
if( (storecert.getIssuerDN().getName()).equals(issuerdn))
{
try{
System.out.println("Found matching issuer DN cert in keystore:\r\nChecking signature on cert ...") ;
cert.verify(storecert.getPublicKey()) ;
System.out.println("Signature verified on certificate") ;
signingcert = storecert;
break;
}
catch(Exception exc){
System.out.println("Failed to verify signature on certificate with matching cert DN");
}
}
}
else
if(keystore.isKeyEntry(ali))
System.out.println(ali + " **** key entry ****");
}
Second Method
Just create a duplicate certificate that looks in the trust store for a certificate with the alias name you are passing.
X509Certificate DuplicateCert = (X509Certificate) trustStore.getCertificate(alias);
First method is safer because you are also looking at the Issuer DN but takes longer, second method is simple and shorter.
Second method works like a charm for me, you can find the complete GUI code review here and see how I am using it: JAX-WS client SSL code review

KeyStore in Android

I am trying to install the certificate using keystore in my app. However I am getting an exception:
Throwable occurred: java.security.cert.CertificateException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: java.lang.RuntimeException: error:0D07207B:asn1 encoding routines:ASN1_get_object:header too long
Tried a few things (ex: getInstance("X.509", "BC");) to get rid off this error but it didn't work. Not quite sure, how to get rid of this.
I am new to development and have a fair idea how keystore works. Any help will be appreciated. Thanks in advance.
try{
String configString = config.getString("imcwingw-latest.cert");
String decodedCert = BASE64Decoder.decode(configString);
InputStream bis = new ByteArrayInputStream(decodedCert.getBytes());
KeyStore ks = KeyStore.getInstance("BKS");
ks.load(null, null);
String alias = "myalias";
CertificateFactory cf = CertificateFactory.getInstance( "X.509");
Certificate cert = cf.generateCertificate(bis);
ks.setCertificateEntry(alias, cert);
}
you can do the same task by using this way:
http://www.instructables.com/id/Publishing-an-Android-App-to-the-Google-Play-Store/?ALLSTEPS
it is easy and fast to work...

Checking for CA's certificate before entering a certificate?

I am inserting a client certificate into my servertruststore using following code
FileInputStream fileInputStream = new FileInputStream( "c:/server.jks" );
keyStore.load( fileInputStream, "keystore".toCharArray() );
fileInputStream.close();
keyStore.setCertificateEntry( alias, new X509Certificate( trustedCertificate ) );
FileOutputStream fileOutputStream = new FileOutputStream("c:/server.jks" );
keyStore.store( fileOutputStream, "keystore".toCharArray() );
fileOutputStream.close();
Now i see that certificate is entered into my truststore but the CA's certificate which signed client's certificate is not present in my truststore. So I want to know is there any way we can check whether the certificate of CA is available or not before entering a certificate into keystore?
I guess what you have to do is to verify if the certificate has been issued by a root authority or it has been self-signed. I presume you are using the default java keystore which is cacerts.
I haven't tested the code but I think this may be a solution to your problem:
Code taken and modified from the following link:
How can I get a list of trusted root certificates in Java?
String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
Set<X509Certificate> additionalCerts = new HashSet<X509Certificate>();
FileInputStream is = new FileInputStream(filename);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = "changeit";
keystore.load(is, password.toCharArray());
// This class retrieves the most-trusted CAs from the keystore
PKIXParameters params = new PKIXParameters(keystore);
// Get the set of trust anchors, which contain the most-trusted CA certificates
Iterator it = params.getTrustAnchors().iterator();
while( it.hasNext() ) {
TrustAnchor ta = (TrustAnchor)it.next();
// Get certificate
X509Certificate cert = ta.getTrustedCert();
additionalCerts.add(cert);
}
Then you may use the following code to pass the client certificate and the Set containing all the root CAs to the verifyCertificate(X509Certificate cert, Set additionalCerts) method of the following code:
http://www.nakov.com/blog/2009/12/01/x509-certificate-validation-in-java-build-and-verify-chain-and-verify-clr-with-bouncy-castle/

Categories

Resources