Keep the constraint on net-ssh tight so we can be confident that the
patching will be successful and that a net-ssh release won't inadvertently
cause our local updates to become non-functional.
Fix patch to only update the behavior for RSA based keys when the server
is recent enough to include the signature changes
187 lines
6.2 KiB
Ruby
187 lines
6.2 KiB
Ruby
require "net/ssh"
|
|
|
|
# Only patch if we have version 6.1.0 loaded as
|
|
# these patches pull 6.1.0 up to the as of now
|
|
# current 6.2.0 beta
|
|
if Net::SSH::Version::STRING == "6.1.0"
|
|
require "net/ssh/authentication/methods/publickey"
|
|
Net::SSH::Authentication::Methods::Publickey.class_eval do
|
|
def rsa_compat_build_request(pub_key, *args)
|
|
s_ver_str = session.transport.server_version.version.match(/OpenSSH_(?<version>\d+\.\d+)/)[:version]
|
|
begin
|
|
s_ver = Gem::Version.new(s_ver_str)
|
|
if s_ver >= Gem::Version.new("7.2") && pub_key.is_a?(OpenSSL::PKey::RSA)
|
|
pub_key.deprecated_ssh_rsa = true
|
|
debug { "public key has been marked for deprecated ssh-rsa SHA1 behavior" }
|
|
info = key_manager.known_identities[pub_key]
|
|
if info && info[:key]
|
|
info[:key].deprecated_ssh_rsa = true
|
|
debug { "private key has been marked for deprecated ssh-rsa SHA1 behavior" }
|
|
else
|
|
warn { "cannot deprecate ssh rsa on private key, not loaded (#{info[:file]})" }
|
|
end
|
|
end
|
|
rescue ArgumentError
|
|
warn { "failed to parse OpenSSH version (raw: #{session.transport.server_version.version} attempted: #{s_ver_str}" }
|
|
end
|
|
_raw_build_request(pub_key, *args)
|
|
end
|
|
alias_method :_raw_build_request, :build_request
|
|
alias_method :build_request, :rsa_compat_build_request
|
|
end
|
|
|
|
require "net/ssh/authentication/agent"
|
|
# net/ssh/authentication/agent
|
|
Net::SSH::Authentication::Agent.class_eval do
|
|
SSH2_AGENT_LOCK = 22
|
|
SSH2_AGENT_UNLOCK = 23
|
|
|
|
# lock the ssh agent with password
|
|
def lock(password)
|
|
type, = send_and_wait(SSH2_AGENT_LOCK, :string, password)
|
|
raise AgentError, "could not lock agent" if type != SSH_AGENT_SUCCESS
|
|
end
|
|
|
|
# unlock the ssh agent with password
|
|
def unlock(password)
|
|
type, = send_and_wait(SSH2_AGENT_UNLOCK, :string, password)
|
|
raise AgentError, "could not unlock agent" if type != SSH_AGENT_SUCCESS
|
|
end
|
|
end
|
|
|
|
require "net/ssh/authentication/certificate"
|
|
# net/ssh/authentication/certificate
|
|
Net::SSH::Authentication::Certificate.class_eval do
|
|
def ssh_do_verify(sig, data, options = {})
|
|
key.ssh_do_verify(sig, data, options)
|
|
end
|
|
end
|
|
|
|
require "net/ssh/authentication/ed25519"
|
|
# net/ssh/authentication/ed25519
|
|
Net::SSH::Authentication::ED25519::PubKey.class_eval do
|
|
def ssh_do_verify(sig, data, options = {})
|
|
@verify_key.verify(sig,data)
|
|
end
|
|
end
|
|
|
|
require "net/ssh/transport/algorithms"
|
|
# net/ssh/transport/algorithms
|
|
Net::SSH::Transport::Algorithms::DEFAULT_ALGORITHMS[:host_key].push("rsa-sha2-256").push("rsa-sha2-512")
|
|
|
|
require "net/ssh/transport/cipher_factory"
|
|
# net/ssh/transport/cipher_factory
|
|
Net::SSH::Transport::CipherFactory::SSH_TO_OSSL["aes256-ctr"] = ::OpenSSL::Cipher.ciphers.include?("aes-256-ctr") ? "aes-256-ctr" : "aes-256-ecb"
|
|
Net::SSH::Transport::CipherFactory::SSH_TO_OSSL["aes192-ctr"] = ::OpenSSL::Cipher.ciphers.include?("aes-192-ctr") ? "aes-192-ctr" : "aes-192-ecb"
|
|
Net::SSH::Transport::CipherFactory::SSH_TO_OSSL["aes128-ctr"] = ::OpenSSL::Cipher.ciphers.include?("aes-128-ctr") ? "aes-128-ctr" : "aes-128-ecb"
|
|
|
|
require "net/ssh/transport/kex/abstract"
|
|
# net/ssh/transport/kex/abstract
|
|
Net::SSH::Transport::Kex::Abstract.class_eval do
|
|
def matching?(key_ssh_type, host_key_alg)
|
|
return true if key_ssh_type == host_key_alg
|
|
return true if key_ssh_type == 'ssh-rsa' && ['rsa-sha2-512', 'rsa-sha2-256'].include?(host_key_alg)
|
|
end
|
|
|
|
def verify_server_key(key) #:nodoc:
|
|
unless matching?(key.ssh_type, algorithms.host_key)
|
|
raise Net::SSH::Exception, "host key algorithm mismatch '#{key.ssh_type}' != '#{algorithms.host_key}'"
|
|
end
|
|
|
|
blob, fingerprint = generate_key_fingerprint(key)
|
|
|
|
unless connection.host_key_verifier.verify(key: key, key_blob: blob, fingerprint: fingerprint, session: connection)
|
|
raise Net::SSH::Exception, 'host key verification failed'
|
|
end
|
|
end
|
|
|
|
def verify_signature(result) #:nodoc:
|
|
response = build_signature_buffer(result)
|
|
|
|
hash = digester.digest(response.to_s)
|
|
|
|
server_key = result[:server_key]
|
|
server_sig = result[:server_sig]
|
|
unless connection.host_key_verifier.verify_signature { server_key.ssh_do_verify(server_sig, hash, host_key: algorithms.host_key) }
|
|
raise Net::SSH::Exception, 'could not verify server signature'
|
|
end
|
|
|
|
hash
|
|
end
|
|
end
|
|
|
|
require "net/ssh/transport/openssl"
|
|
# net/ssh/transport/openssl
|
|
OpenSSL::PKey::RSA.class_eval do
|
|
attr_accessor :deprecated_ssh_rsa
|
|
|
|
def ssh_do_verify(sig, data, options = {})
|
|
digester =
|
|
if options[:host_key] == "rsa-sha2-512"
|
|
OpenSSL::Digest::SHA512.new
|
|
elsif options[:host_key] == "rsa-sha2-256"
|
|
OpenSSL::Digest::SHA256.new
|
|
else
|
|
OpenSSL::Digest::SHA1.new
|
|
end
|
|
|
|
verify(digester, sig, data)
|
|
end
|
|
|
|
def ssh_type
|
|
deprecated_ssh_rsa ? signature_algorithm : "ssh-rsa"
|
|
end
|
|
|
|
def signature_algorithm
|
|
"rsa-sha2-256"
|
|
end
|
|
|
|
def ssh_do_sign(data)
|
|
if deprecated_ssh_rsa
|
|
sign(OpenSSL::Digest::SHA256.new, data)
|
|
else
|
|
sign(OpenSSL::Digest::SHA1.new, data)
|
|
end
|
|
end
|
|
end
|
|
|
|
OpenSSL::PKey::DSA.class_eval do
|
|
def ssh_do_verify(sig, data, options = {})
|
|
sig_r = sig[0,20].unpack("H*")[0].to_i(16)
|
|
sig_s = sig[20,20].unpack("H*")[0].to_i(16)
|
|
a1sig = OpenSSL::ASN1::Sequence([
|
|
OpenSSL::ASN1::Integer(sig_r),
|
|
OpenSSL::ASN1::Integer(sig_s)
|
|
])
|
|
return verify(OpenSSL::Digest::SHA1.new, a1sig.to_der, data)
|
|
end
|
|
end
|
|
|
|
OpenSSL::PKey::EC.class_eval do
|
|
def ssh_do_verify(sig, data, options = {})
|
|
digest = digester.digest(data)
|
|
a1sig = nil
|
|
|
|
begin
|
|
sig_r_len = sig[0, 4].unpack('H*')[0].to_i(16)
|
|
sig_l_len = sig[4 + sig_r_len, 4].unpack('H*')[0].to_i(16)
|
|
|
|
sig_r = sig[4, sig_r_len].unpack('H*')[0]
|
|
sig_s = sig[4 + sig_r_len + 4, sig_l_len].unpack('H*')[0]
|
|
|
|
a1sig = OpenSSL::ASN1::Sequence([
|
|
OpenSSL::ASN1::Integer(sig_r.to_i(16)),
|
|
OpenSSL::ASN1::Integer(sig_s.to_i(16))
|
|
])
|
|
rescue StandardError
|
|
end
|
|
|
|
if a1sig.nil?
|
|
return false
|
|
else
|
|
dsa_verify_asn1(digest, a1sig.to_der)
|
|
end
|
|
end
|
|
end
|
|
end
|