Skip to content

Commit ecde804

Browse files
nogweiiioquatix
andauthored
Change the directory certificates are stored in (#28)
Co-authored-by: Samuel Williams <samuel.williams@oriontransfer.co.nz>
1 parent e014af3 commit ecde804

4 files changed

Lines changed: 58 additions & 25 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/gems.locked
44
/.covered.db
55
/external
6+
/state

guides/getting-started/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ $ bundle add localhost
1818

1919
### Files
2020

21-
The certificate and private key are stored in `~/.localhost/`. You can delete them and they will be regenerated. If you added the certificate to your computer's certificate store/keychain, you'll you'd need to update it.
21+
The certificate and private key are stored in `$XDG_STATE_HOME/localhost.rb/` (defaulting to `~/.local/state/localhost.rb/`). You can delete them and they will be regenerated. If you added the certificate to your computer's certificate store/keychain, you'll you'd need to update it.
2222

2323
## Usage
2424

lib/localhost/authority.rb

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
# Copyright, 2023, by Antonio Terceiro.
99
# Copyright, 2023, by Yuuji Yaginuma.
1010

11+
require 'fileutils'
1112
require 'openssl'
1213

1314
module Localhost
1415
# Represents a single public/private key pair for a given hostname.
1516
class Authority
17+
# Where to store the key pair on the filesystem. This is a subdirectory
18+
# of $XDG_STATE_HOME, or ~/.local/state/ when that's not defined.
1619
def self.path
17-
File.expand_path("~/.localhost")
20+
File.expand_path("localhost.rb", ENV.fetch("XDG_STATE_HOME", "~/.local/state"))
1821
end
1922

2023
# List all certificate authorities in the given directory:
@@ -176,27 +179,27 @@ def client_context(*args)
176179
end
177180

178181
def load(path = @root)
179-
if File.directory?(path)
180-
certificate_path = File.join(path, "#{@hostname}.crt")
181-
key_path = File.join(path, "#{@hostname}.key")
182-
183-
return false unless File.exist?(certificate_path) and File.exist?(key_path)
184-
185-
certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
186-
key = OpenSSL::PKey::RSA.new(File.read(key_path))
187-
188-
# Certificates with old version need to be regenerated.
189-
return false if certificate.version < 2
190-
191-
@certificate = certificate
192-
@key = key
193-
194-
return true
195-
end
182+
ensure_authority_path_exists(path)
183+
184+
certificate_path = File.join(path, "#{@hostname}.crt")
185+
key_path = File.join(path, "#{@hostname}.key")
186+
187+
return false unless File.exist?(certificate_path) and File.exist?(key_path)
188+
189+
certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
190+
key = OpenSSL::PKey::RSA.new(File.read(key_path))
191+
192+
# Certificates with old version need to be regenerated.
193+
return false if certificate.version < 2
194+
195+
@certificate = certificate
196+
@key = key
197+
198+
return true
196199
end
197200

198201
def save(path = @root)
199-
Dir.mkdir(path, 0700) unless File.directory?(path)
202+
ensure_authority_path_exists(path)
200203

201204
lockfile_path = File.join(path, "#{@hostname}.lock")
202205

@@ -214,5 +217,19 @@ def save(path = @root)
214217
)
215218
end
216219
end
220+
221+
# Ensures that the directory to store the certificate exists. If the legacy
222+
# directory (~/.localhost/) exists, it is moved into the new XDG Basedir
223+
# compliant directory.
224+
def ensure_authority_path_exists(path = @root)
225+
old_root = File.expand_path("~/.localhost")
226+
227+
if File.directory?(old_root) and not File.directory?(path)
228+
# Migrates the legacy dir ~/.localhost/ to the XDG compliant directory
229+
File.rename(old_root, path)
230+
elsif not File.directory?(path)
231+
FileUtils.makedirs(path, mode: 0700)
232+
end
233+
end
217234
end
218235
end

test/localhost/authority.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
require 'fileutils'
1616

1717
describe Localhost::Authority do
18-
let(:authority) {subject.new}
19-
18+
let(:xdg_dir) { File.join(Dir.pwd, "state") }
19+
let(:authority) {
20+
ENV["XDG_STATE_HOME"] = xdg_dir
21+
subject.new
22+
}
23+
2024
with '#certificate' do
2125
it "is not valid for more than 1 year" do
2226
certificate = authority.certificate
@@ -28,7 +32,6 @@
2832
end
2933

3034
it "can generate key and certificate" do
31-
FileUtils.mkdir_p("ssl")
3235
authority.save("ssl")
3336

3437
expect(File).to be(:exist?, "ssl/localhost.lock")
@@ -41,8 +44,20 @@
4144
expect(File).to be(:exist?, authority.certificate_path)
4245
expect(File).to be(:exist?, authority.key_path)
4346

44-
expect(authority.key_path).to be == File.join(File.expand_path("~/.localhost"), "localhost.key")
45-
expect(authority.certificate_path).to be == File.join(File.expand_path("~/.localhost"), "localhost.crt")
47+
expect(authority.key_path).to be == File.join(xdg_dir, "localhost.rb", "localhost.key")
48+
expect(authority.certificate_path).to be == File.join(xdg_dir, "localhost.rb", "localhost.crt")
49+
end
50+
51+
it "properly falls back when XDG_STATE_HOME is not set" do
52+
ENV.delete("XDG_STATE_HOME")
53+
authority = subject.new
54+
55+
authority.save(authority.class.path)
56+
expect(File).to be(:exist?, authority.certificate_path)
57+
expect(File).to be(:exist?, authority.key_path)
58+
59+
expect(authority.key_path).to be == File.join(File.expand_path("~/.local/state/"), "localhost.rb", "localhost.key")
60+
expect(authority.certificate_path).to be == File.join(File.expand_path("~/.local/state/"), "localhost.rb", "localhost.crt")
4661
end
4762

4863
with '#store' do

0 commit comments

Comments
 (0)