ENC424J600, PIC24, PIC24FJ256GB206, Security, Technical, TLS/SSL

TLS “simplified”

SSL/TLS Library for PIC24 is a mikro-Pascal library developed by and for the open source community. The aim of open source projects is to provide developers with opportunities to share and learn through collaboration.

Gunnebo_Security

We have some of the greatest minds working on this, and we hope to attract as many developers from the open source community as possible to contribute to the development of the  library and to use it. Today’s post is prepared with support from Jack Lloyd, a TLS security and cryptography expert.

HMAC
This post is an outline of a TLS 1.1 client implementation supporting the TLS_RSA_WITH_AES_128_CBC_SHA ciphersuite on the PIC24 CPU, using the cryptographic hardware interface exposed by the ENC424J600/624J600 ethernet controller. This includes MD5, SHA-1, AES, and modular exponentiation of integers up to 1024 bits.

Note: TLS 1.2 is not used here, because it requires SHA-256, which is not supported by the ENC424J600 and would be quite prohibitive to implement on the PIC24 microcontroller CPU. It seems, some more recent microchip processors do support SHA-256. And it looks relatively straightforward to adapt code written to support TLS 1.1 to subsequently support TLS v1.2 as well, assuming the presence of a SHA-256 accelerator.

TLS Background

The TLS protocol features a number of ugly warts owing to historical reasons (many of which we will discuss in detail later on), but at its core it is quite simple.

It begins with an exchange of “hello” messages, when the client and the server agree on a version and a set of cipher algorithms to use. Then the server sends a certificate from which the client extracts the public key. The client uses the public key to encrypt a secret key to the server. After this, the client and server exchange one final message to verify that the exchange was successful. Then, the shared key is used to encrypt application data.

IMG_0782

Each message will further be explained in some detail in this post in the context of creating a complete connection using the TLS_RSA_WITH_AES_128_CBC_SHA ciphersuite.

Each TLS type begins with a 5-byte header detailing the length and content type. The most important content type, handshake, has its own sub-header described later on.

Simplifying Assumptions

Due to the specific nature of the application, we assume that the TLS features of renegotiation, client authentication, and session resumption are not required. Not supporting them simplifies the implementation without impacting performance or functionality in any way.

Session resumption allows the client and server to cache the master key, and then resume the session in a different TCP connection at a later time without bothering with the relatively expensive public key operations. This feature is well supported by servers and can be easily added in later if required.

Client authentication would allow the sensor nodes to be individually authenticated with the help of a device specific key. Current understanding of application requirements does not indicate this is needed.

Renegotiation allows a TLS session to negotiate a new session nested inside the currently active one. This is required in a few specific cases, but given that AES is used and client authentication is not supported, renegotiation has no purpose.

