TLS Handshake Message Proposal (Was: Re: JEP 244: TLS Application-Layer Protocol Negotiation Extension)

Bradford Wetmore bradford.wetmore at oracle.com
Fri May 22 00:55:41 UTC 2015


Hi Thomas,

After reviewing a lot of the back mail and the desires expressed, I have 
two orthogonal proposals to make.

The first (next email) is an ALPN-specific API using a simple callback 
selector which I think addresses most of the protocol selection concerns.

The second (below) is a more general Handshake Message framework that 
will allow for insertion of other HandshakeMessage types (e.g. 
SupplementalData/NewSessionTicket), packet capture (e.g. Fallback 
Signaling CipherSuite/Channel Bindings), and for 
adding/modifying/deleting TLS Extensions (both known/unknown).  I was 
trying to come up with something less-involved for just the 
Hellos/Extensions, but it became clear that anything for those messages 
could easily be extended to all.  There's a lot of new APIs, but I find 
it pretty straightforward to use.  Sample code is below.

These two approaches are complementary: they don't depend on each other 
at all.  Given that we need to have ALPN support in just over a month 
(to allow for test/JCK/HTTP-2 development), my colleagues are concerned 
about taking on major API development at this date.  Depending on 
feedback, I'm thinking of proposing that we do the first approach for 
JDK 9, and for the second, initiate a JEP and target the JDK 10 timeframe.


On 5/20/2015 12:15 PM, Thomas Lußnig wrote:
> Hi,
>
> 1) There are two types of extensions:
> a) That modify the directly how the engine works like
> [max_fragment_length,heartbeat,encrypt_then_mac,extended_master_secret,SessionTicket,...]

As a third party, these will be impossible without making your own JDK 
(or it being supported by the implementation).

> b) That provide information (modify the network protocol) like
> [npn,alpn,status_request,...]

Yes.  I tried to call those out in my email.

> 2) Some of the extionsions could be called deprecated like heartbeat,
> npn and compression

NPN/compression certainly, but I wasn't aware heartbeat was deprecated. 
  Possibly in the court of public opinion after "Heartbleed."  :)  Is it 
really deprecated in the wider TLS community?

> signed_certificate_timestamp -> could be done without ocsp interference
> via extra handshake message like you can see it on https://suche.org
> there are 3 ways
> how this can be archived Included in Certificate, OCSP-Response, Extra
> handshake Message.
>
> extended_master_secret -> would be hard to implement.
>
> There are two ways to enable better plugin/develop:
> + Expose the client handshake to KeyManager/TrustManager/Client/Server
> + Generic way to add extra messages [status_request, user_mapping,
> client_authz, server_authz, application_layer_protocol_negotiation,
> status_request_v2, signed_certificate_timestamp,
>                                                                 npn,
> TLS_FALLBACK_SCSV

Before I describe the approach, in recent discussions with my 
colleagues, they were also concerned that this would require too much 
intimate knowledge of the TLS protocol.  The other major concern is that 
this is fair amount of change to support a small number of situations. 
Is it worth the time to work this up if no one will actually use it?

Of course, the big plus is that developers can now add functionality 
that we just haven't been able to do.  That said, this approach does 
give developers full freedom to shoot themselves in the foot if they get 
the messages (format/values) wrong.  ;)  We'll do what we can in 
creating APIs for creating valid messages, and leave it to developers to 
create the rest.

Here's the approach.  This is just a first draft, lots of work to be 
done here.

To provide access to the handshake, developers provide callbacks for 
well-defined handshake points:

     SSLSocket.setCallbacks(CallBack[] cbs)
     SSLEngine.setCallbacks(CallBack[] cbs)

with:

     CallBack(
         int handshakeMessageType,
         enum When {INBOUND/OUTBOUND_BEFORE/OUTBOUND_AFTER},
         boolean extensions,
         CallBackHandler)

where:

     handshakeMessageType are the TLS numbers:

            hello_request(0), client_hello(1), server_hello(2),
            ...etc...
            finished(20), change_cipher_spec(254), (255)

         Note:  CCS is not a formal handshake message, but
         is part of the handshake.

     INBOUND         message is from peer, called upon receipt
     OUTBOUND_BEFORE message is for peer, called before generation
     OUTBOUND_AFTER  message is for peer, called after generation
                         message is also available for review

     extensions      if true and handshakeMessageType is
         client_hello/server_hello, any bytes returned from the
         handlers (see below) will be added as extensions.
         If false or any other message type, any bytes are added as a
         separate message (e.g SupplementalData for user_mapping)

         If you duplicate an extension, it will replace the existing
         one in the output message.

The CallBackHandler has:

     byte[] callBackHandler.handle(
         SSLSocket s, int handshakeMessageType, When w,
         boolean extensions, byte[] message) throws SSLException

     byte[] callBackHandler.handle(
         SSLEngine e, int handshakeMessageType, When w,
         boolean extensions, byte[] message) throws SSLException

where:

      message is the raw message bytes, which is null if
      OUTBOUND_BEFORE.

      the return byte[] value is any additional message(s) to send.
      For the case of ClientHello/ServerHello OUTBOUND_AFTER, this
      would be the chance to add/replace extensions to the end of the
      Hello messages.

This approach will also allow applications to generate/respond to 
unknown/future handshake message types, as we simply look in the list of 
callbacks and respond at the appropriate time.  The messagetypes are 
always ints, so INBOUND unknown messages would need to be handled by the 
app.

This would also give direct access to the Channel Bindings, both the raw 
cert encoding (tls-server-end-point) and FINISHED (tls-unique) messages.

