解释由 RFC6066 服务器名称指示定义的 SSL ClientHello SNI 消息扩展语法

Explaining SSL ClientHello SNI message extension syntax defined by RFC6066 Server Name Indication

RFC6066 定义类型 server_name 扩展中的服务器名称指示。此扩展的 extension_data 字段应包含 ServerNameList 其中:

      struct {
          NameType name_type;
          select (name_type) {
              case host_name: HostName;
          } name;
      } ServerName;

      enum {
          host_name(0), (255)
      } NameType;

      opaque HostName<1..2^16-1>;

      struct {
          ServerName server_name_list<1..2^16-1>
      } ServerNameList;

最好能逐步解释这个数据结构。另外,这里是示例代码,可以找到here,如何读取扩展数据:

private static List<SNIServerName> exploreSNIExt(ByteBuffer input,
        int extLen) throws IOException {

    Map<Integer, SNIServerName> sniMap = new LinkedHashMap<>();

    int remains = extLen;
    if (extLen >= 2) {     // "server_name" extension in ClientHello
        int listLen = getInt16(input);     // length of server_name_list
        if (listLen == 0 || listLen + 2 != extLen) {
            throw new SSLProtocolException(
                    "Invalid server name indication extension");
        }

        remains -= 2;     // 0x02: the length field of server_name_list
        while (remains > 0) {
            int code = getInt8(input);      // name_type
            int snLen = getInt16(input);    // length field of server name
            if (snLen > remains) {
                throw new SSLProtocolException(
                        "Not enough data to fill declared vector size");
            }
            byte[] encoded = new byte[snLen];
            input.get(encoded);

            SNIServerName serverName;
            switch (code) {
                case StandardConstants.SNI_HOST_NAME: // 0x00
                    if (encoded.length == 0) {
                        throw new SSLProtocolException(
                                "Empty HostName in server name indication");
                    }
                    serverName = new SNIHostName(encoded);
                    break;
                default:
                    serverName = new UnknownServerName(code, encoded);
            }
            // check for duplicated server name type
            if (sniMap.put(serverName.getType(), serverName) != null) {
                throw new SSLProtocolException(
                        "Duplicated server name of type "
                        + serverName.getType());
            }

            remains -= encoded.length + 3;  // NameType: 1 byte
            // HostName length: 2 bytes
        }
    } else if (extLen == 0) {     // "server_name" extension in ServerHello
        throw new SSLProtocolException(
                "Not server name indication extension in client");
    }

    if (remains != 0) {
        throw new SSLProtocolException(
                "Invalid server name indication extension");
    }

    return Collections.<SNIServerName>unmodifiableList(
            new ArrayList<>(sniMap.values()));
}

字节reader:

private static int getInt16(ByteBuffer input) {
    return ((input.get() & 0xFF) << 8) | (input.get() & 0xFF);
}

Here 是如何读取数据的好例子。例如,扩展类型是通过读取 2 个字节来定义的 - 所以另一个问题是 - 哪个 RFC 定义了它?

如果您已经有了实现它的源代码,还需要了解什么?

用于抽象模式的格式源自XDR but is defined specifically in each TLS specification, like for the last one in 3. Presentation Language

因此,如果我们逐条进行:

  struct {
      NameType name_type;
      select (name_type) {
          case host_name: HostName;
      } name;
  } ServerName;

https://www.rfc-editor.org/rfc/rfc8446#section-3.6,这里定义了一个结构:

  • 称为“ServerName”
  • 其第一个组件称为 name_type 并且类型称为 NameType(稍后定义)
  • 其第二个和最后一个组件称为 name 并且是一个变体 (https://www.rfc-editor.org/rfc/rfc8446#section-3.8):其值取决于 之前的 name_type 内容。如果 name_type 的值为 host_name,那么这第二个组件的值是类型 HostName(稍后定义)

下一个:

enum {
      host_name(0), (255)
} NameType;

参见https://www.rfc-editor.org/rfc/rfc8446#section-3.5,它定义了一个只有一个可能值(0)的枚举,其别名是host_name

(255) 仅用于强制宽度(因此 0 到 255 值适合一个字节,此结构使用 space 的一个字节),如规范中所述:

One may optionally specify a value without its associated tag to force the width definition without defining a superfluous element.

所以这意味着你在线路上使用 0,但如果你有 0,它会在规范的其他部分编码 host_name

  opaque HostName<1..2^16-1>;

https://www.rfc-editor.org/rfc/rfc8446#section-3.2 中我们有:

Single-byte entities containing uninterpreted data are of type opaque.

而在https://www.rfc-editor.org/rfc/rfc8446#section-3.4中,<>用于定义一个变长向量(或一维数组,或列表)。

所以 HostName 是一个包含 1 到 216-1 个字节(不是元素)的向量,每个元素都是“不透明”类型的,即单字节。

请注意,在 RFC 中有关于 SNI 的进一步解释:

"HostName" contains the fully qualified DNS hostname of the server, as understood by the client. The hostname is represented as a byte string using ASCII encoding without a trailing dot.

  struct {
      ServerName server_name_list<1..2^16-1>
  } ServerNameList;

与第一个情况相同,但使用与上述相同的可变长度数组

  • 一个ServerNameList是一个结构
  • 其唯一元素是一个长度在1到2之间的可变数组16-1字节
  • 此数组的每个元素都属于 ServerName 类型,如先前定义的那样

换句话说:

  • 一个ServerNameList结构是一个元素列表,每个元素都是ServerName
  • 类型
  • 这个列表不能为空,因为它至少有 1 个字节的长度 (最多 216-1 个字节)
  • a ServerName 编码一个 name_type(只能是“host_name”,也就是值 0)和一个类型为 HostName 的名称,它是最多 216-1 个字节的非空列表,编码主机名,如 https://www.rfc-editor.org/rfc/rfc6066#section-3
  • 中所述