From 8d5fa0f181328d95cb96052314770823c2a173ea Mon Sep 17 00:00:00 2001 From: Nogweii Date: Thu, 11 Apr 2024 19:02:18 -0700 Subject: [PATCH 1/6] Change the directory certificates are stored in The new directory is compliant with the XDG Basedir specification[1]. The library will migrate the old directory to the new directory when it is not destructive. [1]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html --- .gitignore | 1 + config/sus.rb | 1 + guides/getting-started/README.md | 2 +- lib/localhost/authority.rb | 13 ++++++++++++- test/localhost/authority.rb | 25 +++++++++++++++++++++---- 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 09a72e0..c14184f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /gems.locked /.covered.db /external +/state diff --git a/config/sus.rb b/config/sus.rb index bb035bd..ecd7d97 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -5,3 +5,4 @@ require 'covered/sus' include Covered::Sus +FileUtils.mkdir_p(::File.expand_path(ENV['XDG_STATE_HOME'] || "~/.local/state")) diff --git a/guides/getting-started/README.md b/guides/getting-started/README.md index 9bf8966..9568785 100644 --- a/guides/getting-started/README.md +++ b/guides/getting-started/README.md @@ -18,7 +18,7 @@ $ bundle add localhost ### Files -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. +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. ## Usage diff --git a/lib/localhost/authority.rb b/lib/localhost/authority.rb index 5275b71..2bde4f7 100644 --- a/lib/localhost/authority.rb +++ b/lib/localhost/authority.rb @@ -14,7 +14,7 @@ module Localhost # Represents a single public/private key pair for a given hostname. class Authority def self.path - File.expand_path("~/.localhost") + File.join(File.expand_path(ENV['XDG_STATE_HOME'] || "~/.local/state"), "localhost.rb") end # List all certificate authorities in the given directory: @@ -49,6 +49,7 @@ def self.fetch(*arguments, **options) # @parameter root [String] The root path for loading and saving the certificate. def initialize(hostname = "localhost", root: self.class.path) @root = root + @old_root = File.expand_path("~/.localhost") @hostname = hostname @key = nil @@ -176,6 +177,7 @@ def client_context(*args) end def load(path = @root) + dir_migrate(path) if File.directory?(path) certificate_path = File.join(path, "#{@hostname}.crt") key_path = File.join(path, "#{@hostname}.key") @@ -196,6 +198,7 @@ def load(path = @root) end def save(path = @root) + dir_migrate(path) Dir.mkdir(path, 0700) unless File.directory?(path) lockfile_path = File.join(path, "#{@hostname}.lock") @@ -214,5 +217,13 @@ def save(path = @root) ) end end + + # Migrates the legacy dir ~/.localhost/ to the XDG compliant directory, + # if it exists but the new location does not. + def dir_migrate(path = @root) + if File.directory?(@old_root) and not File.directory?(path) + File.rename(@old_root, path) + end + end end end diff --git a/test/localhost/authority.rb b/test/localhost/authority.rb index 50df694..670a725 100644 --- a/test/localhost/authority.rb +++ b/test/localhost/authority.rb @@ -15,8 +15,12 @@ require 'fileutils' describe Localhost::Authority do - let(:authority) {subject.new} - + let(:xdg_dir) { File.join(Dir.pwd, "state") } + let(:authority) { + ENV["XDG_STATE_HOME"] = xdg_dir + subject.new + } + with '#certificate' do it "is not valid for more than 1 year" do certificate = authority.certificate @@ -37,12 +41,25 @@ end it "have correct key and certificate path" do + FileUtils.mkdir_p(xdg_dir) + authority.save(authority.class.path) + expect(File).to be(:exist?, authority.certificate_path) + expect(File).to be(:exist?, authority.key_path) + + expect(authority.key_path).to be == File.join(xdg_dir, "localhost.rb", "localhost.key") + expect(authority.certificate_path).to be == File.join(xdg_dir, "localhost.rb", "localhost.crt") + end + + it "properly falls back when XDG_STATE_HOME is not set" do + ENV.delete("XDG_STATE_HOME") + authority = subject.new + authority.save(authority.class.path) expect(File).to be(:exist?, authority.certificate_path) expect(File).to be(:exist?, authority.key_path) - expect(authority.key_path).to be == File.join(File.expand_path("~/.localhost"), "localhost.key") - expect(authority.certificate_path).to be == File.join(File.expand_path("~/.localhost"), "localhost.crt") + expect(authority.key_path).to be == File.join(File.expand_path("~/.local/state/"), "localhost.rb", "localhost.key") + expect(authority.certificate_path).to be == File.join(File.expand_path("~/.local/state/"), "localhost.rb", "localhost.crt") end with '#store' do From 182c3780f8aaba7d3b243eb0ab3f083791588c14 Mon Sep 17 00:00:00 2001 From: Nogweii Date: Thu, 11 Apr 2024 19:53:41 -0700 Subject: [PATCH 2/6] Improve thanks to comments from ioquatix --- lib/localhost/authority.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/localhost/authority.rb b/lib/localhost/authority.rb index 2bde4f7..d7b453f 100644 --- a/lib/localhost/authority.rb +++ b/lib/localhost/authority.rb @@ -14,7 +14,7 @@ module Localhost # Represents a single public/private key pair for a given hostname. class Authority def self.path - File.join(File.expand_path(ENV['XDG_STATE_HOME'] || "~/.local/state"), "localhost.rb") + File.expand_path("localhost.rb", ENV.fetch("XDG_STATE_HOME", "~/.local/state")) end # List all certificate authorities in the given directory: @@ -49,7 +49,6 @@ def self.fetch(*arguments, **options) # @parameter root [String] The root path for loading and saving the certificate. def initialize(hostname = "localhost", root: self.class.path) @root = root - @old_root = File.expand_path("~/.localhost") @hostname = hostname @key = nil @@ -177,7 +176,7 @@ def client_context(*args) end def load(path = @root) - dir_migrate(path) + migrate_legacy_authority_path(path) if File.directory?(path) certificate_path = File.join(path, "#{@hostname}.crt") key_path = File.join(path, "#{@hostname}.key") @@ -198,7 +197,7 @@ def load(path = @root) end def save(path = @root) - dir_migrate(path) + migrate_legacy_authority_path(path) Dir.mkdir(path, 0700) unless File.directory?(path) lockfile_path = File.join(path, "#{@hostname}.lock") @@ -220,9 +219,10 @@ def save(path = @root) # Migrates the legacy dir ~/.localhost/ to the XDG compliant directory, # if it exists but the new location does not. - def dir_migrate(path = @root) - if File.directory?(@old_root) and not File.directory?(path) - File.rename(@old_root, path) + def migrate_legacy_authority_path(path = @root) + old_root = File.expand_path("~/.localhost") + if File.directory?(old_root) and not File.directory?(path) + File.rename(old_root, path) end end end From f13fe91cb7bb059a5d07f1573a2954ca4366a1ad Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 12 Apr 2024 15:00:09 +1200 Subject: [PATCH 3/6] Update config/sus.rb --- config/sus.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/sus.rb b/config/sus.rb index ecd7d97..d46571a 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -5,4 +5,6 @@ require 'covered/sus' include Covered::Sus + +require 'fileutils' FileUtils.mkdir_p(::File.expand_path(ENV['XDG_STATE_HOME'] || "~/.local/state")) From 2b22c88ca4da4766b86ff2fc6a14220044635825 Mon Sep 17 00:00:00 2001 From: Nogweii <14547+nogweii@users.noreply.github.com> Date: Fri, 12 Apr 2024 03:00:32 +0000 Subject: [PATCH 4/6] Update config/sus.rb Make sure to require FileUtils Co-authored-by: Samuel Williams --- config/sus.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/sus.rb b/config/sus.rb index d46571a..d0178ab 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -6,5 +6,7 @@ require 'covered/sus' include Covered::Sus +require 'fileutils' + require 'fileutils' FileUtils.mkdir_p(::File.expand_path(ENV['XDG_STATE_HOME'] || "~/.local/state")) From 9583adeb2cf898938a728dbe92c9e659d3cdd4ac Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 12 Apr 2024 15:01:48 +1200 Subject: [PATCH 5/6] Update config/sus.rb --- config/sus.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/sus.rb b/config/sus.rb index d0178ab..d46571a 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -6,7 +6,5 @@ require 'covered/sus' include Covered::Sus -require 'fileutils' - require 'fileutils' FileUtils.mkdir_p(::File.expand_path(ENV['XDG_STATE_HOME'] || "~/.local/state")) From a8f3c1b6fa723a3cd4d34e73b48e16065939fe99 Mon Sep 17 00:00:00 2001 From: Nogweii Date: Thu, 11 Apr 2024 20:39:23 -0700 Subject: [PATCH 6/6] Always create the Authority.path folder --- config/sus.rb | 3 --- lib/localhost/authority.rb | 54 ++++++++++++++++++++----------------- test/localhost/authority.rb | 2 -- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/config/sus.rb b/config/sus.rb index d46571a..bb035bd 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -5,6 +5,3 @@ require 'covered/sus' include Covered::Sus - -require 'fileutils' -FileUtils.mkdir_p(::File.expand_path(ENV['XDG_STATE_HOME'] || "~/.local/state")) diff --git a/lib/localhost/authority.rb b/lib/localhost/authority.rb index d7b453f..debff6a 100644 --- a/lib/localhost/authority.rb +++ b/lib/localhost/authority.rb @@ -8,11 +8,14 @@ # Copyright, 2023, by Antonio Terceiro. # Copyright, 2023, by Yuuji Yaginuma. +require 'fileutils' require 'openssl' module Localhost # Represents a single public/private key pair for a given hostname. class Authority + # Where to store the key pair on the filesystem. This is a subdirectory + # of $XDG_STATE_HOME, or ~/.local/state/ when that's not defined. def self.path File.expand_path("localhost.rb", ENV.fetch("XDG_STATE_HOME", "~/.local/state")) end @@ -176,29 +179,27 @@ def client_context(*args) end def load(path = @root) - migrate_legacy_authority_path(path) - if File.directory?(path) - certificate_path = File.join(path, "#{@hostname}.crt") - key_path = File.join(path, "#{@hostname}.key") - - return false unless File.exist?(certificate_path) and File.exist?(key_path) - - certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path)) - key = OpenSSL::PKey::RSA.new(File.read(key_path)) - - # Certificates with old version need to be regenerated. - return false if certificate.version < 2 - - @certificate = certificate - @key = key - - return true - end + ensure_authority_path_exists(path) + + certificate_path = File.join(path, "#{@hostname}.crt") + key_path = File.join(path, "#{@hostname}.key") + + return false unless File.exist?(certificate_path) and File.exist?(key_path) + + certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path)) + key = OpenSSL::PKey::RSA.new(File.read(key_path)) + + # Certificates with old version need to be regenerated. + return false if certificate.version < 2 + + @certificate = certificate + @key = key + + return true end def save(path = @root) - migrate_legacy_authority_path(path) - Dir.mkdir(path, 0700) unless File.directory?(path) + ensure_authority_path_exists(path) lockfile_path = File.join(path, "#{@hostname}.lock") @@ -216,13 +217,18 @@ def save(path = @root) ) end end - - # Migrates the legacy dir ~/.localhost/ to the XDG compliant directory, - # if it exists but the new location does not. - def migrate_legacy_authority_path(path = @root) + + # Ensures that the directory to store the certificate exists. If the legacy + # directory (~/.localhost/) exists, it is moved into the new XDG Basedir + # compliant directory. + def ensure_authority_path_exists(path = @root) old_root = File.expand_path("~/.localhost") + if File.directory?(old_root) and not File.directory?(path) + # Migrates the legacy dir ~/.localhost/ to the XDG compliant directory File.rename(old_root, path) + elsif not File.directory?(path) + FileUtils.makedirs(path, mode: 0700) end end end diff --git a/test/localhost/authority.rb b/test/localhost/authority.rb index 670a725..2d966e2 100644 --- a/test/localhost/authority.rb +++ b/test/localhost/authority.rb @@ -32,7 +32,6 @@ end it "can generate key and certificate" do - FileUtils.mkdir_p("ssl") authority.save("ssl") expect(File).to be(:exist?, "ssl/localhost.lock") @@ -41,7 +40,6 @@ end it "have correct key and certificate path" do - FileUtils.mkdir_p(xdg_dir) authority.save(authority.class.path) expect(File).to be(:exist?, authority.certificate_path) expect(File).to be(:exist?, authority.key_path)