User talk:不爱思考得猪/SNI

页面内容不支持其他语言。
维基百科,自由的百科全书

以下是火狐浏览器源代码中关于SNI的ClientHello语句生成函数,是一个关键性函数,通过浏览器发送的任何SNI请求都必须经过此函数生成ClientHello。这段源代码来自于火狐浏览器源代码文件系统下的security/nss/lib/ssl/sslext3.c的文件:

/* Format an SNI extension, using the name from the socket's URL,
 * unless that name is a dotted decimal string.
 * Used by client and server.
 */
PRInt32
ssl3_SendServerNameXtn(sslSocket * ss, PRBool append,
                       PRUint32 maxBytes)
{
    SECStatus rv;
    if (!ss)
        return 0;
    if (!ss->sec.isServer) {
        PRUint32 len;
        PRNetAddr netAddr;

        /* must have a hostname */
        if (!ss->url || !ss->url[0])
            return 0;
        /* must not be an IPv4 or IPv6 address */
        if (PR_SUCCESS == PR_StringToNetAddr(ss->url, &netAddr)) {
            /* is an IP address (v4 or v6) */
            return 0;
        }
        len  = PORT_Strlen(ss->url);
        if (append && maxBytes >= len + 9) {
            /* extension_type */
            rv = ssl3_AppendHandshakeNumber(ss, ssl_server_name_xtn, 2);
            if (rv != SECSuccess) return -1;
            /* length of extension_data */
            rv = ssl3_AppendHandshakeNumber(ss, len + 5, 2);
            if (rv != SECSuccess) return -1;
            /* length of server_name_list */
            rv = ssl3_AppendHandshakeNumber(ss, len + 3, 2);
            if (rv != SECSuccess) return -1;
            /* Name Type (sni_host_name) */
            rv = ssl3_AppendHandshake(ss,       "\0",    1);
            if (rv != SECSuccess) return -1;
            /* HostName (length and value) */
            rv = ssl3_AppendHandshakeVariable(ss, (PRUint8 *)ss->url, len, 2);
            if (rv != SECSuccess) return -1;
            if (!ss->sec.isServer) {
                TLSExtensionData *xtnData = &ss->xtnData;
                xtnData->advertised[xtnData->numAdvertised++] =
                    ssl_server_name_xtn;
            }
        }
        return len + 9;
    }
    /* Server side */
    if (append && maxBytes >= 4) {
        rv = ssl3_AppendHandshakeNumber(ss, ssl_server_name_xtn, 2);
        if (rv != SECSuccess)  return -1;
        /* length of extension_data */
        rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
        if (rv != SECSuccess) return -1;
    }
    return 4;
}

其中关键性的代码为如下两行:

        len  = PORT_Strlen(ss->url);

以及:

            rv = ssl3_AppendHandshakeVariable(ss, (PRUint8 *)ss->url, len, 2);

其中ss->url是目标网站域名,也就是SNI的域名(也就是唯一可以被墙看见的那个域名)。为只读变量,不能修改(而且也不应该被修改,因为后续收到安全证书以后必须要能对上安全证书里的域名列表里的某一个域名,而且再后续进行HTTPS GET操作时就必须要有正确的域名才能取得正确的网页和内容)。

但是(我要说但是了!)我们可以把在以上两行里的ss->url完全替换成【另外】的一个string literal(也就是所谓的“hard-coding SNI”)。比如以下两种修改:

        len  = PORT_Strlen("wikimedia.org\0");

            rv = ssl3_AppendHandshakeVariable(ss, (PRUint8 *)"wikimedia.org\0", len, 2);
        len  = PORT_Strlen("\0");

            rv = ssl3_AppendHandshakeVariable(ss, (PRUint8 *)"\0", len, 2);

都能通过编译器编译,生成可执行火狐浏览器。我对以上两种情况分别进行了实验,有以下发现:

  • 如果hard-code空字符串\0,那么所有HTTPS连接一律报错,没有例外,也就是说如此编译出来的浏览器是完全废掉了。
  • 如果hard-code维基媒体总站域名,那么在我测试的网站中,除了Cloudflare网站不能正常工作,其它网站都能正常工作。特别有趣的是对谷歌发送维基媒体总站域名SNI也能得到正确的谷歌证书,成功打开google.com,而浏览器不会报错。

甚至可以做出如下修改:

char url[500];
scanf("%s", url);

        len  = PORT_Strlen(url);

            rv = ssl3_AppendHandshakeVariable(ss, (PRUint8 *)url, len, 2);

当然,以上修改后的火狐浏览器需要从xterm终端里启动,否则没法输入字符串。我个人从未做出或者测试过以上修改。但是我相信以上的修改是最最灵活的,因为允许用户在运行火狐浏览器的时候自行键入想要送出的明文SNI域名。

很可惜,我身在墙外,所以完全不知道这些修改能不能规避墙的SNI重置封锁。但是如果墙内朋友证实这些修改是可行的话,那么这将是非常powerful的修改。这些修改将允许墙内网友浏览维基百科直到墙SNI封杀【最后一个】维基媒体域名(现在除了维基百科和维基新闻以外基本上所有其它维基媒体域名都未被墙封杀)。而且墙内网友可以直接打开维基百科,而不需要先打开比如维基文库,然后利用HTTPS信道余热来打开维基百科。