I'm working on encrypting a tcp connection between a server and a client. In the course of research and testing I'm leaning towards using secret key encryption. My problem is that I cannot find any tutorials on how to implement this feature. The tutorials I have found revolve around one-shot https requests, all I need is a SSL Socket.
The code I've written so far is below. I'm almost certain that it needs to be extended, I just don't know how. Any help is appreciated.
private ServerSocketFactory factory;
private SSLServerSocket serverSocket;
factory = SSLServerSocketFactory.getDefault();
serverSocket = (SSLServerSocket) factory.createServerSocket( <portNum> );
Server code for accepting client connections
SSLSocket socket = (SSLSocket) serverSocket.accept();
socket.startHandshake();
I just don't know how to actually do the handshake.
reference: http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html
SSL socket connections are well supported in Java and are likely a good choice for you. The one thing to understand in advance is that SSL provides both encryption and server authentication; you can't easily get just the encryption. For reference, the encryption protects against network eavesdropping, while the server authentication protects against "man in the middle" attacks, where the attacker acts as a proxy between the client and the server.
Since authentication is an integral part of SSL, the server will need to provide an SSL certificate, and the client will need to be able to authenticate the certificate. The server will need a "key store" file where its certificate is stored. The client will need a "trust store" file where it stores the certificates it trusts, one of which must either be the server's certificate, or a certificate from which a "chain of trust" can be traced to the server's certificate.
Note that you do not have to know anything about the ins and outs of SSL in order to use Java SSL sockets. I do think it is interesting to read through information on how SSL works, for example in the Wikipedia article on TLS, but the complicated multistep handshake and the setup of the actual connection encryption is all handled under the covers by the SSLServerSocket and SSLSocket classes.
The code
All of the above is just background information to explain some of the following code. The code assumes some familiarity with regular, unencrypted sockets. On the server, you will need code like this:
/**
* Returns an SSLServerSocket that uses the specified key store file
* with the specified password, and listens on the specified port.
*/
ServerSocket getSSLServerSocket(
File keyStoreFile,
char[] keyStoreFilePassword,
int port
) throws GeneralSecurityException, IOException {
SSLContext sslContext
= SSLConnections.getSSLContext(keyStoreFile, keyStoreFilePassword);
SSLServerSocketFactory sslServerSocketFactory
= sslContext.getServerSocketFactory();
SSLServerSocket sslServerSocket
= (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
return sslServerSocket;
}
The SSLServerSocket can then be used exactly like you would use any other ServerSocket; the authentication, encryption and decryption will be completely transparent to the calling code. In fact, the cognate function in my own code declares a return type of just plain ServerSocket, so the calling code can't get confused.
Note: if you want to use the JRE's default cacerts file as your key store file, you can skip the line creating the SSLContext, and use ServerSocketFactory.getDefault() to get the ServerSocketFactory. You will still have to install the server's public/private key pair into the key store file, in this case the cacerts file.
On the client, you will need code like this:
SSLSocket getSSLSocket(
File trustStoreFile,
char[] trustStoreFilePassword,
InetAddress serverAddress,
port serverPort
) throws GeneralSecurityException, IOException {
SSLContext sslContext
= SSLConnections.getSSLContext(trustStoreFile, trustStoreFilePassword);
SSLSocket sslSocket
= (SSLSocket) sslContext.getSocketFactory().createSocket
(serverAddress, serverPort);
sslSocket.startHandshake();
return sslSocket;
}
As in the case of the SSLServerSocket in the server code, the returned SSLSocket here is used just like a regular Socket; I/O into and out of the SSLSocket is done with unencrypted data, and all the cryptographic stuff is done inside.
As with the server code, if you want to use the default JRE cacerts file as your trust store, you can skip creation of the SSLContext and use SSLSocketFactory.getDefault() instead of sslContext.getSocketFactory(). In this case, you will only need to install the server's certificate if the server's certificate was self signed or not otherwise issued by one of the major certificating authorities. In addition, to ensure you aren't trusting a certificate that is legitimately issued within a certificate chain you trust, but to an entirely different domain than you are trying to get to, you should add the following (untested) code just after the line where you create the SSLSocket:
sslSocket.getSSLParameters().setEndpointIdentificationAlgorithm("HTTPS");
This would also apply if you are using your own trust store file, but trusting all certificates issued by one or more certificating authorities in that file, or trusting a number of certificates for different servers in that file.
Certificates, key stores, and trust stores
Now for the hard, or at least, slightly harder part: generating and installing the certificates. I recommend using the Java keytool, preferably version 1.7 or above, to do this work.
If you are creating a self signed certificate, first generate the server's keypair from the command line with a command like the following: keytool -genkey -alias server -keyalg rsa -dname "cn=server, ou=unit, o=org, l=City, s=ST, c=US" -validity 365242 -keystore server_key_store_file -ext san=ip:192.168.1.129 -v. Substitute your own names and values. In particular, this command creates a key pair that expires in 365242 days - 1000 years - for a server that will be at IP address 192.168.1.129. If the clients will be finding the server through the domain name system, use something like san=dns:server.example.com instead of san=ip:192.168.1.129. For more information on keytool options, use man keytool.
You will be prompted for the key store's password - or to set the key store's password, if this is a new key store file - and to set the password for the new key pair.
Now, export the server's certificate using keytool -export -alias server -file server.cer -keystore server_key_store_file -rfc -v. This creates a server.cer file containing the certificate with the server's public key.
Finally, move the server.cer file to the client machine and install the certificate into the client's trust store, using something like, keytool -import -alias server -file server.cer -keystore client_trust_store_file -v. You will be prompted for the password to the trust store file; the prompt will say "Enter keystore password", since the Java keytool works with both key store files and trust store files. Note that if you are using the default JRE cacerts file, the initial password is changeit, I believe.
If you are using a certificate purchased from a generally recognized certificating authority, and you're using the default JRE cacerts file on the client, you only have to install the certificate in the server's key store file; you don't have to mess with the client's files. Server installation instructions should be provided by the certificating authority, or again you can check man keytool for instructions.
There's a tremendous amount of mystique surrounding sockets and, especially, SSL sockets, but they're actually quite easy to use. In many cases, ten lines of code will avoid the need for complex and fragile messaging or message queueing infrastructure. Good for you for considering this option.
You've started the handshake. That's all you have to do: in fact you don't even have to do that, as it will happen automatically. All you have to do now is normal input and output, same as you would with a plaintext socket.
Related
So I'm wanting to set up an SSL server/client in Java, my knowledge with networking is not very good I've created normal servers/clients in java many times and wanted to up it and setup SSL so a user can't just send false packets to my client thinking its the server sending them.
I've looked up a few examples etc and came across this GitHub repo which shows a basic server with SSL encryption between the client and server and set it up in my IDE.
Here is the repo:
https://github.com/AlphaGarden/SSL-Client-Server
My question is why does the client and server use both certificates? Can't a user just get the certificates from the client and use them to decrypt the SSL? Also in the client there's 2 strings, password & password2... Am I supposed to hide these from the person using my client too? If not am I supposed to hide anything client sided from the user that could help them decrypt the SSL traffic and feed my client false information?
Just some basics to explain a SSL/TLS connection: Wkipedia https://en.wikipedia.org/wiki/Transport_Layer_Security, for TLS 1.2 RFC5246: https://www.rfc-editor.org/rfc/rfc5246 and for TLS 1.3 RFC8446 https://www.rfc-editor.org/rfc/rfc8446.
The basic principle for a secure connection is to use (each) a certificate on server and client's side and exchange them. The certificate itself is useless as it does not prove that you are whom you say to be. To get trust in the certificate the usual way is to "buy" a certificate from a Certificate Agency (CA) that checks your identity.
The CA's root certificate usually is known to today browsers and so the server and client certificate can get checked
by the browser (client) and server against the CA's root certificate.
Let us see the code for the simple SSL Server & Client code. I'm for sure you noticed that the server and client are using
"twisted" sources for the keystore and the truststore and therefore they need two (different) passwords to get access to the two stores:
SSLServer:
String password = "abcdefg";
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("server/certificate-server.p12");
// TrustManagerFactory
String password2 = "aabbcc";
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
InputStream inputStream1 = ClassLoader.getSystemClassLoader().getResourceAsStream("client/certificate-client.p12");
SSLClient:
String password = "aabbcc";
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("client/certificate-client.p12");
// TrustManagerFactory ()
String password2 = "abcdefg";
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
InputStream inputStream1 = ClassLoader.getSystemClassLoader().getResourceAsStream("server/certificate-server.p12");
What are the keystore and the truststore good for? The keystore holds the (own) private key (of server and client) and in the truststore (all) trusted certificates (usually the CA root certificates) are saved. To check the received certificate from the other party the server and client prove the certificate chain up to the root certificate from the CA.
But here is the problem - as the programs are using self signed certificates there is not "real" and saved root certificate available in the trust store. To get the programs to run without an user interaction ("do you trust this certificate ?") both provide use a truststore with the "approved" certifcates and all is running.
To your second question "Can't a user just get the certificates from the client and use them to decrypt the SSL" the answer is simple: YES. But when securing a communication only between "allowed" partners - how should e.g. the servers knows that the client is the real one and not an attacker? For that reason the client is sending a certificate as well that can get checked by the servers
truststore.
I've been at this way too long and have searched high and low for a solution.
I am trying to do mutual SSL. The target is using a certificate signed by an intermediate that is valid and trusted. My side, a Java web service client using httpclient 4.3.6, is also using a certificate signed by the intermediate.
The intermediate is in a JKS format keystore and has the full chain. My certificate is also in a JSK keystore.
I am also running Wireshark to follow the SSL process, since the Java exceptions are very unhelpful. I can see that the Server Hello finishes (a line in Wireshark called "Server Hello, Certificate, Server Key Exchange, Certificate Request, Server Hello Done"). A couple of packets later, there is a "Certificate, Client Key Exchange" line that seems to run ok. Two packets later, there is an alert about a handshake failure (40). When I go backward two packets to the "Certificate, Client Key Exchange" line and inspect it closely, I can see that while the key was sent off, there is a line that says "Certificates Length: 0" in the packet inspection window.
So it appears that my certificate is not even being sent? I saw on another question here that it may be because my side doesn't see a valid path to my cert, and so doesn't bother including it? (The answer here https://stackoverflow.com/a/14876605/5136448). I do include the trust store that has my intermediate in it when using SSLContext and SSLConnectionSocketFactory.
A colleague of mine got a successful connection using openssl when including my keystore and intermediate together, so I'm trying to add the certificate chain from the intermediate keystore to my own keystore to see if that will solve it. I have no found any successful way of doing this (openssl, keytool, Keystore Explorer), so I may not have the right idea as to properly solve this.
To sum up, using Apache http-client 4.3.6, I need to do mutual SSL to a server where the server is trusted, but the client doesn't seem to be sending its certificate (which was signed by the same entity that signed the server's certificate).
Should it be relevant, here is the client-side code:
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(new File("/path/to/test.keystore")), "abc123".toCharArray());
KeyStore trustStore = KeyStore.getInstance("PKCS12");
trustStore.load(new FileInputStream(new File("/path/to/test.truststore")), "123abc".toCharArray());
SSLContext sslContext = SSLContexts
.custom()
.loadKeyMaterial(keystore, "abc123".toCharArray(), null)
.loadTrustMaterial(trustStore, null)
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2"},
null,
SSLConnectionSocketFactory.STRICT_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient1 = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
I need to integrate an application with external web service which forces to use https. Authors of this web service provided me with .crt file which I should use for making https requests. After some investigation I've found the following code which uses KeyStore class for secured https access:
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream instream = new FileInputStream(new File(file));
try {
trustStore.load(instream, password.toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext =
SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null,
BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
HttpClients.custom().setSSLSocketFactory(sslsf).build();
In this code KeyStore requires input stream along with password trustStore.load(instream, password.toCharArray());. However, as I understand, we don't need password when using .crt file. So this way of loading certificate is not suitable for me. At the same time, from what I've discovered so far, the code I provided here is the only way to configure HttpClient for using SSL certificate. Is there any workaround to configure HttpClient for using .crt certificate?
Thanks,
Andrey
I assume the Web service is providing you with a self-signed certificate (i.e. not signed by a well-known CA). If it is already signed by a well-known CA that is in Java's cacerts file, then you don't need to do anything.
Otherwise, you have a couple of options:
Import the certificate into global cacerts keystore
Launch your application with application-specific keystore
In either case, you first need to convert crt file into jks keystore that Java uses. You can do this by:
$ keytool -import -keystore mykeystore.jks -storepass horsestaple
Note that keytool requires the password (horsestaple above) to be supplied for creating a jks store. You can put anything there; as you mention, public website certificates do not need password protection, they are public after all.
If you are doing option 1, make a backup of your cacerts and supply the cacerts file instead of mykeystore.jks. See the link below for the location of cacerts. For this option, you are all set, your application should be connecting to the Web service via HTTPS without any additional configuration, since Java loads cacerts by default.
If you are doing option 2, which is probably preferred at least for testing phase, you need to run your application with this parameter:
-Djavax.net.ssl.trustStore=mykeystore.jks
This is a JVM parameter, so supply it appropriately. This depends on how you are running you application.
Note that you will only have the imported certificate in this case, so your other HTTPS connections will not work. You can avoid this by first copying the standard cacerts to a temporary location, importing the key into it and using that in the command above. That will give you all the standard certificates, plus the one you need.
A slight downside of option 2 is that if new certificates are added or revoked, your application-specific keystore will not be updated. If this is a concern, you can merge keystores on the fly, for example:
Registering multiple keystores in JVM
In either case, you should now be able to just do a standard URL fetch, such as in example given here:
https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https
i.e.:
final URL url = new URL("https://example.com");
try(final InputStream in = url.openStream()){
//…
}
More information here:
Keytool and general certificates info
https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html
Cacerts location
http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/keytool.html
More options for importing self-signed certificates
How to properly import a selfsigned certificate into Java keystore that is available to all Java applications by default?
Unless they provided you with their .crt file which should be used for validating connections to them, which would indicate they are using a self-signed certificate, they haven't provided you with anything useful. If you need a certificate as a client, the first thing you need is a public/private key pair, from which you generate a CSR, which you get signed. Nobody else can securely provide any of that except the signed certificate.
If they've provided their own (self-signed?) certificate, you need to load it into a truststore, not a keystore, via the keytool with the -trustcacerts option, and then tell Java to use that truststore, either via the javax.net.ssl.trustStore system property or by constructing your own TrustManager and feeding it to a custom SSLContext, as described in the JSSE Reference Guide.
I am creating a Java program to get information from a server but I have to perform a ssl handshake with the server from the Java program.
I have myfilercert.cer file certificate for authentication purpose but I have no idea how I can load that certificate in java so that the java program can perform 'handshake' with the server where I want to get information from. Where to begin?
What you need is the java keystore. The keystore is a repository of security certificates used in SSL encryption.
You can read here about the Server Authentication During SSL Handshake. This is a keystore tutorial.
As an alternative to keytool, i would suggest a tool with a Graphical User Interface called Portecle. You can use it to browse the contents of your .cer file and see what's in it.
It can be useful to know about the various certificate encodings. Also read about the X.509 standard.
This is an article on java keytool essentials (which is the oracle tool that works with the java keystore).
You can google and find a lot of resources that instruct you how to generate. I think you will want to keep the certificate at the application level.
Some SO questions that helped me along the way:
Trust Store vs Key Store - creating with keytool - important to know the difference between the trust manager and keymanager
Java HTTPS client certificate authentication
How to export private key from a keystore of self-signed certificate
What is difference between cacerts and keystore
How to connect to a secure website using SSL in Java with a pkcs12 file?
Received fatal alert: handshake_failure through SSLHandshakeException
How to configure trustStore for javax.net.ssl.trustStore on windows?
Good luck!
You can use Apache HttpClient (or just use the required classes from it to use SslContextBuilder, really), and then it'd be like so:
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
sslContextBuilder.loadTrustMaterial(new File("yourTrustStore.jks"), "thePassWord");
SSLContext sslContext = sslContextBuilder.build();
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) (new URL("https://thesite.com").openConnection());
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
But you need to create a keystore for your certificate, which can be done with keytool. If you need this for android, you'll need SpongyCastle library, and use that as a provider for KeyTool to create a BKS keystore instead of a JKS keystore; and you will need to explicitly open the KeyStore in Java.
KeyStore keyStore = KeyStore.getInstance("BKS",
BouncyCastleProvider.PROVIDER_NAME);
byteArrayInputStream = new ByteArrayInputStream(keyStoreBytes);
keyStore.load(byteArrayInputStream, keyStorePassword);
Certificate[] certificates = keyStore.getCertificateChain("theCertAlias");
Certificate certificate = certificates[0];
I did the following things:
+ generate keystore.jks with keytool
+ exported keystore.cer file with keytool
+ imported keystore.cer file into truststore.jks
+ copied keystore.jks and keystore.cer to the client
Then I call my server with
-Djavax.net.ssl.trustStore=truststore.jks -Djavax.net.ssl.trustStorePassword=*
and my client with
-Djavax.net.ssl.keyStore=forclient.jks -Djavax.net.ssl.keyStorePassword=*
The server exposes its interface with the super() call of UnicastRemoteObject
super(PORT,
new SslRMIClientSocketFactory(),
new SslRMIServerSocketFactory(null, null, true));
The Registry stuff does not use any SSL. Why is that not working out?
It DOES work out if I add the keystore VM arguments in the server run config and the trustore VM arguments in the clien. But I really want to know why?
Please understand the aim of keystore and truststore first. Look at the POST . It says
A keystore contains private keys, and the certificates with their corresponding public keys.
A truststore contains certificates from other parties that you expect to communicate with, or from Certificate Authorities that you trust to identify other parties.
So the client SHOULD have truststore so that it trusts the server its interacting with uses server's public key to encrypt the data. Server SHOULD have keystore which stores the private keys which is used to decrypt the data encrypted by corresponding private key by client.
I hope now you got why your application works when you switch keystore and trustore in client-server.