localhost 1.3.0 → 1.4.0

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: 42bb902395ea685a7f1d14530439871920afb46b852aa9f6c3bbb8b66d07176a
4
- data.tar.gz: 2b63d1af2999cd2ebca0f706bb626654cf765953fbbe1a0a6d4c3b6985482b50
3
+ metadata.gz: 2209cedcf5a6630d9733649ad2c73f1f8665034dc82374feab9087efcc34e7f1
4
+ data.tar.gz: bd7b98f6d5f3bfbe1a39e436de89679cd2eb8d6006c78bac03b1370ba3d45413
5
5
  SHA512:
6
- metadata.gz: f27728bc1e1593af0db04c176bd99c878958aa662d6902649d6d5cb617034269e641a61d201255be7f8a859a08b3015980b56f28672dc301547f7aa3c6c96fa8
7
- data.tar.gz: 66ad2882eef6927aaf6da87280931a81f5928d2cf72bb92a6a10451721465a08b9d51f52a01a1d97847a7019c634499c2cb068ed19401d1e8e713452ec633156
6
+ metadata.gz: dacfd029a503d102243431bc3f012d5a17b946f95ea78bcd3fa0dc9c61bcbfcaac62085e0046cf56f7a8ed92258c95e8130555a11bba69e8cdcd723283b70a49
7
+ data.tar.gz: ba94e80e5ffc354b0bfa55cb07aba0827a09ccfc339111c5210fa371d95aee44b3d1cc07663935c07b1e38271bb00732c9eb80b28c9124d3d1ac114f5d66fe55
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,34 +1,32 @@
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
- def self.path
21
- File.expand_path("localhost.rb", ENV.fetch("XDG_STATE_HOME", "~/.local/state"))
22
- end
23
-
24
22
  # List all certificate authorities in the given directory:
25
- def self.list(root = self.path)
26
- return to_enum(:list) unless block_given?
23
+ def self.list(path = State.path)
24
+ return to_enum(:list, path) unless block_given?
27
25
 
28
- Dir.glob("*.crt", base: root) do |path|
29
- name = File.basename(path, ".crt")
26
+ Dir.glob("*.crt", base: path) do |certificate_path|
27
+ hostname = File.basename(certificate_path, ".crt")
30
28
 
31
- authority = self.new(name, root: root)
29
+ authority = self.new(hostname, path: path)
32
30
 
33
31
  if authority.load
34
32
  yield authority
@@ -50,65 +48,73 @@ module Localhost
50
48
 
51
49
  # Create an authority forn the given hostname.
52
50
  # @parameter hostname [String] The common name to use for the certificate.
53
- # @parameter root [String] The root path for loading and saving the certificate.
54
- def initialize(hostname = "localhost", root: self.class.path)
55
- @root = root
51
+ # @parameter path [String] The path path for loading and saving the certificate.
52
+ def initialize(hostname = "localhost", path: State.path, issuer: Issuer.fetch)
53
+ @path = path
56
54
  @hostname = hostname
55
+ @issuer = issuer
57
56
 
57
+ @subject = nil
58
58
  @key = nil
59
- @name = nil
60
59
  @certificate = nil
61
60
  @store = nil
62
61
  end
63
62
 
63
+ attr :issuer
64
+
64
65
  # The hostname of the certificate authority.
65
66
  attr :hostname
66
67
 
67
68
  BITS = 1024*2
68
69
 
69
- def ecdh_key
70
- @ecdh_key ||= OpenSSL::PKey::EC.new "prime256v1"
71
- end
72
-
70
+ # @returns [OpenSSL::PKey::DH] A Diffie-Hellman key suitable for secure key exchange.
73
71
  def dh_key
74
72
  @dh_key ||= OpenSSL::PKey::DH.new(BITS)
75
73
  end
76
74
 
77
- # The private key path.
75
+ # @returns [String] The path to the private key.
78
76
  def key_path
79
- File.join(@root, "#{@hostname}.key")
77
+ File.join(@path, "#{@hostname}.key")
80
78
  end
81
79
 
82
- # The public certificate path.
80
+ # @returns [String] The path to the public certificate.
83
81
  def certificate_path
84
- File.join(@root, "#{@hostname}.crt")
82
+ File.join(@path, "#{@hostname}.crt")
85
83
  end
86
84
 
87
- # The private key.
85
+ # @returns [OpenSSL::PKey::RSA] The private key.
88
86
  def key
89
87
  @key ||= OpenSSL::PKey::RSA.new(BITS)
90
88
  end
91
89
 
90
+ # Set the private key.
91
+ #
92
+ # @parameter key [OpenSSL::PKey::RSA] The private key.
92
93
  def key= key
