localhost 1.3.1 → 1.4.2

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: 35cad25f5ec79874e21312d5b70e4ffbf8ed616f3b8140a0f22dd6d0e1aa292b
4
- data.tar.gz: 0e34a06090414bdd734c7b5ef094fab8a10a6c3286f0bf737e7a9f93a5d0b87a
3
+ metadata.gz: 2c43cbd483ba75df04398b4a6e534ec13a4456faa6b373ee1629a6673e6bd133
4
+ data.tar.gz: 53e8080f450ce450e76d4085427fc41c9cd56eced5965672c1766798ebfd709d
5
5
  SHA512:
6
- metadata.gz: d6d2aa039d439e2e4e38f506335b5d72b80ffcf316886d85c86ecd1780547b156c97ddc060c93c0a9f67543bceb2d28535dde9ed955f28664a95508297433c19
7
- data.tar.gz: 0142e748e4d118144d455bef736c2c545f18275f7c4503d118a58d239b20c13ba1c0aa3f0339d244eebd3ce30f42565f1d02246c5dc4f73f6698188c2cc1427a
6
+ metadata.gz: 3869a071d7ee5bd45c5e2ea41edd7a164dba4b4aca54ea46b2618b90ffaeb354761b89c1c3398fc0f39917b2342c63648dbd99e320e49d3ce0f94473e7b90ec0
7
+ data.tar.gz: 823506611fb8b34a2152eeeee30a641e5027333973c7ed2fcde5ef0ada9c60650196101c10b4aa05d8a6a1e296fce2fae770fd1857308c106e516eb1d57f0702
checksums.yaml.gz.sig CHANGED
@@ -1,4 +1,2 @@
1
- ���4�i�
2
- (��>sEy;<XYΌh�`��6�P I!8�S(�7�ۘפ��:(�8[fd?]�����Mg���#�uK���bC@_
3
- �"�K�:�O��7��w��ćMr>V�e�S��Ȗ��i3�4�S���@_h)�
4
- ��s�����-n��t0A=�x1[Q!�4(�p�G��gN��Ӳv�[������!h9:po���. L�l�H��W�ØP5L����
1
+ o/�J������)s^iCx~��@]xUM %'�ՋR8+�O!ͪ��F)/�~z�Z�S�iE&� �១���U����#���/����Z5Y�ߙ�<�B�V����ϊ�168d�5��E|�'Shgk���,W�|��֕&X;�W��r9nĜ&@�݄�Z�I�n�h��IX��_��,P���y�M����������tB�B)
2
+ Dʴ]笲�D��_&� (�������(F�^��j�(dV��
data/bake/localhost.rb ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2025, by Samuel Williams.
5
+
6
+ def initialize(...)
7
+ super
8
+
9
+ require_relative "../lib/localhost"
10
+ end
11
+
12
+ # List all local authorities.
13
+ #
14
+ # @returns [Array(Hash)] The certificate and key paths, and the expiry date.
15
+ def list
16
+ Localhost::Authority.list.map do |authority|
17
+ {
18
+ certificate_path: authority.certificate_path,
19
+ key_path: authority.key_path,
20
+ expires_at: authority.certificate.not_after,
21
+ }
22
+ end
23
+ end
24
+
25
+ # Fetch a local authority by hostname. If the authority does not exist, it will be created.
26
+ #
27
+ # @parameter hostname [String] The hostname to fetch.
28
+ # @returns [Hash] The certificate and key paths, and the expiry date.
29
+ def fetch(hostname)
30
+ if authority = Localhost::Authority.fetch(hostname)
31
+ return {
32
+ certificate_path: authority.certificate_path,
33
+ key_path: authority.key_path,
34
+ expires_at: authority.certificate.not_after,
35
+ }
36
+ end
37
+ end
38
+
39
+ def install(name: nil)
40
+ issuer = Localhost::Issuer.fetch(name)
41
+
42
+ Localhost::System.current.install(issuer.certificate_path)
43
+ end
44
+
45
+ def purge
46
+ Localhost::State.purge
47
+ end
@@ -1,53 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2024, by Samuel Williams.
4
+ # Copyright, 2018-2025, by Samuel Williams.
5
5
  # Copyright, 2019, by Richard S. Leung.
6
6
  # Copyright, 2021, by Akshay Birajdar.
7
7
  # Copyright, 2021, by Ye Lin Aung.
8
8
  # Copyright, 2023, by Antonio Terceiro.
9
9
  # Copyright, 2023, by Yuuji Yaginuma.
10
10
  # Copyright, 2024, by Colin Shea.
11
+ # Copyright, 2024, by Aurel Branzeanu.
11
12
 
12
- require 'fileutils'
13
- require 'openssl'
13
+ require "fileutils"
14
+ require "openssl"
15
+
16
+ require_relative "state"
17
+ require_relative "issuer"
14
18
 
15
19
  module Localhost
16
20
  # Represents a single public/private key pair for a given hostname.
17
21
  class Authority
18
- # Where to store the key pair on the filesystem. This is a subdirectory
19
- # of $XDG_STATE_HOME, or ~/.local/state/ when that's not defined.
20
- #
21
- # Ensures that the directory to store the certificate exists. If the legacy
22
- # directory (~/.localhost/) exists, it is moved into the new XDG Basedir
23
- # compliant directory.
24
- #
25
- # After May 2025, the old_root option may be removed.
26
- def self.path(env = ENV, old_root: nil)
27
- path = File.expand_path("localhost.rb", env.fetch("XDG_STATE_HOME", "~/.local/state"))
28
-
29
- unless File.directory?(path)
30
- FileUtils.mkdir_p(path, mode: 0700)
31
- end
32
-
33
- # Migrates the legacy dir ~/.localhost/ to the XDG compliant directory
34
- old_root ||= File.expand_path("~/.localhost")
35
- if File.directory?(old_root)
36
- FileUtils.mv(Dir.glob(File.join(old_root, "*")), path, force: true)
37
- FileUtils.rmdir(old_root)
38
- end
39
-
40
- return path
22
+ # @returns [String] The path to the directory containing the certificate authorities.
23
+ def self.path
24
+ State.path
41
25
  end
42
26
 
43
- # List all certificate authorities in the given directory:
44
- def self.list(root = self.path)
45
- return to_enum(:list, root) unless block_given?
27
+ # List all certificate authorities in the given directory.
28
+ #
29
+ # @parameter path [String] The path to the directory containing the certificate authorities.
30
+ # @yields [Authority] Each certificate authority in the directory.
31
+ def self.list(path = State.path)
32
+ return to_enum(:list, path) unless block_given?
46
33
 
47
- Dir.glob("*.crt", base: root) do |path|
48
- name = File.basename(path, ".crt")
34
+ Dir.glob("*.crt", base: path) do |certificate_path|
35
+ hostname = File.basename(certificate_path, ".crt")
49
36
 
50
- authority = self.new(name, root: root)
37
+ authority = self.new(hostname, path: path)
51
38
 
52
39
  if authority.load
53
40
  yield authority
@@ -69,61 +56,73 @@ module Localhost
69
56
 
70
57
  # Create an authority forn the given hostname.
71
58
  # @parameter hostname [String] The common name to use for the certificate.
72
- # @parameter root [String] The root path for loading and saving the certificate.
73
- def initialize(hostname = "localhost", root: self.class.path)
74
- @root = root
59
+ # @parameter path [String] The path path for loading and saving the certificate.
60
+ def initialize(hostname = "localhost", path: State.path, issuer: Issuer.fetch)
61
+ @path = path
75
62
  @hostname = hostname
63
+ @issuer = issuer
76
64
 
65
+ @subject = nil
77
66
  @key = nil
78
- @name = nil
79
67
  @certificate = nil
80
68
  @store = nil
81
69
  end
82
70
 
71
+ attr :issuer
72
+
83
73
  # The hostname of the certificate authority.
84
74
  attr :hostname
85
75
 
86
76
  BITS = 1024*2
87
77
 
78
+ # @returns [OpenSSL::PKey::DH] A Diffie-Hellman key suitable for secure key exchange.
88
79
  def dh_key
89
80
  @dh_key ||= OpenSSL::PKey::DH.new(BITS)
90
81
  end
91
82
 
92
- # The private key path.
83
+ # @returns [String] The path to the private key.
93
84
  def key_path
94
- File.join(@root, "#{@hostname}.key")
85
+ File.join(@path, "#{@hostname}.key")
95
86
  end
96
87
 
97
- # The public certificate path.
88
+ # @returns [String] The path to the public certificate.
98
89
  def certificate_path
99
- File.join(@root, "#{@hostname}.crt")
90
+ File.join(@path, "#{@hostname}.crt")
100
91
  end
101
92
 
102
- # The private key.
93
+ # @returns [OpenSSL::PKey::RSA] The private key.
103
94
  def key
104
95
  @key ||= OpenSSL::PKey::RSA.new(BITS)
105
96
  end
106
97
 
98
+ # Set the private key.
99
+ #
100
+ # @parameter key [OpenSSL::PKey::RSA] The private key.
107
101
  def key= key
108
102
  @key = key
109
103
  end
110
104
 
111
- # The certificate name.
112
- def name
113
- @name ||= OpenSSL::X509::Name.parse("/O=Development/CN=#{@hostname}")
105
+ # @returns [OpenSSL::X509::Name] The subject name for the certificate.
106
+ def subject
107
+ @subject ||= OpenSSL::X509::Name.parse("/O=localhost.rb/CN=#{@hostname}")
114
108
  end
115
109
 
116
- def name= name
117
- @name = name
110
+ # Set the subject name for the certificate.
111
+ #
112
+ # @parameter subject [OpenSSL::X509::Name] The subject name.
113
+ def subject= subject
114
+ @subject = subject
118
115
  end
119
116
 
120
- # The public certificate.
117
+ # Generates a self-signed certificate if one does not already exist for the given hostname.
118
+ #
121
119
  # @returns [OpenSSL::X509::Certificate] A self-signed certificate.
122
120
  def certificate
121
+ issuer = @issuer || self
122
+
123
123
  @certificate ||= OpenSSL::X509::Certificate.new.tap do |certificate|
124
- certificate.subject = self.name
125
- # We use the same issuer as the subject, which makes this certificate self-signed:
126
- certificate.issuer = self.name
124
+ certificate.subject = self.subject
125
+ certificate.issuer = issuer.subject
127
126
 
128
127
  certificate.public_key = self.key.public_key
129
128
 
@@ -135,24 +134,27 @@ module Localhost
135
134
 
136
135
  extension_factory = OpenSSL::X509::ExtensionFactory.new
137
136
  extension_factory.subject_certificate = certificate
138
- extension_factory.issuer_certificate = certificate
137
+ extension_factory.issuer_certificate = @issuer&.certificate || certificate
139
138
 
140
- certificate.extensions = [
141
- extension_factory.create_extension("basicConstraints", "CA:FALSE", true),
142
- extension_factory.create_extension("subjectKeyIdentifier", "hash"),
143
- ]
144
-
145
- certificate.add_extension extension_factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
139
+ certificate.add_extension extension_factory.create_extension("basicConstraints", "CA:FALSE", true)
140
+ certificate.add_extension extension_factory.create_extension("subjectKeyIdentifier", "hash")
146
141
  certificate.add_extension extension_factory.create_extension("subjectAltName", "DNS: #{@hostname}")
142
+ certificate.add_extension extension_factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
147
143
 
148
- certificate.sign self.key, OpenSSL::Digest::SHA256.new
144
+ certificate.sign issuer.key, OpenSSL::Digest::SHA256.new
149
145
  end
150
146
  end
151
147
 
152
148
  # The certificate store which is used for validating the server certificate.
149
+ #
150
+ # @returns [OpenSSL::X509::Store] The certificate store with the issuer certificate.
153
151
  def store
154
152
  @store ||= OpenSSL::X509::Store.new.tap do |store|
155
- store.add_cert(self.certificate)
153
+ if @issuer
154
+ store.add_cert(@issuer.certificate)
155
+ else
156
+ store.add_cert(self.certificate)
157
+ end
156
158
  end
157
159
  end
158
160
 
@@ -164,6 +166,10 @@ module Localhost
164
166
  context.key = self.key
165
167
  context.cert = self.certificate
166
168
 
169
+ if @issuer
170
+ context.extra_chain_cert = [@issuer.certificate]
171
+ end
172
+
167
173
  context.session_id_context = "localhost"
168
174
 
169
175
  if context.respond_to? :tmp_dh_callback=
@@ -171,7 +177,7 @@ module Localhost
171
177
  end
172
178
 
173
179
  if context.respond_to? :ecdh_curves=
174
- context.ecdh_curves = 'P-256:P-384:P-521'
180
+ context.ecdh_curves = "P-256:P-384:P-521"
175
181
  end
176
182
 
177
183
  context.set_params(
@@ -192,7 +198,11 @@ module Localhost
192
198
  end
193
199
  end
194
200
 
195
- def load(path = @root)
201
+ # Load the certificate and key from the given path.
202
+ #
203
+ # @parameter path [String] The path to the certificate and key.
204
+ # @returns [Boolean] Whether the certificate and key were successfully loaded.
205
+ def load(path = @path)
196
206
  certificate_path = File.join(path, "#{@hostname}.crt")
197
207
  key_path = File.join(path, "#{@hostname}.key")
198
208
 
@@ -210,7 +220,10 @@ module Localhost
210
220
  return true
211
221
  end
212
222
 
213
- def save(path = @root)
223
+ # Save the certificate and key to the given path.
224
+ #
225
+ # @parameter path [String] The path to save the certificate and key.
226
+ def save(path = @path)
214
227
  lockfile_path = File.join(path, "#{@hostname}.lock")
215
228
 
216
229
  File.open(lockfile_path, File::RDWR|File::CREAT, 0644) do |lockfile|
@@ -226,6 +239,8 @@ module Localhost
226
239
  self.key.to_pem
227
240
  )
228
241
  end
242
+
243
+ return true
229
244
  end
230
245
  end
231
246
  end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "openssl"
7
+ require "fileutils"
8
+
9
+ require_relative "state"
10
+
11
+ module Localhost
12
+ # Represents a local Root Certificate Authority used to sign development certificates.
13
+ class Issuer
14
+ # The default number of bits for the private key. 4096 bits.
15
+ BITS = 4096
16
+
17
+ # The default validity period for the certificate. 10 years in seconds.
18
+ VALIDITY = 10 * 365 * 24 * 60 * 60
19
+
20
+ # Fetch (load or create) a certificate issuer with the given name.
21
+ # See {#initialize} for the format of the arguments.
22
+ def self.fetch(*arguments, **options)
23
+ issuer = self.new(*arguments, **options)
24
+
25
+ unless issuer.load
26
+ issuer.save
27
+ end
28
+
29
+ return issuer
30
+ end
31
+
32
+ # Initialize the issuer with the given name.
33
+ #
34
+ # @parameter name [String] The common name to use for the certificate.
35
+ # @parameter path [String] The path path for loading and saving the certificate.
36
+ def initialize(name = "development", path: State.path)
37
+ @name = name
38
+ @path = path
39
+
40
+ @subject = nil
41
+ @key = nil
42
+ @certificate = nil
43
+ end
44
+
45
+ # @returns [String] The path to the private key.
46
+ def key_path
47
+ File.join(@path, "#{@name}.key")
48
+ end
49
+
50
+ # @returns [String] The path to the public certificate.
51
+ def certificate_path
52
+ File.join(@path, "#{@name}.crt")
53
+ end
54
+
55
+ # @returns [OpenSSL::X509::Name] The subject name for the certificate.
56
+ def subject
57
+ @subject ||= OpenSSL::X509::Name.parse("/O=localhost.rb/CN=#{@name}")
58
+ end
59
+
60
+ # Set the subject name for the certificate.
61
+ #
62
+ # @parameter subject [OpenSSL::X509::Name] The subject name for the certificate.
63
+ def subject= subject
64
+ @subject = subject
65
+ end
66
+
67
+ # @returns [OpenSSL::PKey::RSA] The private key.
68
+ def key
69
+ @key ||= OpenSSL::PKey::RSA.new(BITS)
70
+ end
71
+
72
+ # The public certificate.
73
+ #
74
+ # @returns [OpenSSL::X509::Certificate] A self-signed certificate.
75
+ def certificate
76
+ @certificate ||= OpenSSL::X509::Certificate.new.tap do |certificate|
77
+ certificate.subject = self.subject
78
+ # We use the same issuer as the subject, which makes this certificate self-signed:
79
+ certificate.issuer = self.subject
80
+
81
+ certificate.public_key = self.key.public_key
82
+
83
+ certificate.serial = Time.now.to_i
84
+ certificate.version = 2
85
+
86
+ certificate.not_before = Time.now - 10
87
+ certificate.not_after = Time.now + VALIDITY
88
+
89
+ extension_factory = ::OpenSSL::X509::ExtensionFactory.new
90
+ extension_factory.subject_certificate = certificate
91
+ extension_factory.issuer_certificate = certificate
92
+
93
+ certificate.add_extension extension_factory.create_extension("basicConstraints", "CA:TRUE", true)
94
+ certificate.add_extension extension_factory.create_extension("keyUsage", "keyCertSign, cRLSign", true)
95
+ certificate.add_extension extension_factory.create_extension("subjectKeyIdentifier", "hash")
96
+ certificate.add_extension extension_factory.create_extension("authorityKeyIdentifier", "keyid:always", false)
97
+
98
+ certificate.sign self.key, OpenSSL::Digest::SHA256.new
99
+ end
100
+ end
101
+
102
+ # Load the certificate and key from the given path.
103
+ #
104
+ # @parameter path [String] The path to load the certificate and key.
105
+ # @returns [Boolean] True if the certificate and key were loaded successfully.
106
+ def load(path = @root)
107
+ certificate_path = self.certificate_path
108
+ key_path = self.key_path
109
+
110
+ return false unless File.exist?(certificate_path) and File.exist?(key_path)
111
+
112
+ certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
113
+ key = OpenSSL::PKey::RSA.new(File.read(key_path))
114
+
115
+ @certificate = certificate
116
+ @key = key
117
+
118
+ return true
119
+ end
120
+
121
+ # @returns [String] The path to the lockfile.
122
+ def lockfile_path
123
+ File.join(@path, "#{@name}.lock")
124
+ end
125
+
126
+ # Save the certificate and key to the given path.
127
+ #
128
+ # @parameter path [String] The path to save the certificate and key.
129
+ def save(path = @root)
130
+ lockfile_path = self.lockfile_path
131
+
132
+ File.open(lockfile_path, File::RDWR|File::CREAT, 0644) do |lockfile|
133
+ lockfile.flock(File::LOCK_EX)
134
+
135
+ File.write(
136
+ self.certificate_path,
137
+ self.certificate.to_pem
138
+ )
139
+
140
+ File.write(
141
+ self.key_path,
142
+ self.key.to_pem
143
+ )
144
+ end
145
+
146
+ return true
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "fileutils"
7
+
8
+ module Localhost
9
+ # Represents a single public/private key pair for a given hostname.
10
+ module State
11
+ # Where to store the key pair on the filesystem. This is a subdirectory
12
+ # of $XDG_STATE_HOME, or ~/.local/state/ when that's not defined.
13
+ #
14
+ # Ensures that the directory to store the certificate exists. If the legacy
15
+ # directory (~/.localhost/) exists, it is moved into the new XDG Basedir
16
+ # compliant directory.
17
+ #
18
+ # @parameter env [Hash] The environment to use for configuration.
19
+ def self.path(env = ENV)
20
+ path = File.expand_path("localhost.rb", env.fetch("XDG_STATE_HOME", "~/.local/state"))
21
+
22
+ unless File.directory?(path)
23
+ FileUtils.mkdir_p(path, mode: 0700)
24
+ end
25
+
26
+ return path
27
+ end
28
+
29
+ # Delete the directory where the key pair is stored.
30
+ #
31
+ # @parameter env [Hash] The environment to use for configuration.
32
+ def self.purge(env = ENV)
33
+ path = self.path(env)
34
+
35
+ FileUtils.rm_rf(path)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ module Localhost
7
+ module System
8
+ module Darwin
9
+ def self.install(certificate)
10
+ login_keychain = File.expand_path("~/Library/Keychains/login.keychain-db")
11
+
12
+ system(
13
+ "security", "add-trusted-cert",
14
+ "-d", "-r", "trustRoot",
15
+ "-k", login_keychain,
16
+ certificate
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "etc"
7
+
8
+ module Localhost
9
+ module System
10
+ module Darwin
11
+ def self.install(certificate)
12
+ filename = File.basename(certificate)
13
+ destination = "/usr/local/share/ca-certificates/localhost-#{filename}"
14
+
15
+ system("sudo", "cp", certificate, destination)
16
+ system("sudo", "update-ca-certificates")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ module Localhost
7
+ module System
8
+ def self.current
9
+ case RUBY_PLATFORM
10
+ when /darwin/
11
+ require 'localhost/system/darwin'
12
+ Darwin
13
+ when /linux/
14
+ require 'localhost/system/linux'
15
+ Linux
16
+ else
17
+ raise NotImplementedError, "Unsupported platform: #{RUBY_PLATFORM}"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
6
  module Localhost
7
- VERSION = "1.3.1"
7
+ VERSION = "1.4.2"
8
8
  end
data/lib/localhost.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2025, by Samuel Williams.
5
5
 
6
- require_relative 'localhost/version'
7
- require_relative 'localhost/authority'
6
+ require_relative "localhost/version"
7
+ require_relative "localhost/authority"
8
+ require_relative "localhost/system"
9
+
10
+ # @namespace
11
+ module Localhost
12
+ end
data/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2018-2024, by Samuel Williams.
3
+ Copyright, 2018-2025, by Samuel Williams.
4
4
  Copyright, 2018, by Gabriel Sobrinho.
5
5
  Copyright, 2019, by Richard S. Leung.
6
6
  Copyright, 2020-2021, by Olle Jonsson.
@@ -10,6 +10,7 @@ Copyright, 2022, by Juri Hahn.
10
10
  Copyright, 2023, by Antonio Terceiro.
11
11
  Copyright, 2023, by Yuuji Yaginuma.
12
12
  Copyright, 2024, by Colin Shea.
13
+ Copyright, 2024, by Aurel Branzeanu.
13
14
 
14
15
  Permission is hereby granted, free of charge, to any person obtaining a copy
15
16
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -20,6 +20,20 @@ Please see the [project documentation](https://socketry.github.io/localhost/) fo
20
20
 
21
21
  - [Example Server](https://socketry.github.io/localhost/guides/example-server/index) - This guide demonstrates how to use <code class="language-ruby">Localhost::Authority</code> to implement a simple HTTPS client & server.
22
22
 
23
+ ## Releases
24
+
25
+ Please see the [project releases](https://socketry.github.io/localhost/releases/index) for all releases.
26
+
27
+ ### v1.4.0
28
+
29
+ - Add `localhost:purge` to delete all certificates.
30
+ - Add `localhost:install` to install the issuer certificate in the local trust store.
31
+
32
+ ## See Also
33
+
34
+ - [Falcon](https://github.com/socketry/falcon) — Uses `Localhost::Authority` to provide HTTP/2 with minimal configuration.
35
+ - [Puma](https://github.com/puma/puma) — Supports `Localhost::Authority` to provide self-signed HTTP for local development.
36
+
23
37
  ## Contributing
24
38
 
25
39
  We welcome contributions to this project.
@@ -32,13 +46,8 @@ We welcome contributions to this project.
32
46
 
33
47
  ### Developer Certificate of Origin
34
48
 
35
- This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted.
49
+ In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
36
50
 
37
- ### Contributor Covenant
51
+ ### Community Guidelines
38
52
 
39
- This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
40
-
41
- ## See Also
42
-
43
- - [Falcon](https://github.com/socketry/falcon) — Uses `Localhost::Authority` to provide HTTP/2 with minimal configuration.
44
- - [Puma](https://github.com/puma/puma) — Supports `Localhost::Authority` to provide self-signed HTTP for local development.
53
+ This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
data/releases.md ADDED
@@ -0,0 +1,6 @@
1
+ # Releases
2
+
3
+ ## v1.4.0
4
+
5
+ - Add `localhost:purge` to delete all certificates.
6
+ - Add `localhost:install` to install the issuer certificate in the local trust store.
data.tar.gz.sig CHANGED
@@ -1,3 +1,2 @@
1
- m��t�5{�����k$�KYa�k���Gyq7{DU�� 3����Szc�W}�Ц�����a��ya1��s��1f3���Qr�]��ꞏz�������)ͮuF�o*� �i�@�Vv^֯jIwڢ|�8�PNԃ�)��Q��ݏR���(ř̩pEo�Ll�� ��FðK�Ɵ��҉:��^��;n k*��' g\��|z
2
- ��l�F����c륐�8P����/S$�y�'��x���:�Cġk�MR�� ��a����gU�$ i���QA�oR^Xi�yyF��Zq�KY�ba-wL�@�'��-�K�E�T\�� Tܾ�}�M���Y(�t�X
3
- �j'Y���b� 4�[~�6�
1
+ eq(J�\T<����\\q�/��>�h􈵉jdZ���.����չX��AQPao�)!�UYvaYor������;̎]��,�B�m��By��Q4[o�����L���
2
+ �n��O�B����#c�7��_��UAnᓓQ�;eg�`�e��I��cmV'�=�(Jj��3`�fe
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: localhost
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -9,12 +9,12 @@ authors:
9
9
  - Ye Lin Aung
10
10
  - Akshay Birajdar
11
11
  - Antonio Terceiro
12
+ - Aurel Branzeanu
12
13
  - Colin Shea
13
14
  - Gabriel Sobrinho
14
15
  - Juri Hahn
15
16
  - Richard S. Leung
16
17
  - Yuuji Yaginuma
17
- autorequire:
18
18
  bindir: bin
19
19
  cert_chain:
20
20
  - |
@@ -46,26 +46,30 @@ cert_chain:
46
46
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
47
47
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
48
48
  -----END CERTIFICATE-----
49
- date: 2024-04-16 00:00:00.000000000 Z
49
+ date: 2025-03-25 00:00:00.000000000 Z
50
50
  dependencies: []
51
- description:
52
- email:
53
51
  executables: []
54
52
  extensions: []
55
53
  extra_rdoc_files: []
56
54
  files:
55
+ - bake/localhost.rb
57
56
  - lib/localhost.rb
58
57
  - lib/localhost/authority.rb
58
+ - lib/localhost/issuer.rb
59
+ - lib/localhost/state.rb
60
+ - lib/localhost/system.rb
61
+ - lib/localhost/system/darwin.rb
62
+ - lib/localhost/system/linux.rb
59
63
  - lib/localhost/version.rb
60
64
  - license.md
61
65
  - readme.md
66
+ - releases.md
62
67
  homepage: https://github.com/socketry/localhost
63
68
  licenses:
64
69
  - MIT
65
70
  metadata:
66
71
  documentation_uri: https://socketry.github.io/localhost/
67
72
  source_code_uri: https://github.com/socketry/localhost.git
68
- post_install_message:
69
73
  rdoc_options: []
70
74
  require_paths:
71
75
  - lib
@@ -80,8 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
84
  - !ruby/object:Gem::Version
81
85
  version: '0'
82
86
  requirements: []
83
- rubygems_version: 3.5.3
84
- signing_key:
87
+ rubygems_version: 3.6.2
85
88
  specification_version: 4
86
89
  summary: Manage a local certificate authority for self-signed localhost development
87
90
  servers.
metadata.gz.sig CHANGED
Binary file