Question

I'm having trouble calculating the MAC of the finished message.The RFC gives the formula

HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment));

But the tlsCompressed(tlsplaintext in this case because no compression is used) does not contain version information:(hex dump)

14 00 00 0c 2c 93 e6 c5 d1 cb 44 12 bd a0 f9 2d

the first byte is the tlsplaintext.type, followed by uint24 length.
The full message, with the MAC and padding appended and before encryption is

1400000c2c93e6c5d1cb4412bda0f92dbc175a02daab04c6096da8d4736e7c3d251381b10b

I have tried to calculate the hmac with the following parameters(complying to the rfc) but it does not work:

uint64 seq_num  
uint8  tlsplaintext.type  
uint8  tlsplaintext.version_major  
uint8  tlscompressed.version_minor  
uint16 tlsplaintext.length  
opaque tlsplaintext.fragment

I have also tried omitting the version and using uint24 length instead.no luck.
My hmac_hash() function cannot be the problem because it has worked thus far. I am also able to compute the verify_data and verify it. Because this is the first message sent under the new connection state, the sequence number is 0.
So, what exactly are the parameters for the calculation of the MAC for the finished message?

Was it helpful?

Solution

Here's the relevant source from Forge (JS implementation of TLS 1.0):

The HMAC function:

var hmac_sha1 = function(key, seqNum, record) {
    /* MAC is computed like so:
    HMAC_hash(
      key, seqNum +
        TLSCompressed.type +
        TLSCompressed.version +
        TLSCompressed.length +
        TLSCompressed.fragment)
    */
    var hmac = forge.hmac.create();
    hmac.start('SHA1', key);
    var b = forge.util.createBuffer();
    b.putInt32(seqNum[0]);
    b.putInt32(seqNum[1]);
    b.putByte(record.type);
    b.putByte(record.version.major);
    b.putByte(record.version.minor);
    b.putInt16(record.length);
    b.putBytes(record.fragment.bytes());
    hmac.update(b.getBytes());
    return hmac.digest().getBytes();
};

The function that creates the Finished record:

tls.createFinished = function(c) {
    // generate verify_data
    var b = forge.util.createBuffer();
    b.putBuffer(c.session.md5.digest());
    b.putBuffer(c.session.sha1.digest());

    // TODO: determine prf function and verify length for TLS 1.2
    var client = (c.entity === tls.ConnectionEnd.client);
    var sp = c.session.sp;
    var vdl = 12;
    var prf = prf_TLS1;
    var label = client ? 'client finished' : 'server finished';
    b = prf(sp.master_secret, label, b.getBytes(), vdl);

    // build record fragment
    var rval = forge.util.createBuffer();
    rval.putByte(tls.HandshakeType.finished);
    rval.putInt24(b.length());
    rval.putBuffer(b);
    return rval;
};

The code to handle a Finished message is a bit lengthier and can be found here. I see that I have a comment in that code that sounds like it might be relevant to your problem:

 // rewind to get full bytes for message so it can be manually
 // digested below (special case for Finished messages because they
 // must be digested *after* handling as opposed to all others)

Does this help you spot anything in your implementation?

Update 1

Per your comments, I wanted to clarify how TLSPlainText works. TLSPlainText is the main "record" for the TLS protocol. It is the "wrapper" or "envelope" for content-specific types of messages. It always looks like this:

struct {
    ContentType type;
    ProtocolVersion version;
    uint16 length;
    opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

So it always has a version. A Finished message is a type of handshake message. All handshake messages have a content type of 22. A handshake message looks like this:

struct {
    HandshakeType msg_type;
    uint24 length;
    body
} Handshake;

A Handshake message is yet another envelope/wrapper for other messages, like the Finished message. In this case, the body will be a Finished message (HandshakeType 20), which looks like this:

struct {
    opaque verify_data[12];
} Finished;

To actually send a Finished message, you have to wrap it up in a Handshake message envelope, and then like any other message, you have to wrap it up in a TLS record (TLSPlainText). The ultimate result looks/represents something like this:

struct {
    ContentType type=22;
    ProtocolVersion version=<major, minor>;
    uint16 length=<length of fragment>;
    opaque fragment=<struct {
        HandshakeType msg_type=20;
        uint24 length=<length of finished message>;
        body=<struct {
          opaque verify_data[12]>;
        } Finished>
    } Handshake>
} TLSPlainText;

Then, before transport, the record may be altered. You can think of these alterations as operations that take a record and transform its fragment (and fragment length). The first operation compresses the fragment. After compression you compute the MAC, as described above and then append that to the fragment. Then you encrypt the fragment (adding the appropriate padding if using a block cipher) and replace it with the ciphered result. So, when you're finished, you've still got a record with a type, version, length, and fragment, but the fragment is encrypted.

So, just so we're clear, when you're computing the MAC for the Finished message, imagine passing in the above TLSPlainText (assuming there's no compression as you indicated) to a function. This function takes this TLSPlainText record, which has properties for type, version, length, and fragment. The HMAC function above is run on the record. The HMAC key and sequence number (which is 0 here) are provided via the session state. Therefore, you can see that everything the HMAC function needs is available.

In any case, hopefully this better explains how the protocol works and that will maybe reveal what's going wrong with your implementation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top