93
94
  @key = key
94
95
  end
95
96
 
96
- # The certificate name.
97
- def name
98
- @name ||= OpenSSL::X509::Name.parse("/O=Development/CN=#{@hostname}")
97
+ # @returns [OpenSSL::X509::Name] The subject name for the certificate.
98
+ def subject
99
+ @subject ||= OpenSSL::X509::Name.parse("/O=localhost.rb/CN=#{@hostname}")
99
100
  end
100
101
 
101
- def name= name
102
- @name = name
102
+ # Set the subject name for the certificate.
103
+ #
104
+ # @parameter subject [OpenSSL::X509::Name] The subject name.
105
+ def subject= subject
106
+ @subject = subject
103
107
  end
104
108
 
105
- # The public certificate.
109
+ # Generates a self-signed certificate if one does not already exist for the given hostname.
110
+ #
106
111
  # @returns [OpenSSL::X509::Certificate] A self-signed certificate.
107
112
  def certificate
113
+ issuer = @issuer || self
114
+
108
115
  @certificate ||= OpenSSL::X509::Certificate.new.tap do |certificate|
109
- certificate.subject = self.name
110
- # We use the same issuer as the subject, which makes this certificate self-signed:
111
- certificate.issuer = self.name
116
+ certificate.subject = self.subject
117
+ certificate.issuer = issuer.subject
112
118
 
113
119
  certificate.public_key = self.key.public_key
114
120
 
@@ -120,24 +126,27 @@ module Localhost
120
126
 
121
127
  extension_factory = OpenSSL::X509::ExtensionFactory.new
122
128
  extension_factory.subject_certificate = certificate
123
- extension_factory.issuer_certificate = certificate
124
-
125
- certificate.extensions = [
126
- extension_factory.create_extension("basicConstraints", "CA:FALSE", true),
127
- extension_factory.create_extension("subjectKeyIdentifier", "hash"),
128
- ]
129
+ extension_factory.issuer_certificate = @issuer&.certificate || certificate
129
130
 
130
- certificate.add_extension extension_factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
131
+ certificate.add_extension extension_factory.create_extension("basicConstraints", "CA:FALSE", true)
132
+ certificate.add_extension extension_factory.create_extension("subjectKeyIdentifier", "hash")
131
133
  certificate.add_extension extension_factory.create_extension("subjectAltName", "DNS: #{@hostname}")
134
+ certificate.add_extension extension_factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
132
135
 
133
- certificate.sign self.key, OpenSSL::Digest::SHA256.new
136
+ certificate.sign issuer.key, OpenSSL::Digest::SHA256.new
134
137
  end
135
138
  end
136
139
 
137
140
  # The certificate store which is used for validating the server certificate.
141
+ #
142
+ # @returns [OpenSSL::X509::Store] The certificate store with the issuer certificate.
138
143
  def store
139
144
  @store ||= OpenSSL::X509::Store.new.tap do |store|
140
- store.add_cert(self.certificate)
145
+ if @issuer
146
+ store.add_cert(@issuer.certificate)
147
+ else
148
+ store.add_cert(self.certificate)
149
+ end
141
150
  end
142
151
  end
143
152
 
@@ -149,6 +158,10 @@ module Localhost
149
158
  context.key = self.key
150
159
  context.cert = self.certificate
151
160
 
161
+ if @issuer
162
+ context.extra_chain_cert = [@issuer.certificate]
163
+ end
164
+
152
165
  context.session_id_context = "localhost"
153
166
 
154
167
  if context.respond_to? :tmp_dh_callback=
@@ -156,9 +169,7 @@ module Localhost
156
169
  end
157
170
 
158
171
  if context.respond_to? :ecdh_curves=
159
- context.ecdh_curves = 'P-256:P-384:P-521'
160
- elsif context.respond_to? :tmp_ecdh_callback=
161
- context.tmp_ecdh_callback = proc {self.ecdh_key}
172
+ context.ecdh_curves = "P-256:P-384:P-521"
162
173
  end
163
174
 
