In Java Network Programming 4th Edition, Chapter 10 about Secure Socket, there is an example of build Secure Server. The code can be find here.
I'm trying to make more simple version of the code. Here is my code:
try {
SSLContext context = SSLContext.getInstance(algorithm);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
//char[] password = System.console().readPassword("Password: "); // open the .class with command line
char[] password = {'t', 'h', 'i', 's', 'i', 's', 't', 'i', 'a', 'n'};
ks.load(new FileInputStream("src/jnp4e.keys"), password);
kmf.init(ks, password);
context.init(kmf.getKeyManagers(), null, null); // null = accept the default
// wipe the password
Arrays.fill(password, '0');
SSLServerSocketFactory factory
= context.getServerSocketFactory();
SSLServerSocket server
= (SSLServerSocket) factory.createServerSocket(PORT);
// add anonymous (non-authenticated) cipher suites
String[] supported = server.getSupportedCipherSuites();
String[] anonCipherSuitesSupported = new String[supported.length];
int numAnonCipherSuitesSupported = 0;
for (int i = 0; i < supported.length; i++) {
if (supported[i].indexOf("_anon_") > 0) {
anonCipherSuitesSupported[numAnonCipherSuitesSupported++]
= supported[i];
}
}
String[] oldEnabled = server.getEnabledCipherSuites();
String[] newEnabled = new String[oldEnabled.length
+ numAnonCipherSuitesSupported];
System.arraycopy(oldEnabled, 0, newEnabled, 0, oldEnabled.length);
System.arraycopy(anonCipherSuitesSupported, 0, newEnabled,
oldEnabled.length, numAnonCipherSuitesSupported);
server.setEnabledCipherSuites(newEnabled);
System.out.println("OK..");
// Now all the set up is complete and we can focus
// on the actual communication.
while (true) {
// This socket will be secure,
// but there's no indication of that in the code!
try (Socket theConnection = server.accept()) {
InputStream in = theConnection.getInputStream();
Reader r = new InputStreamReader(in, "UTF-8");
int c;
while ((c = r.read()) != -1) {
System.out.write(c);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
} catch (IOException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex) {
ex.printStackTrace();
}
The only diffence is in my code I create a Reader
so the server can read characters.
I tried this server with simple client that send text. Here is the Client:
int port = 7000; // default https port
String host = "localhost";
SSLSocketFactory factory
= (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket socket = null;
try {
socket = (SSLSocket) factory.createSocket(host, port);
// enable all the suites
String[] supported = socket.getSupportedCipherSuites();
socket.setEnabledCipherSuites(supported);
Writer out = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
out.write("Hello");
}catch(Exception e){
}
I run the Server first and then the Cient. But when the Server accept input from Client it throws this exception:
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:189)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
...
UPDATE CLIENT
Based on dave answer, I add 2 lines of code flush()
and close()
...
out.write("Hello");
out.flush();
socket.close();
...
But another exception arrive:
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
OutputStreamWriter on a socket stream apparently buffers and your client didn't
.flush()
or.close()
so your data isn't actually sent.If your Java program (or more exactly JVM) exits without doing
.close()
on a socket stream (including closing a Writer which passes through to the Stream) handling depends on the platform; on Windows it sends a RST which causes the "Connection reset" exception you see at the peer. Unix closes the connection normally at the TCP level, which is actually not fully normal for SSL/TLS, but "close enough" (as it were) that Java treats it as EOF.Edit for followon question:
Server getting SSLHandshakeException "received alert
bad_certificatecertificate_unknown" which theoretically could mean a few things but almost always means that the certificate the server is using (from the keystore you loaded, along with matching privatekey) is not signed by a CA (Certificate Authority) trusted by the client.The code you show for the client doesn't do anything to set or alter its truststore; if there isn't code elsewhere that does so, or external settings like the
java
commandline option-Dx=y
to set system properties, the client will use the JSSE default truststore, which is the fileJRE/lib/security/jssecacerts
if it exists otherwise the fileJRE/lib/security/cacerts
(where JRE is the directory where your JRE is installed; if you use a JDK, the JRE is a subdirectory of the JDK directory). If you (and anyone else on your system) haven't modified these files since the JRE was installed,jssecacerts
doesn't exist andcacerts
contains a set of "well-known root" CAs determined by Oracle, like Verisign and Equifax etc.Thus, you need to either:
use a certificate issued by a well-known CA; if you don't already have such a cert you have to obtain it from the CA by proving (at least) your control of the domain name(s) certified and depending on the CA possibly paying a fee; if you do have or get such a cert, install it in your keystore, in the privatekey entry, with any chain certs (for well-known CAs there almost always is at least one chain cert).
use a certificate issued by any other CA, including an adhoc CA you make up, and including as the limit case a selfsigned certificate which is implicitly its own CA, such as the one
keytool -genkeypair
generates automatically; and put the CA certificate for that CA (or that selfsigned cert) into the truststore used by the client. For that there are two ways:put the server's CA cert (or selfsigned cert) in the default truststore file of the JRE used by the client. This does affect any other programs sharing that default truststore, which is potentially all other programs using that JRE. If you use
jssecacerts
it only affects JSSE, if you usecacerts
it also affects the verification of signed code (if any), plus it gets wiped out if you upgrade your JRE in place, as usually is automatic on Windows.create (or reuse) another truststore, put the server's CA cert in there, and have the client use that nondefault truststore. There are several options for that: set the system properties for the default truststore externally, set them explicitly in your program (before first use of JSSE!), explicitly load a "keystore" file (actually containing the cert(s)) and use its trustmanager in a nondefault SSLSocketFactory much like your server code does for keymanager, or even write your own trustmanager with whatever store(s?) you like and use that similarly.
Edit#2 Simple Example
Covering all these options in detail would be much too long, but one simple option is as follows.
keytool -genkeypair -keystore ksfile -keyalg RSA
For the prompt "first and last name" (which is actually the CommonName attribute in the cert) enter the name of the server, in particular the name the client(s) will use to connect to the server; in the question this is "localhost". The other name fields don't matter; fill or omit them as you like, except that Country if used must be 2 letters as the prompt says. Instead of answering the prompts, you can add on the command line
-dname "CN=common_name_value"
. If you have more than one name for the server(s) there are some options omitted here. For some other applications you may need to specify the entry name with-alias name
; for this question it isn't needed.keytool -exportcert -rfc -keystore ksfile [-alias name] -file certfile
In this example the client is on the same machine as the server. In another case it would be necessary to copy the contents of this file from the server to the client; this is often done most conveniently by copying the file.
keytool -importcert -keystore JRE/lib/security/cacerts -file certfile
where JRE is the directory where your JRE (Java Runtime Environment) is installed. This depends on your OS and how you installed your JRE (or JDK, which includes a JRE) such as with a package manager or not. If you have more than one JRE and/or JDK installed, it depends which one you are using.
On Unix if you invoke
java
without specifying a path,which java
(or in bash and perhaps other shells,type java
) will tell you the full pathname that is run. Note however this is often a symbolic link to the real location, which should be in the form/somewhere/java[version]/bin/java
; change thebin/java
part tolib/security/cacerts
.On Windows, if you install a normal "system-wide" Java, the program you run is
%windir%\system32\java.exe
wherewindir
is usuallyc:\windows
but the actual code and files for JRE are inc:\program files\java\jreN
orc:\program files (x86)\java\jreN
depending on your architecture (32-bit or 64-bit) and jreN is currentlyjre7
orjre8
as applicable, but likely to expand in the future. Or run the Java Control Panel; in the Java tab the View button shows location(s) of the installed JRE(s).