[For interested readers: in some old ciphersuites based on 64-bit ciphers such as DES, periodic renegotiation/rekeying, after no more than 32 GB of traffic is transferred, is equired to prevent specific attacks, see: https://sweet32.info/ for the background. And in the context of client authentication, renegotiation is sometimes used by the server; it initially accepts a handshake without requesting authentication, then renegotiation occurs later if a protected resource is accessed, and in the new session client authentication is requested. Some versions of IIS are known to work in such a way. However, renegotiation results in known vulnerabilities! See  https://mitls.org/pages/attacks/3SHAKE.  So, in the specific negotiation that follows we set a special flag indicating it is not supported.

Hardware Interface

This summarizes the supported cryptographic hardware. Access is performed via DMA to specified addresses.

7800h: MP exponent (up to 1024 bits)
7880h: Input/output (up to 1024 bits)
7900h: Modulus (512, 768, or 1024 bits)

Important note: the modulus must be exactly 512, 768, or 1024 bits, to avoid miscomputation.

Hashing (MD5 or SHA-1)

7a00h: Input block (512 bits)
7a40h: Current state (or hash output if finalized) (160 bits)
7a54h: Hash length counter (55 bits)

AES encryption

7C00: Key register
7C20: TEXTA
7C30: TEXTB
7C40: XOROUT

Building Blocks

Hash Functions

You will need to write an interface to the hash function hardware (described in
sections 15.2.1 and 15.2.2).

The hardware supports saving contexts (in order to allow multiple concurrent hash computations to interleave). However, I do not believe this is necessary for TLS implementation. The following interface (C) suffices:

void MD5(uint8_t *hash_out, const uint8_t* first_block, const size_t first_block_len, const uint8_t* rest, size_t rest_len);

// and similarly for SHA-1

HMAC

Implementing HMAC correctly is a necessary prerequisite to a successful TLS handshake. It does not require any networking code in order to implement or test. I’d recommend implementing and testing this function as the very first task.

The HMAC function is an operation on a hash function H (in TLS v1.1 both MD5 and
SHA-1 are used). HMAC uses two values:

ipad = the byte 0x36 repeated 64 times
opad = the byte 0x5C repeated 64 times.

To compute HMAC over the data ‘text’ we perform:

HMAC(k,m) = H(K XOR opad || H(K XOR ipad || text))

Note how this involves 2 nested hashes. First compute the 64-byte blocks:

ikey = K XOR ipad
okey = K XOR opad

This is the first 64-byte block to be hashed by the hardware (as described
in section 15.2.1 and 15.2.2 in the datasheet). Then hash the entire text,
using MD5 as in example:

MD5(inner_mac, ikey, 64, text, text_len);
MD5(hmac_result, okey, 64, inner_hmac, 128/8);

The final HMAC output is the 128 bits (or 160 bits, for SHA-1) in hmac_result.

You can test the HMAC functionality using the tests in https://tools.ietf.org/html/rfc2202

TLS PRF

This is a standalone-function that must be implemented in order to perform the
TLS handshake. It is suggested to first write and debug the HMAC implementation
and the TLS PRF, before even attempting to write the socket code for
creating/parsing the TLS handshake.

The PRF takes 4 arguments: secret, seed, label, and the desired output length. The secret and seed are octet strings, and the label is an ASCII string which is included without trailing null.

For the TLS ciphersuite in use, 104 bytes of output are required
(two 16 byte AES keys, two 20 byte HMAC keys, and two 16 byte AES IVs).

First, the secret is split into two pieces of equal length, rounding up:

L_S = length in bytes of secret;
L_S1 = L_S2 = ceil(L_S / 2);

S1 = first L_S1 bytes of secret
S2 = last L_S2 bytes of secret

Then S1 and S2 are each passed to a different function P_hash (parameterized by MD5 and SHA1) which produces the desired amount of output:

PRF(secret, label, seed, outlen) = P_MD5(outlen, S1, label + seed) XOR
P_SHA-1(outlen, S2, label + seed);

The description of the P_hash function from RFC 4346 is:

A(0) = seed
A(i) = HMAC_hash(secret, A(i-1))
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
HMAC_hash(secret, A(2) + seed) +
HMAC_hash(secret, A(3) + seed) + ...

The iteration proceeds enough times to produce the desired output length. Here is a test of the PRF:

Secret = 2212169D33FADC6FF94A3E5E0020587953CF1964
Seed = FCD5C9637A21E43F3CFF6ECF65B6E2F97933779F101AD6
Label = <blank>
Output[32] = 1E1C646C2BFBDC62FA4C81F1D0781F5F269D3F45E5C33CAC8A2640226C8C5D16

RSA encryption

RSA encryption is based on computing a modular exponentiation of large integers,
y = x^e mod m

The following (untested) function writes y = x^e mod m

It is easy to generate test inputs for this function, for example, using the Python pow function:

Random Number Generator

An important ingredient missing from the ENC424J600 crypto hardware is random number generator interface. Secure random numbers are essential for creating a secure TLS connection, especially when using the RSA ciphersuites, given that the client alone is responsible for choosing the shared secret key. If the client’s RNG is poor (easily guessed, repeats values, etc.), this will straightforwardly compromise the security of the connection.

Using HMAC and the TLS_PRF, it is, fortunately, trivial to compute a secure if
ad-hoc cryptographic RNG. Define a 160-bit state K, initially all zeros (or any
other value). To add additional seeding material M to the RNG, compute:

K = HMAC_SHA1(K, M)
Counter32 = 0

To produce more output:

Output = TLS_PRF(desired_output_len, K, label="RNG", seed=Counter32 [as uint8_ts])
Counter32++

Note that if you don’t increment the counter here, the RNG output will repeat.

TLS Version

The TLS version is encoded as a uint16. For historical reasons, TLS
1.1 has a ‘wire version’ of 0x03 0x02, which is exposed on the network
is the u16 0x0302

TLS Record Layer

All TLS packets begin with a 5-byte header that specifies the version, the content
type, and the length of the data which follows. In the language of the RFC, this is

Where the opaque fragment refers to the associated packet data.

For unfortunate historical reasons, each TLS packet header includes a 2-byte version field, but this version number does not serve any purpose in terms of TLS version negotiation. Many implementations set the record layer version to “03 01” and emphasize that it is not associated with the final negotiated TLS version. The record layer version should be ignored upon decoding (except perhaps checking if the first byte is 0x03).

While the length is a 16-bit integer, TLS requires all records to be at most 2**14 + 2048 bytes (or 18 KiB) long. Longer records should be rejected. The ContentType is encoded as a single octet taking on one of 4 possible values:

change_cipher_spec = 20 (0x14)
alert = 21 (0x15)
handshake = 22 (0x16)
application_data = 23 (0x17)

In the initial negotiation, a sequence of ‘handshake’ packets are exchanged (the ‘change_cipher_spec’ type is also used in one particular point in the handshake).

After the negotiation is complete, ‘application_data’ packets are transferred. The alert type is used to either notify the peer of an error or to perform an orderly shutdown of the connection.

Details on their specific content types follow.

As an example, the following is the header of a handshake message of length 61 (0x3d):

"16 03 01 00 3d"

TLS Alerts

The alert type indicates the way in which the server will tell you something went wrong :). An alert consists of 2 bytes, a level and an alert type. The ‘level’ can be either warning (1) or fatal (2). In practice, almost all alerts are fatal, except for the close-notify alert which is used to signal orderly shutdown of the connection. TLS provides some general guidelines as for which alert types should be sent in each situation, but implementations vary widely. Some important alert types to know by heart are (all values decimal):

