From 2538a16331b15477719ee8cd7db8736488fa0b58 Mon Sep 17 00:00:00 2001 From: spacewander Date: Mon, 21 Dec 2020 11:39:35 +0800 Subject: [PATCH] feat: validate certificate & key Fix #296 Fix #2816 Signed-off-by: spacewander --- apisix/admin/ssl.lua | 44 +-- apisix/ssl.lua | 111 +++++++ apisix/ssl/router/radixtree_sni.lua | 58 +--- t/admin/ssl2.t | 140 ++++++++ t/router/radixtree-sni.t | 493 +--------------------------- t/router/radixtree-sni2.t | 356 ++++++++++++++++++++ 6 files changed, 637 insertions(+), 565 deletions(-) create mode 100644 apisix/ssl.lua create mode 100644 t/router/radixtree-sni2.t diff --git a/apisix/admin/ssl.lua b/apisix/admin/ssl.lua index 1905685408d7..b97a194e64cd 100644 --- a/apisix/admin/ssl.lua +++ b/apisix/admin/ssl.lua @@ -16,11 +16,9 @@ -- local core = require("apisix.core") local utils = require("apisix.admin.utils") +local apisix_ssl = require("apisix.ssl") local tostring = tostring -local aes = require "resty.aes" -local ngx_encode_base64 = ngx.encode_base64 local type = type -local assert = assert local _M = { version = 0.1, @@ -54,37 +52,25 @@ local function check_conf(id, conf, need_id) return nil, {error_msg = "invalid configuration: " .. err} end + local ok, err = apisix_ssl.validate(conf.cert, conf.key) + if not ok then + return nil, {error_msg = err} + end + local numcerts = conf.certs and #conf.certs or 0 local numkeys = conf.keys and #conf.keys or 0 if numcerts ~= numkeys then return nil, {error_msg = "mismatched number of certs and keys"} end - return need_id and id or true -end - - -local function aes_encrypt(origin) - local local_conf = core.config.local_conf() - local iv - if local_conf and local_conf.apisix - and local_conf.apisix.ssl.key_encrypt_salt then - iv = local_conf.apisix.ssl.key_encrypt_salt - end - local aes_128_cbc_with_iv = (type(iv)=="string" and #iv == 16) and - assert(aes:new(iv, nil, aes.cipher(128, "cbc"), {iv=iv})) or nil - - if aes_128_cbc_with_iv ~= nil and core.string.has_prefix(origin, "---") then - local encrypted = aes_128_cbc_with_iv:encrypt(origin) - if encrypted == nil then - core.log.error("failed to encrypt key[", origin, "] ") - return origin + for i = 1, numcerts do + local ok, err = apisix_ssl.validate(conf.certs[i], conf.keys[i]) + if not ok then + return nil, {error_msg = "failed to handle cert-key pair[" .. i .. "]: " .. err} end - - return ngx_encode_base64(encrypted) end - return origin + return need_id and id or true end @@ -95,11 +81,11 @@ function _M.put(id, conf) end -- encrypt private key - conf.key = aes_encrypt(conf.key) + conf.key = apisix_ssl.aes_encrypt_pkey(conf.key) if conf.keys then for i = 1, #conf.keys do - conf.keys[i] = aes_encrypt(conf.keys[i]) + conf.keys[i] = apisix_ssl.aes_encrypt_pkey(conf.keys[i]) end end @@ -148,11 +134,11 @@ function _M.post(id, conf) end -- encrypt private key - conf.key = aes_encrypt(conf.key) + conf.key = apisix_ssl.aes_encrypt_pkey(conf.key) if conf.keys then for i = 1, #conf.keys do - conf.keys[i] = aes_encrypt(conf.keys[i]) + conf.keys[i] = apisix_ssl.aes_encrypt_pkey(conf.keys[i]) end end diff --git a/apisix/ssl.lua b/apisix/ssl.lua new file mode 100644 index 000000000000..47a4e345fd05 --- /dev/null +++ b/apisix/ssl.lua @@ -0,0 +1,111 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +local core = require("apisix.core") +local ngx_ssl = require("ngx.ssl") +local ngx_encode_base64 = ngx.encode_base64 +local ngx_decode_base64 = ngx.decode_base64 +local aes = require "resty.aes" +local assert = assert +local type = type + + +local _M = {} + + +local _aes_128_cbc_with_iv = false +local function get_aes_128_cbc_with_iv() + if _aes_128_cbc_with_iv == false then + local local_conf = core.config.local_conf() + local iv = core.table.try_read_attr(local_conf, "apisix", "ssl", "key_encrypt_salt") + if type(iv) =="string" and #iv == 16 then + _aes_128_cbc_with_iv = assert(aes:new(iv, nil, aes.cipher(128, "cbc"), {iv = iv})) + else + _aes_128_cbc_with_iv = nil + end + end + return _aes_128_cbc_with_iv +end + + +function _M.aes_encrypt_pkey(origin) + local aes_128_cbc_with_iv = get_aes_128_cbc_with_iv() + if aes_128_cbc_with_iv ~= nil and core.string.has_prefix(origin, "---") then + local encrypted = aes_128_cbc_with_iv:encrypt(origin) + if encrypted == nil then + core.log.error("failed to encrypt key[", origin, "] ") + return origin + end + + return ngx_encode_base64(encrypted) + end + + return origin +end + + +local function decrypt_priv_pkey(iv, key) + local decoded_key = ngx_decode_base64(key) + if not decoded_key then + core.log.error("base64 decode ssl key failed and skipped. key[", key, "] ") + return nil + end + + local decrypted = iv:decrypt(decoded_key) + if not decrypted then + core.log.error("decrypt ssl key failed and skipped. key[", key, "] ") + end + + return decrypted +end + + +local function aes_decrypt_pkey(origin) + if core.string.has_prefix(origin, "---") then + return origin + end + + local aes_128_cbc_with_iv = get_aes_128_cbc_with_iv() + if aes_128_cbc_with_iv ~= nil then + return decrypt_priv_pkey(aes_128_cbc_with_iv, origin) + end + return origin +end +_M.aes_decrypt_pkey = aes_decrypt_pkey + + +function _M.validate(cert, key) + local parsed_cert, err = ngx_ssl.parse_pem_cert(cert) + if not parsed_cert then + return nil, "failed to parse cert: " .. err + end + + key = aes_decrypt_pkey(key) + if not key then + return nil, "failed to decrypt previous encrypted key" + end + + local parsed_key, err = ngx_ssl.parse_pem_priv_key(key) + if not parsed_key then + return nil, "failed to parse key: " .. err + end + + -- TODO: check if key & cert match + return true +end + + +return _M diff --git a/apisix/ssl/router/radixtree_sni.lua b/apisix/ssl/router/radixtree_sni.lua index be96c01dd838..2aa01d2de730 100644 --- a/apisix/ssl/router/radixtree_sni.lua +++ b/apisix/ssl/router/radixtree_sni.lua @@ -17,16 +17,14 @@ local get_request = require("resty.core.base").get_request local radixtree_new = require("resty.radixtree").new local core = require("apisix.core") +local apisix_ssl = require("apisix.ssl") local ngx_ssl = require("ngx.ssl") local config_util = require("apisix.core.config_util") local ipairs = ipairs local type = type local error = error local str_find = core.string.find -local aes = require "resty.aes" -local assert = assert local str_gsub = string.gsub -local ngx_decode_base64 = ngx.decode_base64 local ssl_certificates local radixtree_router local radixtree_router_ver @@ -62,42 +60,12 @@ local function parse_pem_priv_key(sni, pkey) end -local function decrypt_priv_pkey(iv, key) - if core.string.has_prefix(key, "---") then - return key - end - - local decoded_key = ngx_decode_base64(key) - if not decoded_key then - core.log.error("base64 decode ssl key failed and skipped. key[", key, "] ") - return - end - - local decrypted = iv:decrypt(decoded_key) - if not decrypted then - core.log.error("decrypt ssl key failed and skipped. key[", key, "] ") - end - - return decrypted -end - - local function create_router(ssl_items) local ssl_items = ssl_items or {} local route_items = core.table.new(#ssl_items, 0) local idx = 0 - local local_conf = core.config.local_conf() - local iv - if local_conf and local_conf.apisix - and local_conf.apisix.ssl - and local_conf.apisix.ssl.key_encrypt_salt then - iv = local_conf.apisix.ssl.key_encrypt_salt - end - local aes_128_cbc_with_iv = (type(iv)=="string" and #iv == 16) and - assert(aes:new(iv, nil, aes.cipher(128, "cbc"), {iv=iv})) or nil - for _, ssl in config_util.iterate_values(ssl_items) do if ssl.value ~= nil and (ssl.value.status == nil or ssl.value.status == 1) then -- compatible with old version @@ -115,22 +83,18 @@ local function create_router(ssl_items) end -- decrypt private key - if aes_128_cbc_with_iv ~= nil then - if ssl.value.key then - local decrypted = decrypt_priv_pkey(aes_128_cbc_with_iv, - ssl.value.key) - if decrypted then - ssl.value.key = decrypted - end + if ssl.value.key then + local decrypted = apisix_ssl.aes_decrypt_pkey(ssl.value.key) + if decrypted then + ssl.value.key = decrypted end + end - if ssl.value.keys then - for i = 1, #ssl.value.keys do - local decrypted = decrypt_priv_pkey(aes_128_cbc_with_iv, - ssl.value.keys[i]) - if decrypted then - ssl.value.keys[i] = decrypted - end + if ssl.value.keys then + for i = 1, #ssl.value.keys do + local decrypted = apisix_ssl.aes_decrypt_pkey(ssl.value.keys[i]) + if decrypted then + ssl.value.keys[i] = decrypted end end end diff --git a/t/admin/ssl2.t b/t/admin/ssl2.t index 0235273ca9bd..a616bb014ed7 100644 --- a/t/admin/ssl2.t +++ b/t/admin/ssl2.t @@ -196,3 +196,143 @@ __DATA__ } --- response_body {"action":"delete","deleted":"1","key":"/apisix/ssl/1","node":{}} + + + +=== TEST 6: bad cert +--- config + location /t { + content_by_lua_block { + local json = require("toolkit.json") + local t = require("lib.test_admin") + local ssl_key = t.read_file("t/certs/apisix.key") + local data = {cert = [[-----BEGIN CERTIFICATE----- +MIIEojCCAwqgAwIBAgIJAK253pMhgCkxMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxDzANBgNVBAcMBlpodUhhaTEPMA0G +U/OOcSRr39Kuis/JJ+DkgHYa/PWHZhnJQBxcqXXk1bJGw9BNbhM= +-----END CERTIFICATE----- + ]], key = ssl_key, sni = "test.com"} + local code, message, res = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + json.encode(data) + ) + + if code >= 300 then + ngx.status = code + ngx.print(message) + return + end + + ngx.say(res) + } + } +--- error_code: 400 +--- response_body +{"error_msg":"failed to parse cert: PEM_read_bio_X509_AUX() failed"} + + + +=== TEST 7: bad key +--- config + location /t { + content_by_lua_block { + local json = require("toolkit.json") + local t = require("lib.test_admin") + local ssl_cert = t.read_file("t/certs/apisix.crt") + local data = {cert = ssl_cert, key = [[ +-----BEGIN RSA PRIVATE KEY----- +MIIG5AIBAAKCAYEAyCM0rqJecvgnCfOw4fATotPwk5Ba0gC2YvIrO+gSbQkyxXF5 +jhZB3W6BkWUWR4oNFLLSqcVbVDPitz/Mt46Mo8amuS6zTbQetGnBARzPLtmVhJfo +wzarryret/7GFW1/3cz+hTj9/d45i25zArr3Pocfpur5mfz3fJO8jg== +-----END RSA PRIVATE KEY-----]], sni = "test.com"} + local code, message, res = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + json.encode(data) + ) + + if code >= 300 then + ngx.status = code + ngx.print(message) + return + end + + ngx.say(res) + } + } +--- error_code: 400 +--- response_body +{"error_msg":"failed to parse key: PEM_read_bio_PrivateKey() failed"} + + + +=== TEST 8: bad certs +--- config + location /t { + content_by_lua_block { + local json = require("toolkit.json") + local t = require("lib.test_admin") + local ssl_cert = t.read_file("t/certs/apisix.crt") + local ssl_key = t.read_file("t/certs/apisix.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "t.com", + certs = { + [[-----BEGIN CERTIFICATE----- +MIIEojCCAwqgAwIBAgIJAK253pMhgCkxMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxDzANBgNVBAcMBlpodUhhaTEPMA0G +U/OOcSRr39Kuis/JJ+DkgHYa/PWHZhnJQBxcqXXk1bJGw9BNbhM= +-----END CERTIFICATE-----]] + }, + keys = {ssl_key} + } + local code, message, res = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + json.encode(data) + ) + + if code >= 300 then + ngx.status = code + ngx.print(message) + return + end + + ngx.say(res) + } + } +--- error_code: 400 +--- response_body +{"error_msg":"failed to handle cert-key pair[1]: failed to parse cert: PEM_read_bio_X509_AUX() failed"} + + + +=== TEST 9: bad keys +--- config + location /t { + content_by_lua_block { + local json = require("toolkit.json") + local t = require("lib.test_admin") + local ssl_cert = t.read_file("t/certs/apisix.crt") + local ssl_key = t.read_file("t/certs/apisix.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "t.com", + certs = {ssl_cert}, + keys = {[[-----BEGIN RSA PRIVATE KEY----- +MIIG5AIBAAKCAYEAyCM0rqJecvgnCfOw4fATotPwk5Ba0gC2YvIrO+gSbQkyxXF5 +jhZB3W6BkWUWR4oNFLLSqcVbVDPitz/Mt46Mo8amuS6zTbQetGnBARzPLtmVhJfo +wzarryret/7GFW1/3cz+hTj9/d45i25zArr3Pocfpur5mfz3fJO8jg== +-----END RSA PRIVATE KEY-----]]} + } + local code, message, res = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + json.encode(data) + ) + + if code >= 300 then + ngx.status = code + ngx.print(message) + return + end + + ngx.say(res) + } + } +--- error_code: 400 +--- response_body +{"error_msg":"failed to handle cert-key pair[1]: failed to parse key: PEM_read_bio_PrivateKey() failed"} diff --git a/t/router/radixtree-sni.t b/t/router/radixtree-sni.t index b2c5f6040229..479e0a4b87b3 100644 --- a/t/router/radixtree-sni.t +++ b/t/router/radixtree-sni.t @@ -876,500 +876,15 @@ location /t { local code, body = t.test('/apisix/admin/ssl/1', ngx.HTTP_PUT, - core.json.encode(data), - [[{ - "node": { - "value": { - "snis": ["test2.com", "*.test2.com"] - }, - "key": "/apisix/ssl/1" - }, - "action": "set" - }]] - ) - - ngx.status = code - ngx.say(body) - } -} ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 20: client request: test2.com ---- config -listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; -location /t { - content_by_lua_block { - -- etcd sync - ngx.sleep(0.2) - - do - local sock = ngx.socket.tcp() - - sock:settimeout(2000) - - local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") - if not ok then - ngx.say("failed to connect: ", err) - return - end - - ngx.say("connected: ", ok) - - local sess, err = sock:sslhandshake(nil, "test2.com", true) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - - ngx.say("ssl handshake: ", type(sess)) - end -- do - -- collectgarbage() - } -} ---- request -GET /t ---- response_body -connected: 1 -failed to do SSL handshake: handshake failed ---- error_log -decrypt ssl key failed and skipped. - - - -=== TEST 21 set ssl with multiple certificates. ---- config -location /t { - content_by_lua_block { - local core = require("apisix.core") - local t = require("lib.test_admin") - - local ssl_cert = t.read_file("t/certs/apisix.crt") - local ssl_key = t.read_file("t/certs/apisix.key") - local ssl_ecc_cert = t.read_file("t/certs/apisix_ecc.crt") - local ssl_ecc_key = t.read_file("t/certs/apisix_ecc.key") - - local data = { - cert = ssl_cert, - key = ssl_key, - certs = { ssl_ecc_cert }, - keys = { ssl_ecc_key }, - sni = "test.com", - } - - local code, body = t.test('/apisix/admin/ssl/1', - ngx.HTTP_PUT, - core.json.encode(data), - [[{ - "node": { - "value": { - "sni": "test.com" - }, - "key": "/apisix/ssl/1" - }, - "action": "set" - }]] - ) - ngx.status = code - ngx.say(body) - } -} ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 22: client request using ECC certificate ---- config -listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; -location /t { - lua_ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384; - content_by_lua_block { - -- etcd sync - - ngx.sleep(0.2) - - do - local sock = ngx.socket.tcp() - - sock:settimeout(2000) - - local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") - if not ok then - ngx.say("failed to connect: ", err) - return - end - - ngx.say("connected: ", ok) - - local sess, err = sock:sslhandshake(nil, "test.com", false) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - - ngx.say("ssl handshake: ", type(sess)) - end -- do - -- collectgarbage() - } -} ---- request -GET /t ---- response_body -connected: 1 -ssl handshake: userdata - - - -=== TEST 23: client request using RSA certificate ---- config -listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; - -location /t { - lua_ssl_ciphers ECDHE-RSA-AES256-SHA384; - content_by_lua_block { - -- etcd sync - - ngx.sleep(0.2) - - do - local sock = ngx.socket.tcp() - - sock:settimeout(2000) - - local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") - if not ok then - ngx.say("failed to connect: ", err) - return - end - - ngx.say("connected: ", ok) - - local sess, err = sock:sslhandshake(nil, "test.com", false) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - - ngx.say("ssl handshake: ", type(sess)) - end -- do - -- collectgarbage() - } -} ---- request -GET /t ---- response_body -connected: 1 -ssl handshake: userdata - - - -=== TEST 24: set ssl(sni: *.test2.com) once again ---- config -location /t { - content_by_lua_block { - local core = require("apisix.core") - local t = require("lib.test_admin") - - local ssl_cert = t.read_file("t/certs/test2.crt") - local ssl_key = t.read_file("t/certs/test2.key") - local data = {cert = ssl_cert, key = ssl_key, sni = "*.test2.com"} - - local code, body = t.test('/apisix/admin/ssl/1', - ngx.HTTP_PUT, - core.json.encode(data), - [[{ - "node": { - "value": { - "sni": "*.test2.com" - }, - "key": "/apisix/ssl/1" - }, - "action": "set" - }]] + core.json.encode(data) ) ngx.status = code - ngx.say(body) + ngx.print(body) } } --- request GET /t --- response_body -passed ---- no_error_log -[error] - - - -=== TEST 25: caching of parsed certs and pkeys ---- config -listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; - -location /t { - content_by_lua_block { - -- etcd sync - ngx.sleep(0.2) - - local work = function() - local sock = ngx.socket.tcp() - - sock:settimeout(2000) - - local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") - if not ok then - ngx.say("failed to connect: ", err) - return - end - - ngx.say("connected: ", ok) - - local sess, err = sock:sslhandshake(nil, "www.test2.com", false) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - ngx.say("ssl handshake: ", type(sess)) - local ok, err = sock:close() - ngx.say("close: ", ok, " ", err) - end -- do - - work() - work() - - -- collectgarbage() - } -} ---- request -GET /t ---- response_body eval -qr{connected: 1 -ssl handshake: userdata -close: 1 nil -connected: 1 -ssl handshake: userdata -close: 1 nil} ---- grep_error_log eval -qr/parsing (cert|(priv key)) for sni: www.test2.com/ ---- grep_error_log_out -parsing cert for sni: www.test2.com -parsing priv key for sni: www.test2.com - - - -=== TEST 26: set ssl(encrypt ssl keys with another iv) ---- config -location /t { - content_by_lua_block { - -- etcd sync - ngx.sleep(0.2) - - local core = require("apisix.core") - local t = require("lib.test_admin") - - local ssl_cert = t.read_file("t/certs/test2.crt") - local raw_ssl_key = t.read_file("t/certs/test2.key") - local ssl_key = t.aes_encrypt(raw_ssl_key) - local data = { - certs = { ssl_cert }, - keys = { ssl_key }, - snis = {"test2.com", "*.test2.com"}, - cert = ssl_cert, - key = raw_ssl_key, - } - - local code, body = t.test('/apisix/admin/ssl/1', - ngx.HTTP_PUT, - core.json.encode(data), - [[{ - "node": { - "value": { - "snis": ["test2.com", "*.test2.com"] - }, - "key": "/apisix/ssl/1" - }, - "action": "set" - }]] - ) - - ngx.status = code - ngx.say(body) - } -} ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 27: client request: test2.com (with encrypted ssl keys by mistake) ---- config -listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; -location /t { - content_by_lua_block { - -- etcd sync - ngx.sleep(0.2) - - do - local sock = ngx.socket.tcp() - - sock:settimeout(2000) - - local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") - if not ok then - ngx.say("failed to connect: ", err) - return - end - - ngx.say("connected: ", ok) - - local sess, err = sock:sslhandshake(nil, "test2.com", true) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - - ngx.say("ssl handshake: ", type(sess)) - end -- do - -- collectgarbage() - } -} ---- request -GET /t ---- response_body -connected: 1 -failed to do SSL handshake: handshake failed ---- error_log -decrypt ssl key failed and skipped. - - - -=== TEST 28: set miss_head ssl certificate ---- config -location /t { - content_by_lua_block { - local core = require("apisix.core") - local t = require("lib.test_admin") - - --TODO: check the ssl certificate in admin ssl API - local ssl_cert = t.read_file("t/certs/incorrect.crt") - local ssl_key = t.read_file("t/certs/incorrect.key") - local data = {cert = ssl_cert, key = ssl_key, sni = "www.test.com"} - - local code, body = t.test('/apisix/admin/ssl/1', - ngx.HTTP_PUT, - core.json.encode(data), - [[{ - "node": { - "value": { - "sni": "www.test.com" - }, - "key": "/apisix/ssl/1" - }, - "action": "set" - }]] - ) - - ngx.status = code - ngx.say(body) - } -} ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 29: test illegal ssl certificate ---- config -listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; -location /t { - content_by_lua_block { - -- etcd sync - ngx.sleep(0.2) - - do - local sock = ngx.socket.tcp() - - sock:settimeout(2000) - - local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") - if not ok then - ngx.say("failed to connect: ", err) - return - end - - ngx.say("connected: ", ok) - - local sess, err = sock:sslhandshake(nil, "www.test.com", true) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - end -- do - -- collectgarbage() - } -} ---- request -GET /t ---- response_body -connected: 1 -failed to do SSL handshake: handshake failed ---- error_log -base64 decode ssl key failed and skipped. - - - -=== TEST 30: client request without sni ---- config -listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; - -location /t { - content_by_lua_block { - -- etcd sync - ngx.sleep(0.2) - - do - local sock = ngx.socket.tcp() - - sock:settimeout(2000) - - local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") - if not ok then - ngx.say("failed to connect: ", err) - return - end - - local sess, err = sock:sslhandshake(nil, nil, true) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - end -- do - -- collectgarbage() - } -} ---- request -GET /t ---- response_body -failed to do SSL handshake: handshake failed ---- error_log -failed to fetch ssl config: failed to find SNI: please check if the client requests via IP or uses an outdated protocol ---- no_error_log -[alert] +{"error_msg":"failed to decrypt previous encrypted key"} +--- error_code: 400 diff --git a/t/router/radixtree-sni2.t b/t/router/radixtree-sni2.t new file mode 100644 index 000000000000..e6a87ebebd33 --- /dev/null +++ b/t/router/radixtree-sni2.t @@ -0,0 +1,356 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +log_level('debug'); +no_root_location(); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests; + +__DATA__ + +=== TEST 1 set ssl with multiple certificates. +--- config +location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("t/certs/apisix.crt") + local ssl_key = t.read_file("t/certs/apisix.key") + local ssl_ecc_cert = t.read_file("t/certs/apisix_ecc.crt") + local ssl_ecc_key = t.read_file("t/certs/apisix_ecc.key") + + local data = { + cert = ssl_cert, + key = ssl_key, + certs = { ssl_ecc_cert }, + keys = { ssl_ecc_key }, + sni = "test.com", + } + + local code, body = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "node": { + "value": { + "sni": "test.com" + }, + "key": "/apisix/ssl/1" + }, + "action": "set" + }]] + ) + ngx.status = code + ngx.say(body) + } +} +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 2: client request using ECC certificate +--- config +listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; +location /t { + lua_ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384; + content_by_lua_block { + -- etcd sync + + ngx.sleep(0.2) + + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } +} +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + + + +=== TEST 3: client request using RSA certificate +--- config +listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + +location /t { + lua_ssl_ciphers ECDHE-RSA-AES256-SHA384; + content_by_lua_block { + -- etcd sync + + ngx.sleep(0.2) + + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } +} +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata + + + +=== TEST 4: set ssl(sni: *.test2.com) once again +--- config +location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("t/certs/test2.crt") + local ssl_key = t.read_file("t/certs/test2.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "*.test2.com"} + + local code, body = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "node": { + "value": { + "sni": "*.test2.com" + }, + "key": "/apisix/ssl/1" + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } +} +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 5: caching of parsed certs and pkeys +--- config +listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + +location /t { + content_by_lua_block { + -- etcd sync + ngx.sleep(0.2) + + local work = function() + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "www.test2.com", false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + ngx.say("ssl handshake: ", type(sess)) + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + + work() + work() + + -- collectgarbage() + } +} +--- request +GET /t +--- response_body eval +qr{connected: 1 +ssl handshake: userdata +close: 1 nil +connected: 1 +ssl handshake: userdata +close: 1 nil} +--- grep_error_log eval +qr/parsing (cert|(priv key)) for sni: www.test2.com/ +--- grep_error_log_out +parsing cert for sni: www.test2.com +parsing priv key for sni: www.test2.com + + + +=== TEST 6: set ssl(encrypt ssl keys with another iv) +--- config +location /t { + content_by_lua_block { + -- etcd sync + ngx.sleep(0.2) + + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("t/certs/test2.crt") + local raw_ssl_key = t.read_file("t/certs/test2.key") + local ssl_key = t.aes_encrypt(raw_ssl_key) + local data = { + certs = { ssl_cert }, + keys = { ssl_key }, + snis = {"test2.com", "*.test2.com"}, + cert = ssl_cert, + key = raw_ssl_key, + } + + local code, body = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + core.json.encode(data) + ) + + ngx.status = code + ngx.print(body) + } +} +--- request +GET /t +--- error_code: 400 +--- response_body +{"error_msg":"failed to handle cert-key pair[1]: failed to decrypt previous encrypted key"} + + + +=== TEST 7: set miss_head ssl certificate +--- config +location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("t/certs/incorrect.crt") + local ssl_key = t.read_file("t/certs/incorrect.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "www.test.com"} + + local code, body = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + core.json.encode(data) + ) + + ngx.status = code + ngx.print(body) + } +} +--- request +GET /t +--- response_body +{"error_msg":"failed to parse cert: PEM_read_bio_X509_AUX() failed"} +--- error_code: 400 +--- no_error_log +[alert] + + + +=== TEST 8: client request without sni +--- config +listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + +location /t { + content_by_lua_block { + -- etcd sync + ngx.sleep(0.2) + + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local sess, err = sock:sslhandshake(nil, nil, true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + end -- do + -- collectgarbage() + } +} +--- request +GET /t +--- response_body +failed to do SSL handshake: handshake failed +--- error_log +failed to fetch ssl config: failed to find SNI: please check if the client requests via IP or uses an outdated protocol +--- no_error_log +[alert]