How make SSL server socket support both http & https in java? - java

I'm trying to create simple web server using java sockets which should support both http & https. But i can acheive only one at a time. I need to logic which supports both http # port 80 & https # port 443 at same time.
This is the sample code for HTTPS Server using sslsocket. We can acheive HTTP Server using simple ServerSocket.
public class HttpsServer {
public static void main(String[] args) {
try {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream("/opt/p12file.p12"), "p12pass".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, "p12pass".toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, null);
SSLServerSocketFactory ssf = sc.getServerSocketFactory();
SSLServerSocket s = (SSLServerSocket) ssf.createServerSocket(8080);
while (true) {
SSLSocket c = (SSLSocket) s.accept();
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(c.getOutputStream()));
w.write("HTTP/1.0 200 OK");
w.newLine();
w.write("Content-Type: text/html");
w.newLine();
w.newLine();
w.write("<html><body><h1>Https Server Works</h1></body></html>");
w.newLine();
w.flush();
w.close();
c.close();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Can anyone help me please??

How make SSL server socket support both http & https in java?
You can't. HTTP is plaintext, which SSLServerSocket cannot support.
I'm trying to create simple web server using java sockets which should support both http & https. But I can achieve only one at a time. I need to logic which supports both http # port 80 & https # port 443 at same time.
You need:
a plaintext ServerSocket listening at 80
an SSLServerSocket listening at 443
an accept-loop thread for each of these
a connection thread per accepted socket.
You will never ever get it done inside a static main() method. I suggest you read the 'Custom Networking' section of the Java Tutorial, and then the JSSE Reference Guide.
You also of course need to take a really good look at RFC 2616 HTTP 1.1. It is extremely non-trivial to implement correctly.
As suggested in comments, you should really use something off-the-shelf.

You have two options:
Use two different ports, one for http and one for https.
SSL Hello detection / Port unification:
In HTTP and HTTPS the client is expected to talk first. So the server can use this to detect the protocol the client is expecting:
if the client sends a TLS ClientHello, then proceed with a TLS handshake;
if a plain HTTP request is sent instead, then handle the request as it is.
More information:
Can a Java server accept both SSL and plaintext connections on one port?
Is it possible to change plain socket to SSLSocket?

Related

SSLSocket doesn't perform hostname verification

I use SSLSocket for my android app
and according to this post SSLSocket doesn't perform host name verification
here
and I didn't see any exception while using IP instead of domain and everything works fine
so can I use IP or any other domain for connection?
I have got the cert from letsencrypt for one domain and I wonder that I can use it for any domain or IP with SSLSocket!
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, nulls, null);
SocketFactory sslsocketfactory = sc.getSocketFactory();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(host, 443);
sslsocket.setSoTimeout(5000);
sslsocket.startHandshake();
The answer is on your link:
Your app needs to do its own hostname verification, preferably by calling
getDefaultHostnameVerifier() with the expected hostname. Further,
beware that HostnameVerifier.verify() doesn't throw an exception on
error but instead returns a boolean result that you must explicitly
check.
"I wonder that I can use it for any domain or IP with SSLSocket!"
No, you will not be able to do that, the cert of the connection must be in your client's truststore, and we can expect that the different domains use different certificates :)
Please see this How should I do hostname validation when using JSSE? link.

How to read SNI extension in Java?

I want to find host name from TLS Client Hello Message. I want to find host name before java does complete handshake for transparent ssl proxy.
Is there any way to find SNI extension value without writing whole ssl handshake logic ?
Is Java supports ssl handshake with initial memory buffer ?
My Idea is:
Read TLS client hello message, parse it and find SNI value
Call sslSocket.startHandshake(initialBuffer) Initial buffer will contain TLS client hello packet data. So Java can do handshake.
Second Idea is to use SSLEngine class. But it seems a lot more implementation than requirement. I assume SSLEngine is used most of async in case which I don't require it.
Third idea is to implement complete TLS protocol.
Which idea is better ?
Both SSLSocket and SSLEngine lack (quite inexplicable) proper SNI support for server connections.
I came across the same problem myself and ended up writing a library: TLS Channel. It does not only that, it is actually a complete abstraction for SSLEngine, exposed as a ByteChannel. Regarding SNI, the library does the parsing of the first bytes before creating the SSLEngine. The user can then supply a function to the server channel, to select SSLContexts depending on the received domain name.
The accepted answer to this question is a bit old and does not really provide an answer to the question.
You should use a custom KeyManager when initializing the SSLContext and the trick is to use a javax.net.ssl.X509ExtendedKeyManager (note the Extended term). This will offer the possibility to expose the SSLEngine during the key alias selection process instead of the plain (useless) socket.
The method chooseEngineServerAlias(...) will be called during the SSL handshake process in order to select the proper alias (cert/key) from the KeyStore. And the SNI information is already populated and available in the SSLEngine. The trick here is to cast the SSLSession as an ExtendedSSLSession (note the Extended term) and then to getRequestedServerNames().
KeyManager[] km = new KeyManager[]
{
new X509ExtendedKeyManager()
{
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
{
if( engine.getHandshakeSession() instanceof ExtendedSSLSession )
{
List<SNIServerName> sni = ((ExtendedSSLSession)engine.getHandshakeSession()).getRequestedServerNames();
// select the proper certificate alias based on SNI here
}
}
}
}
SSLContext context = SSLContext.getInstance("TLS");
context.init(km, null, null);

Inconsistent SSLv3 handshake failure on Android

I'm working on an application that processes food orders and we send the requests via HttpsURLConnection to a php function that has been ssl certified. The problem I'm having is that it rejects the handshake sometimes and not others. I was wondering if anyone could explain to me why it would reject it one time and not another.
javax.net.ssl.SSLProtocolException: SSL handshake aborted:
ssl=0x56cbe008: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744
0x52eb6d74:0x00000000)
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted:
ssl=0x56cbe008: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744
0x52eb6d74:0x00000000)
url = new URL(request.endpointUri);
Log.d(LOG_TAG, "Opening connection to " + request.endpointUri);
conn = (HttpsURLConnection)url.openConnection();
//setup the connection
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("charset", "UTF-8");
conn.setDoInput(true);
conn.setDoOutput(true);
//setup the parameters
Uri.Builder params = new Uri.Builder();
String paramString;
params.appendQueryParameter("cctoken", request.token.getId());
params.appendQueryParameter("amt", Integer.toString(request.order.amount));
params.appendQueryParameter("email", request.order.customerEmail);
params.appendQueryParameter("order", request.order.details);
paramString = params.build().getEncodedQuery();
conn.setFixedLengthStreamingMode(paramString.getBytes("UTF-8").length);
Log.d(LOG_TAG, "Compiled query into: " + paramString);
//write the POST request params
OutputStream os = conn.getOutputStream();
BufferedWriter streamWriter = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
streamWriter.write(paramString);
streamWriter.flush();
streamWriter.close();
os.close();
//read the response
int responseCode = conn.getResponseCode();
InputStream is;
The line given for failure is when it attempts to collect the output.
OutputStream os = conn.getOutputStream();
SSL handshake errors which only happen sometimes are often related to server side problems, so your code is not of much help here. Possible causes at the server side are multiple servers with different configuration (some work, some don't), timeouts which might be caused by too much load, server side crashes. There might also be some erratic middleware involved (firewalls) or if the connection is unreliable from start it will also affect the SSL handshake.
Thus don't look too much at your code but look at the server and the network. If in doubt try another client and if this one shows a more stable behavior look into the differences in the connection and SSL handshake (i.e. packet captures).
This problem could be due to self signed certificate of server to which you are sending request. You have to do something like this:
`// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load- der.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.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);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
//Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);`
Found it here
Related Issue: Client and server don't support a common encryption protocol
If the error message says something like:
error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0x8d92d990:0x00000000)
(Note that is says 'tlsv1' after 'GET_SERVER_HELLO:' rather than 'sslv3') This is a faint clue that the problem could be with the version of encryption needed. If the client is old, it may only support sslv2 or sslv3. An up to date server may support TLS1.2 and does not support the older (perhaps depracated ssl versions. The opposite may be true as well, with the server supporting only the old and the client only the new.
I ran into this problem with an old Android Jelly Bean client that doesn't support TLS1.2, by default, for HttpsUrlConnection. By creating a TLSSocketFactory and X509TrustManager, and calling setSSLSocketFactory() I could get the old version of Android to use TLS1.2, which made the server happy. Navneet Krishna wrote a good description of how to do this in: https://medium.com/#krisnavneet/how-to-solve-sslhandshakeexception-in-android-ssl23-get-server-hello-tlsv1-alert-protocol-13b457c724ef

How to configure Jersey (javax.ws.rs.*) to reuse ssl sessions

I have written a test client that sends requests over TLS using the Jersey library. I am having a hard time trying to figure out how to configure/code the Jersey client such that it reuses the SSL sessions so that I could make my tests faster. The Jersey client by default uses HTTP keepAlive; meaning it keeps the TCP connections open and reuses them, but it doesn't seem to do the same with SSL sessions.
If anyone had any experience with this, please let me know.
Here is the code snippet with which I am setting up the Jersey Client and also the code with which I am sending a request:
SSLContext sslContext = sslConfig.createSSLContext().getInstance(tlsVersion);
sslContext.init(keyManagerFactory.getKeyManagers(), trustAllCerts, new SecureRandom());
SSLEngine sslEngine = sslContext.createSSLEngine("qa.p.uvvu.com", 7001);
Client client = ClientBuilder.newBuilder().sslContext(sslContext)
.hostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}).build();
response = client.target(uri).request()
.headers(multivaluedMap).post(Entity.entity(object, MediaType.APPLICATION_XML_TYPE));
I use the Apache connector for this:
HttpClientConnectionManager connManager = PoolingHttpClientConnectionManager();
ClientConfig clientConfig = new ClientConfig();
clientConfig.connectorProvider(new ApacheConnectorProvider());
clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connManager);
Client client = ClientBuilder.newClient(clientConfig);
You can configure the HttpClientConnectionManager to your needs depending on how many connections you want to keep in the pool, and for how long you want to keep them in the pool.
Disclaimer: I don't have the code with me, so the code above might not work 100%.