To assist in the parsing/generation of messages/extensions, there would 
be many utility classes.  Anything we don't understand would be left as 
some kind of "undefined" message and left to the user to implement the 
parse/generate routines.  These APIs are right out of the TLS message 
format specs.  We have this functionality already in the 
sun.security.ssl level: to avoid duplication some reorg would be needed 
to bring them up to the javax.net.ssl level, or there needs to be a new 
API to call the underlying parsing implementation (probably the former):

     abstract class HandshakeMessage
         abstract byte[] getEncoded()
         abstract getMsgType()

     HelloRequest() extends HandshakeMessage
     ClientHello(byte[]) extends HandshakeMessage
         String getProtocolVersion()
         byte[] getRandom()
         byte[] getCipherSuites()
         String[] getCipherSuitesByName()
         byte[] getCompression()
         String[] getCompressionByName()
         List<? extends HelloExtension> getExtensions()
     ServerHello(byte[]) extends HandshakeMessage
         String getProtocolVersion()
         byte[] getRandom()
         byte[] getCipherSuites()
         String[] getCipherSuitesByName()
         byte[] getCompression()
         String[] getCompressionByName()
         List<? extends HelloExtension> getExtensions()
     Certificate(byte[]) extends HandshakeMessage
         byte[] getEncoded()
         X509Cert[] getCertificates()

     ...(skipping CertRequest/KeyExchanges/CertVerify/etc)...

     Finished(byte[]) extends HandshakeMessage
         byte[] getEncoded()
     UnknownMessage extends HandshakeMessage

and

     abstract class HelloExtension()
         abstract getEncoded() // In case you need to replace one
                               // (below)

     ALPNExtension(byte[] names)
     ALPNExtension(String[] names)
         String[] getNames()
         void setNames()
     SNIExtension(byte[] names)
     SNIExtension(String[] names)
         String[] getNames()
     etc...
     UnknownExtension()


The above mechanism gives a way for the application to override any of 
the extensions and add additional messages.  I think this addresses many 
of the concerns raised.

For some sample code, here's a scenario with ALPN replacement, channel 
bindings, and generating server_authz+SupplementalData messages.

     // Create the Callbacks

     CallbackHandler myHandler = new MyHandler();

     Callback[] cbs = {

         // Look at the ciphersuites/protocols/ALPN values.
         new Callback(
             MessageType.clientHello, INBOUND, false, myHandler),

         // Look at the selected ALPN value, see if the values are ok
         new Callback(
             MessageType.serverHello, OUTBOUND_AFTER, false, myHandler),

         // Add a RFC 5878 SupplementalData before Certificate
         new Callback(
             MessageType.certificate, OUTBOUND_BEFORE, false, myHandler),

         // Capture the FINISHED (tls-unique) channel binding
         new Callback(
             MessageType.finished, OUTBOUND_AFTER, false, myHandler),
     }

     // register the callbacks
     SSLSocket.setCallbacks(cbs);


Class MyHandler extends CallbackHandler {

     private serverAuthz = null;
     private String[] alpns = null;
     private byte[] finished = null;

     public byte[] callbackHandler.handle(
             SSLSocket s, int handshakeMessageType,
             When when, boolean extensions, byte[] message) {

         switch(handshakeMessageType) {
         case MessageType.clientHello:
             return clientHello(new ClientHello(message));
             break;
         case MessageType.serverHello:
             return serverHello(new ServerHello(message));
             break;
         case MessageType.certificate:
             return certificate();
             break;
         case MessageType.finished:
             return finished(new Finished(message));
             break;
         default:
             // Why were we called, nothing was registered!
             return null;
         }
     }

     /*
      * Capture ALPN names for later.
      */
     private byte[] clientHello(ClientHello ch) {

         HelloExtensions exts = ch.getExtensions();
         for (Extension e : exts) {
             if (e instanceof ALPNExtension)
                 alpns = e.getNames();
                 return null;
             }
             if (e instanceof ServerAuth) {
                 serverAuth = e.get();
             }
         }
         return null;
     }


     /*
      * Any additional extension cleanup?
      */
     private byte[] serverHello(ServerHello sh) {
         byte[] bytes = null;

         HelloExtensions exts = sh.getExtensions();

         for (Extension e : exts) {
             if (e instanceof ALPNExtension
                     && e.getNames()[0].equals("H2")
                     && !ch.getProtocolVersion().equals("TLSv1.2"))
                 // We can't use HTTP/2 without TLSv1.2 (ignoring 1.3)
                 // Replace with a valid element
                 if (alpns == null) {
                     continue;
                 }
                 for (String s : alpns) {
                     if (!s.equals("H2") {
                         bytes = add(new ALPNExtension(s).getEncoded());
                     }
                 }
                 throw new SSLProtocolException("ALPN: only H2 offered");
             }
         }

         if (someOtherConditionThatNeedsToRemoveALPNExtension) {
             return new ALPNExtension(null);
         }

         // SunJSSE doesn't support server_authz, add it.
         if (serverAuthz != null) {
             doSomeProcessing();
             bytes = add(myServerAuthzExtension.getEncoded());
         }

         // Nothing else to do.
         return null;
     }

     /*
      * We got a server_auth extension from the client and sent one in
      * response, now generate the SupplementalData server_authz
      * structure.
      *
      * Handwaving for now, because there could also be one sent
      * before the client's cert.
      */
     private byte[] certificate() {
         if (serverAuthz != null) {
             return new MyPrivateSupplementalDataClass(xxx).getEncoded();
         }
     }

     private byte[] finished(Finished fin) {
         finished = fin.getEncoded();
     }

     public byte[] getFinished(Finished fin) {
         return finished;
     }
}

Hope this is clear.

Brad


> Specially the information what the client can could be interesting for
> site owner to decide what he should take care and what is so unusual
> that it can be ignored.
>
> Gruß Thomas
>


More information about the security-dev mailing list