A few Modest Proposals on Optimizing SSL traffic and round
trips
during TLS 1.2 TLS_RSA_WITH_AES_128_CBC_SHA Handshake
by David Stewart Zink, Version of Mon Dec 25 17:30:26 PST 2006
Based on
The TLS Protocol Version 1.2.
Notation
We use + for addition, xor for bitwise exclusive or, || for
concatenation, ^ for exponentiation. Assuming we get around to doing any of these things.
In ladder diagrams the Client appears on the left and the Server on
the right. The depiction of each message begins with the name of the
message and a centered arrow depicting the direction of motion, either
"<<<——" or "——>>>". Note that these represent messages and not
records: a large message may span many records.
Messages
This can be a little difficult to follow as there is some conflation
of layers in the design. The simplest approach is to imagine that the
HandShake protocol is an application which is exchanging messages
across the record layer. A single Handshake message might be split
across many records, they might begin in the middle of records, they
simply are transferred using records. However, to respond to a
handshake message obviously the record containing the message must be
terminated. Thus in ContentType below, "change_cipher_spec" and
"alert" are flow control meta-messages, while "handshake" and
"application_data" represent two applications multiplexing across the
connection.
Every Handshake message is a "Record" as defined by TLS. The fields of
a Record are as follows:
- ContentType type; // enum { change_cipher_spec, alert,
handshake, application_data }
- uint8 majorProtocolVersion; // Note: +2 for TLS: so TLS 1.2 has
version 3.
- uint8 minorProtocolVersion; // Note: +1 if Major Version is 3, so
TLS 1.2 has version 3.
- uint16 length; // of the record_body.
- uint8 record_body[length]; // actual handshake data; variant
data.
Every Handshake message except for "ChangeCipherSpec" has type
"handshake". The ChangeCipherSpec message for TLS 1.2 is exactly five
bytes with has exactly this content:
- ContentType type = change_cipher_spec;
- uint8 majorProtocolVersion = 3;
- uint8 minorProtocolVersion = 3;
- uint16 length = 0;
Every message with type "handshake" is encapsulated in records with
this format:
- ContentType type = handshake;
- uint8 majorProtocolVersion = 3;
- uint8 minorProtocolVersion = 3;
- uint16 length; // of the following data
- uint8 record_body[length]; // actual handshake data; variant data.
A handshake message has this format:
- uint8 type; // enum { ClientHello, ServerHello, Certificate, ServerKeyExchange, CertificateRequest, ServerHelloDone, CertificateVerify, ClientKeyExchange, Finished, CertificateURL, CertificateStatus }
- uint24 variant_data_length;
- uint8 body[variant_data_length]; // actual handshake data; variant data.
Generic Handshake Message Closure: the ChangeCipherSpec and Finished messages.
The general form of Handshake is that Client and Server exchange a
number of messages establishing a new set of traffic keys, and then
the Client sends ChangeCipherSpec to the Server, followed by Finished.
ChangeCipherSpec has the meaning, "I have changed my Write Keys and
every message after this will be encrypted with the currently agreed
write keys. You need to
change your read keys to continue parsing data."
Finished contains a MAC of all the previous handshake messages, and
since it follows the ChangeCipherSpec it is encrypted with the new
keys. Therefore the ability to decrypt and verify the MAC proves to
the server that the Client has received accurate versions of all
handshake messages and properly computed the new keys.
Then the Server sends ChangeCipherSpec and Finished to the Client,
allowing the Client to verify the server's understanding and keys.
The MAC sent by the Client to the Server and the MAC sent by the
Server to the Client are computed in a slightly different fashion so
that they cannot be reflected.
What is TLS_RSA_WITH_AES_128_CBC_SHA?
This is an SSL cipher-suite enum value which specifies that the
traffic protocol will be AES-128 CBC and the traffic signing protocol
will be SHA1. The "pre master secret" value from which the keys will
eventually be generated will use RSA asymmetric keys. The use of RSA
determines the structure of most of the handshake protocol, and is
essentially the same for every RSA cipher-suite. SHA is used to signify
SHA1 because 4.
Message exchange in TLS_RSA_WITH_AES_128_CBC_SHA with mutual authentication.
This is based on my (David Stewart Zink's) reading of the
specifications and investigation of a sample implementation and should
be considered informed but not authoritative.
We use the extended versions of ClientHello and ServerHello not because we are using extensions but
because the eventual proposals will involve them and we wish to show
their place in the protocol. The non extended versions are identical,
except lack extensions.
Client Server
Extended Client Hello: ———>>>
- uint8 majorProtocolVersion = 3; // client's version
- uint8 minorProtocolVersion = 3;
- kinda-random:
- uint32 gmt_unix; // GMT time in unix format; ignored (legacy?)
- uint8 random[28]; // randomly initialized
- session:
- uint16 session_id_length = 0; // 0 to 32.
- uint8 session_id[session_id_length]; // remnant of existing
session client wants to reinstantiate, or empty
- offered ciphers:
- uint16 num_offered_cipher_suites;
- uint16 cipher_suites[num_offered_cipher_suites]; // 16 bit
cipher_suites enums, must include TLS_RSA_WITH_AES_128_CBC_SHA for
this example.
- offered compressors:
- uint16 num_offered_compressors = 1;
- uint8 compression_methods[num_offered_compressors] = { 0 };
- offered extensions:
- uint16 num_offered_extensions = 0;
- uint8 extensions[num_offered_extensions];
We assume that no extensions, sessions or compressors are present. The
messages therefore contains the client's SSL version number
(presumably TLS 1.2), a 32-byte kinda-random value, and offered
cipher_suites including our darling TLS_RSA_WITH_AES_128_CBC_SHA.
<<<—————— Extended Server Hello
- uint8 majorProtocolVersion = 3;
- uint8 minorProtocolVersion = 3;
- uint32 gmt_unix; // GMT time in unix format; ignored (legacy?)
- uint8 random[28]; // randomly initialized
- uint16 cipher_suite = TLS_RSA_WITH_AES_128_CBC_SHA;
- uint16 compressor = NULL;
- accepted extensions:
- uint16 num_accepted_extensions = 0;
- uint8 extensions[num_accepted_extensions]; // in this list can ONLY appear extensions which were in the Extended Client Hello.
The server presents its SSL versions (presumably TLS 1.2), provides
its own 32-byte kinda-random value, accepts the
TLS_RSA_WITH_AES_128_CBC_SHA cipher suite, and agrees to no
compression.
<<<—————— Server Certificate
- List of certificates:
- uint16 sizeof_certificate_list; // total number of octets.
- uint16 sizeof_one_certificate; // repeated element.
- uint8 one_certificate[sizeof_one_certificate]; // repeated
element; X.509 ASN encoded certificate.
The first certificate must be the server's certificate. Each
certificate after that signs the one before it, until the root
certificate is reached. The root certificate may be omitted since it
is useless if the client does not already have it.
<<<—————— Certificate Request
- uint8 num_allowed_certificate_types;
- uint8 allowed_certificate_types[numcertificate_types]; // enum { rsa_sign, dss_sign, rsa_fixed_dh, dss_fixed_dh, rsa_ephemeral_dh_RESERVED, dss_ephemeral_dh_RESERVED, fortezza_dms_RESERVED }
- uint8 num_acceptable_certificate_hash_types;
- uint8 acceptable_certificate_hash_types[num_certificate_hash_types]; // enum { md5, sha1, sha256, sha384, sha512 }
- uint16 sizeof_acceptable_certificate_authorities = 0; // let's just ignore authorities for now, shall we?
Presumably since we are doing mutual authentication the server would
need a certificate from the client.
<<<—————— Server Hello Done
This is an empty message, just a signifier.
Client Certificate —————>>>
- List of certificates:
- uint16 sizeof_certificate_list; // total number of octets.
- uint16 sizeof_one_certificate; // repeated element.
- uint8 one_certificate[sizeof_one_certificate]; // repeated
element;
X.509 ASN encoded certificate.
The first certificate must be the client's certificate. Each
certificate after that signs the one before it, until the root
certificate is reached. The root certificate may be omitted since it
is useless if the server does not already have it.
Client Key Exchange ——————>>>
- uint16 length = 48; // of encrypted pre-master-secret
- uint8 encrypted pre_master_secret[48];
The pre master secret requires some explanation. Its format is as
follows:
- uint8 client_major_version; // a repeat of the version information in the client hello.
- uint8 client_minor_version; // but this time it is encrypted with a well known public key.
- uint8 random_data[46]; // the actual secret, one supposes.
We encrypt this data with the public key from the server's certificate.
Certificate Verify ——————>>>
- uint8 handshake_messages_so_far_signature[20]; // or whatever size; signature algorithm MUST be same as used in client certificate.
Verifies that the client has the private key associated with the
client's certificate.
At this point the client (and the server after it receives all the
client's messages) has sufficient information to calculate the traffic
keys. This is done by first computing the master secret from the
following information:
- uint8 pre_master_secret[48]; // random; first two bytes are just version, though.
- uint8 ClientHello.random[32]; // random; first four bytes are just time, though.
- uint8 ServerHello.random[32]; // random; first four bytes are just time, though.
This is combined using the "PRF" function to create
Then the magical PRF function is used once more to create the four
traffic keys (encrypt + sign, decrypt and verify) from the following:
- uint8 master_secret[48]; // obfuscated.
- uint8 ClientHello.random[32]; // random; first four bytes are just time, though.
- uint8 ServerHello.random[32]; // random; first four bytes are just time, though.
Now that the client has generated its four keys it sends the
ChangeCipherSpec message and then the finished message.
ChangeCipherSpec ——————>>>
empty non-handshake message signaling time to use new keys.
Finished ——————>>>
The verify_data element is computed by calculating the HASH of all
preceding handshake messages (as with the Certificate Verify message)
and then combining once more using the mystical PRF function the
following data:
- uint8 master_secret[48];
- uint8 client_finished[16]; // the explicit string "client finished"
- uint8 handshake_messages_so_far_hash[20]; // 20 bytes is presumably the size.
And now the server has all the info it needs, calculates the keys and
sends the ChangeCipherSpec message and then the finished message.
<<<—————— ChangeCipherSpec
empty non-handshake message signaling time to use new keys.
<<<—————— Finished
The verify_data element is computed by calculating the HASH of all
preceding handshake messages (as with the Certificate Verify message)
and then combining once more using the mystical PRF function the
following data:
- uint8 master_secret[48];
- uint8 server_finished[16]; // the explicit string "server finished"
- uint8 handshake_messages_so_far_hash[20]; // 20 bytes is presumably the size.
Oh boy we're done!
Existing Extension: Trusted CA Keys
A client can send an extension message with its ClientHello specifying
the root servers from which it is prepared to accept signed
certificates. The exact mechanism is unimportant (there are a few
options), but essentially the client sends a list of names or whatever
identifiers. The server then only accepts the Hello attempt if it can
send a chain of certificates rooted in one of the acceptable root
servers.
Proposed Extension: Versioned Trusted CA Keys
Corporate CAs occasionally find themselves invalidating all previous
certificates, or migrating piecemeal to a new set of certificates. In
this case it would be useful if the Client could specify exactly which
version of the Trusted CA Key it is aware of. Trusted CA Keys have a
pseudo-versioning option in that certificates can be identified by
hash; however name-hash pairs would be better.
Proposed Extension: Trusted Certificates
There is no reason not to extend the "Trusted CA Keys" scheme to
include all certificates. For instance, if the client has previously
connected with the server and been given the chain of certificates
A<-B<-C<-D<-ROOT, then it could specify in its hello message that
entire chain "A<-B<-C<-D<-ROOT". If the server is still
using certificate A then the server could
simply confirm that it is doing so. If the server has a new certificate A2
signed by B, it can simply give A2, knowing that the client will be
able to verify the signature. If none of the certificates are valid
except the root, the fallback behavior would be the standard behavior.
Obviously names are insufficient for this process and some versioning
information (such as a hash or the certificate sequence number) should
be included as well.
Proposed Extension: Proffered Client Certificate
There exists an extension to allow the client to send URLs instead of
actual certificates so that they do not even need enough storage to
hold their own certificates. The URL is sent at the time a certificate
is normally sent. The first part of this proposal is that Client
Certificate URLs if modest in size simply be included in the
ClientHello message so that the Server can get a head start on
processing them. Obviously if the server has spoken with the client
before it might perform an If-Modified-Since operation rather than
retrieving the Certificate every time. This leads to the second part
of the proposal, that the Client simply be able to include a hash of
its certificate or other identifier so that the Server can cache
certificates reliably.
Proposed Extension: Fully Random Randoms
The first four octets of the random values generated for Client and
Server random are specified to be the GMT time, which is further
specified to be ignored. They should simply be generated as random
values. If necessary, an extension could be added specifying this
treatment.
Furthermore the first two bytes of the pre-master secret are not
random but are rather the client version number, almost certainly 3
and 3. The document alleges that these were long ago placed here in
order to demonstrate that the client version was as specified in the
original hello message, but that this was no longer necessary since
the entire handshake process is signed with a MAC in the Finished
message. Therefore these should be randomized. An extension or
version change would be necessary to specify this behavior.
Proposed Extension: Server Generated Pre Master Secrets
In the mutually authenticated environment the distinction between
Client and Server is somewhat nominal. The pre-master secret is simply
random data. It is generated on the Client because in the
singly-authenticated environment only the Server's public key is
available to securely encrypt documents. Since in the mutually
authenticated environment with the Proffered Client Certificate
extension the Server has access to the Client's public key, this could
be used to encrypt the pre-master key. This has the effect of making
it possible to redistribute processing load without
affecting the security of the protocol.
Revision History
- Mon Dec 25 17:23:50 PST 2006
-
Removed "Server Key Exchange". TLS Doc F.1.1.2 clearly states:
With RSA, key exchange and server authentication are combined. The public key may be either contained in the server's certificate or may be a temporary RSA key sent in a server key exchange message.
However 7.4.3 states even more clearly:
It is not legal to send the server key exchange message for the
following key exchange methods: RSA DH_DSS DH_RSA
-
Express "no compression" correctly. Rather than a list with 0 entries,
no compression is expressed as a list with 1 entry of 0.