summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZdenek Zambersky <zzambers@redhat.com>2021-05-06 13:50:20 +0200
committerFlorian Wininger <fw.centrale@gmail.com>2021-10-25 15:59:19 +0200
commit91254c72586179469ee59e4b0bd1d54960ce8d88 (patch)
tree2a2b52c5bf7ac31804d1d6f018a4b5ddf805ca79
parent0150d054f0cd0beacd4ba1000c6df6d8636a2c18 (diff)
downloadnet-ssh-rsa-sha2.tar.gz
Added support for RSA client authentication with SHA-2rsa-sha2
-rw-r--r--lib/net/ssh/authentication/certificate.rb4
-rw-r--r--lib/net/ssh/authentication/ed25519.rb2
-rw-r--r--lib/net/ssh/authentication/key_manager.rb21
-rw-r--r--lib/net/ssh/authentication/methods/abstract.rb10
-rw-r--r--lib/net/ssh/authentication/methods/publickey.rb66
-rw-r--r--lib/net/ssh/authentication/session.rb4
-rw-r--r--lib/net/ssh/transport/openssl.rb16
-rw-r--r--test/authentication/methods/test_publickey.rb61
-rw-r--r--test/authentication/test_session.rb4
9 files changed, 161 insertions, 27 deletions
diff --git a/lib/net/ssh/authentication/certificate.rb b/lib/net/ssh/authentication/certificate.rb
index c9619fa..5250789 100644
--- a/lib/net/ssh/authentication/certificate.rb
+++ b/lib/net/ssh/authentication/certificate.rb
@@ -66,8 +66,8 @@ module Net
).to_s
end
- def ssh_do_sign(data)
- key.ssh_do_sign(data)
+ def ssh_do_sign(data, sig_alg = nil)
+ key.ssh_do_sign(data, sig_alg)
end
def ssh_do_verify(sig, data, options = {})
diff --git a/lib/net/ssh/authentication/ed25519.rb b/lib/net/ssh/authentication/ed25519.rb
index c2a117a..dccc64f 100644
--- a/lib/net/ssh/authentication/ed25519.rb
+++ b/lib/net/ssh/authentication/ed25519.rb
@@ -171,7 +171,7 @@ module Net
PubKey.new(@pk)
end
- def ssh_do_sign(data)
+ def ssh_do_sign(data, sig_alg = nil)
@sign_key.sign(data)
end
diff --git a/lib/net/ssh/authentication/key_manager.rb b/lib/net/ssh/authentication/key_manager.rb
index ac86cc8..563702c 100644
--- a/lib/net/ssh/authentication/key_manager.rb
+++ b/lib/net/ssh/authentication/key_manager.rb
@@ -158,7 +158,7 @@ module Net
# Regardless of the identity's origin or who does the signing, this
# will always return the signature in an SSH2-specified "signature
# blob" format.
- def sign(identity, data)
+ def sign(identity, data, sig_alg = nil)
info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager"
if info[:key].nil? && info[:from] == :file
@@ -170,14 +170,27 @@ module Net
end
if info[:key]
- return Net::SSH::Buffer.from(:string, identity.ssh_signature_type,
- :mstring, info[:key].ssh_do_sign(data.to_s)).to_s
+ if sig_alg.nil?
+ signed = info[:key].ssh_do_sign(data.to_s)
+ sig_alg = identity.ssh_signature_type
+ else
+ signed = info[:key].ssh_do_sign(data.to_s, sig_alg)
+ end
+ return Net::SSH::Buffer.from(:string, sig_alg,
+ :mstring, signed).to_s
end
if info[:from] == :agent
raise KeyManagerError, "the agent is no longer available" unless agent
- return agent.sign(info[:identity], data.to_s)
+ case sig_alg
+ when "rsa-sha2-512"
+ return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_512)
+ when "rsa-sha2-256"
+ return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_256)
+ else
+ return agent.sign(info[:identity], data.to_s)
+ end
end
raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
diff --git a/lib/net/ssh/authentication/methods/abstract.rb b/lib/net/ssh/authentication/methods/abstract.rb
index 07aa5f3..f023011 100644
--- a/lib/net/ssh/authentication/methods/abstract.rb
+++ b/lib/net/ssh/authentication/methods/abstract.rb
@@ -20,12 +20,22 @@ module Net
# this.
attr_reader :key_manager
+ # So far only affects algorithms used for rsa keys, but can be
+ # extended to other keys, e.g after reading of
+ # PubkeyAcceptedAlgorithms option from ssh_config file is implemented.
+ attr_reader :pubkey_algorithms
+
# Instantiates a new authentication method.
def initialize(session, options = {})
@session = session
@key_manager = options[:key_manager]
@options = options
@prompt = options[:password_prompt]
+ @pubkey_algorithms = options[:pubkey_algorithms] \
+ || %w[rsa-sha2-256-cert-v01@openssh.com
+ ssh-rsa-cert-v01@openssh.com
+ rsa-sha2-256
+ ssh-rsa]
self.logger = session.logger
end
diff --git a/lib/net/ssh/authentication/methods/publickey.rb b/lib/net/ssh/authentication/methods/publickey.rb
index eb4d740..48a56ab 100644
--- a/lib/net/ssh/authentication/methods/publickey.rb
+++ b/lib/net/ssh/authentication/methods/publickey.rb
@@ -26,41 +26,40 @@ module Net
# Builds a packet that contains the request formatted for sending
# a public-key request to the server.
- def build_request(pub_key, username, next_service, has_sig)
+ def build_request(pub_key, username, next_service, alg, has_sig)
blob = Net::SSH::Buffer.new
blob.write_key pub_key
userauth_request(username, next_service, "publickey", has_sig,
- pub_key.ssh_type, blob.to_s)
+ alg, blob.to_s)
end
# Builds and sends a request formatted for a public-key
# authentication request.
- def send_request(pub_key, username, next_service, signature = nil)
- msg = build_request(pub_key, username, next_service, !signature.nil?)
+ def send_request(pub_key, username, next_service, alg, signature = nil)
+ msg = build_request(pub_key, username, next_service, alg,
+ !signature.nil?)
msg.write_string(signature) if signature
send_message(msg)
end
- # Attempts to perform public-key authentication for the given
- # username, with the given identity (public key). Returns +true+ if
- # successful, or +false+ otherwise.
- def authenticate_with(identity, next_service, username)
+ def authenticate_with_alg(identity, next_service, username, alg, sig_alg = nil)
debug { "trying publickey (#{identity.fingerprint})" }
- send_request(identity, username, next_service)
+ send_request(identity, username, next_service, alg)
message = session.next_message
case message.type
when USERAUTH_PK_OK
- buffer = build_request(identity, username, next_service, true)
+ buffer = build_request(identity, username, next_service, alg,
+ true)
sig_data = Net::SSH::Buffer.new
sig_data.write_string(session_id)
sig_data.append(buffer.to_s)
- sig_blob = key_manager.sign(identity, sig_data)
+ sig_blob = key_manager.sign(identity, sig_data, sig_alg)
- send_request(identity, username, next_service, sig_blob.to_s)
+ send_request(identity, username, next_service, alg, sig_blob.to_s)
message = session.next_message
case message.type
@@ -88,6 +87,49 @@ module Net
raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
end
end
+
+ # Attempts to perform public-key authentication for the given
+ # username, with the given identity (public key). Returns +true+ if
+ # successful, or +false+ otherwise.
+ def authenticate_with(identity, next_service, username)
+ type = identity.ssh_type
+ if type == "ssh-rsa"
+ pubkey_algorithms.each do |pk_alg|
+ case pk_alg
+ when "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa"
+ if authenticate_with_alg(identity, next_service, username, pk_alg, pk_alg)
+ # success
+ return true
+ end
+ end
+ end
+ elsif type == "ssh-rsa-cert-v01@openssh.com"
+ pubkey_algorithms.each do |pk_alg|
+ case pk_alg
+ when "rsa-sha2-512-cert-v01@openssh.com"
+ if authenticate_with_alg(identity, next_service, username, pk_alg, "rsa-sha2-512")
+ # success
+ return true
+ end
+ when "rsa-sha2-256-cert-v01@openssh.com"
+ if authenticate_with_alg(identity, next_service, username, pk_alg, "rsa-sha2-256")
+ # success
+ return true
+ end
+ when "ssh-rsa-cert-v01@openssh.com"
+ if authenticate_with_alg(identity, next_service, username, pk_alg)
+ # success
+ return true
+ end
+ end
+ end
+ elsif authenticate_with_alg(identity, next_service, username, type)
+ # success
+ return true
+ end
+ # failure
+ return false
+ end
end
end
end
diff --git a/lib/net/ssh/authentication/session.rb b/lib/net/ssh/authentication/session.rb
index e4fc648..773d704 100644
--- a/lib/net/ssh/authentication/session.rb
+++ b/lib/net/ssh/authentication/session.rb
@@ -76,7 +76,9 @@ module Net
debug { "trying #{name}" }
begin
auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join)
- method = auth_class.new(self, key_manager: key_manager, password_prompt: options[:password_prompt])
+ method = auth_class.new(self,
+ key_manager: key_manager, password_prompt: options[:password_prompt],
+ pubkey_algorithms: options[:pubkey_algorithms] || nil)
rescue NameError
debug {"Mechanism #{name} was requested, but isn't a known type. Ignoring it."}
next
diff --git a/lib/net/ssh/transport/openssl.rb b/lib/net/ssh/transport/openssl.rb
index 4ff58cf..1c23651 100644
--- a/lib/net/ssh/transport/openssl.rb
+++ b/lib/net/ssh/transport/openssl.rb
@@ -74,8 +74,16 @@ module OpenSSL
end
# Returns the signature for the given data.
- def ssh_do_sign(data)
- sign(OpenSSL::Digest::SHA1.new, data)
+ def ssh_do_sign(data, sig_alg = nil)
+ digester =
+ if sig_alg == "rsa-sha2-512"
+ OpenSSL::Digest::SHA512.new
+ elsif sig_alg == "rsa-sha2-256"
+ OpenSSL::Digest::SHA256.new
+ else
+ OpenSSL::Digest::SHA1.new
+ end
+ sign(digester, data)
end
end
@@ -109,7 +117,7 @@ module OpenSSL
end
# Signs the given data.
- def ssh_do_sign(data)
+ def ssh_do_sign(data, sig_alg = nil)
sig = sign(OpenSSL::Digest::SHA1.new, data)
a1sig = OpenSSL::ASN1.decode(sig)
@@ -220,7 +228,7 @@ module OpenSSL
end
# Returns the signature for the given data.
- def ssh_do_sign(data)
+ def ssh_do_sign(data, sig_alg = nil)
digest = digester.digest(data)
sig = dsa_sign_asn1(digest)
a1sig = OpenSSL::ASN1.decode(sig)
diff --git a/test/authentication/methods/test_publickey.rb b/test/authentication/methods/test_publickey.rb
index db5e62f..5e76ca0 100644
--- a/test/authentication/methods/test_publickey.rb
+++ b/test/authentication/methods/test_publickey.rb
@@ -104,6 +104,50 @@ module Authentication
assert subject.authenticate("ssh-connection", "jamis")
end
+ def test_authenticate_rsa_sha2
+ key_manager.expects(:sign).with(&signature_parameters_with_alg(keys.first, "rsa-sha2-256")).returns("sig-one")
+
+ transport.expect do |t, packet|
+ assert_equal USERAUTH_REQUEST, packet.type
+ assert verify_userauth_request_packet(packet, keys.first, false, "rsa-sha2-256")
+ t.return(USERAUTH_PK_OK, :string, "rsa-sha2-256", :string, Net::SSH::Buffer.from(:key, keys.first))
+
+ t.expect do |t2, packet2|
+ assert_equal USERAUTH_REQUEST, packet2.type
+ assert verify_userauth_request_packet(packet2, keys.first, true, "rsa-sha2-256")
+ assert_equal "sig-one", packet2.read_string
+ t2.return(USERAUTH_SUCCESS)
+ end
+ end
+
+ assert subject(pubkey_algorithms: %w[rsa-sha2-256]).authenticate("ssh-connection", "jamis")
+ end
+
+ def test_authenticate_rsa_sha2_fallback
+ key_manager.expects(:sign).with(&signature_parameters(keys.first)).returns("sig-one")
+
+ transport.expect do |t, packet|
+ assert_equal USERAUTH_REQUEST, packet.type
+ assert verify_userauth_request_packet(packet, keys.first, false, "rsa-sha2-256")
+ t.return(USERAUTH_FAILURE, :string, "publickey")
+
+ t.expect do |t2, packet2|
+ assert_equal USERAUTH_REQUEST, packet2.type
+ assert verify_userauth_request_packet(packet2, keys.first, false)
+ t2.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first))
+
+ t2.expect do |t3, packet3|
+ assert_equal USERAUTH_REQUEST, packet3.type
+ assert verify_userauth_request_packet(packet3, keys.first, true)
+ assert_equal "sig-one", packet3.read_string
+ t3.return(USERAUTH_SUCCESS)
+ end
+ end
+ end
+
+ assert subject(pubkey_algorithms: %w[rsa-sha2-256 ssh-rsa]).authenticate("ssh-connection", "jamis")
+ end
+
private
def signature_parameters(key)
@@ -117,12 +161,24 @@ module Authentication
end
end
- def verify_userauth_request_packet(packet, key, has_sig)
+ def signature_parameters_with_alg(key, alg)
+ Proc.new do |given_key, data, given_alg|
+ next false unless given_key.to_blob == key.to_blob
+ next false unless given_alg == alg
+
+ buffer = Net::SSH::Buffer.new(data)
+ buffer.read_string == "abcxyz123" && # session-id
+ buffer.read_byte == USERAUTH_REQUEST && # type
+ verify_userauth_request_packet(buffer, key, true, alg)
+ end
+ end
+
+ def verify_userauth_request_packet(packet, key, has_sig, alg = nil)
packet.read_string == "jamis" && # user-name
packet.read_string == "ssh-connection" && # next service
packet.read_string == "publickey" && # auth-method
packet.read_bool == has_sig && # whether a signature is appended
- packet.read_string == key.ssh_type && # ssh key type
+ packet.read_string == (alg || key.ssh_type) && # ssh key type
packet.read_buffer.read_key.to_blob == key.to_blob # key
end
@@ -141,6 +197,7 @@ module Authentication
def subject(options = {})
options[:key_manager] = key_manager(options) unless options.key?(:key_manager)
+ options[:pubkey_algorithms] = %w[ssh-rsa] unless options.key?(:pubkey_algorithms)
@subject ||= Net::SSH::Authentication::Methods::Publickey.new(session(options), options)
end
end
diff --git a/test/authentication/test_session.rb b/test/authentication/test_session.rb
index 0f73a97..4f1d482 100644
--- a/test/authentication/test_session.rb
+++ b/test/authentication/test_session.rb
@@ -168,7 +168,9 @@ module Authentication
private
def session(options = {})
- @session ||= Net::SSH::Authentication::Session.new(transport(options), options)
+ session_opts = options.clone
+ session_opts[:pubkey_algorithms] = %w[ssh-rsa]
+ @session ||= Net::SSH::Authentication::Session.new(transport(options), session_opts)
end
def transport(options = {})