523 lines
15 KiB
Ruby
523 lines
15 KiB
Ruby
require 'date'
|
||
require 'zip'
|
||
require 'i18n'
|
||
require_relative 'runner'
|
||
|
||
class CertManager
|
||
attr_accessor :error, :log, :root_ca
|
||
|
||
def initialize()
|
||
@root_ca = gt_root_dir
|
||
if @root_ca.nil?
|
||
@error = I18n.t('errors.cannot_determine_root_dir')
|
||
end
|
||
end
|
||
|
||
def log?
|
||
@log
|
||
end
|
||
|
||
def rootca?
|
||
@root_ca
|
||
end
|
||
|
||
def error?
|
||
!(@error.nil? || @error.strip == '')
|
||
end
|
||
|
||
def add_cert(days, domains_ips_list)
|
||
begin
|
||
@error = nil
|
||
@log = nil
|
||
new_cert_info = nil
|
||
days = begin
|
||
Integer(days)
|
||
rescue StandardError
|
||
nil
|
||
end
|
||
raise ArgumentError, I18n.t('errors.argument_error_days') if days.nil? || days <= 0
|
||
|
||
domains_ips = domains_ips_list.split(/[\s,]+/).reject(&:empty?).uniq
|
||
raise ArgumentError, I18n.t('errors.argument_error_domains_ips') if domains_ips.empty?
|
||
|
||
result = ''
|
||
current_directory = Dir.pwd
|
||
Dir.chdir('utils') do
|
||
cmd_args = %Q(bash ./make_server_cert.sh -t #{days} #{domains_ips.join(' ')} 2>&1)
|
||
cmd = Runner.new(cmd_args)
|
||
cmd.run_clean
|
||
result = cmd.stdout
|
||
raise StandardError, I18n.t('errors.command_execution_error', command: cmd_args) if cmd.exit_status != 0
|
||
result.each_line do |line|
|
||
if line =~ /\[OUTPUTDATA_CERT\]/
|
||
match = line.match(/([^\/]*?)\.cert\.pem\.(\d+)/)
|
||
if match
|
||
new_cert_info = { name: match[1], seq: match[2] }
|
||
break
|
||
end
|
||
end
|
||
end
|
||
raise StandardError, I18n.t('errors.no_result_file') if new_cert_info.nil?
|
||
end
|
||
rescue ArgumentError => e
|
||
@error = e.message
|
||
rescue StandardError => e
|
||
@error = e.message
|
||
@log = result
|
||
Dir.chdir(current_directory)
|
||
ensure
|
||
end
|
||
@log = result
|
||
if new_cert_info.nil?
|
||
nil
|
||
else
|
||
get_cert_id_by_name(domains_ips[0], new_cert_info[:seq], new_cert_info[:name], 's')
|
||
end
|
||
end
|
||
|
||
def add_client_cert(server_domain, client_id, days)
|
||
begin
|
||
@error = nil
|
||
@log = nil
|
||
new_cert_info = nil
|
||
raise ArgumentError, I18n.t('errors.argument_error_server_domain') if server_domain.strip.empty?
|
||
raise ArgumentError, I18n.t('errors.argument_error_client_id') if client_id.strip.empty?
|
||
|
||
days = begin
|
||
Integer(days)
|
||
rescue StandardError
|
||
nil
|
||
end
|
||
raise ArgumentError, I18n.t('errors.argument_error_days') if days.nil? || days <= 0
|
||
|
||
@error = nil
|
||
result = ''
|
||
current_directory = Dir.pwd
|
||
Dir.chdir('utils') do
|
||
cmd_args = %Q(bash ./make_client_cert.sh -s #{server_domain} -c #{client_id} -d #{days} 2>&1)
|
||
cmd = Runner.new(cmd_args)
|
||
cmd.run_clean
|
||
result = cmd.stdout
|
||
raise StandardError, I18n.t('errors.command_execution_error', command: cmd_args) if cmd.exit_status != 0
|
||
result.each_line do |line|
|
||
if line =~ /\[OUTPUTDATA_CERT\]/
|
||
match = line.match(/([^\/]*?)\.cert\.pem\.(\d+)/)
|
||
if match
|
||
new_cert_info = { name: match[1], seq: match[2] }
|
||
break
|
||
end
|
||
end
|
||
end
|
||
raise StandardError, I18n.t('errors.no_result_file') if new_cert_info.nil?
|
||
end
|
||
rescue ArgumentError => e
|
||
@error = e.message
|
||
rescue StandardError => e
|
||
@error = e.message
|
||
Dir.chdir(current_directory)
|
||
ensure
|
||
end
|
||
@log = result
|
||
if new_cert_info.nil?
|
||
nil
|
||
else
|
||
get_cert_id_by_name(server_domain, new_cert_info[:seq], new_cert_info[:name], 'c')
|
||
end
|
||
end
|
||
|
||
def get_server_certs
|
||
get_list_certs('s')
|
||
end
|
||
|
||
def get_clients_certs(server_domain)
|
||
list = get_list_certs('c')
|
||
if server_domain == ''
|
||
list
|
||
else
|
||
filtered_list = list.select { |entry| entry[:ui][:CN] == server_domain }
|
||
filtered_list.sort_by! { |entry| entry[:id] }
|
||
end
|
||
end
|
||
|
||
def get_cert_info(id)
|
||
@log = ""
|
||
@error = nil
|
||
list_certs = get_list_certs('*')
|
||
target_id = id
|
||
found_entry = list_certs.find do |entry|
|
||
entry[:id] == target_id
|
||
end
|
||
if found_entry
|
||
return found_entry
|
||
else
|
||
@error = I18n.t('errors.record_not_found')
|
||
return nil
|
||
end
|
||
end
|
||
|
||
def get_detail_cert_info(id)
|
||
@log = nil
|
||
@error = nil
|
||
cert_info = { common: nil, revoke: nil, is_client: nil, name: nil, id: id }
|
||
|
||
if @root_ca.nil?
|
||
@error = I18n.t('errors.root_ca_not_detected')
|
||
return cert_info
|
||
end
|
||
|
||
cert_item_data = get_cert_info(id)
|
||
|
||
if !@error.nil? || cert_item_data.nil?
|
||
return cert_info
|
||
end
|
||
|
||
cert_item = get_cert_path(cert_item_data)
|
||
cert_path = if cert_item[:is_client]
|
||
cert_item[:client]
|
||
else
|
||
cert_item[:server]
|
||
end
|
||
|
||
unless File.exist?(cert_path)
|
||
@error = I18n.t('errors.root_ca_not_detected')
|
||
return cert_info
|
||
end
|
||
|
||
cmd_args = %Q(openssl x509 -in "#{cert_path}" -text -noout 2>&1)
|
||
cmd = Runner.new(cmd_args)
|
||
cmd.run_clean
|
||
if cmd.exit_status != 0
|
||
@error = I18n.t('errors.cannot_get_certificate_info')
|
||
@log = cmd.stdout
|
||
return cert_info
|
||
end
|
||
|
||
cert_info[:common] = cmd.stdout
|
||
|
||
cmd_args = %Q(openssl verify -crl_check_all -CAfile "#{@root_ca}/ca/intermediate/certs/ca-chain.cert.pem" -CRLfile "#{@root_ca}/ca/intermediate/crl/ca-full.crl.pem" "#{cert_path}" 2>&1)
|
||
cmd = Runner.new(cmd_args)
|
||
cmd.run_clean
|
||
cert_info[:revoke] = cmd.stdout
|
||
cert_info[:name] = "/#{cert_item_data[:ui][:O]}/#{cert_item_data[:ui][:CN]}/"
|
||
|
||
cert_info
|
||
end
|
||
|
||
def revoke_certificat(id)
|
||
@error = nil
|
||
@log = nil
|
||
cert_info = get_cert_info(id)
|
||
if cert_info.nil?
|
||
nil
|
||
else
|
||
cert_data = get_cert_path(cert_info)
|
||
if cert_data[:is_client]
|
||
revoke_client_cert(cert_data[:server_name], cert_data[:client_id], cert_data[:seq])
|
||
else
|
||
revoke_cert(cert_data[:server_name], cert_data[:seq])
|
||
end
|
||
if @error.nil?
|
||
cert_info = get_cert_info(id)
|
||
if cert_info.nil?
|
||
nil
|
||
else
|
||
cert_info[:is_client] = cert_data[:is_client]
|
||
cert_info
|
||
end
|
||
else
|
||
nil
|
||
end
|
||
end
|
||
end
|
||
|
||
def get_cert_binary(id)
|
||
@error = nil
|
||
@log = nil
|
||
files_list = []
|
||
readme_txt = ""
|
||
cert_data = get_cert_info(id)
|
||
if cert_data.nil?
|
||
nil
|
||
else
|
||
cert_path = get_cert_path(cert_data)
|
||
if cert_path[:is_client]
|
||
files_list << cert_path[:client]
|
||
files_list << "#{@root_ca}/ca/client_certs/#{cert_path[:server_name]}/private/#{cert_path[:client_id]}_private.key.pem"
|
||
files_list << "#{@root_ca}/ca/intermediate/certs/ca-chain.cert.pem"
|
||
readme_txt = I18n.t('messages.client_readme', private_key: File.basename(files_list[1]), server_cert: File.basename(files_list[0]), ca_chain: File.basename(files_list[2]))
|
||
else
|
||
files_list << cert_path[:server]
|
||
files_list << "#{@root_ca}/ca/intermediate/private/#{cert_path[:server_name]}.key.pem"
|
||
files_list << "#{@root_ca}/ca/intermediate/certs/ca-chain.cert.pem"
|
||
files_list << "#{@root_ca}/ca/intermediate/crl/ca-full.crl.pem"
|
||
readme_txt = I18n.t('messages.server_readme', private_key: File.basename(files_list[1]), server_cert: File.basename(files_list[0]), ca_chain: File.basename(files_list[2]), crl: File.basename(files_list[3]))
|
||
end
|
||
if files_list.all? { |file| File.exist?(file) }
|
||
zip_memory = Zip::OutputStream.write_buffer do |zos|
|
||
files_list.each do |file|
|
||
zos.put_next_entry(File.basename(file))
|
||
File.open(file, 'rb') { |f| zos.write f.read }
|
||
end
|
||
text_entry_name = 'readme.txt'
|
||
zos.put_next_entry(text_entry_name)
|
||
zos.write readme_txt
|
||
end
|
||
{ zip: zip_memory.string, is_client: cert_path[:is_client] }
|
||
else
|
||
@error = I18n.t('errors.root_ca_not_detected')
|
||
return nil
|
||
end
|
||
end
|
||
end
|
||
|
||
def get_root_info
|
||
@log = nil
|
||
@error = nil
|
||
cert_info = { common: nil, revoke: nil, is_client: nil, name: nil, id: nil }
|
||
|
||
if @root_ca.nil?
|
||
@error = I18n.t('errors.root_ca_not_detected')
|
||
return cert_info
|
||
end
|
||
|
||
org_nm = nil
|
||
config_sh = File.read('utils/custom_config.sh')
|
||
match = config_sh.match(/ORG_NAME="([^"]+)"/)
|
||
org_nm = match[1] if match
|
||
return nil if org_nm.nil?
|
||
|
||
cert_path = "#{root_ca}/ca/root/certs/ca.cert.pem"
|
||
|
||
unless File.exist?(cert_path)
|
||
@error = I18n.t('errors.root_ca_not_detected')
|
||
return cert_info
|
||
end
|
||
|
||
cmd_args = %Q(openssl x509 -in "#{cert_path}" -text -noout 2>&1)
|
||
cmd = Runner.new(cmd_args)
|
||
cmd.run_clean
|
||
if cmd.exit_status != 0
|
||
@error = I18n.t('errors.cannot_get_certificate_info')
|
||
@log = cmd.stdout
|
||
return cert_info
|
||
end
|
||
|
||
cert_info[:common] = cmd.stdout
|
||
|
||
cmd_args = %Q(openssl verify -crl_check_all -CAfile "#{cert_path}" -CRLfile "#{@root_ca}/ca/root/crl/ca.crl.pem" "#{cert_path}" 2>&1)
|
||
cmd = Runner.new(cmd_args)
|
||
cmd.run_clean
|
||
cert_info[:revoke] = cmd.stdout
|
||
cert_info[:name] = "/CN=#{org_nm}/"
|
||
|
||
cert_info
|
||
end
|
||
|
||
private
|
||
|
||
def revoke_cert(server_domain, seq)
|
||
current_dir = Dir.pwd
|
||
begin
|
||
@log = nil
|
||
raise ArgumentError, I18n.t('errors.argument_error_server_domain') if server_domain.strip.empty?
|
||
|
||
@error = nil
|
||
result = ''
|
||
Dir.chdir('utils') do
|
||
cmd_args = if seq.nil? || seq.empty?
|
||
%Q(bash ./make_server_revoke.sh -s #{server_domain} 2>&1)
|
||
else
|
||
%Q(bash ./make_server_revoke.sh -n #{seq} -s #{server_domain} 2>&1)
|
||
end
|
||
cmd = Runner.new(cmd_args)
|
||
cmd.run_clean
|
||
result = cmd.stdout
|
||
raise StandardError, I18n.t('errors.command_execution_error', command: cmd_args) if cmd.exit_status != 0
|
||
end
|
||
rescue ArgumentError => e
|
||
@error = e.message
|
||
rescue StandardError => e
|
||
@error = e.message
|
||
Dir.chdir(current_dir)
|
||
ensure
|
||
end
|
||
@log = result
|
||
end
|
||
|
||
def revoke_client_cert(server_domain, client_id, seq)
|
||
current_dir = Dir.pwd
|
||
begin
|
||
@log = nil
|
||
raise ArgumentError, I18n.t('errors.argument_error_server_domain') if server_domain.strip.empty?
|
||
raise ArgumentError, I18n.t('errors.argument_error_client_id') if client_id.strip.empty?
|
||
|
||
@error = nil
|
||
result = ''
|
||
Dir.chdir('utils') do
|
||
cmd_args = if seq.nil? || seq.empty?
|
||
%Q(bash ./make_client_revoke.sh -s #{server_domain} -c #{client_id} 2>&1)
|
||
else
|
||
%Q(bash ./make_client_revoke.sh -s #{server_domain} -c #{client_id} -n #{seq} 2>&1)
|
||
end
|
||
cmd = Runner.new(cmd_args)
|
||
cmd.run_clean
|
||
result = cmd.stdout
|
||
raise StandardError, I18n.t('errors.command_execution_error', command: cmd_args) if cmd.exit_status != 0
|
||
end
|
||
rescue ArgumentError => e
|
||
@error = e.message
|
||
rescue StandardError => e
|
||
@error = e.message
|
||
Dir.chdir(current_dir)
|
||
ensure
|
||
end
|
||
@log = result
|
||
end
|
||
|
||
def gt_root_dir
|
||
root_ca = nil
|
||
|
||
config_sh = File.read('utils/custom_config.sh')
|
||
match = config_sh.match(/ROOT_DIR="([^"]+)"/)
|
||
root_ca = match[1] if match
|
||
root_ca
|
||
end
|
||
|
||
def get_cert_path(item)
|
||
cl_name = item[:ui][:O].split(":")
|
||
cert_file = if cl_name.length > 1
|
||
"#{@root_ca}/ca/client_certs/#{item[:ui][:CN]}/#{cl_name[0]}.cert.pem.#{cl_name[1]}"
|
||
else
|
||
"#{@root_ca}/ca/client_certs/#{item[:ui][:CN]}/#{cl_name[0]}.cert.pem"
|
||
end
|
||
sr_name = item[:ui][:CN]
|
||
serv_file = if cl_name.length > 1
|
||
"#{@root_ca}/ca/intermediate/certs/#{sr_name}.cert.pem.#{cl_name[1]}"
|
||
else
|
||
"#{@root_ca}/ca/intermediate/certs/#{sr_name}.cert.pem"
|
||
end
|
||
is_client = File.exist?(cert_file)
|
||
seq = if cl_name.length > 1
|
||
cl_name[1]
|
||
else
|
||
nil
|
||
end
|
||
{ client: cert_file, server: serv_file, is_client: is_client, server_name: sr_name, seq: seq, client_id: cl_name[0] }
|
||
end
|
||
|
||
def get_list_certs(type)
|
||
|
||
if @root_ca.nil?
|
||
@error = I18n.t('errors.root_ca_not_detected')
|
||
return []
|
||
end
|
||
|
||
index_txt_path = "#{@root_ca}/ca/intermediate/index.txt"
|
||
unless File.exist?(index_txt_path)
|
||
@error = I18n.t('errors.root_ca_not_detected')
|
||
return []
|
||
end
|
||
|
||
ca_index_txt = File.read(index_txt_path, encoding: 'utf-8').split("\n").each_with_object([]) do |line, entries|
|
||
match = line.split("\t")
|
||
next if match.length != 6
|
||
|
||
exp = false
|
||
date_tm = parse_time_string(match[1])
|
||
if date_tm.nil?
|
||
date_tm = "нет даты"
|
||
else
|
||
exp = date_tm[1] < DateTime.now
|
||
date_tm = date_tm[0]
|
||
end
|
||
|
||
date_tm_revoke = parse_time_string(match[2])
|
||
if date_tm_revoke.nil?
|
||
date_tm_revoke = "нет даты"
|
||
else
|
||
date_tm_revoke = date_tm_revoke[0]
|
||
end
|
||
|
||
prep = { id: match[3], status: match[0], date: date_tm, fld: match[4], ui: nil, revoke_date: date_tm_revoke, expired: exp }
|
||
|
||
parts = match[5].split('/').reject(&:empty?).map(&:strip)
|
||
cert_info = {}
|
||
parts.each do |part|
|
||
key, value = part.split('=', 2)
|
||
key_downcased = key.upcase
|
||
cert_info[key_downcased.to_sym] = value || 'default_value'
|
||
end
|
||
|
||
prep[:ui] = cert_info
|
||
|
||
cl_name = prep[:ui][:O].split(":")
|
||
cert_file = if cl_name.length > 1
|
||
"#{@root_ca}/ca/client_certs/#{prep[:ui][:CN]}/#{cl_name[0]}.cert.pem.#{cl_name[1]}"
|
||
else
|
||
"#{@root_ca}/ca/client_certs/#{prep[:ui][:CN]}/#{cl_name[0]}.cert.pem"
|
||
end
|
||
|
||
if type == '*'
|
||
entries << prep
|
||
else
|
||
if File.exist?(cert_file)
|
||
entries << prep if type == 'c'
|
||
elsif type == 's'
|
||
entries << prep
|
||
end
|
||
end
|
||
end
|
||
|
||
ca_index_txt.sort_by! { |entry| entry[:id] }
|
||
|
||
ca_index_txt
|
||
end
|
||
|
||
def parse_time_string(str)
|
||
return nil if str.nil? || str.length != 13 || !str[10..11].match?(/[0-9]{2}/)
|
||
year = str[0..1].to_i + 2000 # Первые два символа - год
|
||
month = str[2..3].to_i # Следующие два символа - месяц
|
||
day = str[4..5].to_i # Еще два символа - день
|
||
hour = str[6..7].to_i # Следующие два символа - часы
|
||
minute = str[8..9].to_i # Еще два символа - минуты
|
||
second = str[10..11].to_i # Последние два символа - секунды
|
||
utc_offset = 0 # По умолчанию считаем, что строка содержит Z, т.е. UTC
|
||
[ DateTime.new(year, month, day, hour, minute, second, utc_offset).strftime('%d-%m-%y %H:%M:%S'),
|
||
DateTime.new(year, month, day, hour, minute, second, utc_offset) ]
|
||
end
|
||
|
||
def get_cert_id_by_name(server_name, seq, org_name, type)
|
||
org_nm = nil
|
||
config_sh = File.read('utils/custom_config.sh')
|
||
match = config_sh.match(/ORG_NAME="([^"]+)"/)
|
||
org_nm = match[1] if match
|
||
return nil if org_nm.nil?
|
||
list_certs = get_list_certs('*')
|
||
|
||
if type == 's'
|
||
|
||
found_entry = list_certs.find do |entry|
|
||
if seq == ''
|
||
"#{org_nm}" == entry[:ui][:O] && server_name == entry[:ui][:CN]
|
||
else
|
||
"#{org_nm}:#{seq}" == entry[:ui][:O] && server_name == entry[:ui][:CN]
|
||
end
|
||
end
|
||
else
|
||
found_entry = list_certs.find do |entry|
|
||
if seq == ''
|
||
"#{org_name}" == entry[:ui][:O] && server_name == entry[:ui][:CN]
|
||
else
|
||
"#{org_name}:#{seq}" == entry[:ui][:O] && server_name == entry[:ui][:CN]
|
||
end
|
||
end
|
||
end
|
||
|
||
if found_entry
|
||
found_entry
|
||
else
|
||
nil
|
||
end
|
||
end
|
||
end
|