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
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 15.5
|
||||
Exclude:
|
||||
- 'db/migrate/*.rb'
|
||||
|
||||
|
@ -48,7 +47,6 @@ Metrics/MethodLength:
|
|||
|
||||
Naming/PredicateName:
|
||||
Exclude:
|
||||
- 'app/forms/application_form.rb'
|
||||
- 'app/models/application_record.rb'
|
||||
|
||||
Rails:
|
||||
|
|
|
@ -21,12 +21,7 @@ private
|
|||
@current_account ||= current_user&.account
|
||||
end
|
||||
|
||||
def pundit_user
|
||||
@pundit_user ||= ApplicationPolicy::Context.new(
|
||||
account: current_account,
|
||||
params: params,
|
||||
)
|
||||
end
|
||||
alias pundit_user current_account
|
||||
|
||||
def set_raven_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
|
||||
|
||||
module ApplicationHelper # rubocop:disable Metrics/ModuleLength
|
||||
module ApplicationHelper
|
||||
def federal_subjects_controller?
|
||||
controller_path == 'federal_subjects'
|
||||
end
|
||||
|
@ -13,34 +13,6 @@ module ApplicationHelper # rubocop:disable Metrics/ModuleLength
|
|||
[*negative_timezones_collection, *positive_timezones_collection].freeze
|
||||
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
|
||||
0.upto(11).flat_map do |n|
|
||||
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
|
||||
|
||||
class ApplicationPolicy
|
||||
attr_reader :context, :record
|
||||
attr_reader :account, :record
|
||||
|
||||
delegate :account, :params, to: :context, allow_nil: true
|
||||
|
||||
def initialize(context, record)
|
||||
@context = context
|
||||
def initialize(account, record)
|
||||
@account = account
|
||||
@record = record
|
||||
end
|
||||
|
||||
|
@ -53,12 +51,10 @@ private
|
|||
end
|
||||
|
||||
class Scope
|
||||
attr_reader :context, :scope
|
||||
attr_reader :account, :scope
|
||||
|
||||
delegate :account, :params, to: :context, allow_nil: true
|
||||
|
||||
def initialize(context, scope)
|
||||
@context = context
|
||||
def initialize(account, scope)
|
||||
@account = account
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
|
@ -76,13 +72,4 @@ private
|
|||
Rails.application.restricted?
|
||||
end
|
||||
end
|
||||
|
||||
class Context
|
||||
attr_reader :account, :params
|
||||
|
||||
def initialize(account:, params:)
|
||||
@account = account
|
||||
@params = params
|
||||
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 %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% if policy(%i[staff x509_certificate]).index? %>
|
||||
<li>
|
||||
<%= link_to X509Certificate.model_name.human(count: 0),
|
||||
staff_x509_certificates_path %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</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
|
||||
password
|
||||
password_confirmation
|
||||
private_key_pem_secret
|
||||
reset_password_token
|
||||
secret
|
||||
unlock_token
|
||||
|
@ -61,7 +60,6 @@ module Partynest
|
|||
|
||||
# Custom directories with classes and modules you want to be autoloadable.
|
||||
config.autoload_paths += [
|
||||
config.root.join('app', 'forms'),
|
||||
config.root.join('app', 'primitives'),
|
||||
config.root.join('app', 'validators'),
|
||||
config.root.join('lib'),
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
# inflect.uncountable %w( fish sheep )
|
||||
# end
|
||||
|
||||
ActiveSupport::Inflector.inflections :en do |inflect|
|
||||
inflect.acronym 'RSA'
|
||||
end
|
||||
# These inflection rules are supported but not enabled by default:
|
||||
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
# inflect.acronym 'RESTful'
|
||||
# end
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
Mime::Type.register 'application/pkcs8', :key
|
||||
Mime::Type.register 'application/x-pem-file', :pem
|
||||
# Add new mime types for use in respond_to blocks:
|
||||
# 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:
|
||||
one: Account
|
||||
many: Accounts
|
||||
asymmetric_key:
|
||||
one: Public key
|
||||
few: Public keys
|
||||
many: Public keys
|
||||
contact:
|
||||
one: Contact
|
||||
few: Contacts
|
||||
|
@ -40,10 +36,6 @@ en:
|
|||
user:
|
||||
one: User
|
||||
many: Users
|
||||
x509_certificate:
|
||||
one: X509 certificate
|
||||
few: X509 certificates
|
||||
many: X509 certificates
|
||||
attributes:
|
||||
account:
|
||||
id: ID
|
||||
|
@ -53,12 +45,6 @@ en:
|
|||
avatar: Avatar
|
||||
person: Person
|
||||
timezone: Timezone
|
||||
asymmetric_key:
|
||||
id: ID
|
||||
algo_class: Algorithm class
|
||||
algo_variant: Variant
|
||||
sha1: SHA-1 fingerprint
|
||||
sha256: SHA-256 fingerprint
|
||||
contact:
|
||||
id: ID
|
||||
contact_network: Contact network
|
||||
|
@ -145,13 +131,6 @@ en:
|
|||
unconfirmed_email: Unconfirmed email
|
||||
unlock_token: Unlock token
|
||||
updated_at: Updated at
|
||||
x509_certificate:
|
||||
id: ID
|
||||
pem: PEM
|
||||
subject: Subject
|
||||
issuer: Issuer
|
||||
not_before: Active since
|
||||
not_after: Expires at
|
||||
errors:
|
||||
messages:
|
||||
image_size: 'has too big size'
|
||||
|
|
|
@ -4,10 +4,6 @@ ru:
|
|||
account:
|
||||
one: Аккаунт
|
||||
many: Аккаунты
|
||||
asymmetric_key:
|
||||
one: Публичный ключ
|
||||
few: Публичных ключа
|
||||
many: Публичные ключи
|
||||
contact:
|
||||
one: Контакт
|
||||
few: Контакта
|
||||
|
@ -40,10 +36,6 @@ ru:
|
|||
user:
|
||||
one: Пользователь
|
||||
many: Пользователи
|
||||
x509_certificate:
|
||||
one: Сертификат X509
|
||||
few: Сертификатов X509
|
||||
many: Сертификаты X509
|
||||
attributes:
|
||||
account:
|
||||
id: ID
|
||||
|
@ -53,12 +45,6 @@ ru:
|
|||
avatar: Аватар
|
||||
person: Человек
|
||||
timezone: Часовой пояс
|
||||
asymmetric_key:
|
||||
id: ID
|
||||
algo_class: Класс алгоритма
|
||||
algo_variant: Вариант
|
||||
sha1: Отпечаток SHA-1
|
||||
sha256: Отпечаток SHA-256
|
||||
contact:
|
||||
id: ID
|
||||
contact_network: Сеть контактов
|
||||
|
@ -145,13 +131,6 @@ ru:
|
|||
unconfirmed_email: Неподтвержденный email
|
||||
unlock_token: Токен разблокировки
|
||||
updated_at: Дата обновления
|
||||
x509_certificate:
|
||||
id: ID
|
||||
pem: PEM
|
||||
subject: Имя субъекта
|
||||
issuer: Имя издателя
|
||||
not_before: Активен с
|
||||
not_after: Истекает
|
||||
errors:
|
||||
messages:
|
||||
image_size: 'имеет слишком большой размер'
|
||||
|
|
|
@ -11,8 +11,6 @@ en:
|
|||
display_entries: 'Displaying %{entry_name}
|
||||
<b>%{first} - %{last}</b> of <b>%{total}</b> in total'
|
||||
submit:
|
||||
asymmetric_key:
|
||||
create: Add
|
||||
person:
|
||||
create: Send
|
||||
person_comment:
|
||||
|
|
|
@ -11,8 +11,6 @@ ru:
|
|||
display_entries: 'Отображение %{entry_name}
|
||||
<b>%{first} - %{last}</b> из <b>%{total}</b> всего'
|
||||
submit:
|
||||
asymmetric_key:
|
||||
create: Добавить
|
||||
person:
|
||||
create: Отправить
|
||||
person_comment:
|
||||
|
|
|
@ -16,23 +16,6 @@ en:
|
|||
If this was you, you can ignore this alert. If you suspect
|
||||
any suspicious activity on your account, please change your password
|
||||
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:
|
||||
people:
|
||||
show:
|
||||
|
|
|
@ -17,23 +17,6 @@ ru:
|
|||
Если это были вы, можете проигнорировать это предупреждение.
|
||||
Если вы заметили подозрительную активность вашего аккаунта, пожалуйста
|
||||
измените пароль и включите двухфакторную аутентификацию
|
||||
private_keys:
|
||||
alert:
|
||||
link: Скачать
|
||||
primary_header: >-
|
||||
Ваш приватный ключ готов, но вы должны скачать его прямо сейчас!
|
||||
secondary_header: >-
|
||||
Ваш приватный ключ был удалён.
|
||||
primary_text: >-
|
||||
Для большей безопасности мы зашифровали ваш приватный ключ
|
||||
с помощью временного токена. Вы можете скачать его пока не покините
|
||||
эту страницу. Также учтите, что ключ будет уничтожен через %{delay}
|
||||
после создания в любом случае.
|
||||
secondary_text: >-
|
||||
Для большей безопасности мы удалили ваш приватный ключ.
|
||||
Надеемся, что вы его скачали, потому что мы не можем его восстановить.
|
||||
Если вы не скачали его, вам придётся повторить всю процедуру сначала
|
||||
чтобы сгенерировать новый приватный ключ.
|
||||
settings:
|
||||
people:
|
||||
show:
|
||||
|
|
|
@ -12,7 +12,3 @@ en:
|
|||
person: Person
|
||||
contacts: Contacts
|
||||
sessions: Sessions
|
||||
x509_certificate:
|
||||
overview: Overview
|
||||
pem: PEM
|
||||
text: Text
|
||||
|
|
|
@ -12,7 +12,3 @@ ru:
|
|||
person: Личность
|
||||
contacts: Контакты
|
||||
sessions: Сессии
|
||||
x509_certificate:
|
||||
overview: Обзор
|
||||
pem: PEM
|
||||
text: Текст
|
||||
|
|
|
@ -12,4 +12,3 @@ en:
|
|||
save: Save
|
||||
hello: Hello
|
||||
new_sign_in: New sign in to your account
|
||||
add_existing: Add existing
|
||||
|
|
|
@ -12,4 +12,3 @@ ru:
|
|||
save: Сохранить
|
||||
hello: Здравствуйте
|
||||
new_sign_in: Произведён вход в ваш аккаунт
|
||||
add_existing: Добавить существующий
|
||||
|
|
|
@ -13,10 +13,6 @@ Rails.application.routes.draw do
|
|||
|
||||
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 #
|
||||
###############
|
||||
|
@ -65,8 +61,6 @@ Rails.application.routes.draw do
|
|||
|
||||
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 :person_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: -
|
||||
--
|
||||
|
@ -924,42 +881,6 @@ CREATE SEQUENCE public.users_id_seq
|
|||
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: -
|
||||
--
|
||||
|
@ -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);
|
||||
|
||||
|
||||
--
|
||||
-- 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: -
|
||||
--
|
||||
|
@ -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);
|
||||
|
||||
|
||||
--
|
||||
-- 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: -
|
||||
--
|
||||
|
@ -1118,14 +1025,6 @@ ALTER TABLE ONLY public.ar_internal_metadata
|
|||
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: -
|
||||
--
|
||||
|
@ -1238,14 +1137,6 @@ ALTER TABLE ONLY public.users
|
|||
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: -
|
||||
--
|
||||
|
@ -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);
|
||||
|
||||
|
||||
--
|
||||
-- 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: -
|
||||
--
|
||||
|
@ -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);
|
||||
|
||||
|
||||
--
|
||||
-- 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: -
|
||||
--
|
||||
|
@ -1619,14 +1461,6 @@ ALTER TABLE ONLY public.relationships
|
|||
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: -
|
||||
--
|
||||
|
@ -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);
|
||||
|
||||
|
||||
--
|
||||
-- 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: -
|
||||
--
|
||||
|
@ -1759,6 +1585,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20190910040709'),
|
||||
('20190911081459'),
|
||||
('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/pundit'
|
||||
|
||||
require_relative 'models/shared_examples/asymmetric_key'
|
||||
require_relative 'models/shared_examples/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