close_notify (type 0): it is used to signal orderly shutdown of the connection;

unexpected_message (type 10): a message was received in an unexpected order;

bad_record_mac (type 20): a cryptographic check failed; sometimes decryption_failed (type 21) is also used, though this is a case of deprecated behavior in the most recent version of TLS;

handshake_failure (type 40): a catchall-error indicating some problem prevented the handshake from succeeding; for example, if the client does not offer any ciphersuites which the server supports, it might reply with ‘handshake_failure’ (or, perhaps, ‘insufficient_security type 71’, though this behavior is not common);

decode_error (type 50): a decoding problem (for example, an invalid length field);

protocol_version (type 70): an unknown or unsupported version was attempted.

There is a number of other alert codes documented in RFC 4346,  but they should not come up in practice very often. For example, a ‘warning’ close_notify is 0x01 0x00 and a ‘fatal’ handshake_failure is encoded as 0x02 0x28.

Encapsulated as a TLS record, the fatal handshake_failure would then be:

"16 03 01 00 02 02 28"
^- content_type (handshake)
^- protocol_version (record layer version, ignored)
^- content_length
^- alert level (fatal)
^- alert type (handshake_failure

TLS Handshake Layer

Handshake messages are used at the beginning of the session to negotiate parameters and create a shared key.

[Note: handshake messages can also be passed later on, after the initial handshake is complete, but only if the renegotiation is supported.]

Each handshake message is conveyed in *one or more* TLS record packets. Recall that the maximum size of a TLS record is 2^14 + 2048 bytes. To support very large handshake messages, the handshake containing its own length field can be fragmented across multiple TLS records. However, even if there are no large records, an implementation is allowed to fragment TLS handshake packets into multiple record packets. An example of this will be given later in this section.

All handshake records are preceded by a uint8_t type code, as well as 24-bit length. The current handshake type codes are:

In order to complete a TLS_RSA_WITH_AES_CBC_SHA connection, the client must be
prepared to send:

client_hello (1)
client_key_exchange (16)
finished (20)

And the client must be prepared to parse, in order:

server_hello (2)
certificate (11)
server_hello_done (14)
finished (20)

The certificate_request and certificate_verify messages are only used for the client’s authentication. The server_key_exchange message is only used with Diffie-Hellman cipher suites. The hello_request message is only used for renegotiation.

The handshake message will be of precisely the length specified in the header. If the handshake message is fragmented across records, it will only appear in the first record. A brief example may help make this fragmentation behavior clear.

Consider a client_hello of length 93 bytes. It will have an inner (TLS handshake) header of:

01 00 00 5d [... 0x5d bytes ...]

The outer (TLS record) header will have value:

16 03 01 00 61

where 0x61 – 0x5d is precisely the 4 bytes taken up by the handshake header (3
byte length plus 1 byte handshake_type).

Consider if this handshake message were hypothetically fragmented into two TLS records (which is unnecessary, since the packet fits in a single record, but such fragmentation *is* allowed, and the server *may* do it for any handshake message it sends). The inner bytes would still be exactly the same. Say, the 93-byte client_hello message is split into two parts, of length 29 and 64. The bytes are still exactly the same:

Fragment 1: 01 00 00 5d [... first 0x1d bytes ...]
Fragment 2: [... final 0x40 bytes ...]

Then there will be two record layer-headers, with the length of the physical
size of each fragment, e.g.:

16 03 01 00 21 01 00 00 5d [... first 0x1d bytes of client_hello ...]
16 03 01 00 40 [ ... final 0x40 bytes of client_hello ...]

See how the handshake-level header is only included in the first packet.

In the initial implementation you may be able to ignore such fragmentation;
simply verify that, upon decoding a handshake message, the uint24 in the
handshake header matches exactly the length of the record layer (minus 4 to
compensate for the handshake header itself).

Additionally, it is allowed for an implementation to combine multiple handshake messages into a single TLS record. This is not a common behavior, and again, it should be detected by a check that the handshake-level length matches the record level length.

Client Hello

Finally, the moment we’ve all been waiting for, actually, putting some packets on the wire:).

