I write android application.
How can I use Certificate in https connection when I initialize certificate from directory file and not from packages?
When I have packages file with password, this code works:
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(certificateIs, pass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, pass.toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
But I have certificate initialized from der file:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) cf.generateCertificate(certBytes);
I do not know how use this certificate over https connection.
You seem to be talking about client-certificate authentication (where your Android device is the client).
Firstly, you need the client to have the private key matching the public key in the certificate you're trying to use (that's the whole point, otherwise, it wouldn't authenticated anything). PKCS#12 is one of the usual formats for containing the private key and the certificate. If you only have the certificate in a der file, you probably won't have the private key in it, hence it won't work.
It's not quite clear from your question what you do with your certificate variable, with respect to the KeyManagerFactory (if you have a custom X509KeyManager, it should return the private key in its getPrivateKey method, otherwise it won't work).
Secondly, client-certificate authentication is always initiated by the server, so you'd need the server to be set up accordingly too (it seems to be the case already, if your test based on a PKCS#12 keystore works).
Related
I have a Kubernetes Deployment of my Spring Boot application where I used to update global Java cacerts using keytool at the bootstrap:
keytool -noprompt -import -trustcacerts -cacerts -alias $ALIAS -storepass $PASSWORD
However, I need to make the container immutable using the readOnlyRootFilesystem: true in the securityContext of the image in my Deployment. Therefore, I cannot update the cacert like that with additional certificates to be trusted.
Additional certificates that should be trusted are provided as environment variable CERTS.
I assume that the only proper way would be to do this programmatically, for example during #PostConstruct in the Spring Boot component.
I was looking into some examples how to set the global truststore in code, but all of them refer to update the cacerts and then save it to filesystem, which does not work for me.
Some examples use System.setProperty("javax.net.ssl.trustStore", fileName);, but this does not work either on the read-only filesystem, where I cannot update file.
Another examples suggest to use X509TrustManager, but if I understood correctly, this does not work globally.
Is there any way in Java or Spring Boot to update global truststore in general programmatically so every operation in the code will use and I do not have to implement something like TrustManager to every connection? My goal is to have it imported at the begging (similar like it is done using shell and keytool). Without touching the filesystem, as it is read-only.
You can use the following approach to update the Java truststore programmatically without modifying the read-only filesystem:
Create a KeyStore object in your code.
Load the existing truststore into the KeyStore object using the
truststore password.
Parse the environment variable CERTS and add the certificates to the
KeyStore object.
Use the javax.net.ssl.TrustManagerFactory to create a
TrustManagerFactory with the KeyStore.
Use the TrustManagerFactory to initialize a SSLContext with the
trustmanager.
Use the SSLContext.init() method to set the SSL context as the
default for all SSL connections.
You can achieve this in a Spring Boot component:
#PostConstruct
public void configureGlobalTrustStore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream inputStream = getClass().getResourceAsStream("/cacerts");
trustStore.load(inputStream, "changeit".toCharArray());
inputStream.close();
String certString = System.getenv("CERTS");
if (certString != null) {
String[] certArray = certString.split(" ");
for (int i = 0; i < certArray.length; i++) {
InputStream certInput = new ByteArrayInputStream(Base64.getDecoder().decode(certArray[i]));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(certInput);
certInput.close();
trustStore.setCertificateEntry("cert-" + i, cert);
}
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
SSLContext.setDefault(sslContext);
}
This way, every SSL connection in your code will use the updated truststore, without having to configure it for each individual connection.
I have stunnel running on my server with the following configuration:
[myservice]
accept = 12345
connect = 9999
verifyPeer = yes
cert = /etc/stunnel/stunnel.pem
CAfile = /etc/stunnel/androidApp.crt
Both cert and CAfile has been issued by the same private CA.
I want to achieve a secure communication between stunnel (on port 12345) and my Android application. Moreover, I want stunnel to verify the peer (that its certificate has been issued by the same CA as the stunnel's one) and on the other hand, the Android application should also verify the identity of the stunnel (server) part.
In my application I have the following code
// ...
InputStream caInputStream = ctx.getResources().openRawResource(R.raw.android_app); //PKCS12
KeyStore keyStore;
KeyManagerFactory keyManagerFactory;
SSLContext sslContext;
SSLSocketFactory sslSocketFactory;
Socket socket;
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(caInputStream, "password".toCharArray());
keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, "password".toCharArray());
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, SecureRandom.getInstance("SHA1PRNG"));
sslSocketFactory = sslContext.getSocketFactory();
socket = sslSocketFactory.createSocket("hostname", 12345);
// ...
When the socket is created, I get the following logs from stunnel:
2021.05.13 17:01:21 LOG5[2]: Service [myservice] accepted connection from XXX.XXX.XXX.XXX:YYYYY
2021.05.13 17:01:21 LOG6[2]: Peer certificate required
2021.05.13 17:01:25 LOG3[2]: SSL_accept: 1417C0C7: error:1417C0C7:SSL routines:tls_process_client_certificate:peer did not return a certificate
2021.05.13 17:01:25 LOG5[2]: Connection reset: 0 byte(s) sent to TLS, 0 byte(s) sent to socket
At this stage I am fully aware that I am doing something fundamentally wrong (like I do not send the peer certificate), but I am a bit confused how to do that. Could you please give me a hand with this?
Cheers
This is an assumption, but it looks like the PKCS12 file you are opening does not contain a private key.
Add private key -> create CSR -> sign with CA -> import chain to key store.
Everything else looks in order.
I need to create a javax.net.ssl.SSLSocketFactory that establish a TLS connection but ignores the validation of the server certificate. Not for HTTP protocol. I know this is not the right thing to do, but I NEED it. I don't want or like it.
I made an implementation that works but validate the server cert (as it supposes to be) implementation looks as following:
/**
* Provide a quick method to construct a SSLSocketFactory which is a TCP socket using TLS/SSL
* #param trustStore location of the trust store
* #param keyStore location of the key store
* #param trustStorePassword password to access the trust store
* #param keyStorePassword password to access the key store
* #return the SSLSocketFactory to create secure sockets with the provided certificates infrastructure
* #exception java.lang.Exception in case of something wrong happens
* */
static public SSLSocketFactory getSocketFactory ( final String trustStore, final String keyStore, final String trustStorePassword, final String keyStorePassword) throws Exception
{
// todo check if the CA needs or can use the password
final FileInputStream trustStoreStream = new FileInputStream(trustStore);
final FileInputStream keyStoreStream = new FileInputStream(keyStore);
// CA certificate is used to authenticate server
final KeyStore caKs = KeyStore.getInstance("JKS");
caKs.load(trustStoreStream, trustStorePassword.toCharArray());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(caKs);
trustStoreStream.close();
// client key and certificates are sent to server so it can authenticate us
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(keyStoreStream, keyStorePassword.toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(ks, keyStorePassword.toCharArray());
keyStoreStream.close();
// finally, create SSL socket factory
final SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return context.getSocketFactory();
}
The solution would be to create a dummy X509TrustManager class that basically accepts all server certs without checking.
Then create an instance and pass it as the 2nd argument in your SSLContext::init call.
But seriously, this is a bad idea. If you are not going to check the CERT, then using SSL is pointless or worse1.
1 - Here's my theory. You have been told by your security folks that using MTTQ over an insecure channel is dangerous. Or they have blocked port 1883. But you have a MTTQ / SSL server that you can't be bothered to get a proper CERT for. So you are just trying to throw something together that "works". Guess what. If the people who told you to use SSL find out, they will have your guts for garters! And if you are not doing this for this reason, then ... well ... what you are doing makes no sense. Even a self-signed server CERT is better than this. You just need to add it as a trusted cert in the client-side keystore.
I'm trying to build a one-way authentication socket server using Netty.
First I used keytool to generate keystore, self signed certificate, truststore for both server and client, and I wrote some code in my server/client, the SSL authentication is working.
Here is my question:
Is there any way that I don't need to add truststore to my client, I only add the keystore to my server, and it would still work well? I thought one-way authentication means that only server holds the certificate?
The following is what I wrote in my server/client to add the SslHandler so far:
server:
private void addSslHandlerOneWay(SocketChannel ch) throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(new File("svrks.jks")), "kspassword1".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "kspassword2".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
sslEngine.setNeedClientAuth(false);//one-way
sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
sslEngine.setEnableSessionCreation(true);
ch.pipeline().addFirst("SSL", new SslHandler(sslEngine));
}
client:
private void addSslHandlerOneWay(SocketChannel ch) throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(getInputStream("clits.jks"), "tspassword2".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ts, "tspassword1".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(true);//client
sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
sslEngine.setEnableSessionCreation(true);
ch.pipeline().addFirst("SSL", new SslHandler(sslEngine));
}
Thanks, guys.
I thought one-way authentication means that only server holds the certificate?
The server needs to authenticate itself with the certificate. For this it needs certificate and matching private key.
The client needs to verify the authentication, i.e. that the certificate send by the server is actually the expected one. For this it needs to know either the certificate itself or the CA which issued the certificate - which is the same in case of self-signed certificates.
What you seem to expect is that the client does not need any previous knowledge of the servers certificate or the issuer CA. If this would be the case then the client would just accept any certificate, both corrects ones from the server and also fake ones from an attacker. Without previous knowledge (i.e. a local trust anchor) what to expect the server cannot distinguish between correct and fake certificates.
This is similar to Import PEM into Java Key Store. But the question's answers use OpenSSL for conversions and tools to import them into key stores on the file system.
I'm trying to use a well formed X509 certificate as a trust anchor:
static String CA_FILE = "ca-rsa-cert.pem";
public static void main(String[] args) throws Exception
{
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(CA_FILE), null);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Redirected through hosts file
URL url = new URL("https://example.com:8443");
HttpsURLConnection connection = (HttpsURLConnection) url
.openConnection();
connection.setSSLSocketFactory(context.getSocketFactory());
...
}
When I attempt to run the program, I get an error:
$ java TestCert
Exception in thread "main" java.io.IOException: Invalid keystore format
at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:650)
at sun.security.provider.JavaKeyStore$JKS.engineLoad(JavaKeyStore.java:55)
at java.security.KeyStore.load(KeyStore.java:1214)
at TestCert.main(TestCert.java:30)
I also tried KeyStore ks = KeyStore.getInstance("PEM"); and getInstance("X509");, but they did not work either.
I know Java supports PEM and DER encoded certificates because that's what a web server sends to a client. But none of the KeyStoreType's seem to match my needs, so I suspect I'm not using the right APIs for this.
The reasons I want to use them directly and not import them into a long-lived KeyStore are:
There are hundreds of PEM certs to test
The certs are on my filesystem
Using certs from the filesystem matches my workflow
I don't want to to use openssl or keytool
I don't want to perform key store maintenance
How does on take a well formed PEM encoded certificate on the filesystem and use it directly?
I found the answer while trying to do this another way at Set certificate for KeyStore.TrustedCertificateEntry?. Its based on Vit Hnilica's answer at loading a certificate from keystore. I"m going to leave the question with this answer since most Stack Overflow answers start with "convert with openssl, then use keytool ...".
String CA_FILE = ...;
FileInputStream fis = new FileInputStream(CA_FILE);
X509Certificate ca = (X509Certificate) CertificateFactory.getInstance(
"X.509").generateCertificate(new BufferedInputStream(fis));
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry(Integer.toString(1), ca);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
...