Remove cryptography
This commit is contained in:
parent
557e892e44
commit
358069eeea
82 changed files with 24 additions and 2917 deletions
|
@ -21,7 +21,6 @@ Layout/EmptyLinesAroundArguments:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 15.5
|
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'db/migrate/*.rb'
|
- 'db/migrate/*.rb'
|
||||||
|
|
||||||
|
@ -48,7 +47,6 @@ Metrics/MethodLength:
|
||||||
|
|
||||||
Naming/PredicateName:
|
Naming/PredicateName:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/forms/application_form.rb'
|
|
||||||
- 'app/models/application_record.rb'
|
- 'app/models/application_record.rb'
|
||||||
|
|
||||||
Rails:
|
Rails:
|
||||||
|
|
|
@ -21,12 +21,7 @@ private
|
||||||
@current_account ||= current_user&.account
|
@current_account ||= current_user&.account
|
||||||
end
|
end
|
||||||
|
|
||||||
def pundit_user
|
alias pundit_user current_account
|
||||||
@pundit_user ||= ApplicationPolicy::Context.new(
|
|
||||||
account: current_account,
|
|
||||||
params: params,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_raven_context
|
def set_raven_context
|
||||||
Raven.user_context(
|
Raven.user_context(
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class AsymmetricKeysController < ApplicationController
|
|
||||||
before_action :set_asymmetric_key, except: %i[index new create]
|
|
||||||
|
|
||||||
# GET /asymmetric_keys
|
|
||||||
def index
|
|
||||||
authorize AsymmetricKey
|
|
||||||
@asymmetric_keys = policy_scope(AsymmetricKey).page(params[:page])
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET /asymmetric_keys/:id
|
|
||||||
def show
|
|
||||||
authorize @asymmetric_key
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html
|
|
||||||
format.pem do
|
|
||||||
send_data @asymmetric_key.public_key_pem, filename: 'public.pem'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET /asymmetric_keys/new
|
|
||||||
def new
|
|
||||||
@asymmetric_key_form = AsymmetricKeyForm.new
|
|
||||||
authorize @asymmetric_key_form
|
|
||||||
end
|
|
||||||
|
|
||||||
# POST /asymmetric_keys
|
|
||||||
def create
|
|
||||||
@asymmetric_key_form = AsymmetricKeyForm.new asymmetric_key_form_params
|
|
||||||
authorize @asymmetric_key_form
|
|
||||||
|
|
||||||
result = ImportAsymmetricKey.call @asymmetric_key_form.attributes
|
|
||||||
|
|
||||||
if result.failure?
|
|
||||||
@asymmetric_key_form.errors.add :public_key_pem
|
|
||||||
return render :new
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect_to after_create_url result.asymmetric_key
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_asymmetric_key
|
|
||||||
@asymmetric_key = AsymmetricKey.find params[:id]
|
|
||||||
end
|
|
||||||
|
|
||||||
def asymmetric_key_form_params
|
|
||||||
params.require(:asymmetric_key).permit(
|
|
||||||
:public_key_pem,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_create_url(asymmetric_key)
|
|
||||||
asymmetric_key_url(asymmetric_key)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,30 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class PrivateKeysController < ApplicationController
|
|
||||||
before_action :set_asymmetric_key
|
|
||||||
before_action :set_secret
|
|
||||||
|
|
||||||
# GET /asymmetric_keys/:asymmetric_key_id/private_key
|
|
||||||
def show
|
|
||||||
authorize PrivateKey.new(@asymmetric_key)
|
|
||||||
|
|
||||||
@asymmetric_key.decrypt_private_key_pem
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.key do
|
|
||||||
send_data @asymmetric_key.private_key_pem, filename: 'private.key'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_asymmetric_key
|
|
||||||
@asymmetric_key = AsymmetricKey.find params[:asymmetric_key_id]
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_secret
|
|
||||||
@asymmetric_key.private_key_pem_secret =
|
|
||||||
Base64.urlsafe_decode64 params[:private_key_pem_secret]
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,63 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Staffs::X509CertificatesController < ApplicationController
|
|
||||||
before_action :set_x509_certificate, except: %i[index new create]
|
|
||||||
|
|
||||||
# GET /staff/x509_certificates
|
|
||||||
def index
|
|
||||||
authorize %i[staff x509_certificate]
|
|
||||||
@x509_certificates = policy_scope(
|
|
||||||
X509Certificate,
|
|
||||||
policy_scope_class: Staff::X509CertificatePolicy::Scope,
|
|
||||||
).page(params[:page])
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET /staff/x509_certificates/:id
|
|
||||||
def show
|
|
||||||
authorize [:staff, @x509_certificate]
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET /staff/x509_certificates/new
|
|
||||||
def new
|
|
||||||
@x509_certificate_form = X509CertificateForm.new
|
|
||||||
authorize [:staff, @x509_certificate_form]
|
|
||||||
end
|
|
||||||
|
|
||||||
# POST /staff/x509_certificates
|
|
||||||
def create
|
|
||||||
@x509_certificate_form =
|
|
||||||
X509CertificateForm.new x509_certificate_form_params
|
|
||||||
|
|
||||||
authorize [:staff, @x509_certificate_form]
|
|
||||||
|
|
||||||
return render :new unless @x509_certificate_form.valid?
|
|
||||||
|
|
||||||
result = CreateRSAKeysAndX509SelfSignedCertificate.call \
|
|
||||||
@x509_certificate_form.attributes.merge(account: current_account)
|
|
||||||
|
|
||||||
redirect_to after_create_url result.certificate
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_x509_certificate
|
|
||||||
@x509_certificate = X509Certificate.find params[:id]
|
|
||||||
end
|
|
||||||
|
|
||||||
def x509_certificate_form_params
|
|
||||||
params.require(:x509_certificate).permit(
|
|
||||||
:distinguished_name,
|
|
||||||
:not_before,
|
|
||||||
:not_after,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_create_url(certificate)
|
|
||||||
staff_x509_certificate_url(
|
|
||||||
certificate,
|
|
||||||
private_key_pem_secret: Base64.urlsafe_encode64(
|
|
||||||
certificate.asymmetric_key.private_key_pem_secret,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,16 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ApplicationForm
|
|
||||||
include ActiveModel::Model
|
|
||||||
include ActiveModel::Attributes
|
|
||||||
include ActiveModel::Validations::Callbacks
|
|
||||||
include ActiveRecord::AttributeAssignment
|
|
||||||
|
|
||||||
def has_attribute?(name)
|
|
||||||
attributes.key?(name.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def type_for_attribute(name)
|
|
||||||
self.class.attribute_types[name.to_s]
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,13 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class AsymmetricKeyForm < ApplicationForm
|
|
||||||
attribute :public_key_pem, :string
|
|
||||||
|
|
||||||
def self.model_name
|
|
||||||
ActiveModel::Name.new(self, nil, AsymmetricKey.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.policy_class
|
|
||||||
'AsymmetricKeyPolicy'
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,31 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class X509CertificateForm < ApplicationForm
|
|
||||||
attribute :distinguished_name, :string
|
|
||||||
attribute :not_before, :datetime
|
|
||||||
attribute :not_after, :datetime
|
|
||||||
|
|
||||||
validates :distinguished_name, presence: true
|
|
||||||
|
|
||||||
validates :not_before, presence: true
|
|
||||||
|
|
||||||
validates :not_after, presence: true
|
|
||||||
|
|
||||||
validate :can_be_parsed_with_openssl
|
|
||||||
|
|
||||||
def self.model_name
|
|
||||||
ActiveModel::Name.new(self, nil, X509Certificate.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.policy_class
|
|
||||||
'X509CertificatePolicy'
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def can_be_parsed_with_openssl
|
|
||||||
OpenSSL::X509::Name.parse distinguished_name if distinguished_name.present?
|
|
||||||
rescue
|
|
||||||
errors.add :distinguished_name
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ApplicationHelper # rubocop:disable Metrics/ModuleLength
|
module ApplicationHelper
|
||||||
def federal_subjects_controller?
|
def federal_subjects_controller?
|
||||||
controller_path == 'federal_subjects'
|
controller_path == 'federal_subjects'
|
||||||
end
|
end
|
||||||
|
@ -13,34 +13,6 @@ module ApplicationHelper # rubocop:disable Metrics/ModuleLength
|
||||||
[*negative_timezones_collection, *positive_timezones_collection].freeze
|
[*negative_timezones_collection, *positive_timezones_collection].freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_sha1(str)
|
|
||||||
str = String(str).upcase
|
|
||||||
raise 'Invalid format for SHA-1' unless str.match?(/\A[A-F0-9]{40}\z/)
|
|
||||||
|
|
||||||
tag.small do
|
|
||||||
concat display_fingerprint str[0...20]
|
|
||||||
concat tag.br
|
|
||||||
concat display_fingerprint str[20..-1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def display_sha256(str)
|
|
||||||
str = String(str).upcase
|
|
||||||
raise 'Invalid format for SHA-256' unless str.match?(/\A[A-F0-9]{64}\z/)
|
|
||||||
|
|
||||||
tag.small do
|
|
||||||
concat display_fingerprint str[0...32]
|
|
||||||
concat tag.br
|
|
||||||
concat display_fingerprint str[32..-1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def display_fingerprint(str)
|
|
||||||
tag.samp do
|
|
||||||
String(str).strip.upcase.each_char.each_slice(2).map(&:join).join(':')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def positive_timezones_collection
|
def positive_timezones_collection
|
||||||
0.upto(11).flat_map do |n|
|
0.upto(11).flat_map do |n|
|
||||||
s = n.to_s.rjust(2, '0')
|
s = n.to_s.rjust(2, '0')
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class CreateEcurveKeys
|
|
||||||
include Interactor
|
|
||||||
|
|
||||||
DEFAULT_CURVE = 'prime256v1'
|
|
||||||
|
|
||||||
before :set_curve
|
|
||||||
|
|
||||||
def call
|
|
||||||
context.asymmetric_key =
|
|
||||||
EcurveKey.create!(attributes, &:encrypt_private_key_pem)
|
|
||||||
|
|
||||||
ClearAsymmetricPrivateKeyJob
|
|
||||||
.set(wait: AsymmetricKey::PRIVATE_KEY_CLEAR_DELAY)
|
|
||||||
.perform_later context.asymmetric_key.id
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_curve
|
|
||||||
context.curve ||= DEFAULT_CURVE
|
|
||||||
context.curve = String(context.curve).freeze
|
|
||||||
raise 'Invalid curve' unless EcurveKey::CURVES.include? context.curve
|
|
||||||
end
|
|
||||||
|
|
||||||
def pkey
|
|
||||||
@pkey ||= OpenSSL::PKey::EC.generate context.curve
|
|
||||||
end
|
|
||||||
|
|
||||||
def pkey_public
|
|
||||||
@pkey_public ||=
|
|
||||||
OpenSSL::PKey::EC.new(pkey.public_key.group).tap do |pkey_public|
|
|
||||||
pkey_public.public_key = pkey.public_key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def attributes
|
|
||||||
{
|
|
||||||
account: context.account,
|
|
||||||
|
|
||||||
public_key_pem: public_key_pem,
|
|
||||||
public_key_der: public_key_der,
|
|
||||||
private_key_pem: private_key_pem,
|
|
||||||
|
|
||||||
has_password: context.password.present?,
|
|
||||||
curve: context.curve,
|
|
||||||
sha1: sha1,
|
|
||||||
sha256: sha256,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def sha1
|
|
||||||
@sha1 ||= Digest::SHA1.hexdigest(pkey_public.to_der).freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def sha256
|
|
||||||
@sha256 ||= Digest::SHA256.hexdigest(pkey_public.to_der).freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key_pem
|
|
||||||
@public_key_pem ||= pkey_public.to_pem.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key_der
|
|
||||||
@public_key_der ||= pkey_public.to_der.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def private_key_pem
|
|
||||||
@private_key_pem ||=
|
|
||||||
if context.password.present?
|
|
||||||
pkey.to_pem(OpenSSL::Cipher::AES256.new, context.password).freeze
|
|
||||||
else
|
|
||||||
pkey.to_pem.freeze
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,70 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class CreateRSAKeys
|
|
||||||
include Interactor
|
|
||||||
|
|
||||||
DEFAULT_BITS = 4096
|
|
||||||
|
|
||||||
before :set_bits
|
|
||||||
|
|
||||||
def call
|
|
||||||
context.asymmetric_key =
|
|
||||||
RSAKey.create!(attributes, &:encrypt_private_key_pem)
|
|
||||||
|
|
||||||
ClearAsymmetricPrivateKeyJob
|
|
||||||
.set(wait: AsymmetricKey::PRIVATE_KEY_CLEAR_DELAY)
|
|
||||||
.perform_later context.asymmetric_key.id
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_bits
|
|
||||||
context.bits ||= DEFAULT_BITS
|
|
||||||
context.bits = Integer(context.bits)
|
|
||||||
raise 'Invalid key size' unless RSAKey::BITS.include? context.bits
|
|
||||||
end
|
|
||||||
|
|
||||||
def pkey
|
|
||||||
@pkey ||= OpenSSL::PKey::RSA.new context.bits
|
|
||||||
end
|
|
||||||
|
|
||||||
def attributes
|
|
||||||
{
|
|
||||||
account: context.account,
|
|
||||||
|
|
||||||
public_key_pem: public_key_pem,
|
|
||||||
public_key_der: public_key_der,
|
|
||||||
private_key_pem: private_key_pem,
|
|
||||||
|
|
||||||
has_password: context.password.present?,
|
|
||||||
bits: context.bits,
|
|
||||||
sha1: sha1,
|
|
||||||
sha256: sha256,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def sha1
|
|
||||||
@sha1 ||= Digest::SHA1.hexdigest(pkey.public_key.to_der).freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def sha256
|
|
||||||
@sha256 ||= Digest::SHA256.hexdigest(pkey.public_key.to_der).freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key_pem
|
|
||||||
@public_key_pem ||= pkey.public_key.to_pem.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key_der
|
|
||||||
@public_key_der ||= pkey.public_key.to_der.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def private_key_pem
|
|
||||||
@private_key_pem ||=
|
|
||||||
if context.password.present?
|
|
||||||
pkey.to_pem(OpenSSL::Cipher::AES256.new, context.password).freeze
|
|
||||||
else
|
|
||||||
pkey.to_pem.freeze
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,13 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class CreateRSAKeysAndX509SelfSignedCertificate
|
|
||||||
include Interactor::Organizer
|
|
||||||
|
|
||||||
organize CreateRSAKeys, CreateX509SelfSignedCertificate
|
|
||||||
|
|
||||||
around do |interactor|
|
|
||||||
ActiveRecord::Base.transaction do
|
|
||||||
interactor.call
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,117 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class CreateX509SelfSignedCertificate
|
|
||||||
include Interactor
|
|
||||||
|
|
||||||
before do
|
|
||||||
context.not_before = Time.at(context.not_before).utc
|
|
||||||
context.not_after = Time.at(context.not_after).utc
|
|
||||||
end
|
|
||||||
|
|
||||||
def call # rubocop:disable Metrics/AbcSize
|
|
||||||
context.certificate = X509Certificate.create!(
|
|
||||||
asymmetric_key: context.asymmetric_key,
|
|
||||||
pem: cert.to_pem,
|
|
||||||
subject: cert.subject.to_s,
|
|
||||||
issuer: cert.issuer.to_s,
|
|
||||||
not_before: cert.not_before,
|
|
||||||
not_after: cert.not_after,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def private_key_pkey
|
|
||||||
@private_key_pkey ||= OpenSSL::PKey::RSA.new(
|
|
||||||
context.asymmetric_key.private_key_pem,
|
|
||||||
String(context.password),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key_pkey
|
|
||||||
@public_key_pkey ||= OpenSSL::PKey::RSA.new(
|
|
||||||
context.asymmetric_key.public_key_pem,
|
|
||||||
String(context.password),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def subject
|
|
||||||
@subject ||= OpenSSL::X509::Name.parse context.distinguished_name
|
|
||||||
end
|
|
||||||
|
|
||||||
def cert # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
||||||
@cert ||= OpenSSL::X509::Certificate.new.tap do |cert|
|
|
||||||
cert.version = 2
|
|
||||||
cert.serial = SecureRandom.rand 0...(2**16)
|
|
||||||
cert.subject = subject
|
|
||||||
cert.issuer = cert.subject
|
|
||||||
cert.public_key = public_key_pkey
|
|
||||||
cert.not_before = context.not_before
|
|
||||||
cert.not_after = context.not_after
|
|
||||||
|
|
||||||
AddExtensions.call cert
|
|
||||||
|
|
||||||
cert.sign private_key_pkey, OpenSSL::Digest::SHA256.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AddExtensions
|
|
||||||
def self.call(cert)
|
|
||||||
new(cert).call
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(cert)
|
|
||||||
@cert = cert
|
|
||||||
end
|
|
||||||
|
|
||||||
def call
|
|
||||||
cert.add_extension basic_constraints
|
|
||||||
cert.add_extension key_usage
|
|
||||||
cert.add_extension subject_key_ident
|
|
||||||
cert.add_extension authority_key_ident
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
attr_reader :cert
|
|
||||||
|
|
||||||
def ext_factory
|
|
||||||
@ext_factory ||= OpenSSL::X509::ExtensionFactory.new.tap do |ext_factory|
|
|
||||||
ext_factory.subject_certificate = cert
|
|
||||||
ext_factory.issuer_certificate = cert
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def basic_constraints
|
|
||||||
@basic_constraints ||= ext_factory.create_extension(
|
|
||||||
'basicConstraints',
|
|
||||||
'CA:TRUE',
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def key_usage
|
|
||||||
@key_usage ||= ext_factory.create_extension(
|
|
||||||
'keyUsage',
|
|
||||||
'keyCertSign, cRLSign',
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def subject_key_ident
|
|
||||||
@subject_key_ident ||= ext_factory.create_extension(
|
|
||||||
'subjectKeyIdentifier',
|
|
||||||
'hash',
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def authority_key_ident
|
|
||||||
@authority_key_ident ||= ext_factory.create_extension(
|
|
||||||
'authorityKeyIdentifier',
|
|
||||||
'keyid:always,issuer:always',
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,98 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ImportAsymmetricKey
|
|
||||||
include Interactor
|
|
||||||
|
|
||||||
RSA_BITS_RE = /^RSA Public-Key: \((\d+) bit\)$/.freeze
|
|
||||||
ECURVE_CURVE_RE = /^ASN1 OID: ((-|\w)+)$/.freeze
|
|
||||||
|
|
||||||
before :unset_asymmetric_key
|
|
||||||
before :sanitize_public_key_der
|
|
||||||
before :sanitize_public_key_pem
|
|
||||||
before :set_public_key_openssl_pkey
|
|
||||||
before :validate_public_key_der
|
|
||||||
before :validate_public_key_pem
|
|
||||||
|
|
||||||
def call
|
|
||||||
if context.public_key_openssl_pkey.private?
|
|
||||||
context.fail!
|
|
||||||
end
|
|
||||||
|
|
||||||
find_asymmetric_key
|
|
||||||
|
|
||||||
return unless context.asymmetric_key.nil?
|
|
||||||
|
|
||||||
case context.public_key_openssl_pkey
|
|
||||||
when OpenSSL::PKey::RSA then create_rsa_key
|
|
||||||
when OpenSSL::PKey::EC then create_ecurve_key
|
|
||||||
else context.fail!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_asymmetric_key
|
|
||||||
context.asymmetric_key = AsymmetricKey.find_by(
|
|
||||||
public_key_der: context.public_key_openssl_pkey.to_der,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_rsa_key
|
|
||||||
context.asymmetric_key = RSAKey.create!(
|
|
||||||
public_key_pem: context.public_key_openssl_pkey.to_pem,
|
|
||||||
public_key_der: context.public_key_openssl_pkey.to_der,
|
|
||||||
sha1: Digest::SHA1.hexdigest(context.public_key_openssl_pkey.to_der),
|
|
||||||
sha256: Digest::SHA256.hexdigest(context.public_key_openssl_pkey.to_der),
|
|
||||||
bits: context.public_key_openssl_pkey.to_text.lines
|
|
||||||
.grep(RSA_BITS_RE).first.match(RSA_BITS_RE)[1].to_i,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_ecurve_key
|
|
||||||
context.asymmetric_key = EcurveKey.create!(
|
|
||||||
public_key_pem: context.public_key_openssl_pkey.to_pem,
|
|
||||||
public_key_der: context.public_key_openssl_pkey.to_der,
|
|
||||||
sha1: Digest::SHA1.hexdigest(context.public_key_openssl_pkey.to_der),
|
|
||||||
sha256: Digest::SHA256.hexdigest(context.public_key_openssl_pkey.to_der),
|
|
||||||
curve: context.public_key_openssl_pkey.to_text.lines
|
|
||||||
.grep(ECURVE_CURVE_RE).first.match(ECURVE_CURVE_RE)[1],
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def unset_asymmetric_key
|
|
||||||
context.asymmetric_key = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def sanitize_public_key_der
|
|
||||||
context.public_key_der = String(context.public_key_der).presence&.dup.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def sanitize_public_key_pem
|
|
||||||
context.public_key_pem = String(context.public_key_pem).lines.map do |line|
|
|
||||||
"#{line.strip}\n"
|
|
||||||
end.join.presence.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_public_key_openssl_pkey
|
|
||||||
context.public_key_openssl_pkey ||= OpenSSL::PKey.read(
|
|
||||||
context.public_key_der || context.public_key_pem || '',
|
|
||||||
'',
|
|
||||||
)
|
|
||||||
rescue OpenSSL::PKey::PKeyError
|
|
||||||
context.fail!
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_public_key_der
|
|
||||||
return if context.public_key_der.blank?
|
|
||||||
return if context.public_key_der == context.public_key_openssl_pkey.to_der
|
|
||||||
|
|
||||||
raise 'Invalid DER'
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_public_key_pem
|
|
||||||
return if context.public_key_pem.blank?
|
|
||||||
return if context.public_key_pem == context.public_key_openssl_pkey.to_pem
|
|
||||||
|
|
||||||
raise 'Invalid PEM'
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,11 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ClearAsymmetricPrivateKeyJob < ApplicationJob
|
|
||||||
queue_as :default
|
|
||||||
|
|
||||||
def perform(asymmetric_key_id)
|
|
||||||
AsymmetricKey
|
|
||||||
.find(asymmetric_key_id)
|
|
||||||
.update! private_key_pem_iv: nil, private_key_pem_ciphertext: nil
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,84 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class AsymmetricKey < ApplicationRecord
|
|
||||||
PRIVATE_KEY_CLEAR_DELAY = 1.hour.freeze
|
|
||||||
|
|
||||||
attr_accessor :private_key_pem, :private_key_pem_secret
|
|
||||||
|
|
||||||
################
|
|
||||||
# Associations #
|
|
||||||
################
|
|
||||||
|
|
||||||
belongs_to :account, optional: true
|
|
||||||
|
|
||||||
###############
|
|
||||||
# Validations #
|
|
||||||
###############
|
|
||||||
|
|
||||||
validates :public_key_pem,
|
|
||||||
presence: true,
|
|
||||||
uniqueness: true
|
|
||||||
|
|
||||||
validates :public_key_der,
|
|
||||||
presence: true,
|
|
||||||
uniqueness: true
|
|
||||||
|
|
||||||
validates :bits,
|
|
||||||
allow_nil: true,
|
|
||||||
numericality: {
|
|
||||||
only_integer: true,
|
|
||||||
greater_than: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
validates :sha1,
|
|
||||||
presence: true,
|
|
||||||
uniqueness: { case_sensitive: false }
|
|
||||||
|
|
||||||
validates :sha256,
|
|
||||||
presence: true,
|
|
||||||
uniqueness: { case_sensitive: false }
|
|
||||||
|
|
||||||
###########
|
|
||||||
# Methods #
|
|
||||||
###########
|
|
||||||
|
|
||||||
def self.policy_class
|
|
||||||
AsymmetricKeyPolicy
|
|
||||||
end
|
|
||||||
|
|
||||||
def algo_class
|
|
||||||
raise NotImplementedError, "#{self.class}#algo_class"
|
|
||||||
end
|
|
||||||
|
|
||||||
def algo_variant
|
|
||||||
raise NotImplementedError, "#{self.class}#algo_variant"
|
|
||||||
end
|
|
||||||
|
|
||||||
def encrypt_private_key_pem
|
|
||||||
cipher = OpenSSL::Cipher::AES256.new
|
|
||||||
cipher.encrypt
|
|
||||||
|
|
||||||
self.private_key_pem_iv = cipher.random_iv.freeze
|
|
||||||
self.private_key_pem_secret = cipher.random_key.freeze
|
|
||||||
|
|
||||||
self.private_key_pem_ciphertext = [
|
|
||||||
cipher.update(private_key_pem),
|
|
||||||
cipher.final,
|
|
||||||
].join.freeze
|
|
||||||
|
|
||||||
private_key_pem_secret
|
|
||||||
end
|
|
||||||
|
|
||||||
def decrypt_private_key_pem
|
|
||||||
cipher = OpenSSL::Cipher::AES256.new
|
|
||||||
cipher.decrypt
|
|
||||||
|
|
||||||
cipher.iv = private_key_pem_iv
|
|
||||||
cipher.key = private_key_pem_secret
|
|
||||||
|
|
||||||
self.private_key_pem = [
|
|
||||||
cipher.update(private_key_pem_ciphertext),
|
|
||||||
cipher.final,
|
|
||||||
].join.freeze
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,26 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class EcurveKey < AsymmetricKey
|
|
||||||
ALGO_CLASS = 'Elliptic curve'
|
|
||||||
CURVES = %w[prime256v1 secp384r1].freeze
|
|
||||||
|
|
||||||
###############
|
|
||||||
# Validations #
|
|
||||||
###############
|
|
||||||
|
|
||||||
validates :curve, inclusion: { in: CURVES }
|
|
||||||
|
|
||||||
validates :bits, absence: true
|
|
||||||
|
|
||||||
###########
|
|
||||||
# Methods #
|
|
||||||
###########
|
|
||||||
|
|
||||||
def algo_class
|
|
||||||
ALGO_CLASS
|
|
||||||
end
|
|
||||||
|
|
||||||
def algo_variant
|
|
||||||
curve
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,26 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class RSAKey < AsymmetricKey
|
|
||||||
ALGO_CLASS = 'RSA'
|
|
||||||
BITS = [2048, 4096].freeze
|
|
||||||
|
|
||||||
###############
|
|
||||||
# Validations #
|
|
||||||
###############
|
|
||||||
|
|
||||||
validates :bits, inclusion: { in: BITS }
|
|
||||||
|
|
||||||
validates :curve, absence: true
|
|
||||||
|
|
||||||
###########
|
|
||||||
# Methods #
|
|
||||||
###########
|
|
||||||
|
|
||||||
def algo_class
|
|
||||||
ALGO_CLASS
|
|
||||||
end
|
|
||||||
|
|
||||||
def algo_variant
|
|
||||||
"#{bits} bits"
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,33 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class X509Certificate < ApplicationRecord
|
|
||||||
################
|
|
||||||
# Associations #
|
|
||||||
################
|
|
||||||
|
|
||||||
belongs_to :asymmetric_key
|
|
||||||
|
|
||||||
###############
|
|
||||||
# Validations #
|
|
||||||
###############
|
|
||||||
|
|
||||||
validates :pem, presence: true
|
|
||||||
|
|
||||||
validates :subject, presence: true
|
|
||||||
|
|
||||||
validates :issuer, presence: true
|
|
||||||
|
|
||||||
validates :not_before, presence: true
|
|
||||||
|
|
||||||
validates :not_after, presence: true
|
|
||||||
|
|
||||||
validate :can_be_parsed_and_exported_with_openssl
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def can_be_parsed_and_exported_with_openssl
|
|
||||||
OpenSSL::X509::Certificate.new(pem)&.to_text if pem.present?
|
|
||||||
rescue
|
|
||||||
errors.add :pem
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,12 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApplicationPolicy
|
class ApplicationPolicy
|
||||||
attr_reader :context, :record
|
attr_reader :account, :record
|
||||||
|
|
||||||
delegate :account, :params, to: :context, allow_nil: true
|
def initialize(account, record)
|
||||||
|
@account = account
|
||||||
def initialize(context, record)
|
|
||||||
@context = context
|
|
||||||
@record = record
|
@record = record
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -53,12 +51,10 @@ private
|
||||||
end
|
end
|
||||||
|
|
||||||
class Scope
|
class Scope
|
||||||
attr_reader :context, :scope
|
attr_reader :account, :scope
|
||||||
|
|
||||||
delegate :account, :params, to: :context, allow_nil: true
|
def initialize(account, scope)
|
||||||
|
@account = account
|
||||||
def initialize(context, scope)
|
|
||||||
@context = context
|
|
||||||
@scope = scope
|
@scope = scope
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -76,13 +72,4 @@ private
|
||||||
Rails.application.restricted?
|
Rails.application.restricted?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Context
|
|
||||||
attr_reader :account, :params
|
|
||||||
|
|
||||||
def initialize(account:, params:)
|
|
||||||
@account = account
|
|
||||||
@params = params
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class AsymmetricKeyPolicy < ApplicationPolicy
|
|
||||||
def index?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def show?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def create?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
class Scope < Scope
|
|
||||||
def resolve
|
|
||||||
scope.all
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,14 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class PrivateKeyPolicy < ApplicationPolicy
|
|
||||||
def show?
|
|
||||||
show_alert? && record.exist?
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_alert?
|
|
||||||
return false if account.nil?
|
|
||||||
|
|
||||||
params[:private_key_pem_secret].present? &&
|
|
||||||
(account.superuser? || account == record.account)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,31 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Staff::X509CertificatePolicy < ApplicationPolicy
|
|
||||||
def index?
|
|
||||||
return false if restricted?
|
|
||||||
|
|
||||||
account&.superuser?
|
|
||||||
end
|
|
||||||
|
|
||||||
def show?
|
|
||||||
return false if restricted?
|
|
||||||
|
|
||||||
account&.superuser?
|
|
||||||
end
|
|
||||||
|
|
||||||
def create?
|
|
||||||
return false if restricted?
|
|
||||||
|
|
||||||
account&.superuser?
|
|
||||||
end
|
|
||||||
|
|
||||||
class Scope < Scope
|
|
||||||
def resolve
|
|
||||||
return scope.none if restricted?
|
|
||||||
|
|
||||||
return scope.all if account&.superuser?
|
|
||||||
|
|
||||||
scope.none
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,20 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class PrivateKey
|
|
||||||
attr_reader :asymmetric_key
|
|
||||||
|
|
||||||
delegate :account, to: :asymmetric_key
|
|
||||||
|
|
||||||
def self.policy_class
|
|
||||||
'PrivateKeyPolicy'
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(asymmetric_key)
|
|
||||||
@asymmetric_key = asymmetric_key or raise
|
|
||||||
end
|
|
||||||
|
|
||||||
def exist?
|
|
||||||
asymmetric_key.private_key_pem_iv.present? &&
|
|
||||||
asymmetric_key.private_key_pem_ciphertext.present?
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,31 +0,0 @@
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">
|
|
||||||
<%= AsymmetricKey.human_attribute_name :id %>
|
|
||||||
</th>
|
|
||||||
<th scope="col">
|
|
||||||
<%= AsymmetricKey.human_attribute_name :algo_class %>
|
|
||||||
</th>
|
|
||||||
<th scope="col">
|
|
||||||
<%= AsymmetricKey.human_attribute_name :algo_variant %>
|
|
||||||
</th>
|
|
||||||
<th scope="col"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<% asymmetric_keys.each do |asymmetric_key| %>
|
|
||||||
<tr>
|
|
||||||
<td scope="row"><%= asymmetric_key.id %></td>
|
|
||||||
<td><%= asymmetric_key.algo_class %></td>
|
|
||||||
<td><%= asymmetric_key.algo_variant %></td>
|
|
||||||
<td>
|
|
||||||
<% if policy(asymmetric_key).show? %>
|
|
||||||
<%= open_action asymmetric_key_path(asymmetric_key) %>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<div class="container">
|
|
||||||
<%= nav_breadcrumb AsymmetricKey.model_name.human count: 0 %>
|
|
||||||
|
|
||||||
<% if policy(AsymmetricKey).new? %>
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<% if policy(AsymmetricKey).new? %>
|
|
||||||
<%= link_to translate(:add_existing),
|
|
||||||
new_asymmetric_key_path,
|
|
||||||
class: 'btn btn-primary',
|
|
||||||
role: :button %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="mt-3">
|
|
||||||
<%= render partial: 'table',
|
|
||||||
locals: { asymmetric_keys: @asymmetric_keys } %>
|
|
||||||
<%= pagination @asymmetric_keys %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,14 +0,0 @@
|
||||||
<div class="container">
|
|
||||||
<%= nav_breadcrumb(
|
|
||||||
[AsymmetricKey.model_name.human(count: 0), asymmetric_keys_path],
|
|
||||||
translate(:add_existing),
|
|
||||||
) %>
|
|
||||||
|
|
||||||
<%= simple_form_for @asymmetric_key_form do |f| %>
|
|
||||||
<%= f.error_notification %>
|
|
||||||
|
|
||||||
<%= f.input :public_key_pem, as: :text %>
|
|
||||||
|
|
||||||
<%= f.button :submit %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<div class="container">
|
|
||||||
<%= nav_breadcrumb(
|
|
||||||
[AsymmetricKey.model_name.human(count: 0), asymmetric_keys_path],
|
|
||||||
AsymmetricKey.model_name.human(count: 1),
|
|
||||||
) %>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<dl>
|
|
||||||
<dt><%= AsymmetricKey.human_attribute_name :id %></dt>
|
|
||||||
<dd><%= @asymmetric_key.id %></dd>
|
|
||||||
|
|
||||||
<dt><%= AsymmetricKey.human_attribute_name :algo_class %></dt>
|
|
||||||
<dd><%= @asymmetric_key.algo_class %></dd>
|
|
||||||
|
|
||||||
<dt><%= AsymmetricKey.human_attribute_name :algo_variant %></dt>
|
|
||||||
<dd><%= @asymmetric_key.algo_variant %></dd>
|
|
||||||
|
|
||||||
<dt><%= AsymmetricKey.human_attribute_name :sha1 %></dt>
|
|
||||||
<dd><%= display_sha1 @asymmetric_key.sha1 %></dd>
|
|
||||||
|
|
||||||
<dt><%= AsymmetricKey.human_attribute_name :sha256 %></dt>
|
|
||||||
<dd><%= display_sha256 @asymmetric_key.sha256 %></dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<%= render partial: 'private_keys/alert',
|
|
||||||
locals: {
|
|
||||||
asymmetric_key: @asymmetric_key,
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,40 +0,0 @@
|
||||||
<% if policy(PrivateKey.new(asymmetric_key)).show_alert? %>
|
|
||||||
<% if policy(PrivateKey.new(asymmetric_key)).show? %>
|
|
||||||
<div class="alert alert-primary" role="alert">
|
|
||||||
<h4 class="alert-heading">
|
|
||||||
<%= translate '.primary_header' %>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<%= translate(
|
|
||||||
'.primary_text',
|
|
||||||
delay: distance_of_time_in_words(
|
|
||||||
AsymmetricKey::PRIVATE_KEY_CLEAR_DELAY,
|
|
||||||
),
|
|
||||||
) %>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<%= link_to(
|
|
||||||
translate('.link'),
|
|
||||||
asymmetric_key_private_key_path(
|
|
||||||
asymmetric_key,
|
|
||||||
format: :key,
|
|
||||||
private_key_pem_secret: params[:private_key_pem_secret],
|
|
||||||
),
|
|
||||||
class: 'btn btn-primary',
|
|
||||||
) %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="alert alert-secondary" role="alert">
|
|
||||||
<h4 class="alert-heading">
|
|
||||||
<%= translate '.secondary_header' %>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<%= translate '.secondary_text' %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
|
@ -26,12 +26,5 @@
|
||||||
staff_contact_networks_path %>
|
staff_contact_networks_path %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if policy(%i[staff x509_certificate]).index? %>
|
|
||||||
<li>
|
|
||||||
<%= link_to X509Certificate.model_name.human(count: 0),
|
|
||||||
staff_x509_certificates_path %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">
|
|
||||||
<%= X509Certificate.human_attribute_name :id %>
|
|
||||||
</th>
|
|
||||||
<th scope="col">
|
|
||||||
<%= X509Certificate.human_attribute_name :not_before %>
|
|
||||||
</th>
|
|
||||||
<th scope="col">
|
|
||||||
<%= X509Certificate.human_attribute_name :not_after %>
|
|
||||||
</th>
|
|
||||||
<th scope="col"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<% x509_certificates.each do |x509_certificate| %>
|
|
||||||
<tr>
|
|
||||||
<td scope="row"><%= x509_certificate.id %></td>
|
|
||||||
<td><%= localize x509_certificate.not_before, format: :long %></td>
|
|
||||||
<td><%= localize x509_certificate.not_after, format: :long %></td>
|
|
||||||
<td>
|
|
||||||
<% if policy([:staff, x509_certificate]).show? %>
|
|
||||||
<%= open_action [:staff, x509_certificate] %>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
|
@ -1,23 +0,0 @@
|
||||||
<div class="container">
|
|
||||||
<%= nav_breadcrumb(
|
|
||||||
[translate(:staff_services), staff_root_path],
|
|
||||||
X509Certificate.model_name.human(count: 0),
|
|
||||||
) %>
|
|
||||||
|
|
||||||
<% if policy([:staff, X509Certificate]).new? %>
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<% if policy([:staff, X509Certificate]).new? %>
|
|
||||||
<%= link_to translate(:create),
|
|
||||||
new_staff_x509_certificate_path,
|
|
||||||
class: 'btn btn-primary',
|
|
||||||
role: :button %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="mt-3">
|
|
||||||
<%= render partial: 'table',
|
|
||||||
locals: { x509_certificates: @x509_certificates } %>
|
|
||||||
<%= pagination @x509_certificates %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,17 +0,0 @@
|
||||||
<div class="container">
|
|
||||||
<%= nav_breadcrumb(
|
|
||||||
[translate(:staff_services), staff_root_path],
|
|
||||||
[X509Certificate.model_name.human(count: 0), staff_x509_certificates_path],
|
|
||||||
translate(:create),
|
|
||||||
) %>
|
|
||||||
|
|
||||||
<%= simple_form_for [:staff, @x509_certificate_form] do |f| %>
|
|
||||||
<%= f.error_notification %>
|
|
||||||
|
|
||||||
<%= f.input :distinguished_name %>
|
|
||||||
<%= f.input :not_before %>
|
|
||||||
<%= f.input :not_after %>
|
|
||||||
|
|
||||||
<%= f.button :submit %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
|
@ -1,86 +0,0 @@
|
||||||
<div class="container">
|
|
||||||
<%= nav_breadcrumb(
|
|
||||||
[translate(:staff_services), staff_root_path],
|
|
||||||
[X509Certificate.model_name.human(count: 0), staff_x509_certificates_path],
|
|
||||||
X509Certificate.model_name.human(count: 1),
|
|
||||||
) %>
|
|
||||||
|
|
||||||
<div id="myTab" class="mb-3">
|
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
|
||||||
<li class="nav-item">
|
|
||||||
<%= link_to translate('nav_tabs.x509_certificate.overview'),
|
|
||||||
'#overview',
|
|
||||||
id: 'overview-tab',
|
|
||||||
class: 'nav-link active',
|
|
||||||
role: :tab,
|
|
||||||
'data-toggle': :tab,
|
|
||||||
'aria-controls': :overview,
|
|
||||||
'aria-selected': true %>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<%= link_to translate('nav_tabs.x509_certificate.pem'),
|
|
||||||
'#pem',
|
|
||||||
id: 'pem-tab',
|
|
||||||
class: 'nav-link',
|
|
||||||
role: :tab,
|
|
||||||
'data-toggle': :tab,
|
|
||||||
'aria-controls': :pem,
|
|
||||||
'aria-selected': true %>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<%= link_to translate('nav_tabs.x509_certificate.text'),
|
|
||||||
'#text',
|
|
||||||
id: 'text-tab',
|
|
||||||
class: 'nav-link',
|
|
||||||
role: :tab,
|
|
||||||
'data-toggle': :tab,
|
|
||||||
'aria-controls': :text,
|
|
||||||
'aria-selected': true %>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="myTabContent" class="tab-content">
|
|
||||||
<div id="overview" class="tab-pane show active" role="tabpanel" aria-labelledby="overview-tab">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<dl>
|
|
||||||
<dt><%= X509Certificate.human_attribute_name :id %></dt>
|
|
||||||
<dd><%= @x509_certificate.id %></dd>
|
|
||||||
|
|
||||||
<dt><%= X509Certificate.human_attribute_name :subject %></dt>
|
|
||||||
<dd><%= truncate @x509_certificate.subject %></dd>
|
|
||||||
|
|
||||||
<dt><%= X509Certificate.human_attribute_name :issuer %></dt>
|
|
||||||
<dd><%= truncate @x509_certificate.issuer %></dd>
|
|
||||||
|
|
||||||
<dt><%= X509Certificate.human_attribute_name :not_before %></dt>
|
|
||||||
<dd><%= localize @x509_certificate.not_before, format: :long %></dd>
|
|
||||||
|
|
||||||
<dt><%= X509Certificate.human_attribute_name :not_after %></dt>
|
|
||||||
<dd><%= localize @x509_certificate.not_after, format: :long %></dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<%= render partial: 'private_keys/alert',
|
|
||||||
locals: {
|
|
||||||
asymmetric_key: @x509_certificate.asymmetric_key,
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="pem" class="tab-pane" role="tabpanel" aria-labelledby"pem-tab">
|
|
||||||
<pre><code><%= @x509_certificate.pem %></code></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="text" class="tab-pane" role="tabpanel" aria-labelledby="text-tab">
|
|
||||||
<pre><code><%= OpenSSL::X509::Certificate.new(@x509_certificate.pem).to_text %></code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -26,7 +26,6 @@ module Partynest
|
||||||
confirmation_token
|
confirmation_token
|
||||||
password
|
password
|
||||||
password_confirmation
|
password_confirmation
|
||||||
private_key_pem_secret
|
|
||||||
reset_password_token
|
reset_password_token
|
||||||
secret
|
secret
|
||||||
unlock_token
|
unlock_token
|
||||||
|
@ -61,7 +60,6 @@ module Partynest
|
||||||
|
|
||||||
# Custom directories with classes and modules you want to be autoloadable.
|
# Custom directories with classes and modules you want to be autoloadable.
|
||||||
config.autoload_paths += [
|
config.autoload_paths += [
|
||||||
config.root.join('app', 'forms'),
|
|
||||||
config.root.join('app', 'primitives'),
|
config.root.join('app', 'primitives'),
|
||||||
config.root.join('app', 'validators'),
|
config.root.join('app', 'validators'),
|
||||||
config.root.join('lib'),
|
config.root.join('lib'),
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
# inflect.uncountable %w( fish sheep )
|
# inflect.uncountable %w( fish sheep )
|
||||||
# end
|
# end
|
||||||
|
|
||||||
ActiveSupport::Inflector.inflections :en do |inflect|
|
# These inflection rules are supported but not enabled by default:
|
||||||
inflect.acronym 'RSA'
|
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||||
end
|
# inflect.acronym 'RESTful'
|
||||||
|
# end
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
|
|
||||||
# Be sure to restart your server when you modify this file.
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
Mime::Type.register 'application/pkcs8', :key
|
# Add new mime types for use in respond_to blocks:
|
||||||
Mime::Type.register 'application/x-pem-file', :pem
|
# Mime::Type.register "text/richtext", :rtf
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
en:
|
|
||||||
activemodel:
|
|
||||||
attributes:
|
|
||||||
asymmetric_key:
|
|
||||||
public_key_pem: Public key in PEM format
|
|
||||||
x509_certificate:
|
|
||||||
distinguished_name: Distinguished name
|
|
||||||
not_before: Active since
|
|
||||||
not_after: Expires at
|
|
|
@ -1,9 +0,0 @@
|
||||||
ru:
|
|
||||||
activemodel:
|
|
||||||
attributes:
|
|
||||||
asymmetric_key:
|
|
||||||
public_key_pem: Публичный ключ в формате PEM
|
|
||||||
x509_certificate:
|
|
||||||
distinguished_name: Уникальное имя (distinguished name)
|
|
||||||
not_before: Активен с
|
|
||||||
not_after: Истекает
|
|
|
@ -4,10 +4,6 @@ en:
|
||||||
account:
|
account:
|
||||||
one: Account
|
one: Account
|
||||||
many: Accounts
|
many: Accounts
|
||||||
asymmetric_key:
|
|
||||||
one: Public key
|
|
||||||
few: Public keys
|
|
||||||
many: Public keys
|
|
||||||
contact:
|
contact:
|
||||||
one: Contact
|
one: Contact
|
||||||
few: Contacts
|
few: Contacts
|
||||||
|
@ -40,10 +36,6 @@ en:
|
||||||
user:
|
user:
|
||||||
one: User
|
one: User
|
||||||
many: Users
|
many: Users
|
||||||
x509_certificate:
|
|
||||||
one: X509 certificate
|
|
||||||
few: X509 certificates
|
|
||||||
many: X509 certificates
|
|
||||||
attributes:
|
attributes:
|
||||||
account:
|
account:
|
||||||
id: ID
|
id: ID
|
||||||
|
@ -53,12 +45,6 @@ en:
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
person: Person
|
person: Person
|
||||||
timezone: Timezone
|
timezone: Timezone
|
||||||
asymmetric_key:
|
|
||||||
id: ID
|
|
||||||
algo_class: Algorithm class
|
|
||||||
algo_variant: Variant
|
|
||||||
sha1: SHA-1 fingerprint
|
|
||||||
sha256: SHA-256 fingerprint
|
|
||||||
contact:
|
contact:
|
||||||
id: ID
|
id: ID
|
||||||
contact_network: Contact network
|
contact_network: Contact network
|
||||||
|
@ -145,13 +131,6 @@ en:
|
||||||
unconfirmed_email: Unconfirmed email
|
unconfirmed_email: Unconfirmed email
|
||||||
unlock_token: Unlock token
|
unlock_token: Unlock token
|
||||||
updated_at: Updated at
|
updated_at: Updated at
|
||||||
x509_certificate:
|
|
||||||
id: ID
|
|
||||||
pem: PEM
|
|
||||||
subject: Subject
|
|
||||||
issuer: Issuer
|
|
||||||
not_before: Active since
|
|
||||||
not_after: Expires at
|
|
||||||
errors:
|
errors:
|
||||||
messages:
|
messages:
|
||||||
image_size: 'has too big size'
|
image_size: 'has too big size'
|
||||||
|
|
|
@ -4,10 +4,6 @@ ru:
|
||||||
account:
|
account:
|
||||||
one: Аккаунт
|
one: Аккаунт
|
||||||
many: Аккаунты
|
many: Аккаунты
|
||||||
asymmetric_key:
|
|
||||||
one: Публичный ключ
|
|
||||||
few: Публичных ключа
|
|
||||||
many: Публичные ключи
|
|
||||||
contact:
|
contact:
|
||||||
one: Контакт
|
one: Контакт
|
||||||
few: Контакта
|
few: Контакта
|
||||||
|
@ -40,10 +36,6 @@ ru:
|
||||||
user:
|
user:
|
||||||
one: Пользователь
|
one: Пользователь
|
||||||
many: Пользователи
|
many: Пользователи
|
||||||
x509_certificate:
|
|
||||||
one: Сертификат X509
|
|
||||||
few: Сертификатов X509
|
|
||||||
many: Сертификаты X509
|
|
||||||
attributes:
|
attributes:
|
||||||
account:
|
account:
|
||||||
id: ID
|
id: ID
|
||||||
|
@ -53,12 +45,6 @@ ru:
|
||||||
avatar: Аватар
|
avatar: Аватар
|
||||||
person: Человек
|
person: Человек
|
||||||
timezone: Часовой пояс
|
timezone: Часовой пояс
|
||||||
asymmetric_key:
|
|
||||||
id: ID
|
|
||||||
algo_class: Класс алгоритма
|
|
||||||
algo_variant: Вариант
|
|
||||||
sha1: Отпечаток SHA-1
|
|
||||||
sha256: Отпечаток SHA-256
|
|
||||||
contact:
|
contact:
|
||||||
id: ID
|
id: ID
|
||||||
contact_network: Сеть контактов
|
contact_network: Сеть контактов
|
||||||
|
@ -145,13 +131,6 @@ ru:
|
||||||
unconfirmed_email: Неподтвержденный email
|
unconfirmed_email: Неподтвержденный email
|
||||||
unlock_token: Токен разблокировки
|
unlock_token: Токен разблокировки
|
||||||
updated_at: Дата обновления
|
updated_at: Дата обновления
|
||||||
x509_certificate:
|
|
||||||
id: ID
|
|
||||||
pem: PEM
|
|
||||||
subject: Имя субъекта
|
|
||||||
issuer: Имя издателя
|
|
||||||
not_before: Активен с
|
|
||||||
not_after: Истекает
|
|
||||||
errors:
|
errors:
|
||||||
messages:
|
messages:
|
||||||
image_size: 'имеет слишком большой размер'
|
image_size: 'имеет слишком большой размер'
|
||||||
|
|
|
@ -11,8 +11,6 @@ en:
|
||||||
display_entries: 'Displaying %{entry_name}
|
display_entries: 'Displaying %{entry_name}
|
||||||
<b>%{first} - %{last}</b> of <b>%{total}</b> in total'
|
<b>%{first} - %{last}</b> of <b>%{total}</b> in total'
|
||||||
submit:
|
submit:
|
||||||
asymmetric_key:
|
|
||||||
create: Add
|
|
||||||
person:
|
person:
|
||||||
create: Send
|
create: Send
|
||||||
person_comment:
|
person_comment:
|
||||||
|
|
|
@ -11,8 +11,6 @@ ru:
|
||||||
display_entries: 'Отображение %{entry_name}
|
display_entries: 'Отображение %{entry_name}
|
||||||
<b>%{first} - %{last}</b> из <b>%{total}</b> всего'
|
<b>%{first} - %{last}</b> из <b>%{total}</b> всего'
|
||||||
submit:
|
submit:
|
||||||
asymmetric_key:
|
|
||||||
create: Добавить
|
|
||||||
person:
|
person:
|
||||||
create: Отправить
|
create: Отправить
|
||||||
person_comment:
|
person_comment:
|
||||||
|
|
|
@ -16,23 +16,6 @@ en:
|
||||||
If this was you, you can ignore this alert. If you suspect
|
If this was you, you can ignore this alert. If you suspect
|
||||||
any suspicious activity on your account, please change your password
|
any suspicious activity on your account, please change your password
|
||||||
and enable two-factor authentication
|
and enable two-factor authentication
|
||||||
private_keys:
|
|
||||||
alert:
|
|
||||||
link: Download
|
|
||||||
primary_header: >-
|
|
||||||
Your private key is ready, but you have to download it right now!
|
|
||||||
secondary_header: >-
|
|
||||||
Your private key was deleted.
|
|
||||||
primary_text: >-
|
|
||||||
For better security we have encrypted your private key
|
|
||||||
with temporary secret token. You can download it until
|
|
||||||
you leave this page. Also note that key will be deleted
|
|
||||||
in %{delay} after creation anyway.
|
|
||||||
secondary_text: >-
|
|
||||||
For better security we have deleted your private key.
|
|
||||||
Hope you have downloaded it, because we can not restore it.
|
|
||||||
If you haven't downloaded it, you have to repeat the whole
|
|
||||||
procedure to generate new private key.
|
|
||||||
staffs:
|
staffs:
|
||||||
people:
|
people:
|
||||||
show:
|
show:
|
||||||
|
|
|
@ -17,23 +17,6 @@ ru:
|
||||||
Если это были вы, можете проигнорировать это предупреждение.
|
Если это были вы, можете проигнорировать это предупреждение.
|
||||||
Если вы заметили подозрительную активность вашего аккаунта, пожалуйста
|
Если вы заметили подозрительную активность вашего аккаунта, пожалуйста
|
||||||
измените пароль и включите двухфакторную аутентификацию
|
измените пароль и включите двухфакторную аутентификацию
|
||||||
private_keys:
|
|
||||||
alert:
|
|
||||||
link: Скачать
|
|
||||||
primary_header: >-
|
|
||||||
Ваш приватный ключ готов, но вы должны скачать его прямо сейчас!
|
|
||||||
secondary_header: >-
|
|
||||||
Ваш приватный ключ был удалён.
|
|
||||||
primary_text: >-
|
|
||||||
Для большей безопасности мы зашифровали ваш приватный ключ
|
|
||||||
с помощью временного токена. Вы можете скачать его пока не покините
|
|
||||||
эту страницу. Также учтите, что ключ будет уничтожен через %{delay}
|
|
||||||
после создания в любом случае.
|
|
||||||
secondary_text: >-
|
|
||||||
Для большей безопасности мы удалили ваш приватный ключ.
|
|
||||||
Надеемся, что вы его скачали, потому что мы не можем его восстановить.
|
|
||||||
Если вы не скачали его, вам придётся повторить всю процедуру сначала
|
|
||||||
чтобы сгенерировать новый приватный ключ.
|
|
||||||
settings:
|
settings:
|
||||||
people:
|
people:
|
||||||
show:
|
show:
|
||||||
|
|
|
@ -12,7 +12,3 @@ en:
|
||||||
person: Person
|
person: Person
|
||||||
contacts: Contacts
|
contacts: Contacts
|
||||||
sessions: Sessions
|
sessions: Sessions
|
||||||
x509_certificate:
|
|
||||||
overview: Overview
|
|
||||||
pem: PEM
|
|
||||||
text: Text
|
|
||||||
|
|
|
@ -12,7 +12,3 @@ ru:
|
||||||
person: Личность
|
person: Личность
|
||||||
contacts: Контакты
|
contacts: Контакты
|
||||||
sessions: Сессии
|
sessions: Сессии
|
||||||
x509_certificate:
|
|
||||||
overview: Обзор
|
|
||||||
pem: PEM
|
|
||||||
text: Текст
|
|
||||||
|
|
|
@ -12,4 +12,3 @@ en:
|
||||||
save: Save
|
save: Save
|
||||||
hello: Hello
|
hello: Hello
|
||||||
new_sign_in: New sign in to your account
|
new_sign_in: New sign in to your account
|
||||||
add_existing: Add existing
|
|
||||||
|
|
|
@ -12,4 +12,3 @@ ru:
|
||||||
save: Сохранить
|
save: Сохранить
|
||||||
hello: Здравствуйте
|
hello: Здравствуйте
|
||||||
new_sign_in: Произведён вход в ваш аккаунт
|
new_sign_in: Произведён вход в ваш аккаунт
|
||||||
add_existing: Добавить существующий
|
|
||||||
|
|
|
@ -13,10 +13,6 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
resources :federal_subjects, param: :number, only: %i[index show]
|
resources :federal_subjects, param: :number, only: %i[index show]
|
||||||
|
|
||||||
resources :asymmetric_keys, only: %i[index show new create] do
|
|
||||||
resource :private_key, only: :show
|
|
||||||
end
|
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# User routes #
|
# User routes #
|
||||||
###############
|
###############
|
||||||
|
@ -65,8 +61,6 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
resources :accounts, param: :nickname, only: %i[index show]
|
resources :accounts, param: :nickname, only: %i[index show]
|
||||||
|
|
||||||
resources :x509_certificates, only: %i[index show new create]
|
|
||||||
|
|
||||||
resources :people, only: %i[index show new create] do
|
resources :people, only: %i[index show new create] do
|
||||||
resources :person_comments,
|
resources :person_comments,
|
||||||
path: 'comments',
|
path: 'comments',
|
||||||
|
|
8
db/migrate/20190915131325_drop_x509_tables.rb
Normal file
8
db/migrate/20190915131325_drop_x509_tables.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DropX509Tables < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
drop_table :x509_certificates
|
||||||
|
drop_table :asymmetric_keys
|
||||||
|
end
|
||||||
|
end
|
177
db/structure.sql
177
db/structure.sql
|
@ -364,49 +364,6 @@ CREATE TABLE public.ar_internal_metadata (
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: asymmetric_keys; Type: TABLE; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE TABLE public.asymmetric_keys (
|
|
||||||
id bigint NOT NULL,
|
|
||||||
created_at timestamp(6) without time zone NOT NULL,
|
|
||||||
updated_at timestamp(6) without time zone NOT NULL,
|
|
||||||
type character varying NOT NULL,
|
|
||||||
account_id bigint,
|
|
||||||
public_key_pem text NOT NULL,
|
|
||||||
public_key_der bytea NOT NULL,
|
|
||||||
private_key_pem_iv bytea,
|
|
||||||
private_key_pem_ciphertext bytea,
|
|
||||||
has_password boolean,
|
|
||||||
sha1 character varying NOT NULL,
|
|
||||||
sha256 character varying NOT NULL,
|
|
||||||
bits integer,
|
|
||||||
curve character varying,
|
|
||||||
CONSTRAINT bits CHECK (((bits IS NULL) OR (bits = ANY (ARRAY[2048, 4096])))),
|
|
||||||
CONSTRAINT curve CHECK (((curve IS NULL) OR ((curve)::text = ANY ((ARRAY['prime256v1'::character varying, 'secp384r1'::character varying])::text[]))))
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: asymmetric_keys_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE SEQUENCE public.asymmetric_keys_id_seq
|
|
||||||
START WITH 1
|
|
||||||
INCREMENT BY 1
|
|
||||||
NO MINVALUE
|
|
||||||
NO MAXVALUE
|
|
||||||
CACHE 1;
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: asymmetric_keys_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
ALTER SEQUENCE public.asymmetric_keys_id_seq OWNED BY public.asymmetric_keys.id;
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: contact_lists; Type: TABLE; Schema: public; Owner: -
|
-- Name: contact_lists; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -924,42 +881,6 @@ CREATE SEQUENCE public.users_id_seq
|
||||||
ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
|
ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: x509_certificates; Type: TABLE; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE TABLE public.x509_certificates (
|
|
||||||
id bigint NOT NULL,
|
|
||||||
created_at timestamp(6) without time zone NOT NULL,
|
|
||||||
updated_at timestamp(6) without time zone NOT NULL,
|
|
||||||
asymmetric_key_id bigint NOT NULL,
|
|
||||||
pem text NOT NULL,
|
|
||||||
subject character varying NOT NULL,
|
|
||||||
issuer character varying NOT NULL,
|
|
||||||
not_before timestamp without time zone NOT NULL,
|
|
||||||
not_after timestamp without time zone NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: x509_certificates_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE SEQUENCE public.x509_certificates_id_seq
|
|
||||||
START WITH 1
|
|
||||||
INCREMENT BY 1
|
|
||||||
NO MINVALUE
|
|
||||||
NO MAXVALUE
|
|
||||||
CACHE 1;
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: x509_certificates_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
ALTER SEQUENCE public.x509_certificates_id_seq OWNED BY public.x509_certificates.id;
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: accounts id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: accounts id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -981,13 +902,6 @@ ALTER TABLE ONLY public.active_storage_attachments ALTER COLUMN id SET DEFAULT n
|
||||||
ALTER TABLE ONLY public.active_storage_blobs ALTER COLUMN id SET DEFAULT nextval('public.active_storage_blobs_id_seq'::regclass);
|
ALTER TABLE ONLY public.active_storage_blobs ALTER COLUMN id SET DEFAULT nextval('public.active_storage_blobs_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: asymmetric_keys id; Type: DEFAULT; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
ALTER TABLE ONLY public.asymmetric_keys ALTER COLUMN id SET DEFAULT nextval('public.asymmetric_keys_id_seq'::regclass);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: contact_lists id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: contact_lists id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1079,13 +993,6 @@ ALTER TABLE ONLY public.user_omniauths ALTER COLUMN id SET DEFAULT nextval('publ
|
||||||
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
|
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: x509_certificates id; Type: DEFAULT; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
ALTER TABLE ONLY public.x509_certificates ALTER COLUMN id SET DEFAULT nextval('public.x509_certificates_id_seq'::regclass);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: accounts accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: accounts accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1118,14 +1025,6 @@ ALTER TABLE ONLY public.ar_internal_metadata
|
||||||
ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
|
ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: asymmetric_keys asymmetric_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
ALTER TABLE ONLY public.asymmetric_keys
|
|
||||||
ADD CONSTRAINT asymmetric_keys_pkey PRIMARY KEY (id);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: contact_lists contact_lists_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: contact_lists contact_lists_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1238,14 +1137,6 @@ ALTER TABLE ONLY public.users
|
||||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: x509_certificates x509_certificates_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
ALTER TABLE ONLY public.x509_certificates
|
|
||||||
ADD CONSTRAINT x509_certificates_pkey PRIMARY KEY (id);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_accounts_on_contact_list_id; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_accounts_on_contact_list_id; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1288,48 +1179,6 @@ CREATE UNIQUE INDEX index_active_storage_attachments_uniqueness ON public.active
|
||||||
CREATE UNIQUE INDEX index_active_storage_blobs_on_key ON public.active_storage_blobs USING btree (key);
|
CREATE UNIQUE INDEX index_active_storage_blobs_on_key ON public.active_storage_blobs USING btree (key);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_asymmetric_keys_on_account_id; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE INDEX index_asymmetric_keys_on_account_id ON public.asymmetric_keys USING btree (account_id);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_asymmetric_keys_on_public_key_der; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_asymmetric_keys_on_public_key_der ON public.asymmetric_keys USING btree (public_key_der);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_asymmetric_keys_on_public_key_pem; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_asymmetric_keys_on_public_key_pem ON public.asymmetric_keys USING btree (public_key_pem);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_asymmetric_keys_on_sha1; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_asymmetric_keys_on_sha1 ON public.asymmetric_keys USING btree (sha1);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_asymmetric_keys_on_sha256; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_asymmetric_keys_on_sha256 ON public.asymmetric_keys USING btree (sha256);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_asymmetric_keys_on_type_and_id; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_asymmetric_keys_on_type_and_id ON public.asymmetric_keys USING btree (type, id);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_contact_networks_on_codename; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_contact_networks_on_codename; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1575,13 +1424,6 @@ CREATE UNIQUE INDEX index_users_on_reset_password_token ON public.users USING bt
|
||||||
CREATE UNIQUE INDEX index_users_on_unlock_token ON public.users USING btree (unlock_token);
|
CREATE UNIQUE INDEX index_users_on_unlock_token ON public.users USING btree (unlock_token);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_x509_certificates_on_asymmetric_key_id; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE INDEX index_x509_certificates_on_asymmetric_key_id ON public.x509_certificates USING btree (asymmetric_key_id);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: accounts ensure_contact_list_id_matches_related_person; Type: TRIGGER; Schema: public; Owner: -
|
-- Name: accounts ensure_contact_list_id_matches_related_person; Type: TRIGGER; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1619,14 +1461,6 @@ ALTER TABLE ONLY public.relationships
|
||||||
ADD CONSTRAINT fk_rails_124c042ac0 FOREIGN KEY (initiator_account_id) REFERENCES public.accounts(id);
|
ADD CONSTRAINT fk_rails_124c042ac0 FOREIGN KEY (initiator_account_id) REFERENCES public.accounts(id);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: x509_certificates fk_rails_1671512c40; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
ALTER TABLE ONLY public.x509_certificates
|
|
||||||
ADD CONSTRAINT fk_rails_1671512c40 FOREIGN KEY (asymmetric_key_id) REFERENCES public.asymmetric_keys(id);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: people fk_rails_4f02f930eb; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: people fk_rails_4f02f930eb; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1683,14 +1517,6 @@ ALTER TABLE ONLY public.regional_offices
|
||||||
ADD CONSTRAINT fk_rails_7a6d5fdd9a FOREIGN KEY (federal_subject_id) REFERENCES public.federal_subjects(id);
|
ADD CONSTRAINT fk_rails_7a6d5fdd9a FOREIGN KEY (federal_subject_id) REFERENCES public.federal_subjects(id);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: asymmetric_keys fk_rails_7d85781ea1; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
ALTER TABLE ONLY public.asymmetric_keys
|
|
||||||
ADD CONSTRAINT fk_rails_7d85781ea1 FOREIGN KEY (account_id) REFERENCES public.accounts(id);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: user_omniauths fk_rails_8c1c9cb22e; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: user_omniauths fk_rails_8c1c9cb22e; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1759,6 +1585,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
||||||
('20190910040709'),
|
('20190910040709'),
|
||||||
('20190911081459'),
|
('20190911081459'),
|
||||||
('20190914050858'),
|
('20190914050858'),
|
||||||
('20190915085803');
|
('20190915085803'),
|
||||||
|
('20190915131325');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
FactoryBot.define do
|
|
||||||
factory :ecurve_key do
|
|
||||||
association :account, factory: :usual_account
|
|
||||||
|
|
||||||
public_key_pem do
|
|
||||||
point = OpenSSL::PKey::EC.generate(curve).public_key
|
|
||||||
pkey = OpenSSL::PKey::EC.new point.group
|
|
||||||
pkey.public_key = point
|
|
||||||
pkey.to_pem
|
|
||||||
end
|
|
||||||
|
|
||||||
public_key_der do
|
|
||||||
point = OpenSSL::PKey::EC.generate(curve).public_key
|
|
||||||
pkey = OpenSSL::PKey::EC.new point.group
|
|
||||||
pkey.public_key = point
|
|
||||||
pkey.to_der
|
|
||||||
end
|
|
||||||
|
|
||||||
has_password { [false, true].sample }
|
|
||||||
sha1 { Digest::SHA1.hexdigest SecureRandom.hex }
|
|
||||||
sha256 { Digest::SHA256.hexdigest SecureRandom.hex }
|
|
||||||
|
|
||||||
curve { EcurveKey::CURVES.sample }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,16 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
FactoryBot.define do
|
|
||||||
factory :rsa_key do
|
|
||||||
association :account, factory: :usual_account
|
|
||||||
|
|
||||||
public_key_pem { OpenSSL::PKey::RSA.new(bits).public_key.to_pem }
|
|
||||||
public_key_der { OpenSSL::PKey::RSA.new(bits).public_key.to_der }
|
|
||||||
|
|
||||||
has_password { [false, true].sample }
|
|
||||||
sha1 { Digest::SHA1.hexdigest SecureRandom.hex }
|
|
||||||
sha256 { Digest::SHA256.hexdigest SecureRandom.hex }
|
|
||||||
|
|
||||||
bits { RSAKey::BITS.sample }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,13 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
FactoryBot.define do
|
|
||||||
factory :self_signed_x509_certificate, class: X509Certificate do
|
|
||||||
association :asymmetric_key, factory: :rsa_key
|
|
||||||
|
|
||||||
pem { File.read Rails.root.join 'fixtures', 'ca.crt' }
|
|
||||||
subject { '/CN=example.com' }
|
|
||||||
issuer { subject }
|
|
||||||
not_before { Faker::Time.backward.utc }
|
|
||||||
not_after { Faker::Time.forward.utc }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,30 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIFLzCCAxegAwIBAgIDAOcoMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC2V4
|
|
||||||
YW1wbGUuY29tMB4XDTE5MDkxMTEwMDcxMloXDTIwMDkxMDEwMDcxMlowFjEUMBIG
|
|
||||||
A1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
|
|
||||||
AQDSzS7+xkHL2KsB7P6jzbLPpAAqV7PCbuxqQsxWDHnadmJ4VCRNP8VuecT2hN1Q
|
|
||||||
tJRHb3FpooB+nFvjLt7b1rUoAoaBUF389UBsmCNPiAttGNGnRib0m4wVhxWhHLZ+
|
|
||||||
rTuXrKjZjWci06aFj+rybQJjMYrcSx9/2MhaZpL7o7bsQ9gxV7ohmbEtNCLSGxAP
|
|
||||||
vfhNrhgx8aZi1ZHusior51pt35bhLst+ixvfG/fW8SrJ4rd6nAYlI52RXtkxOanX
|
|
||||||
wJYKDzsJC5lVptjP/s/PuZuYhTDiYlI69w0qK7dMu2HygzmMdCVmzsFVOyCPCZa6
|
|
||||||
SbCSkntS0oSLugY7XSIRp6tD1Lz/FoaQcF8GswDkt4qE5lPOfT6y2xREv6rpTcrZ
|
|
||||||
kC1g/ztfcphkCAdSBHhM1WfZdx2RjpMnyjOGAVTiORX0qgXJ+WKIlzAf0JAmPAB+
|
|
||||||
FM53hPqf+Py4v0jTYAjs+kR8lIoiPUaPIxIWbHFSHmPzQvtmh8V43N3GAXEMCGRw
|
|
||||||
seKoUvInUaq/EEtSoG6qBkXO7FSTwLGF6++VNa57ZGtqgDebf3OTvNQSrhGQRi1x
|
|
||||||
qp9yk+6ob8m2WLHjN6FNnA8R4J+X0RJNiyztTcQxa1BEeDS1fhk3zmsJyMCSa7RE
|
|
||||||
+Q8HPdY08JSUgcVqFazu9CFjv/NNGkaaWXoqSkdQKIlYGwIDAQABo4GFMIGCMA8G
|
|
||||||
A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTkEQS/jMRY
|
|
||||||
5qI4Vm82EJ+qU0bXMjBABgNVHSMEOTA3gBTkEQS/jMRY5qI4Vm82EJ+qU0bXMqEa
|
|
||||||
pBgwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb22CAwDnKDANBgkqhkiG9w0BAQsFAAOC
|
|
||||||
AgEAdlzNvT5JNGMKX7IStrAprY4rWpd3UkM36n2CriMrQHfyv0zWS277eiY+DNHf
|
|
||||||
Hx1qscYSA6YyKj5ow9BwVgKz0NoEjbX5CAN2EebmqKePXDNTgHkhx2h2+1S8JeQp
|
|
||||||
bOei0TANSiQpqCR8lO9kEi8+4vcHdfYC9Hn7EFXuW4xvYbxJ3YVmkRZDQ/3oUX0q
|
|
||||||
HevogupOtH6ZpemSkQx81VOMPoloCCwq9wU+yHDMcYRIxp8ySdsxEAMHTlBXW1wB
|
|
||||||
86eZ6TotzSgiK4VzYTth67O4dQaWYASA/CGBURVWuwV1kzMrDaZn+V8oySDBo9Ct
|
|
||||||
TM3K+UWjNuLlEwgpCMF8ze/TNaJQHg5R0ap34hb0nfdOnIComxM8kzXzmbODSinz
|
|
||||||
/VgndXjnlxiw+JTgR8Fuc5mBolnyZddj9QXTO83Ze0/u2lUU/RP93MrgPQAYGOa2
|
|
||||||
IdnKhkiEvS0xhBdQWevgLLL3aRqfzdiHxXMZ3dtEAo2X5iAZtyH/ZFR8XNeFb2xm
|
|
||||||
yNjID1691R7HHzA/KIh89vimv47H1XdjpTlKS21WcS82pOPZndq4B0q788KZ2npo
|
|
||||||
33dSAd7PlWfzZQjooKEWXVbfTewkN5DIHmWZG8ZMqxLHtf0vzPy5UKe2KVLcZ3N1
|
|
||||||
chdoswVOLa4omXy+pYGiLuhv+xEp/zev18hjcwz1b0Piuds=
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe X509CertificateForm, type: :model do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,194 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe CreateEcurveKeys do
|
|
||||||
subject do
|
|
||||||
described_class.call account: account, password: password, curve: curve
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:account) { create :initial_account }
|
|
||||||
let(:password) { Faker::Internet.password }
|
|
||||||
let(:curve) { EcurveKey::CURVES.sample }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to change(AsymmetricKey, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to change(EcurveKey, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to(
|
|
||||||
have_enqueued_job(ClearAsymmetricPrivateKeyJob)
|
|
||||||
.with do |asymmetric_key_id|
|
|
||||||
expect(asymmetric_key_id).to equal AsymmetricKey.last.id
|
|
||||||
end,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key).to be_instance_of EcurveKey
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.curve).to eq curve
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.bits).to equal nil
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal true
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.sha1).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.sha256).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem_secret).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.account).to eq account
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect do
|
|
||||||
OpenSSL::PKey::EC.new(
|
|
||||||
subject.asymmetric_key.private_key_pem,
|
|
||||||
String(password),
|
|
||||||
)
|
|
||||||
end.not_to raise_error
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect do
|
|
||||||
OpenSSL::PKey::EC.new(
|
|
||||||
subject.asymmetric_key.public_key_pem,
|
|
||||||
String(password),
|
|
||||||
)
|
|
||||||
end.not_to raise_error
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.sha1).to eq(
|
|
||||||
Digest::SHA1.hexdigest(
|
|
||||||
OpenSSL::PKey::EC.new(
|
|
||||||
subject.asymmetric_key.public_key_pem,
|
|
||||||
String(password),
|
|
||||||
).to_der,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.sha256).to eq(
|
|
||||||
Digest::SHA256.hexdigest(
|
|
||||||
OpenSSL::PKey::EC.new(
|
|
||||||
subject.asymmetric_key.public_key_pem,
|
|
||||||
String(password),
|
|
||||||
).to_der,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
private_key = OpenSSL::PKey::EC.new(
|
|
||||||
subject.asymmetric_key.private_key_pem,
|
|
||||||
String(password),
|
|
||||||
)
|
|
||||||
|
|
||||||
public_key = OpenSSL::PKey::EC.new private_key.public_key.group
|
|
||||||
public_key.public_key = private_key.public_key
|
|
||||||
|
|
||||||
expect(subject.asymmetric_key.public_key_pem).to eq public_key.to_pem
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem_iv).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem_ciphertext).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
cipher = OpenSSL::Cipher::AES256.new
|
|
||||||
cipher.decrypt
|
|
||||||
cipher.iv = subject.asymmetric_key.private_key_pem_iv
|
|
||||||
cipher.key = subject.asymmetric_key.private_key_pem_secret
|
|
||||||
|
|
||||||
cleartext = [
|
|
||||||
cipher.update(subject.asymmetric_key.private_key_pem_ciphertext),
|
|
||||||
cipher.final,
|
|
||||||
].join.freeze
|
|
||||||
|
|
||||||
expect(cleartext).to eq subject.asymmetric_key.private_key_pem
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when owner is not specified' do
|
|
||||||
let(:account) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.account).to equal nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when password is nil' do
|
|
||||||
let(:password) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when password is blank' do
|
|
||||||
let(:password) { ' ' * rand(1..3) }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when password.to_s returns nil' do
|
|
||||||
let :password do
|
|
||||||
Class.new do
|
|
||||||
def to_s
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end.new
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to raise_error TypeError
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when curve value is invalid' do
|
|
||||||
let(:curve) { 'secp521r1' }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to raise_error RuntimeError, 'Invalid curve'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when curve value is nil' do
|
|
||||||
let(:curve) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.curve).to eq 'prime256v1'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,90 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe CreateRSAKeysAndX509SelfSignedCertificate do
|
|
||||||
subject do
|
|
||||||
described_class.call(
|
|
||||||
account: account,
|
|
||||||
password: password,
|
|
||||||
distinguished_name: distinguished_name,
|
|
||||||
not_before: not_before,
|
|
||||||
not_after: not_after,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:account) { create :initial_account }
|
|
||||||
let(:password) { Faker::Internet.password }
|
|
||||||
let(:distinguished_name) { "CN=#{Faker::Internet.domain_name}" }
|
|
||||||
let(:not_before) { Faker::Time.backward.utc }
|
|
||||||
let(:not_after) { Faker::Time.forward.utc }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to change(AsymmetricKey, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to change(RSAKey, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to change(X509Certificate, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to(
|
|
||||||
have_enqueued_job(ClearAsymmetricPrivateKeyJob)
|
|
||||||
.with do |asymmetric_key_id|
|
|
||||||
expect(asymmetric_key_id).to equal AsymmetricKey.last.id
|
|
||||||
end,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key).to be_instance_of RSAKey
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal true
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.certificate).to be_instance_of X509Certificate
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem_secret).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.account).to eq account
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when owner is not specified' do
|
|
||||||
let(:account) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.account).to equal nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when password is nil' do
|
|
||||||
let(:password) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when password is blank' do
|
|
||||||
let(:password) { ' ' * rand(1..3) }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,192 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe CreateRSAKeys do
|
|
||||||
subject do
|
|
||||||
described_class.call account: account, password: password, bits: bits
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:account) { create :initial_account }
|
|
||||||
let(:password) { Faker::Internet.password }
|
|
||||||
let(:bits) { RSAKey::BITS.sample }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to change(AsymmetricKey, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to change(RSAKey, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to(
|
|
||||||
have_enqueued_job(ClearAsymmetricPrivateKeyJob)
|
|
||||||
.with do |asymmetric_key_id|
|
|
||||||
expect(asymmetric_key_id).to equal AsymmetricKey.last.id
|
|
||||||
end,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key).to be_instance_of RSAKey
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.bits).to equal bits
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.curve).to equal nil
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal true
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.sha1).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.sha256).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem_secret).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.account).to eq account
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect do
|
|
||||||
OpenSSL::PKey::RSA.new(
|
|
||||||
subject.asymmetric_key.private_key_pem,
|
|
||||||
String(password),
|
|
||||||
)
|
|
||||||
end.not_to raise_error
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect do
|
|
||||||
OpenSSL::PKey::RSA.new(
|
|
||||||
subject.asymmetric_key.public_key_pem,
|
|
||||||
String(password),
|
|
||||||
)
|
|
||||||
end.not_to raise_error
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.sha1).to eq(
|
|
||||||
Digest::SHA1.hexdigest(
|
|
||||||
OpenSSL::PKey::RSA.new(
|
|
||||||
subject.asymmetric_key.public_key_pem,
|
|
||||||
String(password),
|
|
||||||
).to_der,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.sha256).to eq(
|
|
||||||
Digest::SHA256.hexdigest(
|
|
||||||
OpenSSL::PKey::RSA.new(
|
|
||||||
subject.asymmetric_key.public_key_pem,
|
|
||||||
String(password),
|
|
||||||
).to_der,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.public_key_pem).to eq(
|
|
||||||
OpenSSL::PKey::RSA.new(
|
|
||||||
subject.asymmetric_key.private_key_pem,
|
|
||||||
String(password),
|
|
||||||
)
|
|
||||||
.public_key.to_pem,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem_iv).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.private_key_pem_ciphertext).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
cipher = OpenSSL::Cipher::AES256.new
|
|
||||||
cipher.decrypt
|
|
||||||
cipher.iv = subject.asymmetric_key.private_key_pem_iv
|
|
||||||
cipher.key = subject.asymmetric_key.private_key_pem_secret
|
|
||||||
|
|
||||||
cleartext = [
|
|
||||||
cipher.update(subject.asymmetric_key.private_key_pem_ciphertext),
|
|
||||||
cipher.final,
|
|
||||||
].join.freeze
|
|
||||||
|
|
||||||
expect(cleartext).to eq subject.asymmetric_key.private_key_pem
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when owner is not specified' do
|
|
||||||
let(:account) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.account).to equal nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when password is nil' do
|
|
||||||
let(:password) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when password is blank' do
|
|
||||||
let(:password) { ' ' * rand(1..3) }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.has_password).to equal false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when password.to_s returns nil' do
|
|
||||||
let :password do
|
|
||||||
Class.new do
|
|
||||||
def to_s
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end.new
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to raise_error TypeError
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when bits value is invalid' do
|
|
||||||
let(:bits) { 1024 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to raise_error RuntimeError, 'Invalid key size'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when bits value is nil' do
|
|
||||||
let(:bits) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.asymmetric_key.bits).to equal 4096
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,52 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe CreateX509SelfSignedCertificate do
|
|
||||||
subject do
|
|
||||||
described_class.call(
|
|
||||||
asymmetric_key: asymmetric_key,
|
|
||||||
distinguished_name: distinguished_name,
|
|
||||||
not_before: not_before,
|
|
||||||
not_after: not_after,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:asymmetric_key) { CreateRSAKeys.call.asymmetric_key }
|
|
||||||
let(:distinguished_name) { "CN=#{Faker::Internet.domain_name}" }
|
|
||||||
let(:not_before) { Faker::Time.backward.utc }
|
|
||||||
let(:not_after) { Faker::Time.forward.utc }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject }.to change(X509Certificate, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.certificate).to be_instance_of X509Certificate
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.certificate.asymmetric_key).to eq asymmetric_key
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.certificate.pem).to \
|
|
||||||
be_start_with "-----BEGIN CERTIFICATE-----\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.certificate.subject).to eq "/#{distinguished_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.certificate.issuer).to eq "/#{distinguished_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.certificate.not_before).to eq not_before
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.certificate.not_after).to eq not_after
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe ImportAsymmetricKey do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe ClearAsymmetricPrivateKeyJob do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AsymmetricKey do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,20 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe EcurveKey do
|
|
||||||
subject { create :ecurve_key }
|
|
||||||
|
|
||||||
it_behaves_like 'asymmetric_key'
|
|
||||||
|
|
||||||
describe '#curve' do
|
|
||||||
it do
|
|
||||||
is_expected.to \
|
|
||||||
validate_inclusion_of(:curve).in_array(%w[prime256v1 secp384r1])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#bits' do
|
|
||||||
it { is_expected.to validate_absence_of :bits }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,212 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe RSAKey do
|
|
||||||
subject { create :rsa_key }
|
|
||||||
|
|
||||||
it_behaves_like 'asymmetric_key'
|
|
||||||
|
|
||||||
describe '#bits' do
|
|
||||||
it { is_expected.to validate_inclusion_of(:bits).in_array([2048, 4096]) }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#curve' do
|
|
||||||
it { is_expected.to validate_absence_of :curve }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#encrypt_private_key_pem' do
|
|
||||||
subject { create :rsa_key, private_key_pem: cleartext }
|
|
||||||
|
|
||||||
let(:cleartext) { OpenSSL::PKey::RSA.new.to_pem.freeze }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.encrypt_private_key_pem).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.encrypt_private_key_pem).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.encrypt_private_key_pem).to \
|
|
||||||
equal subject.private_key_pem_secret
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject.encrypt_private_key_pem }.to \
|
|
||||||
change(subject, :private_key_pem_iv)
|
|
||||||
.from(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject.encrypt_private_key_pem }.to \
|
|
||||||
change(subject, :private_key_pem_secret)
|
|
||||||
.from(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject.encrypt_private_key_pem }.to \
|
|
||||||
change(subject, :private_key_pem_ciphertext)
|
|
||||||
.from(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'after call' do
|
|
||||||
before { subject.encrypt_private_key_pem }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_iv).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_secret).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_ciphertext).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_iv).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_secret).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_ciphertext).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem).to eq cleartext
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_iv).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_secret).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_ciphertext).not_to be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
cipher = OpenSSL::Cipher::AES256.new
|
|
||||||
cipher.encrypt
|
|
||||||
|
|
||||||
cipher.iv = subject.private_key_pem_iv
|
|
||||||
cipher.key = subject.private_key_pem_secret
|
|
||||||
|
|
||||||
ciphertext = [
|
|
||||||
cipher.update(cleartext),
|
|
||||||
cipher.final,
|
|
||||||
].join.freeze
|
|
||||||
|
|
||||||
expect(subject.private_key_pem_ciphertext).to eq ciphertext
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#decrypt_private_key_pem' do
|
|
||||||
subject do
|
|
||||||
create(
|
|
||||||
:rsa_key,
|
|
||||||
private_key_pem_iv: iv,
|
|
||||||
private_key_pem_secret: secret,
|
|
||||||
private_key_pem_ciphertext: ciphertext,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:cleartext) { OpenSSL::PKey::RSA.new.to_pem.freeze }
|
|
||||||
|
|
||||||
let!(:cipher) { OpenSSL::Cipher::AES256.new.tap(&:encrypt) }
|
|
||||||
|
|
||||||
let!(:iv) { cipher.random_iv.freeze }
|
|
||||||
let!(:secret) { cipher.random_key.freeze }
|
|
||||||
|
|
||||||
let!(:ciphertext) { [cipher.update(cleartext), cipher.final].join.freeze }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.decrypt_private_key_pem).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.decrypt_private_key_pem).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.decrypt_private_key_pem).to equal subject.private_key_pem
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { subject.decrypt_private_key_pem }.to \
|
|
||||||
change(subject, :private_key_pem)
|
|
||||||
.from(nil)
|
|
||||||
.to(cleartext)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'after call' do
|
|
||||||
before { subject.decrypt_private_key_pem }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_iv).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_secret).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_ciphertext).to be_instance_of String
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_iv).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_secret).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_ciphertext).to be_frozen
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem).to eq cleartext
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_iv).to equal iv
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_secret).to equal secret
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(subject.private_key_pem_ciphertext).to equal ciphertext
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,52 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
RSpec.shared_examples 'asymmetric_key' do
|
|
||||||
describe '#account' do
|
|
||||||
it { is_expected.to belong_to(:account).optional }
|
|
||||||
|
|
||||||
it { is_expected.not_to validate_presence_of :account }
|
|
||||||
it { is_expected.not_to validate_uniqueness_of :account }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#public_key_pem' do
|
|
||||||
it { is_expected.to validate_presence_of :public_key_pem }
|
|
||||||
it { is_expected.to validate_uniqueness_of :public_key_pem }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#public_key_der' do
|
|
||||||
it { is_expected.to validate_presence_of :public_key_der }
|
|
||||||
it { is_expected.to validate_uniqueness_of :public_key_der }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#has_password' do
|
|
||||||
it { is_expected.not_to validate_presence_of :has_password }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#bits' do
|
|
||||||
it do
|
|
||||||
is_expected.to \
|
|
||||||
validate_numericality_of(:bits)
|
|
||||||
.allow_nil
|
|
||||||
.only_integer
|
|
||||||
.is_greater_than(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#sha1' do
|
|
||||||
it { is_expected.to validate_presence_of :sha1 }
|
|
||||||
it { is_expected.to validate_uniqueness_of(:sha1).case_insensitive }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#sha256' do
|
|
||||||
it { is_expected.to validate_presence_of :sha256 }
|
|
||||||
it { is_expected.to validate_uniqueness_of(:sha256).case_insensitive }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#private_key_pem_iv' do
|
|
||||||
it { is_expected.not_to validate_presence_of :private_key_pem_iv }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#private_key_pem_ciphertext' do
|
|
||||||
it { is_expected.not_to validate_presence_of :private_key_pem_ciphertext }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,50 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe X509Certificate do
|
|
||||||
subject { create :self_signed_x509_certificate }
|
|
||||||
|
|
||||||
describe '#asymmetric_key' do
|
|
||||||
it { is_expected.to belong_to(:asymmetric_key).required }
|
|
||||||
|
|
||||||
it do
|
|
||||||
is_expected.to \
|
|
||||||
validate_presence_of(:asymmetric_key).with_message(:required)
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.not_to validate_uniqueness_of :asymmetric_key }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#pem' do
|
|
||||||
def allow_value(*)
|
|
||||||
super.for :pem
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.to validate_presence_of :pem }
|
|
||||||
|
|
||||||
it 'is allowed to be a valid certificate' do
|
|
||||||
is_expected.to allow_value File.read Rails.root.join 'fixtures', 'ca.crt'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is not allowed to be an invalid certificate' do
|
|
||||||
is_expected.not_to allow_value OpenSSL::X509::Certificate.new.to_pem
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#subject' do
|
|
||||||
it { is_expected.to validate_presence_of :subject }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#issuer' do
|
|
||||||
it { is_expected.to validate_presence_of :issuer }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#not_before' do
|
|
||||||
it { is_expected.to validate_presence_of :not_before }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#not_after' do
|
|
||||||
it { is_expected.to validate_presence_of :not_after }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AsymmetricKeyPolicy do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe PrivateKeyPolicy do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Staff::X509CertificatePolicy do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe PrivateKey, type: :model do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -37,7 +37,6 @@ require_relative 'support/database_cleaner'
|
||||||
require_relative 'support/devise'
|
require_relative 'support/devise'
|
||||||
require_relative 'support/pundit'
|
require_relative 'support/pundit'
|
||||||
|
|
||||||
require_relative 'models/shared_examples/asymmetric_key'
|
|
||||||
require_relative 'models/shared_examples/nameable'
|
require_relative 'models/shared_examples/nameable'
|
||||||
require_relative 'models/shared_examples/required_nameable'
|
require_relative 'models/shared_examples/required_nameable'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'POST /asymmetric_keys' do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,69 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'GET /asymmetric_keys' do
|
|
||||||
let(:current_account) { create :superuser_account }
|
|
||||||
|
|
||||||
let :asymmetric_keys_count do
|
|
||||||
[0, 1, rand(2..4), rand(5..10), rand(20..40)].sample
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:rsa_keys_count) { rand(1...asymmetric_keys_count) || 0 }
|
|
||||||
let(:ecurve_keys_count) { asymmetric_keys_count - rsa_keys_count }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in current_account.user if current_account&.user
|
|
||||||
|
|
||||||
create_list :rsa_key, rsa_keys_count
|
|
||||||
create_list :ecurve_key, ecurve_keys_count
|
|
||||||
|
|
||||||
get '/asymmetric_keys'
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types nil, :usual, :superuser do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are no asymmetric keys' do
|
|
||||||
let(:asymmetric_keys_count) { 0 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there is one asymmetric key' do
|
|
||||||
let(:asymmetric_keys_count) { 1 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are few asymmetric keys' do
|
|
||||||
let(:asymmetric_keys_count) { rand 2..4 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are many asymmetric keys' do
|
|
||||||
let(:asymmetric_keys_count) { rand 5..10 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are lot of asymmetric keys' do
|
|
||||||
let(:asymmetric_keys_count) { rand 20..40 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,17 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'GET /asymmetric_keys/new' do
|
|
||||||
before do
|
|
||||||
sign_in current_account.user if current_account&.user
|
|
||||||
|
|
||||||
get '/asymmetric_keys/new'
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types nil, :usual, :superuser do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,40 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'GET /asymmetric_keys/:id' do
|
|
||||||
let(:current_account) { nil }
|
|
||||||
|
|
||||||
let(:asymmetric_key) { create %i[rsa_key ecurve_key].sample }
|
|
||||||
|
|
||||||
def make_request
|
|
||||||
get "/asymmetric_keys/#{asymmetric_key.id}"
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in current_account.user if current_account&.user
|
|
||||||
make_request
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types nil, :usual, :superuser do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for RSA key' do
|
|
||||||
let(:asymmetric_key) { create :rsa_key }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for elliptic-curve key' do
|
|
||||||
let(:asymmetric_key) { create :ecurve_key }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'GET /private_keys/:id' do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -1,140 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'POST /staff/x509_certificates' do
|
|
||||||
let(:current_account) { create :superuser_account }
|
|
||||||
|
|
||||||
let :x509_certificate_form_attributes do
|
|
||||||
{
|
|
||||||
distinguished_name: distinguished_name,
|
|
||||||
not_before: not_before,
|
|
||||||
not_after: not_after,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
let :x509_certificate_attributes do
|
|
||||||
{
|
|
||||||
subject: distinguished_name,
|
|
||||||
issuer: distinguished_name,
|
|
||||||
not_before: not_before,
|
|
||||||
not_after: not_after,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:distinguished_name) { "/CN=#{Faker::Internet.domain_name}" }
|
|
||||||
let(:not_before) { Faker::Time.backward.utc }
|
|
||||||
let(:not_after) { Faker::Time.forward.utc }
|
|
||||||
|
|
||||||
def make_request
|
|
||||||
post '/staff/x509_certificates',
|
|
||||||
params: { x509_certificate: x509_certificate_form_attributes }
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in current_account.user if current_account&.user
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types nil, :usual do
|
|
||||||
specify do
|
|
||||||
expect { make_request }.not_to change(X509Certificate, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'after request' do
|
|
||||||
before { make_request }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :forbidden
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types :superuser do
|
|
||||||
specify do
|
|
||||||
expect { make_request }.to change(X509Certificate, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'after request' do
|
|
||||||
before { make_request }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to \
|
|
||||||
redirect_to(/\A#{staff_x509_certificate_url(X509Certificate.last)}\?/)
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(X509Certificate.last).to \
|
|
||||||
have_attributes x509_certificate_attributes
|
|
||||||
end
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(X509Certificate.last.asymmetric_key.account).to \
|
|
||||||
eq current_account
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when distinguished name is missing' do
|
|
||||||
let(:distinguished_name) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { make_request }.not_to change(X509Certificate, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'after request' do
|
|
||||||
before { make_request }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when activation time is missing' do
|
|
||||||
let(:not_before) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { make_request }.not_to change(X509Certificate, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'after request' do
|
|
||||||
before { make_request }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when expiration time is missing' do
|
|
||||||
let(:not_after) { nil }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { make_request }.not_to change(X509Certificate, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'after request' do
|
|
||||||
before { make_request }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when distinguished name is invalid' do
|
|
||||||
let(:distinguished_name) { 'Hello!' }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect { make_request }.not_to change(X509Certificate, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'after request' do
|
|
||||||
before { make_request }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,71 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'GET /staff/x509_certificates' do
|
|
||||||
let(:current_account) { create :superuser_account }
|
|
||||||
|
|
||||||
let :x509_certificates_count do
|
|
||||||
[0, 1, rand(2..4), rand(5..10), rand(20..40)].sample
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in current_account.user if current_account&.user
|
|
||||||
|
|
||||||
create_list :self_signed_x509_certificate, x509_certificates_count
|
|
||||||
|
|
||||||
get '/staff/x509_certificates'
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types nil, :usual do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :forbidden
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types :superuser do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are no X509 certificates' do
|
|
||||||
let(:x509_certificates_count) { 0 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there is one X509 certificate' do
|
|
||||||
let(:x509_certificates_count) { 1 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are few X509 certificates' do
|
|
||||||
let(:x509_certificates_count) { rand 2..4 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are many X509 certificates' do
|
|
||||||
let(:x509_certificates_count) { rand 5..10 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are lot of X509 certificates' do
|
|
||||||
let(:x509_certificates_count) { rand 20..40 }
|
|
||||||
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,23 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'GET /staff/x509_certificates/new' do
|
|
||||||
before do
|
|
||||||
sign_in current_account.user if current_account&.user
|
|
||||||
|
|
||||||
get '/staff/x509_certificates/new'
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types nil, :usual do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :forbidden
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types :superuser do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,28 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'GET /staff/x509_certificates/:id' do
|
|
||||||
let(:x509_certificate) { create :self_signed_x509_certificate }
|
|
||||||
|
|
||||||
def make_request
|
|
||||||
get "/staff/x509_certificates/#{x509_certificate.id}"
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in current_account.user if current_account&.user
|
|
||||||
make_request
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types nil, :usual do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :forbidden
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for_account_types :superuser do
|
|
||||||
specify do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Reference in a new issue