The certificates used in this section created by openssl
were discussed in the section
TL: General
.
They are
Java has long had its own formats for certificates known as Java Key Stores (JKS). Since Java 9 this has using PKCS #12. The Java terminology is
If you are working in Java only and using keytool
alone, the steps are
keytool -genkeypair -keystore mykeystore -alias "..." -keypass "..." -keyalg RSA -keysize 2048
For example,
keytool -genkeypair -keystore mykeystore -alias "localhost" -keypass "abcdefg" -keyalg RSA -keysize 2048
keytool -export -alias "localhost" -keystore mykeystore -file mycertfile.cer
keytool -import -alias "localhost" -keystore mytruststore -file mycertfile.cer
-Djavax.net.debug=ssl
We have created a set of certificates using openssl
already,
which are used by all the languages we discuss.
So we need to convert them into the format required by Java.
To create a Java keystore from PEM certificates, follow the process in
Import private key and certificate into java keystore
, you need to first convert the PEM files to PKCS #12
using openssl
and then import into a keystore using keytool
.
A server wanting to use a keystore with our own CA will need the keystore built from the certificate signed by the CA:
openssl pkcs12 -export -in ../certs/CA-signed-cert.pem \
-inkey ../certs/key.pem -CAfile ../certs/CA-cert.pem \
-name "localhost" -out CA-signed-cert.p12
keytool -importkeystore -deststorepass abcdefg \
-destkeystore CA-signed-cert-keystore.jks \
-srckeystore CA-signed-cert.p12 -srcstoretype PKCS12
Give the keystore a password such as "abcdefg" and trust the resultant certificate.
To build a truststore, we just need the certificate, not the private key. The process is described at ADD A CERTIFICATE TO A TRUSTSTORE USING KEYTOOL .
A client trusting our CA needs a truststore made from the CA's certificate. This becomes here
keytool -import -alias localhost -file ../certs/CA-cert.pem \
-storetype JKS -keystore CA.truststore.jks
HTTP servers offering HTTPS are giving HTTP over TLS. Most will have valid TLS certificates, properly signed by a recognised CA. The client just needs to validate the certificate and can then talk.
Validation is done by the Java SSLSocket
,
without any extra work by the client. The client, talking to an
HTTPS server, will use the HTTP protocol. The simplest
messages is to send an HTTP Head
request,
which returns information about the server.
(HHTP will be discussed in more detail later.)
A simple client talking to
www.verisign.com
is
TLSGetHead.java:
/**
* From https://docs.oracle.com/javase/10/security/sample-code-illustrating-secure-socket-connection-client-and-server.htm
* Copyright © 1993, 2018, Oracle and/or its affiliates. All rights reserved.
*/
import java.net.*;
import java.io.*;
import javax.net.ssl.*;
/*
* This example demostrates how to use a SSLSocket as client to
* send a HTTP request and get response from an HTTPS server.
* It assumes that the client is not behind a firewall
*/
public class TLSGetHead {
public static void main(String[] args) throws Exception {
try {
SSLSocketFactory factory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket =
(SSLSocket)factory.createSocket("www.verisign.com", 443);
/*
* send http request
*
* Before any application data is sent or received, the
* SSL socket will do SSL handshaking first to set up
* the security attributes.
*
* SSL handshaking can be initiated by either flushing data
* down the pipe, or by starting the handshaking by hand.
*
* Handshaking is started manually in this example because
* PrintWriter catches all IOExceptions (including
* SSLExceptions), sets an internal error flag, and then
* returns without rethrowing the exception.
*
* Unfortunately, this means any error messages are lost,
* which caused lots of confusion for others using this
* code. The only way to tell there was an error is to call
* PrintWriter.checkError().
*/
socket.startHandshake();
PrintWriter out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())));
out.print("HEAD / HTTP/1.0\r\n\r\n");
out.flush();
/*
* Make sure there were no surprises
*/
if (out.checkError())
System.out.println(
"SSLSocketClient: java.io.PrintWriter error");
/* read response */
BufferedReader in = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
out.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
When compiled and run by
javac TLSGetHead.java
java TLSGetHead
it produces something like
HTTP/1.1 200 OK
Date: Mon, 15 Jun 2020 10:59:59 GMT
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Transfer-Encoding: chunked
Set-Cookie: locale-name=en_US; Expires=Sun, 13-Sep-2020 10:59:59 GMT; Path=/
Set-Cookie: locale-name=en_US; Expires=Sun, 13-Sep-2020 10:59:59 GMT; Path=/
Set-Cookie: JSESSIONID=BF08615501E1518E02727B6B1DABD54E.brn2; Path=/; Secure; HttpOnly
Strict-Transport-Security: max-age=15768000;
Connection: close
The client uses an SSLSocketFactory
to create
an SSLSocket
.
From then on, the code is essentially the same as the earlier
TCP clients.
The code is TLSEchoClient.java:
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
public class TLSEchoClient{
public static final int MYECHOPORT = 1200;
public static void main(String[] args){
if (args.length != 1) {
System.err.println("Usage: Client address");
System.exit(1);
}
InetAddress address = null;
try {
address = InetAddress.getByName(args[0]);
} catch(UnknownHostException e) {
e.printStackTrace();
System.exit(2);
}
Socket sock = null;
try {
sock = new Socket(address, MYECHOPORT);
} catch(IOException e) {
e.printStackTrace();
System.exit(3);
}
SSLSocketFactory factory =
(SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslSocket = null;
try {
sslSocket =
(SSLSocket) factory.createSocket(sock, args[0], MYECHOPORT, true);
} catch(IOException e) {
e.printStackTrace();
System.exit(3);
}
BufferedReader reader = null;
PrintStream out = null;
try {
reader = new BufferedReader(new InputStreamReader(
sslSocket.getInputStream()));
out = new PrintStream(sslSocket.getOutputStream());
} catch(IOException e) {
e.printStackTrace();
System.exit(6);
}
BufferedReader consoleReader =
new BufferedReader(new InputStreamReader(System.in));
String line = null;
while (true) {
line = null;
try {
System.out.print("Enter line:");
line = consoleReader.readLine();
System.out.println("Read '" + line + "'");
} catch(IOException e) {
e.printStackTrace();
System.exit(6);
}
if (line.equals("BYE"))
break;
try {
// Just send a goodbye message, for testing
out.println(line);
line = reader.readLine();
} catch(IOException e) {
e.printStackTrace();
System.exit(6);
}
System.out.println(line);
}
System.exit(0);
}
} // Client
The difference comes at runtime, where the client has to be told where to pick up the trust keys. Run the client using the truststore
java -Djavax.net.ssl.trustStore=CA.truststore \
-Djavax.net.ssl.trustStorePassword="abcdefg" \
TLSEchoClient localhost
The server similarly needs to create an
SSLSocket
, and then continues like the other TCP servers.
The code is TLSEchoServer.java:
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
public class TLSEchoServer {
public static int MYECHOPORT = 1200;
public static void main(String argv[]) {
try {
SSLServerSocketFactory factory =
(SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket sslSocket =
(SSLServerSocket) factory.createServerSocket(MYECHOPORT);
while (true) {
Socket incoming = sslSocket.accept();
new SocketHandler(incoming).start();
}
} catch(IOException e) {
e.printStackTrace();
System.exit(30);
}
}
}
class SocketHandler extends Thread {
Socket incoming;
SocketHandler(Socket incoming) {
this.incoming = incoming;
}
public void run() {
byte[] data = new byte[1024];
InputStream in;
OutputStream out;
try {
in = incoming.getInputStream();
out = incoming.getOutputStream();
} catch(IOException e) {
System.err.println(e.toString());
return;
}
int numRead = 0;
try {
while (true) {
try {
numRead = in.read(data);
} catch(IOException e) {
System.err.println(e.toString());
return;
}
if (numRead == -1) {
System.out.println("Disconnecting");
return;
}
try {
out.write(data, 0, numRead);
} catch(IOException e) {
System.err.println(e.toString());
return;
}
}
} finally {
System.out.println("Closing all");
try {
in.close();
out.close();
incoming.close();
} catch(IOException e) {
// nothing to do really
}
}
}
}
The server needs to pick up a certificate from the keystore. Run the server using the keystore
java -Djavax.net.ssl.keyStore=CA-signed-cert-keystore.jks \
-Djavax.net.ssl.keyStorePassword="abcdefg" \
TLSEchoServer
For more complex cases see Sample Code Illustrating a Secure Socket Connection Between a Client and a Server
Copyright © Jan Newmarch, jan@newmarch.name
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia"
by
Jan Newmarch
is licensed under a
Creative Commons Attribution-ShareAlike 4.0 International License
.
Based on a work at
https://jan.newmarch.name/NetworkProgramming/
.