UPDATE:
Here is the link to the official API documentation.
And here is a video with the requests I made from NodeJS and here is the java client app that I implemented.
I have a request in NodeJS and now I want to implement the same request in Java for my Android app.
The NodeJS request looks like this:
var options = {
method: 'GET',
url: 'https://webapi.developers.erstegroup.com/api/bcr/sandbox/v1/aisp/v1/accounts',
cert: "-----BEGIN CERTIFICATE-----....-----END CERTIFICATE-----",
key: "-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----",
headers:
{
'x-request-id': '30fb2676-8c2e-11e9-b683-526af7764f64',
'web-api-key': '#########',
'Accept': 'application/json'
}
};
request(options, function (error, response, body) {
console.log(body);
});
My problem is that I don't know how to include that certificate and private key to make the request.
I found this answer regarding how to read the certificate and the key in Java, but I don't know how to configure the SSLContext to use the certificate and also the key.
Currently, I tried the next solution, but don't work. The first problem I have is that I get an error when I parse the key:
The error message: org.bouncycastle.openssl.PEMException: malformed sequence in RSA private key
The method used to read the key:
private static PrivateKey readPrivateKey(String filename) throws Exception {
PEMParser pemParser = new PEMParser(new BufferedReader(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream(filename))));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject();
KeyPair kp = converter.getKeyPair(pemKeyPair);
return kp.getPrivate();
}
The private key:
-----BEGIN RSA PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJZunh6Nj5aBtb
hJJ9eaSDjDiiERBOZfVrmEwz9Ea5ldLAf8NUy+t71etzGeHtCKyTuSlhj1Clhla+
iG/r1uz25H3T6wUQbmw1+pFsaSovkamQaKy+2GJSPCx66li6z2JBv0I66DtoGl6Q
Xcy5JO2sBjZaO4m11bcFFVkvo9lh2QCC2x68w8bHeBuPUMnU6rupVQfgPPWMH+Wq
qaPgxoQr5KmQ03ItY2/TBqoX6xTTbL6+B8OMbX41Lxah+g+5XPOlDoC64HiBO2T+
FL62W+51ehUCORuuUt6/AYzXcCHSu1FXsk25KeObe9Na2AfobGNL83I68Bl0K2Wt
jbEtHs1FAgMBAAECggEAb7K3Bga4x2IYwiH9iL99IUQUaLXkAEcF3N2DbdENpIHW
d9KkB5RtDqouwhBZv7du1yL7M1Njm9mspFFRGVCC7c79hhmzHlDPjQRhwOl2bxlv
HFsha1rg9NDQrn7oJPs9eE9VsQv5Xpw5VAHht9EmS6DKZjLdBk74CUa0xvotZtkS
UrvRLOWhedsS0Ckf3vDfqU5NZBEecEj1vLGnD1ET6znRhag2VqUYS83fKUR5UpLq
X4PqMfYucF40ddd+L/iMeAnGmKukYEfew1R3ez5rKSS+cyJkoAKtL1WR0nFSDskw
zFQaf6PNIUIL7CDnOdUlyiRYx6a5y6NzJcQyI0bn5QKBgQDhUPck4ekVT8z29Llf
kkXWoF15l7KNiiq2DZfWlH2pax4G3NBUimb62fHcSQVovD0aLCJOF8N0n6vv5YbF
MIURRWXRTNxO2S16xUpUMD2Ospv50UpNIMAJlgnkTt/DTsd6MYQx2j+qyf4wPvWO
QSOQNpdebd/59LWbAye5WROuJwKBgQDk1D4sEWti9dR0LTJS0B+FHLPPJhpNg+cD
zqFEXvSICQjAhyJ3Fir/u3HhX4966+dhODaDphAOQcG+4iyXUQVEh+qQJ5p+MJQU
ue0yZgQvPIo5a+gnFyzEmCOtaENBqJqK1tbCklFZtbJswVEtlqjq+qMAzjNOzf2F
6krA9VC4swKBgQCIrJ5eJxNGNDP2kZho2se2W2yYN2a96NPjvvcd2NEpFasPKp7M
yW+SNuY5Y6n+UEEYQTFGAbA0bC7VxHst3jK5uUj73w28Xozx7f8adnDAwKNQtJ3H
j1gt+G9jqFyfkof6HVM9ElCQfxrLlUVK10SFVDgZtbipXMFUmGNeUSRY/QKBgQCW
a/Lmsxi9d84OBLvk9k0SCrkkfe6icAfHV+ho8maamh23ud1tHRRtAYIt3cyKyFJU
dUhYqCw7wvwih7k6SxdEYnhOBMqpEzP0n/gNvkQX7RsL/iQgtjpGjaA+WKCFo9jb
VbjdNKPnbep5VWcQqc4mkVXfrKzLq9txUX+McnZ6wwKBgAy2RVQ/ja3mvVUiZ3ZN
U4Y+nObKV6Z9JoMC4VNbbwufVPSoj/j20jj8uB29WupQ/BG2W0jHS3GfjrwH9IiN
7Pm+Nco8E/Yywh7YYNFJOxNesc7kB7RJuhfTabif9Ea+LqF6CQQ10rHzzT3WlY5p
rAJmu7k+JlKn5Aad+KQF4RXJ
-----END RSA PRIVATE KEY-----
The rest of the code:
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
final X509Certificate cert = readCertificate("public-key-bcr.cer");
keyStore.setCertificateEntry("key-bcr", cert);
keyStore.setKeyEntry("key-bcr", readPrivateKey("private-key-bcr.key"), "".toCharArray(), new Certificate[]{cert});
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), null);
final OkHttpClient.Builder builder = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory())
.hostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
OkHttpClient client = builder.build();
Also, I want to know if there is an easier alternative.
TLDR: your keyfile is labelled wrong.
Your privatekey file has PEM labels claiming it is RSA PRIVATE KEY which according to the de-facto standard should contain data which is the encoded form of a PKCS1-format privatekey. However, the data in your file is actually a PKCS8-format PrivateKeyInfo, which per rfc7468 should have labels PRIVATE KEY (NO RSA).
If you correct your file's labels to PRIVATE KEY with no RSA, BouncyCastle can read it, but you need to change the type and method used:
PEMParser pemParser = new PEMParser(/* appropriate Reader */);
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
// you don't actually need to set the provider, the default provider(s) work fine.
PrivateKeyInfo privkey = (PrivateKeyInfo) pemParser.readObject();
return /*PrivateKey*/ converter.getPrivateKey(privkey);
However, you don't need BouncyCastle; this (unencrypted) format can be read by Java crypto directly:
String pem = /* read all chars from file/resource/whatever, or read all bytes and convert to String */;
byte[] der = Base64.getDecoder().decode( pem.replaceAll("-----(BEGIN|END) PRIVATE KEY-----\r?\n", "") );
KeyFactory fact = KeyFactory.getInstance("RSA");
return /*PrivateKey*/ fact.generatePrivate(new PKCS8EncodedKeySpec(der));
Finally, your security scheme doesn't make sense. You aren't actually using this key for anything; you seem to be using the cert, only, as a CA cert (trust anchor). If this cert is indeed a CA cert, by including the CA's privatekey in your app you have allowed everyone who has a copy of the app to replace or impersonate your server(s?) and steal, modify, or destroy all your data -- and publishing it on Stack extends this to everyone in the world. Your nodejs code, in contrast, configures this key&cert to be used as a client key&cert and not as a CA or anchor at all -- although the server you specify in your URL doesn't appear to request a client key&cert at all, and it uses a cert under a normal root (Digicert) that doesn't require any modification to the Java defaults, and has a correct serve rname which also doesn't require disabling hostname verification.
The problem was that you are not able to use .cert and .key files in Java because the KeyStore doesn't know to work with these files.
To fix it you need to convert your files to .p12 (PKCS#12) file. To do this I used KeyStore Explorer.
After I converted the file I was able to make the call.
The solution code is here:
private static SSLSocketFactory getFactory(String fileName, String password) {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(Main.class.getClassLoader().getResourceAsStream(fileName), password.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, password.toCharArray());
SSLContext context = SSLContext.getInstance("SSL");
context.init(
keyManagerFactory.getKeyManagers(),
null,
new SecureRandom()
);
return context.getSocketFactory();
}
public static void main(String[] args) throws Exception {
final OkHttpClient.Builder builder = new OkHttpClient.Builder()
.sslSocketFactory(getFactory("converted_file.p12", "1234"))
.hostnameVerifier((hostname, session) -> true);
OkHttpClient client = builder.build();
//...
}
It looks like a standard question, but I couldn't find clear directions anywhere.
I have java code trying to connect to a server with probably self-signed (or expired) certificate. The code reports the following error :
[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught
when processing request: sun.security.validator.ValidatorException: PKIX path
building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
As I understand it, I have to use keytool and tell java that it's OK to allow this connection.
All instructions to fix this problem assume I'm fully proficient with keytool, such as
generate private key for server and import it into keystore
Is there anybody who could post detailed instructions?
I'm running unix, so bash script would be best.
Not sure if it's important, but code executed in jboss.
You have basically two options here: add the self-signed certificate to your JVM truststore or configure your client to
Option 1
Export the certificate from your browser and import it in your JVM truststore (to establish a chain of trust):
<JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit
Option 2
Disable Certificate Validation (code from Example Depot):
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
}
// Now you can access an https URL without having the certificate in the truststore
try {
URL url = new URL("https://hostname/index.html");
} catch (MalformedURLException e) {
}
Note that I do not recommend the Option #2 at all. Disabling the trust manager defeats some parts of SSL and makes you vulnerable to man in the middle attacks. Prefer Option #1 or, even better, have the server use a "real" certificate signed by a well known CA.
There's a better alternative to trusting all certificates: Create a TrustStore that specifically trusts a given certificate and use this to create a SSLContext from which to get the SSLSocketFactory to set on the HttpsURLConnection. Here's the complete code:
File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
// Or if the crt-file is packaged into a jar file:
// CertificateFactory.getInstance("X.509").generateCertificate(this.class.getClassLoader().getResourceAsStream("server.crt"));
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
You can alternatively load the KeyStore directly from a file or retrieve the X.509 Certificate from any trusted source.
Note that with this code, the certificates in cacerts will not be used. This particular HttpsURLConnection will only trust this specific certificate.
Apache HttpClient 4.5 supports accepting self-signed certificates:
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(new TrustSelfSignedStrategy())
.build();
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", socketFactory)
.build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);
This builds an SSL socket factory which will use the TrustSelfSignedStrategy, registers it with a custom connection manager then does an HTTP GET using that connection manager.
I agree with those who chant "don't do this in production", however there are use-cases for accepting self-signed certificates outside production; we use them in automated integration tests, so that we're using SSL (like in production) even when not running on the production hardware.
I chased down this problem to a certificate provider that is not part of the default JVM trusted hosts as of JDK 8u74. The provider is www.identrust.com, but that was not the domain I was trying to connect to. That domain had gotten its certificate from this provider. See Will the cross root cover trust by the default list in the JDK/JRE? -- read down a couple entries. Also see Which browsers and operating systems support Let’s Encrypt.
So, in order to connect to the domain I was interested in, which had a certificate issued from identrust.com I did the following steps. Basically, I had to get the identrust.com (DST Root CA X3) certificate to be trusted by the JVM. I was able to do that using Apache HttpComponents 4.5 like so:
1: Obtain the certificate from indettrust at Certificate Chain Download Instructions. Click on the DST Root CA X3 link.
2: Save the string to a file named "DST Root CA X3.pem". Be sure to add the lines "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" in the file at the beginning and the end.
3: Create a java keystore file, cacerts.jks with the following command:
keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword
4: Copy the resulting cacerts.jks keystore into the resources directory of your java/(maven) application.
5: Use the following code to load this file and attach it to the Apache 4.5 HttpClient. This will solve the problem for all domains that have certificates issued from indetrust.com util oracle includes the certificate into the JRE default keystore.
SSLContext sslcontext = SSLContexts.custom()
.loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
new TrustSelfSignedStrategy())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
When the project builds then the cacerts.jks will be copied into the classpath and loaded from there. I didn't, at this point in time, test against other ssl sites, but if the above code "chains" in this certificate then they will work too, but again, I don't know.
Reference: Custom SSL context and How do I accept a self-signed certificate with a Java HttpsURLConnection?
Rather than setting the default socket factory (which IMO is a bad thing) - yhis will just affect the current connection rather than every SSL connection you try to open:
URLConnection connection = url.openConnection();
// JMD - this is a better way to do it that doesn't override the default SSL factory.
if (connection instanceof HttpsURLConnection)
{
HttpsURLConnection conHttps = (HttpsURLConnection) connection;
// Set up a Trust all manager
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
{
public java.security.cert.X509Certificate[] getAcceptedIssuers()
{
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType)
{
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType)
{
}
} };
// Get a new SSL context
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
// Set our connection to use this SSL context, with the "Trust all" manager in place.
conHttps.setSSLSocketFactory(sc.getSocketFactory());
// Also force it to trust all hosts
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// and set the hostname verifier.
conHttps.setHostnameVerifier(allHostsValid);
}
InputStream stream = connection.getInputStream();
Trust all SSL certificates:-
You can bypass SSL if you want to test on the testing server.
But do not use this code for production.
public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";
public static void nuke() {
try {
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] myTrustedAnchors = new X509Certificate[0];
return myTrustedAnchors;
}
#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) {
}
}
}
Please call this function in onCreate() function in Activity or in your Application Class.
NukeSSLCerts.nuke();
This can be used for Volley in Android.
Download your self-signed certificate with your browser from target page and add it to default storage with default password:
keytool -import -v -trustcacerts -file selfsigned.crt -alias myserver -keystore /etc/alternatives/jre/lib/security/cacerts -storepass changeit
Use file $JAVA_HOME/jre/lib/security/cacerts , my example here is from Oracle linux 7.7 .
If 'they' are using a self-signed certificate it is up to them to take the steps required to make their server usable. Specifically that means providing their certificate to you offline in a trustworthy way. So get them to do that. You then import that into your truststore using the keytool as described in the JSSE Reference Guide. Don't even think about the insecure TrustManager posted here.
EDIT For the benefit of the seventeen (!) downvoters, and numerous commenters below, who clearly have not actually read what I have written here, this is not a jeremiad against self-signed certificates. There is nothing wrong with self-signed certificates when implemented correctly. But, the correct way to implement them is to have the certificate delivered securely via an offline process, rather than via the unauthenticated channel they are going to be used to authenticate. Surely this is obvious? It is certainly obvious to every security-aware organization I have ever worked for, from banks with thousands of branches to my own companies. The client-side code-base 'solution' of trusting all certificates, including self-signed certificates signed by absolutely anybody, or any arbitary body setting itself up as a CA, is ipso facto not secure. It is just playing at security. It is pointless. You are having a private, tamperproof, reply-proof, injection-proof conversation with ... somebody. Anybody. A man in the middle. An impersonator. Anybody. You may as well just use plaintext.
I had the issue that I was passing a URL into a library which was calling url.openConnection(); I adapted jon-daniel's answer,
public class TrustHostUrlStreamHandler extends URLStreamHandler {
private static final Logger LOG = LoggerFactory.getLogger(TrustHostUrlStreamHandler.class);
#Override
protected URLConnection openConnection(final URL url) throws IOException {
final URLConnection urlConnection = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()).openConnection();
// adapated from
// https://stackoverflow.com/questions/2893819/accept-servers-self-signed-ssl-certificate-in-java-client
if (urlConnection instanceof HttpsURLConnection) {
final HttpsURLConnection conHttps = (HttpsURLConnection) urlConnection;
try {
// Set up a Trust all manager
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
#Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
}
#Override
public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
}
} };
// Get a new SSL context
final SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
// Set our connection to use this SSL context, with the "Trust all" manager in place.
conHttps.setSSLSocketFactory(sc.getSocketFactory());
// Also force it to trust all hosts
final HostnameVerifier allHostsValid = new HostnameVerifier() {
#Override
public boolean verify(final String hostname, final SSLSession session) {
return true;
}
};
// and set the hostname verifier.
conHttps.setHostnameVerifier(allHostsValid);
} catch (final NoSuchAlgorithmException e) {
LOG.warn("Failed to override URLConnection.", e);
} catch (final KeyManagementException e) {
LOG.warn("Failed to override URLConnection.", e);
}
} else {
LOG.warn("Failed to override URLConnection. Incorrect type: {}", urlConnection.getClass().getName());
}
return urlConnection;
}
}
Using this class it is possible to create a new URL with:
trustedUrl = new URL(new URL(originalUrl), "", new TrustHostUrlStreamHandler());
trustedUrl.openConnection();
This has the advantage that it is localized and not replacing the default URL.openConnection.
The accepted answer is fine, but I'd like to add something to this as I was using IntelliJ on Mac and couldn't get it to work using the JAVA_HOME path variable.
It turns out Java Home was different when running the application from IntelliJ.
To figure out exactly where it is, you can just do System.getProperty("java.home") as that's where the trusted certificates are read from.
The accepted answer needs an Option 3
ALSO
Option 2 is TERRIBLE. It should NEVER be used (esp. in production) since it provides a FALSE sense of security. Just use HTTP instead of Option 2.
OPTION 3
Use the self-signed certificate to make the Https connection.
Here is an example:
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.KeyStore;
/*
* Use a SSLSocket to send a HTTP GET request and read the response from an HTTPS server.
* It assumes that the client is not behind a proxy/firewall
*/
public class SSLSocketClientCert
{
private static final String[] useProtocols = new String[] {"TLSv1.2"};
public static void main(String[] args) throws Exception
{
URL inputUrl = null;
String certFile = null;
if(args.length < 1)
{
System.out.println("Usage: " + SSLSocketClient.class.getName() + " <url>");
System.exit(1);
}
if(args.length == 1)
{
inputUrl = new URL(args[0]);
}
else
{
inputUrl = new URL(args[0]);
certFile = args[1];
}
SSLSocket sslSocket = null;
PrintWriter outWriter = null;
BufferedReader inReader = null;
try
{
SSLSocketFactory sslSocketFactory = getSSLSocketFactory(certFile);
sslSocket = (SSLSocket) sslSocketFactory.createSocket(inputUrl.getHost(), inputUrl.getPort() == -1 ? inputUrl.getDefaultPort() : inputUrl.getPort());
String[] enabledProtocols = sslSocket.getEnabledProtocols();
System.out.println("Enabled Protocols: ");
for(String enabledProtocol : enabledProtocols) System.out.println("\t" + enabledProtocol);
String[] supportedProtocols = sslSocket.getSupportedProtocols();
System.out.println("Supported Protocols: ");
for(String supportedProtocol : supportedProtocols) System.out.println("\t" + supportedProtocol + ", ");
sslSocket.setEnabledProtocols(useProtocols);
/*
* Before any data transmission, the SSL socket needs to do an SSL handshake.
* We manually initiate the handshake so that we can see/catch any SSLExceptions.
* The handshake would automatically be initiated by writing & flushing data but
* then the PrintWriter would catch all IOExceptions (including SSLExceptions),
* set an internal error flag, and then return without rethrowing the exception.
*
* This means any error messages are lost, which causes problems here because
* the only way to tell there was an error is to call PrintWriter.checkError().
*/
sslSocket.startHandshake();
outWriter = sendRequest(sslSocket, inputUrl);
readResponse(sslSocket);
closeAll(sslSocket, outWriter, inReader);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
closeAll(sslSocket, outWriter, inReader);
}
}
private static PrintWriter sendRequest(SSLSocket sslSocket, URL inputUrl) throws IOException
{
PrintWriter outWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(sslSocket.getOutputStream())));
outWriter.println("GET " + inputUrl.getPath() + " HTTP/1.1");
outWriter.println("Host: " + inputUrl.getHost());
outWriter.println("Connection: Close");
outWriter.println();
outWriter.flush();
if(outWriter.checkError()) // Check for any PrintWriter errors
System.out.println("SSLSocketClient: PrintWriter error");
return outWriter;
}
private static void readResponse(SSLSocket sslSocket) throws IOException
{
BufferedReader inReader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
String inputLine;
while((inputLine = inReader.readLine()) != null)
System.out.println(inputLine);
}
// Terminate all streams
private static void closeAll(SSLSocket sslSocket, PrintWriter outWriter, BufferedReader inReader) throws IOException
{
if(sslSocket != null) sslSocket.close();
if(outWriter != null) outWriter.close();
if(inReader != null) inReader.close();
}
// Create an SSLSocketFactory based on the certificate if it is available, otherwise use the JVM default certs
public static SSLSocketFactory getSSLSocketFactory(String certFile)
throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException
{
if (certFile == null) return (SSLSocketFactory) SSLSocketFactory.getDefault();
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(new File(certFile)));
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
}
This is the first ancient question with far too many answers where I think I can provide a more helpful idea: This option is what I would use if the server owner refuses to provide their certificate to me offline in a trustworthy way:
Retrieve the cert from the server itself (using command line tools instead of the browser)
Add that cert to the java keystore to trust it. You will be displayed with the certificate's details to verify it.
# HOSTNAME_PORT is the host that you want to connect to - example: HOSTNAME_PORT=stackoverflow.com:443
HOSTNAME_PORT=hostname_part_of_url_without_https:port
# whatever you want to call the key within the Java key store
MY_KEY_ALIAS=the_key_I_added_with_help_from_stackoverflow
openssl s_client -showcerts -connect $HOSTNAME_PORT </dev/null 2>/dev/null|openssl x509 -outform PEM >mycertfile.pem
sudo keytool -trustcacerts -keystore $JAVA_HOME/jre/lib/security/cacerts/pki/java/cacerts -storepass changeit -importcert -alias $MY_KEY_ALIAS -file mycertfile.pem
Type in yes when prompted, but only if you really trust the certificate displayed to you and want to add it to the global java keystore of your computer.
FYI:$JAVA_HOME/jre/lib/security/cacerts
is in my case (CentOS 7) pointing at:
/etc/pki/java/cacerts
This is not a solution to the complete problem but oracle has good detailed documentation on how to use this keytool. This explains how to
use keytool.
generate certs/self signed certs using keytool.
import generated certs to java clients.
https://docs.oracle.com/cd/E54932_01/doc.705/e54936/cssg_create_ssl_cert.htm#CSVSG178
Instead of using keytool as suggested by the top comment, on RHEL you can use update-ca-trust starting in newer versions of RHEL 6. You'll need to have the cert in pem format. Then
trust anchor <cert.pem>
Edit /etc/pki/ca-trust/source/cert.p11-kit and change "certificate category: other-entry" to "certificate category: authority". (Or use sed to do this in a script.) Then do
update-ca-trust
A couple caveats:
I couldn't find "trust" on my RHEL 6 server and yum didn't offer to install it. I ended up using it on an RHEL 7 server and copying the .p11-kit file over.
To make this work for you, you may need to do update-ca-trust enable. This will replace /etc/pki/java/cacerts with a symbolic link pointing to /etc/pki/ca-trust/extracted/java/cacerts. (So you might want to back up the former first.)
If your java client uses cacerts stored in some other location, you'll want to manually replace it with a symlink to /etc/pki/ca-trust/extracted/java/cacerts, or replace it with that file.
The variant in Kotlin
#SuppressLint("CustomX509TrustManager", "TrustAllX509TrustManager")
fun ignoreSsl() {
val trustAllCerts: Array<TrustManager> = arrayOf(
object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate>? = null
override fun checkClientTrusted(certs: Array<X509Certificate?>?, authType: String?) {}
override fun checkServerTrusted(certs: Array<X509Certificate?>?, authType: String?) {}
})
val sc = SSLContext.getInstance("SSL")
sc.init(null, trustAllCerts, SecureRandom())
HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
}
Late to the party, you can do so by disabling it at the RestTemplate level.
Note that this TrustStrategy will trust all certificates, and you disable hostname verification with NoopHostnameVerifier().
public RestTemplate getRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);
}
Inspired by below annser, I find a way to trust self-signed and keep trust default CA.
File file = new File(System.getProperty("java.home"), "lib/security/cacerts");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(file), "changeit".toCharArray());
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("testCer.cer");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(resourceAsStream);
keyStore.setCertificateEntry("my-server-alias", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContexts.createDefault();
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
// check domain
// SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
// not check domain
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1","TLSv1.1","TLSv1.2","SSLv3"},null, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
factory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
I was running into this issue because the maven repo where the dependencies are coming from is my local server with a self-signed certificate and a self-signed CA cert. To resolve the error, I had to run these two commands:
<my_java_install_dir>\bin\keytool.exe -importcert -file <my-self-signed-CA-cert>.crt -keystore <my_java_install_dir>\lib\security\cacerts -alias my-CA-cert
and then
<my_java_install_dir>\jdk11.0.14_10\bin\keytool.exe -importcert -file <my-self-signed-maven-repo-cert>.crt -keystore <my_java_install_dir>\lib\security\cacerts -alias my-maven-repo-cert
I am trying to make a two way SSL authentication to a server.
The server has SSL implemented to I need to make a HTTPS connection to the server. The server is with a third party vendor so i don't have server access. Although they have given me a server certificate test.crt.
Then I created my own self signed certificate by following the following commands.
generating keystore
keytool -genkey -alias myssl -keystore /home/user/mykey.keystore -validity 365
generating cer file
keytool -export -alias myssl -keystore /home/user/mykey.keystore -file /home/user/mykey.cer
import the cer file into the bks file
keytool -import -alias test_cer -file /home/user/mykey.cer -keystore /home/user/mykey.bks -storetype BKS -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /home/user/bcprov-jdk15on-146.jar
Now I have 3 files : mykey.keystore, mykey.cer and mykey.bks.
So i gave the .cer file to the server vendors and they imported the certificate into their truststore.
Now, in my android application I try to connect to the server. I put the certificate from the server test.crt into the trust store and my mykey.cer into the key store and pass the trust manager and the key manager into the SSLContext.
Below is the relevant code.
allowTrustedSSL(this.activity);
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(method,url,dataToSend, onResponseListener,onErrorListener);
/**
* Method implements self-signed certificates
* #param context
*/
public static void allowTrustedSSL(Context context){
/**
* We shall accept traffic with any host names
*/
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
Logger.d(TAG, "verify() called with: hostname = [" + hostname + "], session = [" + session + "]");
return true;
}
});
try{
//the NoSSLv3SocketFactory will disable the SSLv3
SSLSocketFactory noSSLv3Factory = new NoSSLv3SocketFactory(sslSocketFactoryGenerator(context));
HttpsURLConnection.setDefaultSSLSocketFactory(noSSLv3Factory);
} catch (Exception e) {
Logger.e(TAG, "allowTrustedSSL: "+e.getMessage());
e.printStackTrace();
}
}
public static SSLSocketFactory sslSocketFactoryGenerator(Context context) throws
UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, IOException, KeyManagementException {
//the test.crt file
TrustManagerFactory trustManagerFactory = getTrustManagerFactory(context.getResources().openRawResource(R.raw.test));
//the mykey.cer file
KeyManagerFactory keyManagerFactory = getKeyManagerFactory(context.getResources().openRawResource(R.raw.mykey),
MYKEY_PASS.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(),null);
return sslContext.getSocketFactory();
}
public static TrustManagerFactory getTrustManagerFactory(InputStream fileInput) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca;
try {
ca = cf.generateCertificate(fileInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
fileInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
return tmf;
}
public static KeyManagerFactory getKeyManagerFactory(InputStream fileInput,char[] password)
throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca;
try {
ca = cf.generateCertificate(fileInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
fileInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String keyManAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManAlgorithm);
keyManagerFactory.init(keyStore, password);
return keyManagerFactory;
}
But when i start the connection to the server all I am getting is SSLException: Connection closed by peer. I have googled a lot for a proper documentation or example for implementing the two way ssl from android. And I have lost many precious days on figuring it out on myself.
Is my process of creating the certificates incorrect?
After reading through numerous answers concerning these topics, I have found myself completely unable to string together the pieces of the puzzle, I hope you will excuse me for this.
I am trying to change my simple socket connection in Java to use SSL. I would like both the server and client to authenticate themselves if possible, but only server authentication would be good start.
Currently, this is the extremely simple code on the server side:
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
And this is the code on the client side:
Socket socket = null;
while (true) {
try {
socket = new Socket(ipAddress, port);
break;
} catch (Exception e) {}
}
This works fine, but without SSL.
I have generated SSL certificates for the server and client using OpenSSL, ending up with:
A certificate for the server (PEM format)
A certificate for the client (PEM format)
A private key for the server (PEM format)
A private key for the client (PEM format)
A CA file (PEM, CER and CRT format)
From this I have used OpenSSL to create PKCS12 (.p12) keystores for both the client and the server, as follows
server.p12, made by doing openssl pkcs12 -export -in server-cert.pem -inkey server-private-key.pem -out server.p12
client.p12, made by doing openssl pkcs12 -export -in client-cert.pem -inkey client-private-key.pem -out client.p12
Then, I turned these into JKS keystores by using keytool (for the server, for example, the command was keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore server.jks -deststoretype JKS), resulting in two files named server.jks and client.jks.
I then use the following code as replacement for the previous server snippet:
char[] keyStorePassword = "JKSPassword".toCharArray();
FileInputStream keyStoreFile = new FileInputStream("somepath/server.jks");
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(keyStoreFile, keyStorePassword);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "PKCS12Password".toCharArray());
SSLContext sslContext = SSLContext.getDefault();
ServerSocket serverSocket = sslContext.getServerSocketFactory().createServerSocket(port);
Socket socket = serverSocket.accept();
And the following code as replacement for the client snippet:
Socket socket = null;
while (true) {
try {
char[] keyStorePassword = "JKSPassword".toCharArray();
FileInputStream keyStoreFile = new FileInputStream("somepath/client.jks");
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(keyStoreFile, keyStorePassword);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "PKCS12Password".toCharArray());
SSLContext sslContext = SSLContext.getDefault();
socket = sslContext.getSocketFactory().createSocket(ipAddress, port);
break;
} catch (Exception e) {}
}
I now still get javax.net.ssl.SSLHandshakeException: no cipher suites in common on the server (and javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure on the client).
What could be wrong?
I found the missing part of the puzzle (I believe).
Instead of the line SSLContext sslContext = SSLContext.getDefault(); on the client, I put:
BufferedInputStream serverCertificateFile = new BufferedInputStream(new FileInputStream("somepath/server-cert.der"));
X509Certificate serverCertificate = (X509Certificate)
CertificateFactory.getInstance("X.509").generateCertificate(serverCertificateFile);
sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[] {
new X509TrustManager() {
#Override
public void checkClientTrusted(X509Certificate[] arg0,
String arg1) throws CertificateException {
throw new CertificateException();
}
#Override
public void checkServerTrusted(X509Certificate[] arg0,
String arg1) throws CertificateException {
boolean valid = false;
for (X509Certificate certificate : arg0) {
try {
certificate.verify(serverCertificate.getPublicKey());
valid = true;
break;
} catch (SignatureException e) {}
}
if (!valid) {
throw new CertificateException();
}
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}, new SecureRandom());
server-cert.der is a file, created with openssl509 -outform der -in server-cert.pem -out server-cert.der.
I was not able to find good tutorials or question on this matter on StackOverflow, so this is what I created by attempting to understand the reference guide.
Essentially, I am creating a TrustManager that, for a server, trusts it when one of the provided certificates is the one that belongs to your own server.
It appears to work, please let me know if there is anything principally wrong with this approach (I unfortunately assume there is). Thanks for having read so much!
I want to connect to my server via SSL. Therefore I generated the certificates on the server with these commands:
openssl genrsa -out server.pem 2048
openssl req -new -x509 -nodes -sha1 -days 3650 -key server.pem > server.cert
The connection works, if I trust all certificates on the client with a TrustManager like this:
X509TrustManager tm = new X509TrustManager() {
#Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkClientTrusted(
X509Certificate[] certs, String authType) {
}
#Override
public void checkServerTrusted(
X509Certificate[] certs, String authType) {
}
};
But I do not want to trust all certificates of course, but only mine. I tried several commands to import the certificates like:
keytool -import -alias ca -file server.cert -keystore cacerts
But I always 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
What do I need to do to get this working? Can someone explain the steps necessary for a person not very familiar with the cryto field?
Edit: As proposed by Donal Fellows, I tried the approach with the custom X509TrustManager and it works. But is it secure as well like that? If i just return "null" in the method "getAcceptedIssuers" its working as well and I am not quite sure why:
X509TrustManager tm = new X509TrustManager() {
#Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
X509Certificate[] trustedCerts = new X509Certificate[1];
try{
InputStream inStream = new FileInputStream("server.cert");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
inStream.close();
trustedCerts[0] = cert;
}catch(Exception e){
e.printStackTrace();
}
return trustedCerts;
}
#Override
public void checkClientTrusted(
X509Certificate[] certs, String authType) {
}
#Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
boolean match = false;
try{
InputStream inStream = new FileInputStream("server.cert");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
inStream.close();
for(X509Certificate c : chain){
if(c.equals(cert)){
match = true;
}
}
}catch(Exception e){
throw new CertificateException();
}
if(!match)
throw new CertificateException();
}
};
If you're really locking things down, you can do it by installing a custom X509TrustManager that tests to see if the certificate used is equal to the certificate you think it should be (which you know for sure; you've generated it). This is actually pretty secure, but allows no operational flexibility at all; if the server gets compromised and you have to regenerate the key, all the clients will also need updating.
Because that's really annoying (and doesn't scale to things like the WWW) it's more normal to use trust root. The trust root is a self-signed CA certificate, typically with a long life, that is used to sign the working certificate that you deploy to your server. (You can use the CA cert itself, but it's usually better to keep that offline, probably on removable media in a fireproof safe.) Then you put the CA certificate into a truststore (i.e., a keystore that just holds certificates) on the client and tell Java to use it.
In practice, you're almost there. You probably just need to tell Java to use your cacerts truststore. You do that by setting the correct system property.
// Be careful on Windows; this property (unusually!) uses “/” instead of “\”
System.setProperty("javax.net.ssl.trustStore", "/path/to/cacerts");