欢迎光临
我们一直在努力

由动态修改ssl protocols引发的问题

概述

最近我一直在做动态加载的事情,希望把常用的变更都通过lua动态设置并生效到nginx内核中。
这就包括了根据SNI动态修改tls的版本和加密套件,因为理论上在SSL的client hello后才进行版本的协商,在SNI回调函数修改ssl版本应该可以行的通的,为此我修改了openresty的源码,从增加一个函数(ssl.set_ssl_protocols)到增加一个阶段(ssl_sni_by_lua_block)都尝试过,并翻看了openssl相关代码,也没有得到解决。 通过翻看openssl的代码,大概看到通过SNI的回调函数修改版本是非常困难的一件事情。不同版本的TLS_method定义的回调函数差异还是蛮大的。因此我决定测试一下nginx的ssl_protocols是否生效,最后发现也不生效。于是就想要探究一下nginx是如何对待这个问题的。

问题跟踪

搜了一下,已经有人提了该问题。
https://trac.nginx.org/nginx/ticket/1714
https://trac.nginx.org/nginx/ticket/844

The ciphers are used from the SNI-based virtual server. Protocols aren’t, and this is unlikely to change, see #844.

nginx维护者说,加密套件是可以改变的,协议版本不可以改变。

This is not something nginx can fix: OpenSSL selects the protocol to use before it calls SNI callback, so it’s not possible for nginx to impose different protocol preferences for SNI-based virtual servers. Moreover, nginx already provides all relevant preferences to OpenSSL in the SNI callback – though they are ignored due to the above.

nginx维护者说,这也不是nginx能解决的,OpenSSL在调用SNI的回调函数之前已经选择好了SSL版本,即便是nginx在SNI回调函数中做了处理,也被OpenSSL忽略了。

Another solution would be to check $ssl_protocol on HTTP level, and return an HTTP error if it’s not high enough.

nginx维护者也给出一个方案,检查$ssl_protocol变量,版本不够高就返回http错误。

也有人因Apache的ssl版本问题,在openssl提了issue:openssl/openssl#4302

we have fixed the underlying bug (no way to modify the supported TLS versions in response to the SNI value in the ClientHello) with the clienthello callback in master/1.1.1.

得到的回复是在openssl1.1.1中修复底层的bug,但是还是没有办法在SNI回调中修改SSL版本。

并且在openssl中另外一个issue中 openssl/openssl#4301

It seems like perhaps the core issue here is that SNI processing (where SSL_CTXs are commonly switched to map to a different vhost) is performed after TLS version negotiation, so even though the SSL_CTX can be changed, the TLS version has already been decided. Rich alluded to this by mentioning the early callback (new in OpenSSL 1.1.1, not yet released), which allows the application to switch SSL_CTXes before TLS version negotiation. But if this ordering between TLS version negotiation and SSL_CTX switching is in fact the key matter, there is simply nothing to be done until OpenSSL 1.1.1 is available — chanigng the ordering of SNI processing and TLS version negotiation is a major undertaking that presents far too much risk to attempt in a stable release branch.

可能在openssl1.1.1 在新加一个回调。现阶段改变TLS版本协商和SSL_CTX切换之间的顺序还是很困难的。

https://trac.nginx.org/nginx/ticket/844

No, certainly they haven’t fixed this, see my response at ​http://mailman.nginx.org/pipermail/nginx/2018-April/056036.html. The other issue linked there suggests that the new clienthello callback they added in the upcoming OpenSSL 1.1.1 can be used to implement this in some cases (note that in some cases it is simply not possible, see above), yet this is something to look into and will certainly involve more work than using dedicated servername callback as we do now.

nginx维护者说,即使在openssl1.1.1中提供clienthello回调,nginx改起来也比较麻烦。并且也不能解决所有问题。

因此目前nginx最新的代码还是使用servername回调。并且看这个意思,要根据SNI回调修改SSL结构体的options实现修改SSL版本就更难了。

结论

直到现在,nginx的该指令ssl_protocols也仅仅在default server中的是有效的。

附录:

测试配置:

    server {
        listen 127.0.0.1:8443 ssl;
        server_name   x.com;

        ssl_sni_by_lua_block {
            local ssl = require "ngx.ssl"
            local sn = ssl.server_name()
            ssl.set_ssl_protocols("SSLv2")
        }

        # ssl_certificate_by_lua_block {
        #     local ssl = require "ngx.ssl"
        #     local sn = ssl.server_name()
        #     ssl.set_ssl_protocols("SSLv2")
        #     ngx.log(ngx.WARN, "==2==========",sn)
        # }

        ssl_certificate ../../cert/test.crt;
        ssl_certificate_key ../../cert/test.key;

        server_tokens off;
        location /foo {
            default_type 'text/plain';
            content_by_lua_block {ngx.status = 201 ngx.say("xxx") ngx.exit(201)}
            more_clear_headers Date;
        }
    }

ssl.lua 中新加:

function _M.set_ssl_protocols( ... )
    local r = getfenv(0).__ngx_req
    if not r then
        return error("no request found")
    end

    local NGX_SSL_SSLv2    = 0x0002
    local NGX_SSL_SSLv3    = 0x0004
    local NGX_SSL_TLSv1    = 0x0008
    local NGX_SSL_TLSv1_1  = 0x0010
    local NGX_SSL_TLSv1_2  = 0x0020
    local NGX_SSL_TLSv1_3  = 0x0040

    local protocols_bits = {
        ["SSLv2"] = NGX_SSL_SSLv2,
        ["SSLv3"] = NGX_SSL_SSLv3,
        ["TLSv1"] = NGX_SSL_TLSv1,
        ["TLSv1.1"] = NGX_SSL_TLSv1_1,
        ["TLSv1.2"] = NGX_SSL_TLSv1_2,
        ["TLSv1.3"] = NGX_SSL_TLSv1_3
    }

    local bit = 0x0
    local protocols = { ... }
    for _, p in ipairs(protocols) do
        local b = protocols_bits[p]
        bit = bor(bit, b)
        if b == nil then
            return nil, "unknown protocol: " .. p
        end
    end

    if bit == 0x0 then
        return nil, "protocols is nil"
    end

    local rc = C.ngx_http_lua_ffi_set_ssl_protocols(r, bit, errmsg);
        if rc == FFI_OK then
        return true
    end

    return nil, ffi_str(errmsg[0])
end

添加对应的c代码

int
ngx_http_lua_ffi_set_ssl_protocols(ngx_http_request_t *r, uintptr_t protocols,
    char **err)
{
    ngx_ssl_conn_t    *ssl_conn;

    if (r->connection == NULL || r->connection->ssl == NULL) {
        *err = "bad request";
        return NGX_ERROR;
    }

    ssl_conn = r->connection->ssl->connection;
    if (ssl_conn == NULL) {
        *err = "bad ssl conn";
        return NGX_ERROR;
    }

    // SSL_set_cipher_list(ssl_conn, "AES128-SHA");

#if OPENSSL_VERSION_NUMBER >= 0x009080dfL
    SSL_clear_options(ssl_conn,
                    SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
#endif

    if (!(protocols & NGX_SSL_SSLv2)) {
        SSL_set_options(ssl_conn, SSL_OP_NO_SSLv2);
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "SSL_set_options: SSL_OP_NO_SSLv2");
    }
    if (!(protocols & NGX_SSL_SSLv3)) {
        SSL_set_options(ssl_conn, SSL_OP_NO_SSLv3);
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "SSL_set_options: SSL_OP_NO_SSLv3");
    }
    if (!(protocols & NGX_SSL_TLSv1)) {
        SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1);
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "SSL_set_options: SSL_OP_NO_TLSv1");
    }
#ifdef SSL_OP_NO_TLSv1_1
    SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_1);
    if (!(protocols & NGX_SSL_TLSv1_1)) {
        SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_1);
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "SSL_set_options: SSL_OP_NO_TLSv1_1");
    }
#endif
#ifdef SSL_OP_NO_TLSv1_2
    SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_2);
    if (!(protocols & NGX_SSL_TLSv1_2)) {
        SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_2);
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "SSL_set_options: SSL_OP_NO_TLSv1_2");
    }
#endif
#ifdef SSL_OP_NO_TLSv1_3
    SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_3);
    if (!(protocols & NGX_SSL_TLSv1_3)) {
        SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_3);
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "SSL_set_options: SSL_OP_NO_TLSv1_3");
    }
#endif

    return NGX_OK;
}
赞(1) 打赏
转载请注明来源:IT技术资讯 » 由动态修改ssl protocols引发的问题

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