You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

523 lines
15 KiB

1 week ago
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