After establishing the TCP connection, the client forms a client hello handshake message, encapsulates it in a TLS record header, and sends it.

If the server finds everything in the client’ hello acceptable, it will reply with a server hello. Otherwise it will likely send an alert message and close the connection.

The structure of a ClientHello is as follows:

First come the two bytes representing which protocol version the client is attempting
to negotiate. Unlike the version in the record, this field matters! It should be precisely
0x0302 to request TLS v1.1

Next come 32 random bytes. These should be generated by a cryptographically strong PRNG.

Following that is the session ID, which is encoded as a single-byte length field followed by
the provided number of bytes. For a client hello where no session is being resumed, the
session_id field should be simply [00].

Next stage includes the selection of cipher suites. They are encoded as a pair of octets, preceded by a 2 byte length field. Here, we are only attempting to negotiate or offer a single ciphersuite, TLS_RSA_WITH_AES_128_CBC_SHA, which has value 0x00,0x2F. So, the list would consist of a 2-byte length 0x00,0x01 followed by 0x00,0x2F.

Notice here that the length field is 1 even though 2 bytes are sent to comprise the single ciphersuite – that is because the count is defined in terms of the “ciphersuite values” or uint16_ts, rather than bytes.

However, for security reasons we must also specify that no renegotiation is supported. For details see RFC 5746 (https://tools.ietf.org/html/rfc5746), otherwise just trust that it is so. We do this by including a ‘special’ ciphersuite value 0x00,0xFF to the list of ciphersuites (it does not matter in which order this value is placed, since the server will never select it).

So, the ciphersuite list in our case must be 0x00,0x02,0x00,0x2F,0x00,0xFF

Finally, following the ciphersuites, there goes the selection of compression method. Only ‘null’ (no-op) compression should be indicated. This is done by encoding [01 00], i.e. a length-1 octet string containing 0x00 (representing the null compression scheme).

So, a full (if minimal) client_hello has length 2+32+1+6+2 = 43 (0x2b) bytes

03 02 # protocol version
[32 bytes of random data] # the client random
00 # the empty sessionId
00 02 00 2F 00 FF # the ciphersuite list
01 00 # the compression list

To this, the TLS handshake header is prepended, with the type and length:

01 00 00 2b

Keep in mind the resulting TLS  “handshake message” has length 43+4 = 47 (0x2f) bytes And then in front of that is the TLS record header:

16 03 01 00 30

Recall that 0x16 is == 22, or handshake content_type.

Putting it all together, the client sends the following magic packet, and, with a stroke of luck, the server replies! Here FF represents the random field, which should vary for each connection:

16 03 01 00 30 01 00 00 2b 03 02
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00 00 00 04 00 2F 00 FF 00 01

Finally (for this section anyway) a brief proof of concept that the server might reply
to our handshake is attached in gen_hello.py; a sample response (generated by OpenSSL) is:

server_hello=16030200510200004d0302be170c5df057b46c9034ab7e8a5cd2d206603a6ee6f2610e19ab8ef9490100442085d071401a4e98ca107e77601aedaf92dff29a9a3fd548ab8ef953cd6caa7e4a002f000005ff01000100
server_certificate=16030201eb0b0001e70001e40001e1308201dd30820146a003020102021100cd28fd45fef844cea66ad6a337d271f5300d06092a864886f70d01010b0500301431123010060355040313096c6f63616c686f7374301e170d3137303632373139353930335a170d3138303632373139353930335a301431123010060355040313096c6f63616c686f737430819f300d06092a864886f70d010101050003818d0030818902818100bb9d1da05a178066a3d84e03d9e5ede76da49084363c4169696a3ea1793dd9561c9f7286608d03fef0faa5447441b7aa186b1ef71445ee1d6a500f630085aaffb68617bd8b6226c865b4c37ffd2dca43379bedaf4b967220769d1ad952c4c25b253cb5eb1c08fc1286d59af3b2c83b13fbf18545539dbe4d42704c69b806fd3b0203010001a32f302d300c0603551d130101ff04023000301d0603551d0e04160414fadc8c302c5be5adac90923a3513dfc1e114bade300d06092a864886f70d01010b05000381810068287a41341f659ca93de639e4acd601d632f062b22857bb094d64517c79c617fad40d141d519195a602a46ec0161b19c92e01897587592464f75f6cf191fbdc535db6e51fadd9316bcd25afbf7f5df5f162d216dd16ffd939d426a8defce019bac4e2e689a17dbede16dd4219da4460a12aa4f245bcaa519a5b7f0632f96821
server_hello_done=16030200040e000000

Notice how this reply is three handshake messages concatenated. These are server hello, server certificate, and server “hello done” messages. We examine parsing/processing of these messages in turn.

[Note for the future work: there are extensions that might be useful to send in
the client hello. In particular there is an extension which allows clients to
notify the server that they support only a smaller record size than the normal
TLS default of 18 KiB. This may help reduce the RAM usage notably.]

Server Hello

The server hello has a structure very much alike to the client hello that was sent, though, instead of a list of ciphers and compression methods, only the single value selected by the server is included. Though, of course, since the client only sent one value in each list (neglecting the no_renegotiation SCSV), the server replies with exactly what was requested:

We break down the exemplar OpenSSL reply noted in the client hello section:

server_hello=16030200510200004d0302be170c5df057b46c9034ab7e8a5cd2d206603a6ee6f2610e19ab8ef9490100442085d071401a4e98ca107e77601aedaf92dff29a9a3fd548ab8ef953cd6caa7e4a002f000005ff01000100

Record header: 1603020051

Upon reading just the 5-byte header, we know that exactly 0x51 bytes follow as a part of the fragment. We read first the handshake header:

0200004d

Here it simply says that  this is handshake type 2 (server_hello) followed by 0x4d bytes.
The first bytes are the server selected version

0302

The next 32 bytes are the server random field; they are used later on during the computation of the keys:

server_randomness=be170c5df057b46c9034ab7e8a5cd2d206603a6ee6f2610e19ab8ef949010044

This is followed by the server-created session ID (which may be empty). Here the server returns a 32-byte field (0x20 length followed by 0x20 bytes)

sessionId=20 85d071401a4e98ca107e77601aedaf92dff29a9a3fd548ab8ef953cd6caa7e4a
^- length byte

Then we come to the ciphersuite value:

002f

The client can simply verify that the server chose the value it expected.

Followed by the chosen compression method,

00

you will see a few  final bytes,

0005ff01000100

These are the server’s “extensions”, which are sent in response to whatever client extension(s) are sent if any. But you could have mentioned that we did not send any extensions, which is correct, but according to the rules of the RFC 5746, sending the “indicator” ciphersuite 0x00,0xff is treated as equivalent to sending a client extension with a somewhat more complicated encoding. For now, don’t worry about these bytes. They indicate the server understood the SCSV.

Important note: if talking with an older or misconfigured server, it’s very possible that the server will not send any extension in response to the SCSV, in which case the null compression byte will be the final byte of the server hello.

Server Certificate

The certificate message is typically the longest message in the handshake. It always starts with the record header:

16 03 02 01 eb

Here, 0x01eb = 491 bytes are enclosed, as further described by the handshake header:

0b0001e7

Here, 0x0b == 11 or certificate in the panoply of handshake types. The three byte length field specifies 0x1e7 bytes following in the handshake message.

Then, follow two (further) nested 24-bit length fields:

0001e4 0001e1

The first specifies that the total length of the following certificates is 0x1e4 bytes. [If you think that this field is utterly redundant with the value included in the handshake header, you are correct…]

The next length header is the length of the individual certificate. Since only one certificate was sent by the server in this case, it was necessarily 3 bytes less than the length specified for the overall list.

Finally, there go the actual bytes of the certificate.

In a fully general TLS client, one would need to parse this certificate as ASN.1 data and verify its authenticity using some relatively complex algorithms. For the purposes of the proof of a concept, it is enough to hardcode the expected server’s certificate into the binary, and memcmp what the server sent against the expected value. Attacks are still prevented, because later, when the client negotiates a key, it will do so in a way that only the server can decrypt. Of course, this simplicity comes at a cost, that the client can only successfully interop with a single server, and if the server certificate changes (e.g. due to certificate rollover at the end of an expiration period), the sensor software would have to be updated.

Server Hello Done

Finally, a simple message. This is used by the server to signal that it has completed sending messages, and is now waiting for the client reply:

server_hello_done=16030200040e000000

This consists of the record header

16 03 02 00 04

followed by the handshake header, with 0x0e == 14 (server_hello_done)

0e000000

There are never any data bytes in this handshake message.

At this point, the client must prepare its reply, so that the key exchange can
complete and encrypted traffic can commence. In the simplified case considered
here, this involves sending a Client Key Exchange, a Change Cipher Spec, and a
Finished message.

Client Key Exchange

In a RSA ciphersuite, the client chooses the key to use and sends it to the server by encrypting it as an RSA ciphersuite.

It is formatted as a 2-byte length field, followed by a RSA ciphertext.

The server starts by choosing a 48-byte “pre-master secret”:

Here, client_version is the highest version of the protocol that the client supports.
In this document, that would be TLS 1.1, or 0x0302. This is followed by 46 random bytes.

The client then forms a 1024-bit integer, formatted as follows:

00 01 <PS> 00 03 02 <RND>

Here, 03 02 is the encoding of TLS 1.1 and <RND> is the 46-byte random value. <PS> consists of padding bytes, all of them non-zero [*] Then it is encrypted using the server’s RSA key. XXX need more detail here.

[*] This can be done simply by generating PS with a random number generator. If the RNG outputs a 00 byte, call the RNG again.

Key Derivation

At this point, the client has chosen the pre-master secret, encrypted it and sent it to the server. It has all data necessary to compute the full set of encryption keys and exchange application data.

It begins by using the PRF to compute the 48 byte master_secret:

master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)[0..47];

The TLS_PRF is used to derive sub-keys from the master_secret. A single block of output of sufficient length is generated and then split into multiple values:

key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);

client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]

For the ciphersuite considered here the mac_key_length is 20
and enc_key_length and fixed_iv_length are 16.

ChangeCipherSpec

The change cipher spec (CCS) is basically a handshake message, but has its own
type/encoding for historical reasons. It denotes that all cryptographic keying
has been computed, and it is time to enable the encryption algorithms.

The ChangeCipherSpec has its own content_type (20, 0x16) and contains a single
byte 0x01:

160301000101

Client Finished Message

This is the first encrypted message sent in the exchange. It is used by the server to verify that the client has the correct keys computed, and to prevent an attacker from tampering with handshake messages in transit. The finished message consists of 12 bytes of PRF output:

verify_data = PRF(12, master_secret, "client finished", MD5(handshake_messages) + SHA-1(handshake_messages))

Here handshake_messages includes the full contents of all handshake messages exchanged so far, in their order (so, for this ciphersuite: client_hello, server_hello, certificate, server_hello_done, client_key_exchanged). This includes the handshake-level headers (with the handshake type and 24-bit length field) but *not* the 5-byte TLS record header.

Note that this does not include the change of cipher spec message, which is not technically a handshake message.

The plaintext finished message has a TLS handshake header as normal:

