11

There is a plain socket server listening on port 12345;

ServerSocket s = new ServerSocket(12345);

What I want to know is that it is possible that:

  1. If the client send a http request, the server handle the request directly,
  2. If the client send a https request, the server change client socket to SSLSocket?

Thanks

Jared Farrish
  • 48,585
  • 17
  • 95
  • 104
sodarfish
  • 135
  • 1
  • 1
  • 6

4 Answers4

22

Is it possible to change plain socket to SSLSocket?

Yes, it is. On the server side, the following works:

ServerSocketFactory ssf = ServerSocketFactory.getDefault();
ServerSocket serverSocket = ssf.createServerSocket(12345);

// I've initialised an sslContext with a keystore, as you normally would.
Socket socket = serverSocket.accept();
SSLSocketFactory sslSf = sslContext.getSocketFactory();
// The host name doesn't really matter, since we're turning it into a server socket
// (No need to match the host name to the certificate on this side).
SSLSocket sslSocket = (SSLSocket) sslSf.createSocket(socket, null,
    socket.getPort(), false);
sslSocket.setUseClientMode(false);

// Use the sslSocket InputStream/OutputStream as usual.

SSLSocketFactory.createSocket(Socket, ...) will by default convert the existing Socket into a client-mode SSLSocket. Since the handshake only starts when you start reading/writing with the I/O streams, it's still time to change the mode using setUseClientMode(false).

Regarding the rest of the question:

What I want to know is that it is possible that:

  • If the client send a http request, the server handle the request directly,
  • If the client send a https request, the server change client socket to SSLSocket?

Again, yes, it's possible. It's sometimes referred to as "port unification" and it's implemented in Grizzly and thus Glassfish.

It works because both HTTP and TLS (upon which HTTPS works) are protocols where the client is expected to talk first. Therefore, the server can detect whether what the client initially sends is a TLS ClientHello message (in which case it should try to proceed with the TLS handshake) or a plain HTTP request (e.g. GET / HTTP/1.1...).

I suspect port unification is "easier" to do using SSLEngine, otherwise, it might be hard to implement a read-ahead on a plain socket, which you would still be able to convert via SSLSocketFactory.createSocket(Socket, ...).

Note that this is still rather unusual, though.

Bruno
  • 119,590
  • 31
  • 270
  • 376
  • Thanks, it actually helped me to solve slightly different task as implementing STARTTLS, although I am still uncertain which certificate to use. – Singagirl Aug 09 '16 at 03:38
3

It is not possible to serve both http and https on the same port. However, it should in theory be possible to upgrade an existing http connection to use TLS if both the client and server supports it, see RFC 2817. The SSLSocketFactory class has a createSocket() function which can be used to upgrade an existing socket to SSL/TLS.

Disclaimer: I have not attempted to do this, and implementing RFC 2817 is likely non-trivial.

Edit: Apparently I was wrong, like Bruno wrote it is possible to serve http and https on the same port using a technology called port unification, which uses the first few bytes received to detect the protocol. There is an example of this included with the Netty (JBoss) framework.

Soulman
  • 2,910
  • 24
  • 21
  • I have used createSocket to upgrade a plain socket to an SSLSocket and it is relatively straightforward. But that is at the socket level. I suspect you're correct about RFC 2817. – President James K. Polk Jul 03 '11 at 00:12
  • 1
    @Soulman: it is possible to serve both HTTP and TLS (HTTPS) on the same port, via *port unification*. (I don't know of any widely used product that implements RFC 2817). – Bruno Jul 03 '11 at 16:33
1

The Socket/SSLSocket interface does not allow convenient content-recognition here - when you start reading from a socket and see that it is only garbage (not plain HTTP), you can't supply this data to a new SSLSocket wrapped around a normal one.

You could use a Socket (or SocketChannel) and look at the data, and then (if it is not the start of a plain HTTP request) pass the same data to an SSLEngine object for decryption/encryption. This means that you have to handle all the encryption/decryption calls yourself, which is not totally trivial (at least, it is a lot more complicated than simply using an SSLSocket with it's two streams - I did it once).

Of course, if you do this, you would likely better implement the RFC 2817 interface, instead of trying automated content sniffing.

Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
  • thanks for your reply! SSLEngine was introduced in JDK1.5, is there any way to do the same thing without SSLEngine ? – sodarfish Jul 03 '11 at 04:44
  • Take the TLS RFCs and implement the protocol yourself (using the JSSE for the cryptographic primitives). (Not recommended, though, since there are many ways to make silly mistakes here.) – Paŭlo Ebermann Jul 03 '11 at 12:03
  • @sodarfish: I understand you'd want to look for other solutions than `SSLEngine` (mainly because it's notoriously hard to use), but are you implying your still using a pre-Java5 JDK? @Paŭlo Ebermann, the problem with implementing RFC 2817 is that virtually no client support it as far as I know, whereas looking at the data would work in principle with any HTTP/HTTPS client (although at least one URL would have to specify the port explicitly). – Bruno Jul 03 '11 at 16:51
  • @sodarfish: I really hope you're not using pre-1.5 JDK; that'd be _really_ old now. In fact, I wouldn't really recommend developing new code against 1.5 either, not now that 1.6 has been out for years on all major platforms. (A pre-1.5 Java requirement would indicate that the tagging on the question ought to be revised too.) – Donal Fellows Jul 03 '11 at 17:38
0

I think it's no on both counts, based on what you've posted.

  1. If the listener on the ServerSocket is not interpreting the request as HTTP, then there's no magic that will make it so.
  2. If the listener on the ServerSocket is not decrypting the encrypted request, then there's no magic that will make it so.

Did you write the code that's listening on the ServerSocket? If yes, you can answer your own question by looking at your code.

Did someone else write the code that's listening on the ServerSocket? If yes, you'll have to ask them. Were you given any information about the expected protocol?

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • 1
    You can actually look ahead and check whether what you get is a `ClientHello` or a valid plain HTTP request. – Bruno Jul 03 '11 at 16:34