SSLSocket: why do we need to do the handshake before accessing the server certificate?

I'm connecting through a SSLSocket to a distant host which is using a certificate for the handshake. As we don't use the default JVM truststore with all the certificate authorities, i need to add the remote host certificates to my truststore.
How can i get the certificates that i should trust from the SSLSocket? It seems do retrieve them i need to use the SSLSession which seems to require the handshake.
Why do we need to perform the handshake to be able to retrieve the certificates?
Is there any tool that permits to extract the remote host certificates used?
Actually certificates are presented during the handshake, so that the server can identify itself, and the eventually the same for the client.
When you do:
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
[...]
SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
try {
socket.startHandshake();
socket.close();
} catch (SSLException e) {
e.printStackTrace(System.out);
}
if you don't get an exception on startHandshake(), it means the certificate is already trusted for some reason (directly present in keystore, of signed by a trusted entity).
Exception occurred or not, you can access the downloaded chain:
X509Certificate[] chain = tm.chain;
if (chain == null) {
// error in downloading certificate chain
return;
}
// loop through chain
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
[....]
}
With the X509Certificate object instance, you can actually update your k-ieth keystore:
X509Certificate cert = chain[k];
String alias = host + "-" + (k + 1);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
[...]
ks.setCertificateEntry(alias, cert);
OutputStream out = new FileOutputStream("jssecacerts");
ks.store(out, passphrase);
out.close();
Look here for the complete sample.
Alternatively, another maybe safer way to download the certificate for a server you trust, is using openssl command:
# openssl s_client -showcerts -connect $SERVER:$PORT 2>&1 | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >/tmp/$SERVERNAME.cert
then import it as usual with keytool.
Normally, you shouldn't get the certificate you should trust from an SSLSocket, instead, it should be a configured setting that you obtained independently, as a reference for what you want to trust.
What you seem to want to do is to get the certificate for the first connection, hoping that that connection wasn't intercepted, and then use this as a reference for subsequent connections (similar to what's commonly done with SSH, when you don't necessarily know the server key's fingerprint on the first connection, but check that you get the same later).
Security-wise, this isn't ideal because the initial connection may be intercepted by a MITM attacker (which would make all subsequent connections vulnerable), but that's certainly a way to mitigate the risks. Ideally, you should compare that certificate with a known reference you've obtained some other way.
You can access the remote certificate during the handshake using a custom X509TrustManager (or you can disable trust verification with it and get the certificate later), which you can then use to initialise an SSLContext, from which you can obtain your SSLSocketFactory. It's generally a bad idea to disable trust verification in a trust manager (since it opens the connection to MITM attacks), but it can be acceptable for this purpose. You may be interested in the InstallCert utility, which should do more or less what you're after.
Why do we need to do the handshake before accessing the server
certificate?
This is done during the handshake, because the purpose of the SSL/TLS socket API is to provide the application layer with a socket it can consider secure and use more or less as a normal socket at that stage. Typically, for most uses of JSSE (or generally other SSL/TLS stacks), as an application developer using that stack, you don't want to have to do the verification explicitly. Checking the certificate during the handshake is also recommended as part of the TLS specification:
Upon receipt of the server hello done message, the client SHOULD
verify that the server provided a valid certificate, if required
and check that the server hello parameters are acceptable.

Categories

Resources