164
175
  context.set_params(
@@ -179,12 +190,14 @@ module Localhost
179
190
  end
180
191
  end
181
192
 
182
- def load(path = @root)
183
- ensure_authority_path_exists(path)
184
-
193
+ # Load the certificate and key from the given path.
194
+ #
195
+ # @parameter path [String] The path to the certificate and key.
196
+ # @returns [Boolean] Whether the certificate and key were successfully loaded.
197
+ def load(path = @path)
185
198
  certificate_path = File.join(path, "#{@hostname}.crt")
186
199
  key_path = File.join(path, "#{@hostname}.key")
187
-
200
+
188
201
  return false unless File.exist?(certificate_path) and File.exist?(key_path)
189
202
 
190
203
  certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
@@ -199,9 +212,10 @@ module Localhost
199
212
  return true
200
213
  end
201
214
 
202
- def save(path = @root)
203
- ensure_authority_path_exists(path)
204
-
215
+ # Save the certificate and key to the given path.
216
+ #
217
+ # @parameter path [String] The path to save the certificate and key.
218
+ def save(path = @path)
205
219
  lockfile_path = File.join(path, "#{@hostname}.lock")
206
220
 
207
221
  File.open(lockfile_path, File::RDWR|File::CREAT, 0644) do |lockfile|
@@ -217,20 +231,8 @@ module Localhost
217
231
  self.key.to_pem
218
232
  )
219
233
  end
220
- end
221
-
222
- # Ensures that the directory to store the certificate exists. If the legacy
223
- # directory (~/.localhost/) exists, it is moved into the new XDG Basedir
224
- # compliant directory.
225
- def ensure_authority_path_exists(path = @root)
226
- old_root = File.expand_path("~/.localhost")
227
234
 
228
- if File.directory?(old_root) and not File.directory?(path)
229
- # Migrates the legacy dir ~/.localhost/ to the XDG compliant directory
230
- File.rename(old_root, path)
231
- elsif not File.directory?(path)
232
- FileUtils.makedirs(path, mode: 0700)
233
- end
235
+ return true
234
236
  end
235
237
  end
236
238
  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.0"
7
+ VERSION = "1.4.0"
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
@@ -12,7 +12,27 @@ I wanted to provide a server-agnostic way of doing this, primarily because I thi
12
12
 
13
13
  ## Usage
14
14
 
15
- Please see the [project documentation](https://socketry.github.io/localhost/).
15
+ Please see the [project documentation](https://socketry.github.io/localhost/) for more details.
16
+
17
+ - [Getting Started](https://socketry.github.io/localhost/guides/getting-started/index) - This guide explains how to use `localhost` for provisioning local TLS certificates for development.
18
+
19
+ - [Browser Configuration](https://socketry.github.io/localhost/guides/browser-configuration/index) - This guide explains how to configure your local browser in order to avoid warnings about insecure self-signed certificates.
20
+
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
+
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.
16
36
 
17
37
  ## Contributing
18
38
 
@@ -26,13 +46,8 @@ We welcome contributions to this project.
26
46
 
27
47
  ### Developer Certificate of Origin
28
48
 
29
- 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.
30
-
31
- ### Contributor Covenant
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.
32
50
 
33
- This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
51
+ ### Community Guidelines
34
52
 
35
- ## See Also
36
-
37
- - [Falcon](https://github.com/socketry/falcon) — Uses `Localhost::Authority` to provide HTTP/2 with minimal configuration.
38
- - [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
Binary file
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.0
4
+ version: 1.4.0
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,29 @@ cert_chain:
46
46
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
47
47
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
48
48
  -----END CERTIFICATE-----
49
- date: 2024-04-12 00:00:00.000000000 Z
49
+ date: 2025-03-24 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:
57
55
  - lib/localhost.rb
58
56
  - lib/localhost/authority.rb
57
+ - lib/localhost/issuer.rb
58
+ - lib/localhost/state.rb
59
+ - lib/localhost/system.rb
60
+ - lib/localhost/system/darwin.rb
61
+ - lib/localhost/system/linux.rb
59
62
  - lib/localhost/version.rb
60
63
  - license.md
61
64
  - readme.md
65
+ - releases.md
62
66
  homepage: https://github.com/socketry/localhost
63
67
  licenses:
64
68
  - MIT
65
69
  metadata:
66
70
  documentation_uri: https://socketry.github.io/localhost/
67
71
  source_code_uri: https://github.com/socketry/localhost.git
68
- post_install_message:
69
72
  rdoc_options: []
70
73
  require_paths:
71
74
  - lib
@@ -73,15 +76,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
76
  requirements:
74
77
  - - ">="
75
78
  - !ruby/object:Gem::Version
76
- version: '3.0'
79
+ version: '3.1'
77
80
  required_rubygems_version: !ruby/object:Gem::Requirement
78
81
  requirements:
79
82
  - - ">="
80
83
  - !ruby/object:Gem::Version
81
84
  version: '0'
82
85
  requirements: []
83
- rubygems_version: 3.5.3
84
- signing_key:
86
+ rubygems_version: 3.6.2
85
87
  specification_version: 4
86
88
  summary: Manage a local certificate authority for self-signed localhost development
87
89
  servers.
metadata.gz.sig CHANGED
Binary file