Skip to content

Commit

Permalink
Change the directory certificates are stored in (#28)
Browse files Browse the repository at this point in the history
Co-authored-by: Samuel Williams <samuel.williams@oriontransfer.co.nz>
  • Loading branch information
nogweii and ioquatix authored Apr 12, 2024
1 parent e014af3 commit ecde804
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/gems.locked
/.covered.db
/external
/state
2 changes: 1 addition & 1 deletion guides/getting-started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
55 changes: 36 additions & 19 deletions lib/localhost/authority.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
# 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")
File.expand_path("localhost.rb", ENV.fetch("XDG_STATE_HOME", "~/.local/state"))
end

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

def load(path = @root)
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)
Dir.mkdir(path, 0700) unless File.directory?(path)
ensure_authority_path_exists(path)

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

Expand All @@ -214,5 +217,19 @@ def save(path = @root)
)
end
end

# 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
end
25 changes: 20 additions & 5 deletions test/localhost/authority.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,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")
Expand All @@ -41,8 +44,20 @@
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(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("~/.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
Expand Down

0 comments on commit ecde804

Please sign in to comment.