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

Reply via email to