Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: validate certificate & key #3085

Merged
merged 1 commit into from
Dec 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 15 additions & 29 deletions apisix/admin/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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


Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down
111 changes: 111 additions & 0 deletions apisix/ssl.lua
Original file line number Diff line number Diff line change
@@ -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
58 changes: 11 additions & 47 deletions apisix/ssl/router/radixtree_sni.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading