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

(FACT-3110) Support for ecdsa 384 and 521 bit keys #2656

Merged
merged 4 commits into from
Dec 13, 2023
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
8 changes: 1 addition & 7 deletions lib/facter/util/resolvers/fingerprint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@
module Facter
module Util
module Resolvers
class FingerPrint
attr_accessor :sha1, :sha256
def initialize(sha1, sha256)
@sha1 = sha1
@sha256 = sha256
end
end
FingerPrint = Struct.new(:sha1, :sha256)
end
end
end
10 changes: 1 addition & 9 deletions lib/facter/util/resolvers/ssh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,7 @@
module Facter
module Util
module Resolvers
class Ssh
attr_accessor :fingerprint, :type, :key, :name
def initialize(fingerprint, type, key, name)
@fingerprint = fingerprint
@type = type
@key = key
@name = name
end
end
Ssh = Struct.new(:fingerprint, :type, :key, :name)
end
end
end
2 changes: 2 additions & 0 deletions lib/facter/util/resolvers/ssh_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ module Resolvers
class SshHelper
class << self
SSH_NAME = { 'ssh-dss' => 'dsa', 'ecdsa-sha2-nistp256' => 'ecdsa',
'ecdsa-sha2-nistp384' => 'ecdsa', 'ecdsa-sha2-nistp521' => 'ecdsa',
'ssh-ed25519' => 'ed25519', 'ssh-rsa' => 'rsa' }.freeze
SSH_FINGERPRINT = { 'rsa' => 1, 'dsa' => 2, 'ecdsa' => 3, 'ed25519' => 4 }.freeze

def create_ssh(key_type, key)
key_name = SSH_NAME[key_type]
return unless key_name

# decode64 ignores non-base64 characters including newlines
decoded_key = Base64.decode64(key)
ssh_fp = SSH_FINGERPRINT[key_name]
sha1 = "SSHFP #{ssh_fp} 1 #{Digest::SHA1.new.update(decoded_key)}"
Expand Down
146 changes: 79 additions & 67 deletions spec/facter/resolvers/ssh_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,6 @@

describe Facter::Resolvers::Ssh do
describe '#folders' do
let(:ecdsa_content) { load_fixture('ecdsa').read.strip! }
let(:rsa_content) { load_fixture('rsa').read.strip! }
let(:ed25519_content) { load_fixture('ed25519').read.strip! }

let(:ecdsa_fingerprint) do
Facter::Util::Resolvers::FingerPrint.new(
'SSHFP 3 1 fd92cf867fac0042d491eb1067e4f3cabf54039a',
'SSHFP 3 2 a51271a67987d7bbd685fa6d7cdd2823a30373ab01420b094480523fabff2a05'
)
end

let(:rsa_fingerprint) do
Facter::Util::Resolvers::FingerPrint.new(
'SSHFP 1 1 90134f93fec6ab5e22bdd88fc4d7cd6e9dca4a07',
'SSHFP 1 2 efaa26ff8169f5ffc372ebcad17aef886f4ccaa727169acdd0379b51c6c77e99'
)
end

let(:ed25519_fingerprint) do
Facter::Util::Resolvers::FingerPrint.new(
'SSHFP 4 1 f5780634d4e34c6ef2411ac439b517bfdce43cf1',
'SSHFP 4 2 c1257b3865df22f3349f9ebe19961c8a8edf5fbbe883113e728671b42d2c9723'
)
end

let(:ecdsa_result) do
Facter::Util::Resolvers::Ssh.new(ecdsa_fingerprint, 'ecdsa-sha2-nistp256', ecdsa_content, 'ecdsa')
end

let(:rsa_result) do
Facter::Util::Resolvers::Ssh.new(rsa_fingerprint, 'ssh-rsa', rsa_content, 'rsa')
end
let(:ed25519_result) do
Facter::Util::Resolvers::Ssh.new(ed25519_fingerprint, 'ssh-ed22519', ed25519_content, 'ed25519')
end

let(:paths) { %w[/etc/ssh /usr/local/etc/ssh /etc /usr/local/etc /etc/opt/ssh] }
let(:file_names) { %w[ssh_host_rsa_key.pub ssh_host_ecdsa_key.pub ssh_host_ed25519_key.pub] }

Expand All @@ -46,43 +10,95 @@
allow(File).to receive(:directory?).with('/etc').and_return(true)

allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_ecdsa_key.pub', nil).and_return(ecdsa_content)
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_dsa_key.pub', nil).and_return(nil)
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_rsa_key.pub', nil).and_return(rsa_content)
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_ed25519_key.pub', nil).and_return(ed25519_content)

allow(Facter::Util::Resolvers::SshHelper).to receive(:create_ssh)
.with('ssh-rsa', load_fixture('rsa_key').read.strip!)
.and_return(rsa_result)
allow(Facter::Util::Resolvers::SshHelper).to receive(:create_ssh)
.with('ecdsa-sha2-nistp256', load_fixture('ecdsa_key').read.strip!)
.and_return(ecdsa_result)
allow(Facter::Util::Resolvers::SshHelper).to receive(:create_ssh)
.with('ssh-ed25519', load_fixture('ed25519_key').read.strip!)
.and_return(ed25519_result)
.with(a_string_starting_with('/etc/ssh_host'), nil).and_return(nil)
end

after do
Facter::Resolvers::Ssh.invalidate_cache
end

context 'when ssh_host_dsa_key.pub file is not readable' do
it 'returns resolved ssh' do
expect(Facter::Resolvers::Ssh.resolve(:ssh)).to eq([rsa_result, ecdsa_result, ed25519_result])
shared_examples 'an ssh key' do
it 'resolves the key' do
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with(path, nil).and_return(content)

expect(Facter::Resolvers::Ssh.resolve(:ssh)).to eq([result])
end
end

context 'when ssh_host_ecdsa_key.pub file is also not readable' do
before do
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_ecdsa_key.pub', nil).and_return(nil)
context 'when rsa' do
let(:path) { '/etc/ssh_host_rsa_key.pub' }
let(:content) { load_fixture('rsa').read }
let(:result) do
fingerprint = Facter::Util::Resolvers::FingerPrint.new(
'SSHFP 1 1 90134f93fec6ab5e22bdd88fc4d7cd6e9dca4a07',
'SSHFP 1 2 efaa26ff8169f5ffc372ebcad17aef886f4ccaa727169acdd0379b51c6c77e99'
)
Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'rsa')
end

it 'returns resolved ssh' do
expect(Facter::Resolvers::Ssh.resolve(:ssh)).to eq([rsa_result, ed25519_result])
include_examples 'an ssh key'
end

context 'when ecdsa' do
let(:path) { '/etc/ssh_host_dsa_key.pub' }
let(:content) { load_fixture('ecdsa').read }
let(:result) do
fingerprint = Facter::Util::Resolvers::FingerPrint.new(
'SSHFP 3 1 fd92cf867fac0042d491eb1067e4f3cabf54039a',
'SSHFP 3 2 a51271a67987d7bbd685fa6d7cdd2823a30373ab01420b094480523fabff2a05'
)
Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'ecdsa')
end

include_examples 'an ssh key'
end

context 'when ed25519' do
let(:path) { '/etc/ssh_host_ed25519_key.pub' }
let(:content) { load_fixture('ed25519').read }
let(:result) do
fingerprint = Facter::Util::Resolvers::FingerPrint.new(
'SSHFP 4 1 1c02084d251368b98a3af97820d9fbf2b8dc9558',
'SSHFP 4 2 656bd7aa3f8ad4703bd581888231f822cb8cd4a2a258584469551d2c2c9f6b62'
)
Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'ed25519')
end

include_examples 'an ssh key'
end

context 'when ecdsa 384-bit' do
let(:path) { '/etc/ssh_host_ecdsa_key.pub' }
let(:content) { load_fixture('ecdsa384').read }
let(:result) do
fingerprint = Facter::Util::Resolvers::FingerPrint.new(
'SSHFP 3 1 a3c1dc40a07cd76ea2ffe3f57e96aae146427174',
'SSHFP 3 2 949d92d65c6bb3908727bef5cdafef5b546650d64a081a4f85e7dcaf6b7cb7ab'
)
Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'ecdsa')
end

include_examples 'an ssh key'
end

context 'when ecdsa 521-bit' do
let(:path) { '/etc/ssh_host_ecdsa_key.pub' }
let(:content) { load_fixture('ecdsa521').read }
let(:result) do
fingerprint = Facter::Util::Resolvers::FingerPrint.new(
'SSHFP 3 1 61046cb5f7b38df21fe4511a9280436ce89514ee',
'SSHFP 3 2 b74da480da3411a79abf37d0bcfbbcaa8c1dbfc6a983365276b3c7f0c7a8de3e'
)
Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'ecdsa')
end

include_examples 'an ssh key'
end

context 'when no files are readable' do
it 'returns an empty array' do
expect(Facter::Resolvers::Ssh.resolve(:ssh)).to eq([])
end
end

Expand All @@ -108,11 +124,7 @@
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_ecdsa_key.pub', nil).and_return('invalid key')
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_dsa_key.pub', nil).and_return(nil)
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_rsa_key.pub', nil).and_return(nil)
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/etc/ssh_host_ed25519_key.pub', nil).and_return(nil)
.with(a_string_starting_with('/etc/ssh_host'), nil)
end

after do
Expand Down
28 changes: 22 additions & 6 deletions spec/facter/util/resolvers/ssh_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,32 @@
subject(:ssh_helper) { Facter::Util::Resolvers::SshHelper }

describe '#create_ssh' do
let(:fingerprint) { instance_spy(Facter::Util::Resolvers::FingerPrint) }
let(:key) { load_fixture('rsa_key').read.strip }

before do
allow(Facter::Util::Resolvers::FingerPrint).to receive(:new).and_return(fingerprint)
it 'returns an RSA ssh object' do
expect(ssh_helper.create_ssh('ssh-rsa', key)).to \
be_an_instance_of(Facter::Util::Resolvers::Ssh).and \
have_attributes(name: 'rsa', type: 'ssh-rsa')
end

it 'returns a ssh object' do
expect(ssh_helper.create_ssh('ssh-rsa', key)).to be_an_instance_of(Facter::Util::Resolvers::Ssh).and \
have_attributes(name: 'rsa', type: 'ssh-rsa', fingerprint: fingerprint)
it 'returns sha1 fingerprint' do
expect(ssh_helper.create_ssh('ssh-rsa', key).fingerprint.sha1).to \
eq('SSHFP 1 1 90134f93fec6ab5e22bdd88fc4d7cd6e9dca4a07')
end

it 'returns sha256 fingerprint' do
expect(ssh_helper.create_ssh('ssh-rsa', key).fingerprint.sha256).to \
eq('SSHFP 1 2 efaa26ff8169f5ffc372ebcad17aef886f4ccaa727169acdd0379b51c6c77e99')
end

it 'ignores non-base64 characters' do
nonbase64_key = "\x00\n-_#{key}"
expect(ssh_helper.create_ssh('ssh-rsa', nonbase64_key).fingerprint.sha1).to \
eq('SSHFP 1 1 90134f93fec6ab5e22bdd88fc4d7cd6e9dca4a07')
end

it 'implements value semantics' do
expect(ssh_helper.create_ssh('ssh-rsa', key)).to eq(ssh_helper.create_ssh('ssh-rsa', key))
end
end
end
1 change: 1 addition & 0 deletions spec/fixtures/ecdsa384
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBI+YmNHUdvMtZSEdCDJLruZjtUGsi59cf/TNkmRKFcVGgaWO54NUXT/PlTwjm7g9uS1FKbZY4+MKP0Q4KsgfGJAwn9MLsdSeUGY2UIrhQ0UM6KUUZCDot0G7Xm2pAdy/Qw==
1 change: 1 addition & 0 deletions spec/fixtures/ecdsa384_key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBI+YmNHUdvMtZSEdCDJLruZjtUGsi59cf/TNkmRKFcVGgaWO54NUXT/PlTwjm7g9uS1FKbZY4+MKP0Q4KsgfGJAwn9MLsdSeUGY2UIrhQ0UM6KUUZCDot0G7Xm2pAdy/Qw==
1 change: 1 addition & 0 deletions spec/fixtures/ecdsa521
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHKr7fO2HGs84ihV9+Z4Dkk4rX+FqhtKV4vGEIwnwR3r0GUIER1aIk+shXzOhCEPNqTiik5CRdE9sDhXkYDJa35+QFIBvo1i2qCNEQ1EowBbYZYBAhk3CPAhIUIYe+Achz+PCqBhqkPC+vHhqHpECAzOI0qjFuoT17rbEb4stl3n8yHfQ==
1 change: 1 addition & 0 deletions spec/fixtures/ecdsa521_key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHKr7fO2HGs84ihV9+Z4Dkk4rX+FqhtKV4vGEIwnwR3r0GUIER1aIk+shXzOhCEPNqTiik5CRdE9sDhXkYDJa35+QFIBvo1i2qCNEQ1EowBbYZYBAhk3CPAhIUIYe+Achz+PCqBhqkPC+vHhqHpECAzOI0qjFuoT17rbEb4stl3n8yHfQ==