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