mirror of
https://github.com/internetee/registry.git
synced 2025-07-28 13:36:15 +02:00
feat: Implement P12 certificate generation improvements
- Replace hardcoded P12 password with randomly generated one - Add p12_password column to certificates table - Update certificate serializer to include p12 password in response - Remove deprecated certificate revocation logic - Add tests for certificate revocation functionality - Implement async P12 generation via Sidekiq job - Add job uniqueness to prevent parallel certificate generation Migration changes: - Replace p12_password_digest with p12_password column - Add safety measures for column removal
This commit is contained in:
parent
072f4440e2
commit
ae96863b88
13 changed files with 157 additions and 64 deletions
|
@ -13,8 +13,8 @@ module Repp
|
|||
api_user_id = p12_params[:api_user_id]
|
||||
render_error(I18n.t('errors.messages.not_found'), :not_found) and return if api_user_id.blank?
|
||||
|
||||
certificate = ::Certificates::CertificateGenerator.new(api_user_id: api_user_id, interface: 'registrar').execute
|
||||
render_success(data: { certificate: certificate })
|
||||
P12GeneratorJob.perform_later(api_user_id)
|
||||
render_success(message: 'P12 certificate generation started. Please refresh the page in a few seconds.')
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -98,7 +98,6 @@ module Repp
|
|||
end
|
||||
|
||||
def notify_admins
|
||||
# Simply use AdminUser model to get all admin emails
|
||||
admin_users_emails = AdminUser.pluck(:email).reject(&:blank?)
|
||||
|
||||
return if admin_users_emails.empty?
|
||||
|
|
17
app/jobs/p12_generator_job.rb
Normal file
17
app/jobs/p12_generator_job.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class P12GeneratorJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
sidekiq_options(
|
||||
unique: :until_executed,
|
||||
lock_timeout: 1.hour
|
||||
)
|
||||
|
||||
def perform(api_user_id)
|
||||
api_user = ApiUser.find(api_user_id)
|
||||
|
||||
Certificates::CertificateGenerator.new(
|
||||
api_user_id: api_user_id,
|
||||
interface: 'registrar'
|
||||
).execute
|
||||
end
|
||||
end
|
|
@ -4,6 +4,8 @@ class Certificate < ApplicationRecord
|
|||
include Versions
|
||||
include Certificate::CertificateConcern
|
||||
|
||||
self.ignored_columns = ["p12_password_digest"]
|
||||
|
||||
belongs_to :api_user
|
||||
|
||||
SIGNED = 'signed'.freeze
|
||||
|
@ -20,6 +22,13 @@ class Certificate < ApplicationRecord
|
|||
scope 'registrar', -> { where(interface: REGISTRAR) }
|
||||
scope 'unrevoked', -> { where(revoked: false) }
|
||||
|
||||
# Базовые причины отзыва (самые частые)
|
||||
REVOCATION_REASONS = {
|
||||
unspecified: 0,
|
||||
key_compromise: 1,
|
||||
cessation_of_operation: 5
|
||||
}.freeze
|
||||
|
||||
validate :validate_csr_and_crt_presence
|
||||
def validate_csr_and_crt_presence
|
||||
return if csr.try(:scrub).present? || crt.try(:scrub).present?
|
||||
|
@ -65,14 +74,14 @@ class Certificate < ApplicationRecord
|
|||
return nil if p12.blank?
|
||||
|
||||
decoded_p12 = Base64.decode64(p12)
|
||||
OpenSSL::PKCS12.new(decoded_p12, Certificates::CertificateGenerator::P12_PASSWORD)
|
||||
OpenSSL::PKCS12.new(decoded_p12, p12_password)
|
||||
rescue OpenSSL::PKCS12::PKCS12Error => e
|
||||
Rails.logger.error("Failed to parse PKCS12: #{e.message}")
|
||||
nil
|
||||
end
|
||||
|
||||
def revoked?
|
||||
status == REVOKED
|
||||
revoked
|
||||
end
|
||||
|
||||
def revokable?
|
||||
|
@ -81,17 +90,10 @@ class Certificate < ApplicationRecord
|
|||
|
||||
def status
|
||||
return UNSIGNED if crt.blank?
|
||||
return @cached_status if @cached_status
|
||||
|
||||
@cached_status = SIGNED
|
||||
|
||||
if certificate_expired?
|
||||
@cached_status = EXPIRED
|
||||
elsif certificate_revoked?
|
||||
@cached_status = REVOKED
|
||||
end
|
||||
|
||||
@cached_status
|
||||
return REVOKED if revoked?
|
||||
return EXPIRED if expires_at && expires_at < Time.current
|
||||
|
||||
SIGNED
|
||||
end
|
||||
|
||||
def sign!(password:)
|
||||
|
@ -106,18 +108,14 @@ class Certificate < ApplicationRecord
|
|||
false
|
||||
end
|
||||
|
||||
def revoke!(password:)
|
||||
crt_file = create_tempfile('client_crt', crt)
|
||||
def revoke!(password:, reason: :unspecified)
|
||||
return false unless password == ENV['ca_key_password']
|
||||
|
||||
err_output = execute_openssl_revoke_command(password, crt_file.path)
|
||||
|
||||
if revocation_successful?(err_output)
|
||||
update_revocation_status
|
||||
self.class.update_crl
|
||||
return self
|
||||
end
|
||||
|
||||
handle_revocation_failure(err_output)
|
||||
update!(
|
||||
revoked: true,
|
||||
revoked_at: Time.current,
|
||||
revoked_reason: REVOCATION_REASONS[reason]
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -3,17 +3,6 @@ module Certificate::CertificateConcern
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def tostdout(message)
|
||||
time = Time.zone.now.utc
|
||||
$stdout << "#{time} - #{message}\n" unless Rails.env.test?
|
||||
end
|
||||
|
||||
def update_crl
|
||||
tostdout('Running crlupdater')
|
||||
system('/bin/bash', ENV['crl_updater_path'].to_s)
|
||||
tostdout('Finished running crlupdater')
|
||||
end
|
||||
|
||||
def parse_md_from_string(crt)
|
||||
return if crt.blank?
|
||||
|
||||
|
|
|
@ -3,15 +3,14 @@ module Certificates
|
|||
attribute :api_user_id, Types::Coercible::Integer
|
||||
attribute? :interface, Types::String.optional
|
||||
|
||||
P12_PASSWORD = 'todo-change-me'
|
||||
|
||||
def execute
|
||||
api_user = ApiUser.find(api_user_id)
|
||||
password = generate_random_password
|
||||
|
||||
private_key = generate_user_key
|
||||
csr = generate_user_csr(private_key)
|
||||
certificate = sign_user_certificate(csr)
|
||||
p12 = create_user_p12(private_key, certificate)
|
||||
p12 = create_user_p12(private_key, certificate, password)
|
||||
|
||||
certificate_record = api_user.certificates.build(
|
||||
private_key: private_key.to_pem,
|
||||
|
@ -20,7 +19,7 @@ module Certificates
|
|||
p12: Base64.strict_encode64(p12),
|
||||
expires_at: certificate.not_after,
|
||||
interface: interface || 'registrar',
|
||||
p12_password_digest: P12_PASSWORD,
|
||||
p12_password: password,
|
||||
serial: certificate.serial.to_s,
|
||||
common_name: api_user.username
|
||||
)
|
||||
|
@ -124,7 +123,7 @@ module Certificates
|
|||
cert
|
||||
end
|
||||
|
||||
def create_user_p12(key, cert, password = P12_PASSWORD)
|
||||
def create_user_p12(key, cert, password)
|
||||
ca_cert = OpenSSL::X509::Certificate.new(File.read(ca_cert_path))
|
||||
|
||||
p12 = OpenSSL::PKCS12.create(
|
||||
|
@ -137,5 +136,11 @@ module Certificates
|
|||
|
||||
p12.to_der
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_random_password
|
||||
SecureRandom.hex(8)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue