kybus-ssl 0.1.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2bf5e7b4893134432a12134b41fb41b318b7ef6928e5690a4ec2a978b06cebe
4
- data.tar.gz: 9576e4b32580447fc71678eec7ee7f56e5a7a5c6f34e8d6015aee3d846a00bb1
3
+ metadata.gz: 6b608e41afe35bae9646207f98879a12ab73e3754ccb344115729f2cb0a774c3
4
+ data.tar.gz: cc05b289a65b53859ff5b2f30579cd2bbf8d7195f12a28b601ab9aa45cc45f19
5
5
  SHA512:
6
- metadata.gz: 726fae80d20e37cc77fe945a33cbee37f093d39e862bb1228c0397c624c476144e91a26ddfefae7e2200c5a05aeaafba483c60c3c3986d31b83dd63afe080a09
7
- data.tar.gz: c7adaeedef2ea71b15c948eb79eb70c8a10082a550aceba4b9a0965b974b6005b26169bc259872d0f7c9342645c25b8ee1d7826158b5999d9d1bd218371423e0
6
+ metadata.gz: 8bd173d87ad8ce311af2e4c35bd29a7a6376ce2805a0b308c7a8ac21509708a16965ef01134b3ac564a1541085858d851556fb488dd4a9bb17335603d0d786e5
7
+ data.tar.gz: 1a94c96c310f264110bd59dcf7eecf0063ac467de48a8de72c7c46c5779fe74d30109a11b00d142a576d925f03a32498ff0adf0faf873831a8b1314daed74158
@@ -1,16 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'openssl'
4
+ require 'securerandom'
4
5
 
5
6
  module Kybus
6
7
  module SSL
7
8
  # Stores a X509 certificate.
8
9
  class Certificate
9
- attr_reader :cert, :key
10
+ attr_reader :cert, :key, :config
10
11
 
11
12
  def initialize(config, inventory)
12
13
  @config = config
13
14
  @inventory = inventory
15
+
16
+ if File.file?(@config.key_path) && File.file?(@config.crt_path)
17
+ load_key!
18
+ else
19
+ create_key!
20
+ end
21
+ end
22
+
23
+ def create_key!
24
+ puts @config.instance_variable_get(:@config)
14
25
  @key = OpenSSL::PKey::RSA.new(@config['key_size'])
15
26
  @cert = OpenSSL::X509::Certificate.new
16
27
  @cert.public_key = @key.public_key
@@ -18,8 +29,15 @@ module Kybus
18
29
  @extensions.subject_certificate = @cert
19
30
  end
20
31
 
32
+ def load_key!
33
+ @key = OpenSSL::PKey::RSA.new(File.read(@config.key_path))
34
+ @cert = OpenSSL::X509::Certificate.new(File.read(@config.crt_path))
35
+ end
36
+
21
37
  def create!
22
- return if File.file?(@config.key_path)
38
+ if File.file?(@config.key_path) && File.file?(@config.crt_path)
39
+ return puts "Certificate already exists #{@config.key_path} #{@cert.subject}"
40
+ end
23
41
 
24
42
  @ca = @inventory.ca(@config['parent'])
25
43
  configure_details!
@@ -39,12 +57,22 @@ module Kybus
39
57
 
40
58
  def sign!
41
59
  @cert.issuer = @ca.cert.subject
42
- @cert.sign(@ca.key, OpenSSL::Digest::SHA256.new)
60
+ @cert.sign(@ca.key, OpenSSL::Digest.new('SHA256'))
43
61
  end
44
62
 
45
63
  def save!
64
+ puts "Saving certificate #{@cert.subject}"
46
65
  File.write(@config.key_path, @key.to_s)
47
66
  File.write(@config.crt_path, @cert.to_s)
67
+ export_to_pfx!
68
+ end
69
+
70
+ def export_to_pfx!
71
+ passphrase = SecureRandom.alphanumeric(15)
72
+ chain = [@cert] + @inventory.ca_cert_chain(@config['parent'])
73
+ pkcs12 = OpenSSL::PKCS12.create(passphrase, @config['email'] || @config['name'], @key, @cert, chain)
74
+ File.write(@config.pfx_path, pkcs12.to_der)
75
+ puts "PFX certificate saved with passphrase: #{passphrase}"
48
76
  end
49
77
 
50
78
  def ca_name
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kybus
4
+ module SSL
5
+ module CLI
6
+ class AddCA < BaseCommand
7
+ def run
8
+ load_template
9
+ update_yaml_file
10
+ end
11
+
12
+ private
13
+
14
+ KEYS = %i[caname name expiration key_size].freeze
15
+
16
+ def update_yaml_file
17
+ new_ca = opts_to_cert_config(KEYS,
18
+ parent: @opts[:ca] || 'root',
19
+ serial: next_serial,
20
+ name: @opts[:ca_name],
21
+ extensions: {
22
+ basicConstraints: {
23
+ details: 'CA:true, pathlen:0',
24
+ critical: true
25
+ }
26
+ })
27
+
28
+ @template['certificate_descriptions']['authorities']['certificates'] << new_ca
29
+
30
+ save_template
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kybus
4
+ module SSL
5
+ module CLI
6
+ class AddCertificate < BaseCommand
7
+ def run
8
+ load_template
9
+ update_yaml_file
10
+ end
11
+
12
+ private
13
+
14
+ KEYS = %i[name expiration key_size team country city state email].freeze
15
+
16
+ def update_yaml_file
17
+ new_certificate = opts_to_cert_config(KEYS, {
18
+ parent: @opts[:ca],
19
+ serial: next_serial,
20
+ organization: @opts[:org],
21
+ revoked: false
22
+ })
23
+
24
+ @template['certificate_descriptions']['clients']['certificates'] << new_certificate
25
+
26
+ save_template
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kybus
4
+ module SSL
5
+ module CLI
6
+ class BaseCommand
7
+ def initialize(opts)
8
+ @opts = opts
9
+ end
10
+
11
+ def transform_keys_recursively(hash)
12
+ hash.each_with_object({}) do |(key, value), new_hash|
13
+ new_key = key.is_a?(Symbol) ? key.to_s : key
14
+ new_value = if value.is_a?(Hash)
15
+ transform_keys_recursively(value)
16
+ elsif value.is_a?(Array)
17
+ value.map { |v| transform_keys_recursively(v) }
18
+ else
19
+ value
20
+ end
21
+ new_hash[new_key] = new_value
22
+ end
23
+ end
24
+
25
+ def opts_to_cert_config(keys, extra_args)
26
+ cert = {}
27
+ keys.each { |key| cert[key] = @opts[key] }
28
+ cert.merge(extra_args).compact
29
+ end
30
+
31
+ def load_template
32
+ @template = YAML.load_file(@opts[:pki_file])
33
+ end
34
+
35
+ def save_template
36
+ @template = transform_keys_recursively(@template)
37
+ File.write(@opts[:pki_file], @template.to_yaml)
38
+ end
39
+
40
+ def next_serial
41
+ @template['serial_counter'] += 1
42
+ end
43
+
44
+ def pki_file_exist?
45
+ File.file?(@opts[:pki_file])
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,12 @@
1
+ module Kybus
2
+ module SSL
3
+ module CLI
4
+ class Build < BaseCommand
5
+ def run
6
+ inv = Kybus::SSL::Inventory.load_inventory(@opts[:pki_file])
7
+ inv.create_certificates!
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Kybus
6
+ module SSL
7
+ module CLI
8
+ DEFAULT_EXPIRATION = 5
9
+ ROOT_CA_EXPIRATION = 20
10
+ SUB_CA_EXPIRATION = 10
11
+ ROOT_CA_SERIAL = 1
12
+ SERVERS_CA_SERIAL = 2
13
+ CLIENTS_CA_SERIAL = 3
14
+ ROOT_CA_KEY_SIZE = 4096
15
+ SUB_CA_KEY_SIZE = 2048
16
+ SERVERS_CA_EXPIRATION = 5
17
+
18
+ class Init < BaseCommand
19
+ def build_default_config
20
+ @template = {
21
+ serial_counter: 3,
22
+ certificate_descriptions: {
23
+ defaults: certificate_defaults,
24
+ authorities: default_authorities,
25
+ clients: default_clients_config,
26
+ servers: default_servers_config
27
+ }
28
+ }
29
+ end
30
+
31
+ def default_certificate_extensions
32
+ {
33
+ subjectKeyIdentifier: extension_details('hash'),
34
+ authorityKeyIdentifier: extension_details('keyid:always'),
35
+ basicConstraints: extension_details('CA:false')
36
+ }
37
+ end
38
+
39
+ def extension_details(details, critical: false)
40
+ { details:, critical: }
41
+ end
42
+
43
+ def certificate_defaults
44
+ {
45
+ saving_directory: @opts[:path],
46
+ country: @opts[:country],
47
+ state: @opts[:state],
48
+ city: @opts[:city],
49
+ organization: @opts[:organization],
50
+ team: @opts[:team],
51
+ key_size: @opts[:key_size],
52
+ expiration: DEFAULT_EXPIRATION,
53
+ extensions: default_certificate_extensions
54
+ }
55
+ end
56
+
57
+ def root_ca
58
+ ca_config("#{@opts[:organization]} Root CA", ROOT_CA_EXPIRATION, ROOT_CA_SERIAL, ROOT_CA_KEY_SIZE, 'root',
59
+ 'root')
60
+ end
61
+
62
+ def servers_ca
63
+ sub_ca_config("#{@opts[:organization]} Servers CA", SERVERS_CA_EXPIRATION, SERVERS_CA_SERIAL, 'servers')
64
+ end
65
+
66
+ def clients_ca
67
+ sub_ca_config("#{@opts[:organization]} Clients CA", SUB_CA_EXPIRATION, CLIENTS_CA_SERIAL, 'clients')
68
+ end
69
+
70
+ def ca_config(name, expiration, serial, key_size, ca, parent, extensions: {}) # rubocop: disable Metrics/ParameterLists:
71
+ { name:, expiration:, serial:, key_size:, ca:, parent:, extensions: }
72
+ end
73
+
74
+ def sub_ca_config(name, expiration, serial, ca)
75
+ ca_config(name, expiration, serial, SUB_CA_KEY_SIZE, ca, 'root', extensions: {
76
+ basicConstraints: extension_details('CA:true, pathlen:0', critical: true)
77
+ })
78
+ end
79
+
80
+ def default_authorities
81
+ {
82
+ defaults: {
83
+ parent: 'root',
84
+ extensions: {
85
+ basicConstraints: extension_details('CA:true', critical: true),
86
+ keyUsage: extension_details('Digital Signature, keyCertSign, cRLSign', critical: true)
87
+ }
88
+ },
89
+ certificates: [root_ca, servers_ca, clients_ca]
90
+ }
91
+ end
92
+
93
+ def default_config(parent, extensions, extra_defaults = {})
94
+ {
95
+ defaults: {
96
+ parent:,
97
+ extensions:
98
+ }.merge(extra_defaults),
99
+ certificates: []
100
+ }
101
+ end
102
+
103
+ def default_servers_config
104
+ extensions = {
105
+ 'Netscape Cert Type': extension_details('SSL Server'),
106
+ 'Netscape Comment': extension_details('Server certificate'),
107
+ keyUsage: extension_details('Digital Signature, Key Encipherment', critical: true),
108
+ extendedKeyUsage: extension_details('TLS Web Server Authentication'),
109
+ authorityKeyIdentifier: extension_details('keyid, issuer:always'),
110
+ subjectAltName: extension_details('$dns')
111
+ }
112
+ default_config('servers', extensions)
113
+ end
114
+
115
+ def default_clients_config
116
+ extensions = {
117
+ 'Netscape Cert Type': extension_details('SSL Client, S/MIME'),
118
+ 'Netscape Comment': extension_details('Client certificate'),
119
+ keyUsage: extension_details('Digital Signature, Non Repudiation, Key Encipherment', critical: true),
120
+ extendedKeyUsage: extension_details('TLS Web Client Authentication, E-mail Protection'),
121
+ subjectAltName: extension_details('$email')
122
+ }
123
+ default_config('clients', extensions, team: @opts[:team])
124
+ end
125
+
126
+ def run
127
+ abort 'File already exists. Use --force to overwrite.' if pki_file_exist? && !@opts[:force]
128
+ build_default_config
129
+ FileUtils.mkdir_p(@opts[:path])
130
+ save_template
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,12 @@
1
+ module Kybus
2
+ module SSL
3
+ module CLI
4
+ class UpdateCRL < BaseCommand
5
+ def run
6
+ inv = Kybus::SSL::Inventory.load_inventory(@opts[:pki_file])
7
+ inv.update_crl!
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cli/init'
4
+ require_relative 'cli/add_ca'
5
+ require_relative 'cli/add_certificate'
6
+ require_relative 'cli/build'
@@ -11,8 +11,6 @@ module Kybus
11
11
  end
12
12
 
13
13
  def saving_directory(type)
14
- path = @config['saving_directory']
15
- serial = @config['serial']
16
14
  "#{path}/#{serial}.#{type}.pem"
17
15
  end
18
16
 
@@ -24,10 +22,14 @@ module Kybus
24
22
  saving_directory('key')
25
23
  end
26
24
 
25
+ def pfx_path
26
+ "#{path}/#{serial}-#{@config['email'] || @config['name']}.pfx"
27
+ end
28
+
27
29
  def subject_string
28
30
  "/C=#{@config['country']}/ST=#{@config['state']}" \
29
- "/L=#{@config['city']}/O=#{@config['organization']}" \
30
- "/OU=#{@config['team']}/CN=#{@config['name']}"
31
+ "/L=#{@config['city']}/O=#{@config['organization']}" \
32
+ "/OU=#{@config['team']}/CN=#{@config['name']}"
31
33
  end
32
34
 
33
35
  def configure_cert_details!(cert)
@@ -35,14 +37,23 @@ module Kybus
35
37
  cert.serial = @config['serial']
36
38
  cert.subject = OpenSSL::X509::Name.parse(subject_string)
37
39
  cert.not_before = Time.now
38
- cert.not_after = cert.not_before + ONE_YEAR * @config['expiration']
40
+ cert.not_after = cert.not_before + (ONE_YEAR * @config['expiration'])
41
+ end
42
+
43
+ def apply_placeholders(description)
44
+ if description.include?('$')
45
+ description.gsub('$email', "email:#{@config['email']}").gsub('$dns', "DNS:#{@config['dns']}")
46
+ else
47
+ description
48
+ end
39
49
  end
40
50
 
41
51
  def configure_extensions!(cert, extension_factory)
42
52
  @config['extensions'].each do |name, details|
53
+ applied_description = apply_placeholders(details['details'])
43
54
  extension = extension_factory.create_extension(
44
55
  name,
45
- details['details'],
56
+ applied_description,
46
57
  details['critical']
47
58
  )
48
59
  cert.add_extension(extension)
@@ -52,6 +63,15 @@ module Kybus
52
63
  def [](key)
53
64
  @config[key]
54
65
  end
66
+ private
67
+
68
+ def path
69
+ @config['saving_directory']
70
+ end
71
+
72
+ def serial
73
+ @config['serial']
74
+ end
55
75
  end
56
76
  end
57
77
  end
@@ -3,6 +3,7 @@
3
3
  require_relative 'configuration'
4
4
  require_relative 'certificate'
5
5
  require_relative 'revocation_list'
6
+ require 'yaml'
6
7
 
7
8
  require 'fileutils'
8
9
 
@@ -23,17 +24,31 @@ module Kybus
23
24
  @servers = SubInventory.new(servers, self)
24
25
  end
25
26
 
27
+ def self.load_inventory(path)
28
+ inventory = YAML.load_file(path)
29
+ data = inventory['certificate_descriptions']
30
+ new(data['defaults'], data['authorities'], data['clients'], data['servers'])
31
+ end
32
+
26
33
  def create_certificates!
27
34
  validate_inventories!
28
35
  create_directory!
29
36
  [@authorities, @clients, @servers].each(&:create_certificates!)
30
37
  end
31
38
 
39
+ def ca_cert_chain(parent)
40
+ @authorities.ca_cert_chain(parent)
41
+ end
42
+
32
43
  # TODO: Implement validation of inventories
33
44
  def validate_inventories!
34
45
  true
35
46
  end
36
47
 
48
+ def update_crl
49
+ @authorities.update_crl
50
+ end
51
+
37
52
  def create_directory!
38
53
  FileUtils.mkdir_p(@defaults['saving_directory'])
39
54
  end
@@ -47,6 +62,8 @@ module Kybus
47
62
  # configurations.
48
63
  class SubInventory
49
64
  def initialize(configs, inventory)
65
+ raise 'Nil config' if configs.nil?
66
+
50
67
  defaults = configs['defaults']
51
68
  @parent = inventory
52
69
  @certificates = configs['certificates'].map do |cert|
@@ -59,12 +76,30 @@ module Kybus
59
76
  end
60
77
  end
61
78
 
79
+ def ca_cert_chain(name)
80
+ chain = []
81
+ cert = ca(name)
82
+
83
+ while cert && cert.ca_name != 'root'
84
+ puts cert.ca_name
85
+ chain << cert.cert
86
+ cert = @certificates.find { |c| c.ca_name == cert.config['parent'] }
87
+ end
88
+ chain
89
+ end
90
+
91
+ def update_crl
92
+ end
93
+
62
94
  def create_certificates!
63
95
  @certificates.each(&:create!)
64
96
  end
65
97
 
66
98
  def ca(name)
67
- @certificates.find { |cert| cert.ca_name == name }
99
+ ca = @certificates.find { |cert| cert.ca_name == name }
100
+ raise "CA #{name} not found" if ca.nil?
101
+
102
+ ca
68
103
  end
69
104
  end
70
105
  end
@@ -4,7 +4,9 @@ module Kybus
4
4
  module SSL
5
5
  # Generates revocation list after revocating a list of certs
6
6
  # TODO: Implement CRL
7
+ # rubocop: disable Lint/EmptyClass
7
8
  class RevocationList
8
9
  end
10
+ # rubocop: enable Lint/EmptyClass
9
11
  end
10
12
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kybus
4
4
  module SSL
5
- VERSION = '0.1.0'
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,117 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kybus-ssl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilberto Vargas
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-15 00:00:00.000000000 Z
11
+ date: 2025-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: minitest
14
+ name: optimist
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '5.11'
20
- type: :development
19
+ version: '3.0'
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '5.11'
27
- - !ruby/object:Gem::Dependency
28
- name: pry
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.12'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '0.12'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '12.3'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '12.3'
55
- - !ruby/object:Gem::Dependency
56
- name: rdoc
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '6.1'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '6.1'
69
- - !ruby/object:Gem::Dependency
70
- name: simplecov
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '0.16'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '0.16'
83
- - !ruby/object:Gem::Dependency
84
- name: webmock
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '3.5'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '3.5'
26
+ version: '3.0'
97
27
  description: Package for creating self signed certificates for development purpose
98
28
  email:
99
- - tachoguitar@gmail.com
29
+ - tachomexgems@gmail.com
100
30
  executables: []
101
31
  extensions: []
102
32
  extra_rdoc_files: []
103
33
  files:
104
34
  - lib/kybus/ssl.rb
105
35
  - lib/kybus/ssl/certificate.rb
36
+ - lib/kybus/ssl/cli.rb
37
+ - lib/kybus/ssl/cli/add_ca.rb
38
+ - lib/kybus/ssl/cli/add_certificate.rb
39
+ - lib/kybus/ssl/cli/base_command.rb
40
+ - lib/kybus/ssl/cli/build.rb
41
+ - lib/kybus/ssl/cli/init.rb
42
+ - lib/kybus/ssl/cli/update_crl.rb
106
43
  - lib/kybus/ssl/configuration.rb
107
44
  - lib/kybus/ssl/inventory.rb
108
45
  - lib/kybus/ssl/revocation_list.rb
109
46
  - lib/kybus/ssl/version.rb
110
- homepage: https://github.com/KueskiEngineering/ruby-kybus-server
47
+ homepage: https://github.com/tachomex/kybus
111
48
  licenses:
112
49
  - MIT
113
- metadata: {}
114
- post_install_message:
50
+ metadata:
51
+ rubygems_mfa_required: 'true'
52
+ post_install_message:
115
53
  rdoc_options: []
116
54
  require_paths:
117
55
  - lib
@@ -126,8 +64,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
64
  - !ruby/object:Gem::Version
127
65
  version: '0'
128
66
  requirements: []
129
- rubygems_version: 3.1.4
130
- signing_key:
67
+ rubygems_version: 3.5.9
68
+ signing_key:
131
69
  specification_version: 4
132
70
  summary: Kybus SSL tools
133
71
  test_files: []