Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the directory certificates are stored in #28

Merged
merged 6 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"))
nogweii marked this conversation as resolved.
Show resolved Hide resolved
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
Loading