Take the 2-minute tour ×

How would you extract the Server Name Indication from a TLS Client Hello message. I'm curently struggling to understand this very cryptic RFC 3546 on TLS Extensions, in which the SNI is defined.

Things I've understood so far:

  • The host is utf8 encoded and readable when you utf8 enocde the buffer.
  • Theres one byte before the host, that determines it's length.

If I could find out the exact position of that length byte, extracting the SNI would be pretty simple. But how do I get to that byte in the first place?

share|improve this question
The straightforward approach you attempt to take is wrong. You need to parse the request including its extensions and then you get the data from the corresponding extension. –  Eugene Mayevski 'EldoS Corp Jul 24 '13 at 15:20
Yeah, I'm certain about that, but I don't actually know how to parse it. Do you understand how the TLS handshake works? –  silvinci Jul 25 '13 at 12:49
Sure, I do as we offer a security library as one of our main products. You need to open the RFC ( tools.ietf.org/html/rfc5246 ) and implement it. –  Eugene Mayevski 'EldoS Corp Jul 25 '13 at 13:16
Haha, well thank you, that's like 100 pages of pure tech. I guess things start getting interesting on page 41. This is where the extensions are mentioned, that in return are described in RFC 3546. Oh my, Oh my. :D –  silvinci Jul 25 '13 at 14:13
add comment

2 Answers

I did this in sniproxy, examining a TLS client hello packet in Wireshark while reading that RFC is a pretty good way to go. It's not too hard, just lots of variable length fields you have to skip past and check checking if you have the correct element type.

I'm working on my tests right now, and have this annotated sample packet that might help:

const unsigned char good_data_2[] = {
    // TLS record
    0x16, // Content Type: Handshake
    0x03, 0x01, // Version: TLS 1.0
        // Handshake
        0x01, // Handshake Type: Client Hello
        0x03, 0x03, // Version: TLS 1.2
        // Random (32 bytes fixed length)
            0x00, 0x01, // NULL-MD5
            0x00, 0xff, // RENEGOTIATION INFO SCSV
            0x00, // NULL
            // Extension
            0x00, 0x0c, // Server Name Indication Length
                // "localhost" (data your after)
            // Extension
            // Data
            // Extension
            0x01 // Mode: Peer allows to send requests
share|improve this answer
add comment
up vote 1 down vote accepted

I noticed that the domain is always prepend by two zero bytes and one length byte. Maybe it's unsigned 24 bit integer, but I can't test it, as my DNS server won't allow domain names beyond 77 characters.

Upon that knowledge I came up with this (Node.js) code.

function getSNI(buf) {
  var sni = null
    , regex = /^(?:[a-z0-9-]+\.)+[a-z]+$/i;
  for(var b = 0, prev, start, end, str; b < buf.length; b++) {
    if(prev === 0 && buf[b] === 0) {
      start = b + 2;
      end   = start + buf[b + 1];
      if(start < end && end < buf.length) {
        str = buf.toString("utf8", start, end);
        if(regex.test(str)) {
          sni = str;
    prev = buf[b];
  return sni;

This code looks for a sequence of two zero bytes. If it finds one, it assumes the following byte is a length parameter. It checks if the length is still in the boundary of the buffer and if so reads the byte sequence as UTF-8. Later on, one could RegEx the array and extract the domain.

Works amazingly well! Still, I noticed something odd.


Always, no matter what subdomain I choose, the domain is targeted twice. It seems like the SNI field is nested inside another field.

I am open to suggestions and improvements! :)

I turned this into a Node module, for everyone, who cares: sni.

share|improve this answer
Reason for downvote? –  silvinci Jan 8 at 21:16
I don't think regular expressions are the best way to extract data from a binary cryptographic protocol. The Client Hello message includes 32 bytes of random data that might match your regexp. –  dlundquist Feb 21 at 6:35
add comment

Your Answer


