internetee-registry/app/services/certificates/certificate_generator.rb
oleghasjanov ae96863b88 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
2025-04-16 11:47:52 +03:00

146 lines
No EOL
4.3 KiB
Ruby

module Certificates
class CertificateGenerator < Dry::Struct
attribute :api_user_id, Types::Coercible::Integer
attribute? :interface, Types::String.optional
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, password)
certificate_record = api_user.certificates.build(
private_key: private_key.to_pem,
csr: csr.to_pem,
crt: certificate.to_pem,
p12: Base64.strict_encode64(p12),
expires_at: certificate.not_after,
interface: interface || 'registrar',
p12_password: password,
serial: certificate.serial.to_s,
common_name: api_user.username
)
certificate_record.save!
certificate_record
end
def self.generate_serial_number
serial = Time.now.to_i.to_s + Random.rand(10000).to_s.rjust(5, '0')
OpenSSL::BN.new(serial)
end
def self.openssl_config_path
ENV['openssl_config_path'] || Rails.root.join('test/fixtures/files/test_ca/openssl.cnf').to_s
end
def self.ca_cert_path
ENV['ca_cert_path'] || Rails.root.join('test/fixtures/files/test_ca/certs/ca.crt.pem').to_s
end
def self.ca_key_path
ENV['ca_key_path'] || Rails.root.join('test/fixtures/files/test_ca/private/ca.key.pem').to_s
end
def self.ca_password
ENV['ca_key_password'] || '123456'
end
def ca_cert_path
self.class.ca_cert_path
end
def ca_key_path
self.class.ca_key_path
end
def ca_password
self.class.ca_password
end
def openssl_config_path
self.class.openssl_config_path
end
def username
@username ||= ApiUser.find(api_user_id).username
end
def registrar_name
@registrar_name ||= ApiUser.find(api_user_id).registrar_name
end
# openssl genrsa -out ./ca/client/client.key 4096
def generate_user_key
OpenSSL::PKey::RSA.new(4096)
end
# openssl req -new -key ./ca/client/client.key -out ./ca/client/client.csr -config ./ca/openssl.cnf
def generate_user_csr(key)
request = OpenSSL::X509::Request.new
request.version = 0
request.subject = OpenSSL::X509::Name.new([
['CN', username, OpenSSL::ASN1::UTF8STRING],
['OU', 'REGISTRAR', OpenSSL::ASN1::UTF8STRING],
['O', registrar_name, OpenSSL::ASN1::UTF8STRING]
])
request.public_key = key.public_key
request.sign(key, OpenSSL::Digest::SHA256.new)
request
end
# openssl ca -config ./ca/openssl.cnf -keyfile ./ca/ca.key.pem -cert ./ca/ca_2025.pem -extensions usr_cert -notext -md sha256 -in ./ca/client/client.csr -out ./ca/client/client.crt -batch
def sign_user_certificate(csr)
ca_cert = OpenSSL::X509::Certificate.new(File.read(ca_cert_path))
ca_key = OpenSSL::PKey::RSA.new(File.read(ca_key_path), ca_password)
cert = OpenSSL::X509::Certificate.new
cert.serial = self.class.generate_serial_number
cert.version = 2
cert.not_before = Time.now
cert.not_after = Time.now + 365 * 24 * 60 * 60 # TODO: 1 year (temporary)
cert.subject = csr.subject
cert.public_key = csr.public_key
cert.issuer = ca_cert.subject
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.subject_certificate = cert
extension_factory.issuer_certificate = ca_cert
cert.add_extension(extension_factory.create_extension("basicConstraints", "CA:FALSE"))
cert.add_extension(extension_factory.create_extension("keyUsage", "nonRepudiation,digitalSignature,keyEncipherment"))
cert.add_extension(extension_factory.create_extension("subjectKeyIdentifier", "hash"))
cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
cert
end
def create_user_p12(key, cert, password)
ca_cert = OpenSSL::X509::Certificate.new(File.read(ca_cert_path))
p12 = OpenSSL::PKCS12.create(
password,
username,
key,
cert,
[ca_cert]
)
p12.to_der
end
private
def generate_random_password
SecureRandom.hex(8)
end
end
end