1400000CFFFFFFFFFFFFFFFFFFFFFFFF

Here, FF… stands for the 12 byte PRF output.

Now, the entire finished message is encrypted and authenticated using the AES and HMAC keys previously derived.

Encrypting Records

Each direction (client->server and server->client) maintains distinct
keys, as well as a pair of 64-bit sequence numbers which begins at 0,
one for sending and one for receiving.

Each record begins with a 16-byte random initialization vector. This
should be generated by the cryptographic RNG.

Since AES-CBC requires encrypting some exact multiple of 16 bytes,
padding is applied at the end of the message. The TLS protocol, in
fact, allows more than a minimum amount of padding (up to 255 bytes).
Padding is always added, even if the plaintext is already an exact
multiple of 16 bytes.

The format of an encrypted TLS record begins with the 5-byte TLS record header. Unlike the handshake records, there is no ‘nested’ header, instead, the record contains a sequence in a somewhat odd order:

IV: 16 bytes [0-15]
plaintext_contents: arbitrary number of bytes
MAC: 20 bytes
padding_bytes: remainder

The IV is not encrypted, but the plaintext, authentication code,
and padding bytes are.

The MAC is computed as:

HMAC_hash(MAC_write_secret, seq_num + record_type +
record_version + record_length + record_contents)

