mirror of
https://github.com/internetee/registry.git
synced 2025-07-28 13:36:15 +02:00
- 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
146 lines
No EOL
4.3 KiB
Ruby
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 |