diff --git a/.travis.yml b/.travis.yml index 4aebcf6996..98c2def936 100644 --- a/.travis.yml +++ b/.travis.yml @@ -106,7 +106,7 @@ install: - git clone https://github.com/openresty/rds-json-nginx-module.git ../rds-json-nginx-module - git clone https://github.com/openresty/srcache-nginx-module.git ../srcache-nginx-module - git clone https://github.com/openresty/redis2-nginx-module.git ../redis2-nginx-module - - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core + - git clone https://github.com/bzp2010/lua-resty-core.git -b "bzp/feat-tcpsock-sslhandshake-alpn" ../lua-resty-core - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache - git clone https://github.com/openresty/lua-resty-mysql.git ../lua-resty-mysql - git clone https://github.com/spacewander/lua-resty-rsa.git ../lua-resty-rsa diff --git a/README.markdown b/README.markdown index 8c6edec844..9545e365fa 100644 --- a/README.markdown +++ b/README.markdown @@ -8191,7 +8191,7 @@ This method was first introduced in the `v0.10.22` release. tcpsock:sslhandshake -------------------- -**syntax:** *session, err = tcpsock:sslhandshake(reused_session?, server_name?, ssl_verify?, send_status_req?)* +**syntax:** *session, err = tcpsock:sslhandshake(reused_session?, server_name?, ssl_verify?, send_status_req?, alpn?)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** @@ -8227,6 +8227,10 @@ to validate the server name in the server certificate. The optional `send_status_req` argument takes a boolean that controls whether to send the OCSP status request in the SSL handshake request (which is for requesting OCSP stapling). +The optional `alpn` argument specifies the Application-Layer Protocol Negotiation (ALPN) +extension value to be sent during the SSL handshake. It accepts a Lua table where each +element is a string representing a protocol name. For example: `{ "h2", "http/1.1" }`. + For connections that have already done SSL/TLS handshake, this method returns immediately. diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c index 85cf49069e..d897eeec9a 100644 --- a/src/ngx_http_lua_socket_tcp.c +++ b/src/ngx_http_lua_socket_tcp.c @@ -1676,7 +1676,7 @@ ngx_http_lua_ffi_socket_tcp_sslhandshake(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, ngx_ssl_session_t *sess, int enable_session_reuse, ngx_str_t *server_name, int verify, int ocsp_status_req, STACK_OF(X509) *chain, EVP_PKEY *pkey, - const char **errmsg) + ngx_str_t *alpn, const char **errmsg) { ngx_int_t rc, i; ngx_connection_t *c; @@ -1843,6 +1843,29 @@ ngx_http_lua_ffi_socket_tcp_sslhandshake(ngx_http_request_t *r, #endif } + if (alpn != NULL && alpn->data != NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua ssl ALPN: \"%V\"", alpn); + +#if (defined TLSEXT_TYPE_application_layer_protocol_negotiation) + if (alpn->len > 255) { + *errmsg = "too large ALPN list"; + return NGX_ERROR; + } + + if (SSL_set_alpn_protos(c->ssl->connection, + alpn->data, + (unsigned int) (alpn->len)) != 0) + { + *errmsg = "SSL_set_alpn_protos failed"; + return NGX_ERROR; + } +#else + *errmsg = "no ALPN support"; + return NGX_ERROR; +#endif + } + if (server_name == NULL || server_name->len == 0) { u->ssl_name.len = 0; diff --git a/t/129-ssl-socket.t b/t/129-ssl-socket.t index 5651f3f817..1ec2c3aed0 100644 --- a/t/129-ssl-socket.t +++ b/t/129-ssl-socket.t @@ -3046,3 +3046,141 @@ SSL reused session [error] [alert] --- timeout: 10 + + + +=== TEST 36: sslhandshake (ALPN, h2) +--- skip_openssl: 8: < 1.1.1 +--- http_config + server { + listen $TEST_NGINX_SERVER_SSL_PORT ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.3; + + http2 on; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } +--- config + server_tokens off; + lua_ssl_protocols TLSv1.3; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + do + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_SSL_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local session, err = sock:sslhandshake(nil, "test.com", nil, nil, {"h2"}) + if not session then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(session)) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + collectgarbage() + } + } +--- request +GET /t +--- response_body_like +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- log_level: debug +--- error_log +SSL ALPN supported by client: h2 +--- no_error_log +SSL reused session +[error] +[alert] +--- timeout: 10 + + + +=== TEST 37: sslhandshake (ALPN, h2, http/1.1, my-proto) +--- skip_openssl: 8: < 1.1.1 +--- http_config + server { + listen $TEST_NGINX_SERVER_SSL_PORT ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.3; + + http2 on; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } +--- config + server_tokens off; + lua_ssl_protocols TLSv1.3; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + do + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_SSL_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local session, err = sock:sslhandshake(nil, "test.com", nil, nil, {"h2", "http/1.1", "my-proto"}) + if not session then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(session)) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + collectgarbage() + } + } +--- request +GET /t +--- response_body_like +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- log_level: debug +--- error_log +SSL ALPN supported by client: h2 +SSL ALPN supported by client: http/1.1 +SSL ALPN supported by client: my-proto +--- no_error_log +SSL reused session +[error] +[alert] +--- timeout: 10