Note that the sequence number is implicit in TLS; since TLS runs over TCP, each side is assumed to keep track of how many records have been sent and received so far. The sequence number is encoded as a big-endian sequence. This is followed by a 5-byte record header.

However, note carefully that the record_length as it appears in the MAC computation is *not* the length of the padded record (i.e. as it appears on the wire in the header of the encrypted record) but is instead the length of the plaintext record after the padding and MAC have been removed.

First, you must decide how much padding to apply. For example, if the plaintext is 12 bytes (for sending the Finished message), and the MAC is 20 bytes (as with HMAC/SHA-1), then 32 bytes are being encrypted.

Say, the finished contents (computed by the PRF) are:

FF FF FF FF FF FF FF FF FF FF FF FF

The MAC is computed by HMAC_SHA1ing the following values:

00 00 00 00 00 00 00 00 # assumes 0 sequence ie first message
16 # handshake record type
03 02 # record version
00 0C # record length (12 bytes)
FF FF FF FF FF FF FF FF FF FF FF FF

Note that the IV is not included in the MAC computation.

Assume the MAC comes out with value

MAC=EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE

Now we must apply padding. Already the 12+20=32 bytes are an even multiple of 16, so we must add 16 bytes of padding to round up to the next multiple (it would also be possible to add 32, 48, 64, … bytes instead). All padding bytes must have the same value, and consist of the byte i-1, where ‘i’ is the number of padding bytes added [*].

[*] This doesn’t exactly match how padding is described in the RFC, but it is functionally equivalent, and, IMO, somewhat easier to understand.

So, for example, if exactly 5 bytes of padding are needed, it would have value 04 04 04 04 04 (i.e., 5 bytes of padding, each with value 4). Here we need 16 bytes of padding, so each has value 15:

FF FF FF FF FF FF FF FF FF FF FF FF
EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F

FF FF FF FF FF FF FF FF FF FF FF FF
EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F

Now we encrypt these contents with AES-128 in CBC mode using AES_128_CBC_encrypt(write_key, record_iv, record, 32+16);

Server CCS

This message exactly mirrors the client’s change cipher spec.
The client must simply verify that it is correct.

Server Finished Message

It mirrors the client’s Finished message, but using the server cipher and MAC
keys and a label of “server finished”.

The server Finished message will be encrypted using the server’s send keys.

Application Data

Finally, application data can flow using the content_type=application_data (23) type, each encrypted with the help of the encrypted record layer.

The library is located on GitHub and we encourage you to contribute 🙂

Micropascal ssl library

 

If you want to discuss more about SSL/TLS, feel free to contact me at bjorn.nostdahl@nostdahl.com or check out these previous articles on SSL/TLS and x.509:

 

Leave a Reply