Skip to content

Commit

Permalink
Implement validity check
Browse files Browse the repository at this point in the history
  • Loading branch information
jage committed Sep 28, 2023
1 parent d3dbaa8 commit e92a22b
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 2 deletions.
17 changes: 17 additions & 0 deletions lib/net/ssh/known_hosts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,26 @@ def initialize(key, comment: nil)
end

def matches_principal?(server_key, hostname)
pp [:debug, :valid_principals, server_key.valid_principals]
server_key.valid_principals.empty? || server_key.valid_principals.include?(hostname)
end

def matches_validity?(server_key)
# If valid_after is in the future, fail
if server_key.valid_after && server_key.valid_after > Time.now
pp [:debug, :after, server_key.valid_after, Time.now]
return false
end

# if valid_before is in the past, fail
if server_key.valid_before && server_key.valid_before < Time.now
pp [:debug, :before, server_key.valid_before, Time.now]
return false
end

true
end

def matches_key?(server_key)
if ssh_types.include?(server_key.ssh_type)
server_key.signature_valid? && (server_key.signature_key.to_blob == @key.to_blob)
Expand Down
9 changes: 9 additions & 0 deletions lib/net/ssh/verifiers/always.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def verify(arguments)
# We've never seen this host before, so raise an exception.
process_cache_miss(host_keys, arguments, HostKeyUnknown, "is unknown") if host_keys.empty?


# If we found any matches, check to see that the key type and
# blob also match.
found_keys = host_keys.find do |key|
Expand All @@ -33,6 +34,14 @@ def verify(arguments)
# indicating that the key was not recognized.
process_cache_miss(host_keys, arguments, HostKeyMismatch, "does not match") unless found_keys

if found_keys.respond_to?(:matches_validity?)
unless found_keys.matches_validity?(arguments[:key])
binding.break
# TODO why not valid?
process_cache_miss(host_keys, arguments, HostKeyUnknown, "Certificate not valid")
end
end

# If found host_key has principal support (CertAuthority), it must match
if found_keys.respond_to?(:matches_principal?)
return true if found_keys.matches_principal?(arguments[:key], host_keys.hostname)
Expand Down
56 changes: 54 additions & 2 deletions test/integration/test_cert_host_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
class TestCertHostAuth < NetSSHTest
include IntegrationTestHelpers

def setup_ssh_env(&block)
def setup_ssh_env(principals: "one.hosts.netssh", validity: "+30d", &block)
tmpdir do |dir|
cert_type = "rsa"
# cert_type = "ssh-ed25519"
Expand All @@ -24,7 +24,8 @@ def setup_ssh_env(&block)
sh "ssh-keygen -t #{cert_type} -N '' -C 'ca@hosts.netssh' -f #{@cert} #{debug ? '' : '-q'}"
FileUtils.cp "/etc/ssh/ssh_host_#{host_key_type}_key.pub", "#{dir}/one.hosts.netssh.pub"
Dir.chdir(dir) do
sh "ssh-keygen -s #{@cert} -h -I one.hosts.netssh -n one.hosts.netssh #{debug ? '' : '-q'} #{dir}/one.hosts.netssh.pub"
principals_arg = principals.to_s.empty? ? "" : "-n #{principals}"
sh "ssh-keygen -s #{@cert} -h -I one.hosts.netssh -V #{validity} #{principals_arg} #{debug ? '' : '-q'} #{dir}/one.hosts.netssh.pub"
sh "ssh-keygen -L -f one.hosts.netssh-cert.pub" if debug
end
signed_host_key = "/etc/ssh/ssh_host_#{host_key_type}_key-cert.pub"
Expand Down Expand Up @@ -92,6 +93,32 @@ def test_with_other_pub_key_host_key_should_not_match
end
end

def test_with_expired_certificate_should_fail
Tempfile.open('cert_kh') do |f|
setup_ssh_env(validity: "-30d:-1d") do |params|
data = File.read(params[:cert_pub])
f.write("@cert-authority [*.hosts.netssh]:2200 #{data}")
f.close

config_lines = ["HostCertificate #{params[:signed_host_key]}"]
start_sshd_7_or_later(config: config_lines) do |_pid, port|
Timeout.timeout(500) do
# sleep 0.2
# sh "ssh -v -i ~/.ssh/id_ed25519 one.hosts.netssh -o UserKnownHostsFile=#{f.path} -p 2200"
assert_raises(Net::SSH::HostKeyMismatch) do
Net::SSH.start("one.hosts.netssh", "net_ssh_1", password: 'foopwd', port: port, verify_host_key: :always, user_known_hosts_file: [f.path]) do |ssh|
ssh.exec! "echo 'foo'"
end
end
rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
sleep 0.25
retry
end
end
end
end
end

def test_host_should_match_when_host_key_was_signed_by_key_and_matching_principal
Tempfile.open('cert_kh') do |f|
setup_ssh_env do |params|
Expand All @@ -117,6 +144,31 @@ def test_host_should_match_when_host_key_was_signed_by_key_and_matching_principa
end
end

def test_host_should_match_when_host_key_was_signed_by_key_and_no_principal_in_certificate
Tempfile.open('cert_kh') do |f|
setup_ssh_env(principals: "") do |params|
data = File.read(params[:cert_pub])
f.write("@cert-authority [*.hosts.netssh]:2200 #{data}")
f.close

config_lines = ["HostCertificate #{params[:signed_host_key]}"]
start_sshd_7_or_later(config: config_lines) do |_pid, port|
Timeout.timeout(500) do
# sleep 0.2
# sh "ssh -v -i ~/.ssh/id_ed25519 one.hosts.netssh -o UserKnownHostsFile=#{f.path} -p 2200"
ret = Net::SSH.start("one.hosts.netssh", "net_ssh_1", password: 'foopwd', port: port, verify_host_key: :always, user_known_hosts_file: [f.path]) do |ssh|
ssh.exec! "echo 'foo'"
end
assert_equal "foo\n", ret
rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
sleep 0.25
retry
end
end
end
end
end

def test_host_should_not_match_when_host_key_was_signed_by_key_not_not_matching_principal
Tempfile.open('cert_kh') do |f|
setup_ssh_env do |params|
Expand Down

0 comments on commit e92a22b

Please sign in to comment.