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 +4 -4
- checksums.yaml.gz.sig +2 -4
- data/bake/localhost.rb +47 -0
- data/lib/localhost/authority.rb +77 -62
- data/lib/localhost/issuer.rb +149 -0
- data/lib/localhost/state.rb +38 -0
- data/lib/localhost/system/darwin.rb +21 -0
- data/lib/localhost/system/linux.rb +20 -0
- data/lib/localhost/system.rb +21 -0
- data/lib/localhost/version.rb +1 -1
- data/lib/localhost.rb +8 -3
- data/license.md +2 -1
- data/readme.md +17 -8
- data/releases.md +6 -0
- data.tar.gz.sig +2 -3
- metadata +11 -8
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c43cbd483ba75df04398b4a6e534ec13a4456faa6b373ee1629a6673e6bd133
|
4
|
+
data.tar.gz: 53e8080f450ce450e76d4085427fc41c9cd56eced5965672c1766798ebfd709d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3869a071d7ee5bd45c5e2ea41edd7a164dba4b4aca54ea46b2618b90ffaeb354761b89c1c3398fc0f39917b2342c63648dbd99e320e49d3ce0f94473e7b90ec0
|
7
|
+
data.tar.gz: 823506611fb8b34a2152eeeee30a641e5027333973c7ed2fcde5ef0ada9c60650196101c10b4aa05d8a6a1e296fce2fae770fd1857308c106e516eb1d57f0702
|
checksums.yaml.gz.sig
CHANGED
@@ -1,4 +1,2 @@
|
|
1
|
-
|
2
|
-
|
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|�'Shg�k���,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
|
data/lib/localhost/authority.rb
CHANGED
@@ -1,53 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2018-
|
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
|
13
|
-
require
|
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
|
-
#
|
19
|
-
|
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
|
-
|
45
|
-
|
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:
|
48
|
-
|
34
|
+
Dir.glob("*.crt", base: path) do |certificate_path|
|
35
|
+
hostname = File.basename(certificate_path, ".crt")
|
49
36
|
|
50
|
-
authority = self.new(
|
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
|
73
|
-
def initialize(hostname = "localhost",
|
74
|
-
@
|
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
|
83
|
+
# @returns [String] The path to the private key.
|
93
84
|
def key_path
|
94
|
-
File.join(@
|
85
|
+
File.join(@path, "#{@hostname}.key")
|
95
86
|
end
|
96
87
|
|
97
|
-
# The public certificate
|
88
|
+
# @returns [String] The path to the public certificate.
|
98
89
|
def certificate_path
|
99
|
-
File.join(@
|
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
|
112
|
-
def
|
113
|
-
@
|
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
|
-
|
117
|
-
|
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
|
-
#
|
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.
|
125
|
-
|
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.
|
141
|
-
|
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
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
data/lib/localhost/version.rb
CHANGED
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-
|
4
|
+
# Copyright, 2018-2025, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
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-
|
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
|
-
|
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
|
-
###
|
51
|
+
### Community Guidelines
|
38
52
|
|
39
|
-
This project is
|
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
data.tar.gz.sig
CHANGED
@@ -1,3 +1,2 @@
|
|
1
|
-
|
2
|
-
��
|
3
|
-
�j'Y���b�4�[~�6�
|
1
|
+
eq�(�J�\T<����\\q�/��>�hjdZ���.����չX��A�QPao�)!�U�YvaYor������;̎]��,�B�m��By��Q�4�[o�����L���
|
2
|
+
�n��O�B����#c�7��_��UAnᓓQ�;eg�`�e��I��cmV'�=�(�J�j��3`�f�e�
|
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.
|
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:
|
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.
|
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
|