mirror of
https://github.com/internetee/registry.git
synced 2025-07-30 14:36:22 +02:00
feat: improve certificate download extensions
Update certificate download functionality to use appropriate file extensions: - Use .p12 extension for PKCS#12 files - Keep .pem extension for PEM-encoded files (CSR, CRT, private key) This change ensures that downloaded certificate files have the correct extension based on their format, making it easier for users to identify and use the files correctly.
This commit is contained in:
parent
51035d1ddf
commit
5355397025
15 changed files with 281 additions and 262 deletions
29
app/controllers/repp/v1/certificates/p12_controller.rb
Normal file
29
app/controllers/repp/v1/certificates/p12_controller.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
module Repp
|
||||
module V1
|
||||
module Certificates
|
||||
class P12Controller < BaseController
|
||||
load_and_authorize_resource param_method: :cert_params
|
||||
|
||||
THROTTLED_ACTIONS = %i[create].freeze
|
||||
include Shunter::Integration::Throttle
|
||||
|
||||
api :POST, '/repp/v1/certificates/p12'
|
||||
desc 'Generate a P12 certificate'
|
||||
def create
|
||||
api_user_id = cert_params[:api_user_id]
|
||||
render_error(I18n.t('errors.messages.not_found'), :not_found) and return if api_user_id.blank?
|
||||
|
||||
api_user = current_user.registrar.api_users.find(api_user_id)
|
||||
certificate = Certificate.generate_for_api_user(api_user: api_user)
|
||||
render_success(data: { certificate: certificate })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cert_params
|
||||
params.require(:certificate).permit(:api_user_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,7 +20,6 @@ class ApiUser < User
|
|||
# TODO: should have max request limit per day?
|
||||
belongs_to :registrar
|
||||
has_many :certificates
|
||||
has_many :user_certificates
|
||||
|
||||
validates :username, :plain_text_password, :registrar, :roles, presence: true
|
||||
validates :plain_text_password, length: { minimum: min_password_length }
|
||||
|
|
|
@ -52,6 +52,24 @@ class Certificate < ApplicationRecord
|
|||
@p_csr ||= OpenSSL::X509::Request.new(csr) if csr
|
||||
end
|
||||
|
||||
def parsed_private_key
|
||||
return nil if private_key.blank?
|
||||
|
||||
decoded_key = Base64.decode64(private_key)
|
||||
OpenSSL::PKey::RSA.new(decoded_key, Certificates::CertificateGenerator::CA_PASSWORD)
|
||||
rescue OpenSSL::PKey::RSAError
|
||||
nil
|
||||
end
|
||||
|
||||
def parsed_p12
|
||||
return nil if p12.blank?
|
||||
|
||||
decoded_p12 = Base64.decode64(p12)
|
||||
OpenSSL::PKCS12.new(decoded_p12)
|
||||
rescue OpenSSL::PKCS12::PKCS12Error
|
||||
nil
|
||||
end
|
||||
|
||||
def revoked?
|
||||
status == REVOKED
|
||||
end
|
||||
|
@ -101,6 +119,55 @@ class Certificate < ApplicationRecord
|
|||
handle_revocation_failure(err_output)
|
||||
end
|
||||
|
||||
def renewable?
|
||||
return false if revoked?
|
||||
return false if crt.blank?
|
||||
return false if expires_at.blank?
|
||||
|
||||
expires_at > Time.current && expires_at <= 30.days.from_now
|
||||
end
|
||||
|
||||
def expired?
|
||||
return false if revoked?
|
||||
return false if crt.blank?
|
||||
return false if expires_at.blank?
|
||||
|
||||
expires_at < Time.current
|
||||
end
|
||||
|
||||
def renew
|
||||
raise "Certificate cannot be renewed" unless renewable?
|
||||
|
||||
generator = Certificates::CertificateGenerator.new(
|
||||
username: api_user.username,
|
||||
registrar_code: api_user.registrar_code,
|
||||
registrar_name: api_user.registrar_name,
|
||||
certificate: self
|
||||
)
|
||||
|
||||
generator.renew_certificate
|
||||
end
|
||||
|
||||
def self.generate_for_api_user(api_user:)
|
||||
generator = Certificates::CertificateGenerator.new(
|
||||
username: api_user.username,
|
||||
registrar_code: api_user.registrar_code,
|
||||
registrar_name: api_user.registrar_name
|
||||
)
|
||||
|
||||
cert_data = generator.call
|
||||
|
||||
create!(
|
||||
api_user: api_user,
|
||||
interface: 'api',
|
||||
private_key: Base64.encode64(cert_data[:private_key]),
|
||||
csr: cert_data[:csr],
|
||||
crt: cert_data[:crt],
|
||||
p12: Base64.encode64(cert_data[:p12]),
|
||||
expires_at: cert_data[:expires_at]
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def certificate_origin
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
class UserCertificate < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
validates :user, presence: true
|
||||
validates :private_key, presence: true
|
||||
|
||||
enum status: {
|
||||
pending: 'pending',
|
||||
active: 'active',
|
||||
revoked: 'revoked'
|
||||
}
|
||||
|
||||
def renewable?
|
||||
return false unless certificate.present?
|
||||
return false if revoked?
|
||||
|
||||
expires_at.present? && expires_at < 30.days.from_now
|
||||
end
|
||||
|
||||
def expired?
|
||||
return false unless certificate.present?
|
||||
return false if revoked?
|
||||
|
||||
expires_at.present? && expires_at < Time.current
|
||||
end
|
||||
|
||||
def renew
|
||||
raise "Certificate cannot be renewed" unless renewable?
|
||||
|
||||
generator = Certificates::CertificateGenerator.new(
|
||||
username: user.username,
|
||||
registrar_code: user.registrar_code,
|
||||
registrar_name: user.registrar_name,
|
||||
user_certificate: self
|
||||
)
|
||||
|
||||
generator.renew_certificate
|
||||
end
|
||||
|
||||
def self.generate_certificates_for_api_user(api_user:)
|
||||
cert = UserCertificate.create!(
|
||||
user: api_user,
|
||||
status: 'pending',
|
||||
private_key: ''
|
||||
)
|
||||
|
||||
Certificates::CertificateGenerator.new(
|
||||
username: api_user.username,
|
||||
registrar_code: api_user.registrar_code,
|
||||
registrar_name: api_user.registrar_name,
|
||||
user_certificate: cert
|
||||
).call
|
||||
end
|
||||
end
|
|
@ -3,7 +3,6 @@ module Certificates
|
|||
attribute :username, Types::Strict::String
|
||||
attribute :registrar_code, Types::Coercible::String
|
||||
attribute :registrar_name, Types::Strict::String
|
||||
attribute :user_certificate, Types.Instance(UserCertificate)
|
||||
|
||||
CERTS_PATH = Rails.root.join('certs')
|
||||
CA_PATH = CERTS_PATH.join('ca')
|
||||
|
@ -26,15 +25,23 @@ module Certificates
|
|||
|
||||
def call
|
||||
csr, key = generate_csr_and_key
|
||||
certificate = sign_certificate(csr)
|
||||
create_p12(key, certificate)
|
||||
cert = sign_certificate(csr)
|
||||
p12 = create_p12(key, cert)
|
||||
|
||||
{
|
||||
private_key: key.export(OpenSSL::Cipher.new('AES-256-CBC'), CA_PASSWORD),
|
||||
csr: csr.to_pem,
|
||||
crt: cert.to_pem,
|
||||
p12: p12.to_der,
|
||||
expires_at: cert.not_after
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_csr_and_key
|
||||
key = OpenSSL::PKey::RSA.new(4096)
|
||||
encrypted_key = key.export(OpenSSL::Cipher.new('AES-256-CBC'), CA_PASSWORD)
|
||||
save_private_key(encrypted_key)
|
||||
|
||||
|
||||
request = OpenSSL::X509::Request.new
|
||||
request.version = 0
|
||||
request.subject = OpenSSL::X509::Name.new([
|
||||
|
@ -47,12 +54,7 @@ module Certificates
|
|||
request.sign(key, OpenSSL::Digest::SHA256.new)
|
||||
|
||||
save_csr(request)
|
||||
|
||||
user_certificate&.update!(
|
||||
private_key: encrypted_key,
|
||||
csr: request.to_pem,
|
||||
status: 'pending'
|
||||
)
|
||||
save_private_key(key.export(OpenSSL::Cipher.new('AES-256-CBC'), CA_PASSWORD))
|
||||
|
||||
[request, key]
|
||||
end
|
||||
|
@ -80,64 +82,35 @@ module Certificates
|
|||
cert.add_extension(extension_factory.create_extension("subjectKeyIdentifier", "hash"))
|
||||
|
||||
cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
|
||||
|
||||
save_certificate(cert)
|
||||
|
||||
user_certificate&.update!(
|
||||
certificate: cert.to_pem,
|
||||
status: 'active',
|
||||
expires_at: cert.not_after
|
||||
)
|
||||
|
||||
cert
|
||||
end
|
||||
|
||||
def create_p12(key, certificate, password = nil)
|
||||
def create_p12(key, cert)
|
||||
ca_cert = OpenSSL::X509::Certificate.new(File.read(CA_CERT_PATH))
|
||||
|
||||
p12 = OpenSSL::PKCS12.create(
|
||||
password, # P12 password (optional)
|
||||
username, # Friendly name
|
||||
key, # User's private key
|
||||
certificate, # User's certificate
|
||||
[ca_cert], # Chain of certificates
|
||||
nil, # password
|
||||
username,
|
||||
key,
|
||||
cert,
|
||||
[ca_cert]
|
||||
)
|
||||
|
||||
File.open(CERTS_PATH.join(USER_P12_NAME), 'wb') do |file|
|
||||
file.write(p12.to_der)
|
||||
end
|
||||
|
||||
user_certificate&.update!(
|
||||
p12: p12.to_der,
|
||||
p12_password_digest: password ? BCrypt::Password.create(password) : nil
|
||||
)
|
||||
|
||||
p12
|
||||
end
|
||||
|
||||
def renew_certificate
|
||||
raise "Certificate not found" unless user_certificate&.certificate.present?
|
||||
raise "Cannot renew revoked certificate" if user_certificate.revoked?
|
||||
|
||||
# Используем существующий CSR
|
||||
csr = OpenSSL::X509::Request.new(user_certificate.csr)
|
||||
|
||||
# Создаем новый сертификат
|
||||
certificate = sign_certificate(csr)
|
||||
|
||||
# Создаем новый P12 с существующим ключом
|
||||
key = OpenSSL::PKey::RSA.new(user_certificate.private_key, CA_PASSWORD)
|
||||
create_p12(key, certificate)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_directories_exist
|
||||
FileUtils.mkdir_p(CERTS_PATH)
|
||||
FileUtils.mkdir_p(CA_PATH.join('certs'))
|
||||
FileUtils.mkdir_p(CA_PATH.join('private'))
|
||||
|
||||
# Set proper permissions for private directory
|
||||
FileUtils.chmod(0700, CA_PATH.join('private'))
|
||||
end
|
||||
|
||||
|
|
|
@ -118,7 +118,11 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
resources :white_ips, only: %i[index show update create destroy]
|
||||
resources :certificates, only: %i[create]
|
||||
resources :certificates, only: %i[create] do
|
||||
scope module: :certificates do
|
||||
post 'p12', to: 'p12#create', on: :collection
|
||||
end
|
||||
end
|
||||
namespace :registrar do
|
||||
resources :notifications, only: %i[index show update] do
|
||||
collection do
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
class CreateUserCertificates < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
create_table :user_certificates do |t|
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.binary :private_key, null: false
|
||||
t.text :csr
|
||||
t.text :certificate
|
||||
t.binary :p12
|
||||
t.string :status
|
||||
t.datetime :expires_at
|
||||
t.datetime :revoked_at
|
||||
t.string :p12_password_digest
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :user_certificates, [:user_id, :status]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
class AddP12FieldsToCertificates < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :certificates, :private_key, :binary
|
||||
add_column :certificates, :p12, :binary
|
||||
add_column :certificates, :p12_password_digest, :string
|
||||
add_column :certificates, :expires_at, :timestamp
|
||||
end
|
||||
end
|
|
@ -587,7 +587,11 @@ CREATE TABLE public.certificates (
|
|||
common_name character varying,
|
||||
md5 character varying,
|
||||
interface character varying,
|
||||
revoked boolean DEFAULT false NOT NULL
|
||||
revoked boolean DEFAULT false NOT NULL,
|
||||
private_key bytea,
|
||||
p12 bytea,
|
||||
p12_password_digest character varying,
|
||||
expires_at timestamp without time zone
|
||||
);
|
||||
|
||||
|
||||
|
@ -2812,45 +2816,6 @@ CREATE SEQUENCE public.settings_id_seq
|
|||
ALTER SEQUENCE public.settings_id_seq OWNED BY public.settings.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_certificates; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.user_certificates (
|
||||
id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
private_key bytea NOT NULL,
|
||||
csr text,
|
||||
certificate text,
|
||||
p12 bytea,
|
||||
status character varying,
|
||||
expires_at timestamp without time zone,
|
||||
revoked_at timestamp without time zone,
|
||||
p12_password_digest character varying,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_certificates_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.user_certificates_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_certificates_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.user_certificates_id_seq OWNED BY public.user_certificates.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: users; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -3551,13 +3516,6 @@ ALTER TABLE ONLY public.setting_entries ALTER COLUMN id SET DEFAULT nextval('pub
|
|||
ALTER TABLE ONLY public.settings ALTER COLUMN id SET DEFAULT nextval('public.settings_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_certificates id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_certificates ALTER COLUMN id SET DEFAULT nextval('public.user_certificates_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -4256,14 +4214,6 @@ ALTER TABLE ONLY public.zones
|
|||
ADD CONSTRAINT unique_zone_origin UNIQUE (origin);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_certificates user_certificates_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_certificates
|
||||
ADD CONSTRAINT user_certificates_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -4949,20 +4899,6 @@ CREATE UNIQUE INDEX index_setting_entries_on_code ON public.setting_entries USIN
|
|||
CREATE UNIQUE INDEX index_settings_on_thing_type_and_thing_id_and_var ON public.settings USING btree (thing_type, thing_id, var);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_user_certificates_on_user_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_user_certificates_on_user_id ON public.user_certificates USING btree (user_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_user_certificates_on_user_id_and_status; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_user_certificates_on_user_id_and_status ON public.user_certificates USING btree (user_id, status);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_users_on_identity_code; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -5108,14 +5044,6 @@ ALTER TABLE ONLY public.domains
|
|||
ADD CONSTRAINT domains_registrar_id_fk FOREIGN KEY (registrar_id) REFERENCES public.registrars(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_certificates fk_rails_03b0a0c9d8; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_certificates
|
||||
ADD CONSTRAINT fk_rails_03b0a0c9d8 FOREIGN KEY (user_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: invoices fk_rails_242b91538b; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -5795,6 +5723,6 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20241129095711'),
|
||||
('20241206085817'),
|
||||
('20250204094550'),
|
||||
('20250218115707');
|
||||
('20250219102811');
|
||||
|
||||
|
||||
|
|
|
@ -8,16 +8,38 @@ module Serializers
|
|||
end
|
||||
|
||||
def to_json(obj = certificate)
|
||||
json = obj.as_json.except('csr', 'crt')
|
||||
json = obj.as_json.except('csr', 'crt', 'private_key', 'p12')
|
||||
csr = obj.parsed_csr
|
||||
crt = obj.parsed_crt
|
||||
p12 = obj.parsed_p12
|
||||
private_key = obj.parsed_private_key
|
||||
|
||||
json[:private_key] = private_key_data(private_key) if private_key
|
||||
json[:p12] = p12_data(obj) if obj.p12.present?
|
||||
json[:expires_at] = obj.expires_at if obj.expires_at.present?
|
||||
|
||||
json[:csr] = csr_data(csr) if csr
|
||||
json[:crt] = crt_data(crt) if crt
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def private_key_data(key)
|
||||
{
|
||||
body: key.to_pem,
|
||||
type: 'RSA PRIVATE KEY'
|
||||
}
|
||||
end
|
||||
|
||||
def p12_data(obj)
|
||||
{
|
||||
body: obj.p12,
|
||||
type: 'PKCS12'
|
||||
}
|
||||
end
|
||||
|
||||
def csr_data(csr)
|
||||
{
|
||||
version: csr.version,
|
||||
|
|
9
test/fixtures/files/user.crt
vendored
Normal file
9
test/fixtures/files/user.crt
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUBgtGh4Pw8Luqq/HG4tqG3oIzfHIwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMTkxMjAwMDBaFw0yNTAy
|
||||
MTkxMjAwMDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQDUVURLKdmhmEht7yz3MeQQtn9kMIaIzZDwggZvUg6J
|
||||
5PlTabEixVfPzlRJixJBj37hh0Ree6mr19KECtPymy1L9U3oGfF18CJhdzc=
|
||||
-----END CERTIFICATE-----
|
7
test/fixtures/user_certificates.yml
vendored
7
test/fixtures/user_certificates.yml
vendored
|
@ -1,7 +0,0 @@
|
|||
one:
|
||||
user: api_bestnames
|
||||
private_key: "encrypted_private_key_data"
|
||||
csr: "dummy_csr_data"
|
||||
certificate: "dummy_certificate_data"
|
||||
status: "active"
|
||||
expires_at: <%= 60.days.from_now %>
|
|
@ -3,7 +3,22 @@ require 'test_helper'
|
|||
class CertificateTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@certificate = certificates(:api)
|
||||
@certificate.update!(csr: "-----BEGIN CERTIFICATE REQUEST-----\nMIICszCCAZsCAQAwbjELMAkGA1UEBhMCRUUxFDASBgNVBAMMC2ZyZXNoYm94LmVl\nMRAwDgYDVQQHDAdUYWxsaW5uMREwDwYDVQQKDAhGcmVzaGJveDERMA8GA1UECAwI\nSGFyanVtYWExETAPBgNVBAsMCEZyZXNoYm94MIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA1VVESynZoZhIbe8s9zHkELZ/ZDCGiM2Q8IIGb1IOieT5U2mx\nIsVXz85USYsSQY9+4YdEXnupq9fShArT8pstS/VN6BnxdfAiYXc3UWWAuaYAdNGJ\nDr5Jf6uMt1wVnCgoDL7eJq9tWMwARC/viT81o92fgqHFHW0wEolfCmnpik9o0ACD\nFiWZ9IBIevmFqXtq25v9CY2cT9+eZW127WtJmOY/PKJhzh0QaEYHqXTHWOLZWpnp\nHH4elyJ2CrFulOZbHPkPNB9Nf4XQjzk1ffoH6e5IVys2VV5xwcTkF0jY5XTROVxX\nlR2FWqic8Q2pIhSks48+J6o1GtXGnTxv94lSDwIDAQABoAAwDQYJKoZIhvcNAQEL\nBQADggEBAEFcYmQvcAC8773eRTWBJJNoA4kRgoXDMYiiEHih5iJPVSxfidRwYDTF\nsP+ttNTUg3JocFHY75kuM9T2USh+gu/trRF0o4WWa+AbK3JbbdjdT1xOMn7XtfUU\nZ/f1XCS9YdHQFCA6nk4Z+TLWwYsgk7n490AQOiB213fa1UIe83qIfw/3GRqRUZ7U\nwIWEGsHED5WT69GyxjyKHcqGoV7uFnqFN0sQVKVTy/NFRVQvtBUspCbsOirdDRie\nAB2KbGHL+t1QrRF10szwCJDyk5aYlVhxvdI8zn010nrxHkiyQpDFFldDMLJl10BW\n2w9PGO061z+tntdRcKQGuEpnIr9U5Vs=\n-----END CERTIFICATE REQUEST-----\n")
|
||||
@valid_crt = <<~CRT
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUBgtGh4Pw8Luqq/HG4tqG3oIzfHIwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMTkxMjAwMDBaFw0yNTAy
|
||||
MTkxMjAwMDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQDUVURLKdmhmEht7yz3MeQQtn9kMIaIzZDwggZvUg6J
|
||||
5PlTabEixVfPzlRJixJBj37hh0Ree6mr19KECtPymy1L9U3oGfF18CJhdzc=
|
||||
-----END CERTIFICATE-----
|
||||
CRT
|
||||
|
||||
@certificate.update!(
|
||||
csr: "-----BEGIN CERTIFICATE REQUEST-----\nMIICszCCAZsCAQAwbjELMAkGA1UEBhMCRUUxFDASBgNVBAMMC2ZyZXNoYm94LmVl\nMRAwDgYDVQQHDAdUYWxsaW5uMREwDwYDVQQKDAhGcmVzaGJveDERMA8GA1UECAwI\nSGFyanVtYWExETAPBgNVBAsMCEZyZXNoYm94MIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA1VVESynZoZhIbe8s9zHkELZ/ZDCGiM2Q8IIGb1IOieT5U2mx\nIsVXz85USYsSQY9+4YdEXnupq9fShArT8pstS/VN6BnxdfAiYXc3UWWAuaYAdNGJ\nDr5Jf6uMt1wVnCgoDL7eJq9tWMwARC/viT81o92fgqHFHW0wEolfCmnpik9o0ACD\nFiWZ9IBIevmFqXtq25v9CY2cT9+eZW127WtJmOY/PKJhzh0QaEYHqXTHWOLZWpnp\nHH4elyJ2CrFulOZbHPkPNB9Nf4XQjzk1ffoH6e5IVys2VV5xwcTkF0jY5XTROVxX\nlR2FWqic8Q2pIhSks48+J6o1GtXGnTxv94lSDwIDAQABoAAwDQYJKoZIhvcNAQEL\nBQADggEBAEFcYmQvcAC8773eRTWBJJNoA4kRgoXDMYiiEHih5iJPVSxfidRwYDTF\nsP+ttNTUg3JocFHY75kuM9T2USh+gu/trRF0o4WWa+AbK3JbbdjdT1xOMn7XtfUU\nZ/f1XCS9YdHQFCA6nk4Z+TLWwYsgk7n490AQOiB213fa1UIe83qIfw/3GRqRUZ7U\nwIWEGsHED5WT69GyxjyKHcqGoV7uFnqFN0sQVKVTy/NFRVQvtBUspCbsOirdDRie\nAB2KbGHL+t1QrRF10szwCJDyk5aYlVhxvdI8zn010nrxHkiyQpDFFldDMLJl10BW\n2w9PGO061z+tntdRcKQGuEpnIr9U5Vs=\n-----END CERTIFICATE REQUEST-----\n",
|
||||
private_key: "encrypted_private_key"
|
||||
)
|
||||
end
|
||||
|
||||
def test_does_metadata_is_api
|
||||
|
@ -12,6 +27,41 @@ class CertificateTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_certificate_sign_returns_false
|
||||
assert_not @certificate.sign!(password: ENV['ca_key_password']), 'false'
|
||||
ENV['ca_key_password'] = 'test_password'
|
||||
assert_not @certificate.sign!(password: ENV['ca_key_password'])
|
||||
end
|
||||
|
||||
def test_renewable_when_not_expired
|
||||
@certificate.update!(
|
||||
crt: @valid_crt,
|
||||
expires_at: 20.days.from_now
|
||||
)
|
||||
|
||||
assert @certificate.renewable?
|
||||
end
|
||||
|
||||
def test_not_renewable_when_expired
|
||||
@certificate.update!(
|
||||
crt: @valid_crt,
|
||||
expires_at: 1.day.ago
|
||||
)
|
||||
|
||||
assert @certificate.expired?
|
||||
assert_not @certificate.renewable?
|
||||
end
|
||||
|
||||
def test_generate_for_api_user
|
||||
api_user = users(:api_bestnames)
|
||||
|
||||
certificate = nil
|
||||
assert_nothing_raised do
|
||||
certificate = Certificate.generate_for_api_user(api_user: api_user)
|
||||
end
|
||||
|
||||
assert certificate.persisted?
|
||||
assert_equal api_user, certificate.api_user
|
||||
assert certificate.private_key.present?
|
||||
assert certificate.csr.present?
|
||||
assert certificate.expires_at.present?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
require 'test_helper'
|
||||
|
||||
class UserCertificateTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@user = users(:api_bestnames)
|
||||
@certificate = user_certificates(:one)
|
||||
end
|
||||
|
||||
test "should be valid with required attributes" do
|
||||
certificate = UserCertificate.new(
|
||||
user: @user,
|
||||
private_key: 'dummy_key',
|
||||
status: 'pending'
|
||||
)
|
||||
assert certificate.valid?
|
||||
end
|
||||
|
||||
test "should not be valid without user" do
|
||||
@certificate.user = nil
|
||||
assert_not @certificate.valid?
|
||||
end
|
||||
|
||||
test "should not be valid without private_key" do
|
||||
@certificate.private_key = nil
|
||||
assert_not @certificate.valid?
|
||||
end
|
||||
|
||||
test "renewable? should be false without certificate" do
|
||||
@certificate.certificate = nil
|
||||
assert_not @certificate.renewable?
|
||||
end
|
||||
|
||||
test "renewable? should be false when revoked" do
|
||||
@certificate.status = 'revoked'
|
||||
assert_not @certificate.renewable?
|
||||
end
|
||||
|
||||
test "renewable? should be true when expires in less than 30 days" do
|
||||
@certificate.expires_at = 29.days.from_now
|
||||
assert @certificate.renewable?
|
||||
end
|
||||
|
||||
test "expired? should be true when certificate is expired" do
|
||||
@certificate.expires_at = 1.day.ago
|
||||
assert @certificate.expired?
|
||||
end
|
||||
|
||||
test "renew should raise error when certificate is not renewable" do
|
||||
@certificate.status = 'revoked'
|
||||
assert_raises(RuntimeError) { @certificate.renew }
|
||||
end
|
||||
end
|
62
test/services/certificates/certificate_generator_test.rb
Normal file
62
test/services/certificates/certificate_generator_test.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
require 'test_helper'
|
||||
|
||||
module Certificates
|
||||
class CertificateGeneratorTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@certificate = certificates(:api)
|
||||
@generator = CertificateGenerator.new(
|
||||
username: "test_user",
|
||||
registrar_code: "REG123",
|
||||
registrar_name: "Test Registrar"
|
||||
)
|
||||
end
|
||||
|
||||
def test_generates_new_certificate
|
||||
result = @generator.call
|
||||
|
||||
assert result[:private_key].present?
|
||||
assert result[:csr].present?
|
||||
assert result[:crt].present?
|
||||
assert result[:p12].present?
|
||||
assert result[:expires_at].present?
|
||||
|
||||
assert_instance_of String, result[:private_key]
|
||||
assert_instance_of String, result[:csr]
|
||||
assert_instance_of String, result[:crt]
|
||||
assert_instance_of String, result[:p12]
|
||||
assert_instance_of Time, result[:expires_at]
|
||||
end
|
||||
|
||||
def test_uses_existing_csr_and_private_key
|
||||
existing_csr = @certificate.csr
|
||||
existing_private_key = "existing_private_key"
|
||||
@certificate.update!(private_key: existing_private_key)
|
||||
|
||||
result = @generator.call
|
||||
|
||||
assert result[:csr].present?
|
||||
assert result[:private_key].present?
|
||||
assert_not_equal existing_csr, result[:csr]
|
||||
assert_not_equal existing_private_key, result[:private_key]
|
||||
end
|
||||
|
||||
def test_renew_certificate
|
||||
@certificate.update!(
|
||||
expires_at: 20.days.from_now
|
||||
)
|
||||
|
||||
generator = CertificateGenerator.new(
|
||||
username: "test_user",
|
||||
registrar_code: "REG123",
|
||||
registrar_name: "Test Registrar"
|
||||
)
|
||||
|
||||
result = generator.call
|
||||
|
||||
assert result[:crt].present?
|
||||
assert result[:expires_at] > Time.current
|
||||
assert_instance_of String, result[:crt]
|
||||
assert_instance_of Time, result[:expires_at]
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue