net-ssh 4.2.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +6 -0
  4. data/.github/config/rubocop_linter_action.yml +4 -0
  5. data/.github/workflows/ci-with-docker.yml +44 -0
  6. data/.github/workflows/ci.yml +87 -0
  7. data/.github/workflows/rubocop.yml +13 -0
  8. data/.gitignore +7 -0
  9. data/.rubocop.yml +19 -2
  10. data/.rubocop_todo.yml +619 -667
  11. data/CHANGES.txt +110 -1
  12. data/Dockerfile +27 -0
  13. data/Dockerfile.openssl3 +17 -0
  14. data/Gemfile +3 -7
  15. data/{Gemfile.norbnacl → Gemfile.noed25519} +3 -1
  16. data/Manifest +4 -5
  17. data/README.md +293 -0
  18. data/Rakefile +45 -29
  19. data/appveyor.yml +8 -6
  20. data/docker-compose.yml +23 -0
  21. data/lib/net/ssh/authentication/agent.rb +248 -223
  22. data/lib/net/ssh/authentication/certificate.rb +178 -164
  23. data/lib/net/ssh/authentication/constants.rb +17 -15
  24. data/lib/net/ssh/authentication/ed25519.rb +141 -116
  25. data/lib/net/ssh/authentication/ed25519_loader.rb +28 -28
  26. data/lib/net/ssh/authentication/key_manager.rb +79 -36
  27. data/lib/net/ssh/authentication/methods/abstract.rb +62 -47
  28. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  29. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -3
  30. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  31. data/lib/net/ssh/authentication/methods/password.rb +15 -16
  32. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  33. data/lib/net/ssh/authentication/pageant.rb +468 -465
  34. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  35. data/lib/net/ssh/authentication/session.rb +131 -122
  36. data/lib/net/ssh/buffer.rb +385 -332
  37. data/lib/net/ssh/buffered_io.rb +150 -151
  38. data/lib/net/ssh/config.rb +316 -239
  39. data/lib/net/ssh/connection/channel.rb +635 -613
  40. data/lib/net/ssh/connection/constants.rb +29 -29
  41. data/lib/net/ssh/connection/event_loop.rb +104 -95
  42. data/lib/net/ssh/connection/keepalive.rb +55 -51
  43. data/lib/net/ssh/connection/session.rb +614 -611
  44. data/lib/net/ssh/connection/term.rb +125 -123
  45. data/lib/net/ssh/errors.rb +101 -99
  46. data/lib/net/ssh/key_factory.rb +194 -108
  47. data/lib/net/ssh/known_hosts.rb +212 -134
  48. data/lib/net/ssh/loggable.rb +50 -49
  49. data/lib/net/ssh/packet.rb +83 -79
  50. data/lib/net/ssh/prompt.rb +51 -51
  51. data/lib/net/ssh/proxy/command.rb +105 -91
  52. data/lib/net/ssh/proxy/errors.rb +12 -10
  53. data/lib/net/ssh/proxy/http.rb +81 -81
  54. data/lib/net/ssh/proxy/https.rb +37 -36
  55. data/lib/net/ssh/proxy/jump.rb +49 -48
  56. data/lib/net/ssh/proxy/socks4.rb +2 -6
  57. data/lib/net/ssh/proxy/socks5.rb +14 -17
  58. data/lib/net/ssh/service/forward.rb +365 -362
  59. data/lib/net/ssh/test/channel.rb +145 -143
  60. data/lib/net/ssh/test/extensions.rb +131 -127
  61. data/lib/net/ssh/test/kex.rb +34 -32
  62. data/lib/net/ssh/test/local_packet.rb +46 -44
  63. data/lib/net/ssh/test/packet.rb +87 -84
  64. data/lib/net/ssh/test/remote_packet.rb +32 -30
  65. data/lib/net/ssh/test/script.rb +155 -155
  66. data/lib/net/ssh/test/socket.rb +49 -48
  67. data/lib/net/ssh/test.rb +82 -80
  68. data/lib/net/ssh/transport/algorithms.rb +433 -364
  69. data/lib/net/ssh/transport/cipher_factory.rb +95 -91
  70. data/lib/net/ssh/transport/constants.rb +32 -24
  71. data/lib/net/ssh/transport/ctr.rb +37 -15
  72. data/lib/net/ssh/transport/hmac/abstract.rb +81 -63
  73. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  74. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  75. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  76. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  77. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  78. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  79. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  80. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  81. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  82. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  83. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  84. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  85. data/lib/net/ssh/transport/hmac.rb +14 -12
  86. data/lib/net/ssh/transport/identity_cipher.rb +54 -52
  87. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  88. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  89. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  90. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  92. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  93. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
  94. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -63
  95. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  96. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  97. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  98. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  99. data/lib/net/ssh/transport/kex.rb +15 -12
  100. data/lib/net/ssh/transport/key_expander.rb +24 -21
  101. data/lib/net/ssh/transport/openssl.rb +158 -133
  102. data/lib/net/ssh/transport/packet_stream.rb +223 -191
  103. data/lib/net/ssh/transport/server_version.rb +55 -56
  104. data/lib/net/ssh/transport/session.rb +306 -259
  105. data/lib/net/ssh/transport/state.rb +178 -176
  106. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  107. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  108. data/lib/net/ssh/verifiers/always.rb +58 -0
  109. data/lib/net/ssh/verifiers/never.rb +19 -0
  110. data/lib/net/ssh/version.rb +55 -53
  111. data/lib/net/ssh.rb +47 -34
  112. data/net-ssh-public_cert.pem +18 -19
  113. data/net-ssh.gemspec +12 -11
  114. data/support/ssh_tunnel_bug.rb +5 -5
  115. data.tar.gz.sig +0 -0
  116. metadata +78 -73
  117. metadata.gz.sig +0 -0
  118. data/.travis.yml +0 -51
  119. data/Gemfile.norbnacl.lock +0 -41
  120. data/README.rdoc +0 -169
  121. data/lib/net/ssh/ruby_compat.rb +0 -24
  122. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  123. data/lib/net/ssh/verifiers/null.rb +0 -12
  124. data/lib/net/ssh/verifiers/secure.rb +0 -52
  125. data/lib/net/ssh/verifiers/strict.rb +0 -24
  126. data/support/arcfour_check.rb +0 -20
@@ -3,130 +3,216 @@ require 'net/ssh/prompt'
3
3
 
4
4
  require 'net/ssh/authentication/ed25519_loader'
5
5
 
6
- module Net; module SSH
7
-
8
- # A factory class for returning new Key classes. It is used for obtaining
9
- # OpenSSL key instances via their SSH names, and for loading both public and
10
- # private keys. It used used primarily by Net::SSH itself, internally, and
11
- # will rarely (if ever) be directly used by consumers of the library.
12
- #
13
- # klass = Net::SSH::KeyFactory.get("rsa")
14
- # assert klass.is_a?(OpenSSL::PKey::RSA)
15
- #
16
- # key = Net::SSH::KeyFactory.load_public_key("~/.ssh/id_dsa.pub")
17
- class KeyFactory
18
- # Specifies the mapping of SSH names to OpenSSL key classes.
19
- MAP = {
20
- "dh" => OpenSSL::PKey::DH,
21
- "rsa" => OpenSSL::PKey::RSA,
22
- "dsa" => OpenSSL::PKey::DSA,
23
- }
24
- if defined?(OpenSSL::PKey::EC)
25
- MAP["ecdsa"] = OpenSSL::PKey::EC
6
+ module Net
7
+ module SSH
8
+ # A factory class for returning new Key classes. It is used for obtaining
9
+ # OpenSSL key instances via their SSH names, and for loading both public and
10
+ # private keys. It used used primarily by Net::SSH itself, internally, and
11
+ # will rarely (if ever) be directly used by consumers of the library.
12
+ #
13
+ # klass = Net::SSH::KeyFactory.get("rsa")
14
+ # assert klass.is_a?(OpenSSL::PKey::RSA)
15
+ #
16
+ # key = Net::SSH::KeyFactory.load_public_key("~/.ssh/id_dsa.pub")
17
+ class KeyFactory
18
+ # Specifies the mapping of SSH names to OpenSSL key classes.
19
+ MAP = {
20
+ 'dh' => OpenSSL::PKey::DH,
21
+ 'rsa' => OpenSSL::PKey::RSA,
22
+ 'dsa' => OpenSSL::PKey::DSA,
23
+ 'ecdsa' => OpenSSL::PKey::EC
24
+ }
26
25
  MAP["ed25519"] = Net::SSH::Authentication::ED25519::PrivKey if defined? Net::SSH::Authentication::ED25519
27
- end
28
-
29
- class <<self
30
- # Fetch an OpenSSL key instance by its SSH name. It will be a new,
31
- # empty key of the given type.
32
- def get(name)
33
- MAP.fetch(name).new
34
- end
35
-
36
- # Loads a private key from a file. It will correctly determine
37
- # whether the file describes an RSA or DSA key, and will load it
38
- # appropriately. The new key is returned. If the key itself is
39
- # encrypted (requiring a passphrase to use), the user will be
40
- # prompted to enter their password unless passphrase works.
41
- def load_private_key(filename, passphrase=nil, ask_passphrase=true, prompt=Prompt.default)
42
- data = File.read(File.expand_path(filename))
43
- load_data_private_key(data, passphrase, ask_passphrase, filename, prompt)
44
- end
45
26
 
46
- # Loads a private key. It will correctly determine
47
- # whether the file describes an RSA or DSA key, and will load it
48
- # appropriately. The new key is returned. If the key itself is
49
- # encrypted (requiring a passphrase to use), the user will be
50
- # prompted to enter their password unless passphrase works.
51
- def load_data_private_key(data, passphrase=nil, ask_passphrase=true, filename="", prompt=Prompt.default)
52
- key_read, error_classes = classify_key(data, filename)
27
+ class << self
28
+ # Fetch an OpenSSL key instance by its SSH name. It will be a new,
29
+ # empty key of the given type.
30
+ def get(name)
31
+ MAP.fetch(name).new
32
+ end
53
33
 
54
- encrypted_key = data.match(/ENCRYPTED/)
55
- tries = 0
34
+ # Loads a private key from a file. It will correctly determine
35
+ # whether the file describes an RSA or DSA key, and will load it
36
+ # appropriately. The new key is returned. If the key itself is
37
+ # encrypted (requiring a passphrase to use), the user will be
38
+ # prompted to enter their password unless passphrase works.
39
+ def load_private_key(filename, passphrase = nil, ask_passphrase = true, prompt = Prompt.default)
40
+ data = File.read(File.expand_path(filename))
41
+ load_data_private_key(data, passphrase, ask_passphrase, filename, prompt)
42
+ end
56
43
 
57
- prompter = nil
58
- result =
59
- begin
60
- key_read[data, passphrase || 'invalid']
61
- rescue *error_classes
62
- if encrypted_key && ask_passphrase
63
- tries += 1
64
- if tries <= 3
65
- prompter ||= prompt.start(type: 'private_key', filename: filename, sha: Digest::SHA256.digest(data))
66
- passphrase = prompter.ask("Enter passphrase for #{filename}:", false)
67
- retry
44
+ # Loads a private key. It will correctly determine
45
+ # whether the file describes an RSA or DSA key, and will load it
46
+ # appropriately. The new key is returned. If the key itself is
47
+ # encrypted (requiring a passphrase to use), the user will be
48
+ # prompted to enter their password unless passphrase works.
49
+ def load_data_private_key(data, passphrase = nil, ask_passphrase = true, filename = "", prompt = Prompt.default)
50
+ key_type = classify_key(data, filename)
51
+
52
+ encrypted_key = nil
53
+ tries = 0
54
+
55
+ prompter = nil
56
+ result =
57
+ begin
58
+ key_type.read(data, passphrase || 'invalid')
59
+ rescue *key_type.error_classes => e
60
+ encrypted_key = !!key_type.encrypted_key?(data, e) if encrypted_key.nil?
61
+ if encrypted_key && ask_passphrase
62
+ tries += 1
63
+ if tries <= 3
64
+ prompter ||= prompt.start(type: 'private_key', filename: filename, sha: Digest::SHA256.digest(data))
65
+ passphrase = prompter.ask("Enter passphrase for #{filename}:", false)
66
+ retry
67
+ else
68
+ raise
69
+ end
68
70
  else
69
71
  raise
70
72
  end
71
- else
72
- raise
73
73
  end
74
- end
75
- prompter.success if prompter
76
- result
77
- end
74
+ prompter.success if prompter
75
+ result
76
+ end
78
77
 
79
- # Loads a public key from a file. It will correctly determine whether
80
- # the file describes an RSA or DSA key, and will load it
81
- # appropriately. The new public key is returned.
82
- def load_public_key(filename)
83
- data = File.read(File.expand_path(filename))
84
- load_data_public_key(data, filename)
85
- end
78
+ # Loads a public key from a file. It will correctly determine whether
79
+ # the file describes an RSA or DSA key, and will load it
80
+ # appropriately. The new public key is returned.
81
+ def load_public_key(filename)
82
+ data = File.read(File.expand_path(filename))
83
+ load_data_public_key(data, filename)
84
+ end
86
85
 
87
- # Loads a public key. It will correctly determine whether
88
- # the file describes an RSA or DSA key, and will load it
89
- # appropriately. The new public key is returned.
90
- def load_data_public_key(data, filename="")
91
- fields = data.split(/ /)
86
+ # Loads a public key. It will correctly determine whether
87
+ # the file describes an RSA or DSA key, and will load it
88
+ # appropriately. The new public key is returned.
89
+ def load_data_public_key(data, filename = "")
90
+ fields = data.split(/ /)
92
91
 
93
- blob = nil
94
- begin
92
+ blob = nil
93
+ begin
94
+ blob = fields.shift
95
+ end while !blob.nil? && !/^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp\d+)(-cert-v01@openssh\.com)?$/.match(blob)
95
96
  blob = fields.shift
96
- end while !blob.nil? && !/^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp\d+)(-cert-v01@openssh\.com)?$/.match(blob)
97
- blob = fields.shift
98
97
 
99
- raise Net::SSH::Exception, "public key at #{filename} is not valid" if blob.nil?
98
+ raise Net::SSH::Exception, "public key at #{filename} is not valid" if blob.nil?
100
99
 
101
- blob = blob.unpack("m*").first
102
- reader = Net::SSH::Buffer.new(blob)
103
- reader.read_key or raise OpenSSL::PKey::PKeyError, "not a public key #{filename.inspect}"
104
- end
100
+ blob = blob.unpack("m*").first
101
+ reader = Net::SSH::Buffer.new(blob)
102
+ reader.read_key or raise OpenSSL::PKey::PKeyError, "not a public key #{filename.inspect}"
103
+ end
104
+
105
+ private
106
+
107
+ # rubocop:disable Style/Documentation, Lint/DuplicateMethods
108
+ class KeyType
109
+ def self.read(key_data, passphrase)
110
+ raise Exception, "TODO subclasses should implement read"
111
+ end
112
+
113
+ def self.error_classes
114
+ raise Exception, "TODO subclasses should implement read"
115
+ end
116
+
117
+ def self.encrypted_key?(data, error)
118
+ raise Exception, "TODO subclasses should implement is_encrypted_key"
119
+ end
120
+ end
121
+
122
+ class OpenSSHPrivateKeyType < KeyType
123
+ def self.read(key_data, passphrase)
124
+ Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader.read(key_data, passphrase)
125
+ end
126
+
127
+ def self.error_classes
128
+ [Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader::DecryptError]
129
+ end
130
+
131
+ def self.encrypted_key?(key_data, decode_error)
132
+ decode_error.is_a?(Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader::DecryptError) && decode_error.encrypted_key?
133
+ end
134
+ end
105
135
 
106
- private
107
-
108
- # Determine whether the file describes an RSA or DSA key, and return how load it
109
- # appropriately.
110
- def classify_key(data, filename)
111
- if data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/)
112
- Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("OpenSSH keys only supported if ED25519 is available")
113
- return ->(key_data, passphrase) { Net::SSH::Authentication::ED25519::PrivKey.read(key_data, passphrase) }, [ArgumentError]
114
- elsif OpenSSL::PKey.respond_to?(:read)
115
- return ->(key_data, passphrase) { OpenSSL::PKey.read(key_data, passphrase) }, [ArgumentError, OpenSSL::PKey::PKeyError]
116
- elsif data.match(/-----BEGIN DSA PRIVATE KEY-----/)
117
- return ->(key_data, passphrase) { OpenSSL::PKey::DSA.new(key_data, passphrase) }, [OpenSSL::PKey::DSAError]
118
- elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
119
- return ->(key_data, passphrase) { OpenSSL::PKey::RSA.new(key_data, passphrase) }, [OpenSSL::PKey::RSAError]
120
- elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) && defined?(OpenSSL::PKey::EC)
121
- return ->(key_data, passphrase) { OpenSSL::PKey::EC.new(key_data, passphrase) }, [OpenSSL::PKey::ECError]
122
- elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/)
123
- raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'"
124
- else
125
- raise OpenSSL::PKey::PKeyError, "not a private key (#{filename})"
136
+ class OpenSSLKeyTypeBase < KeyType
137
+ def self.open_ssl_class
138
+ raise Exception, "TODO: subclasses should implement"
139
+ end
140
+
141
+ def self.read(key_data, passphrase)
142
+ open_ssl_class.new(key_data, passphrase)
143
+ end
144
+
145
+ def self.encrypted_key?(key_data, error)
146
+ key_data.match(/ENCRYPTED/)
147
+ end
148
+ end
149
+
150
+ class OpenSSLPKeyType < OpenSSLKeyTypeBase
151
+ def self.read(key_data, passphrase)
152
+ open_ssl_class.read(key_data, passphrase)
153
+ end
154
+
155
+ def self.open_ssl_class
156
+ OpenSSL::PKey
157
+ end
158
+
159
+ def self.error_classes
160
+ [ArgumentError, OpenSSL::PKey::PKeyError]
161
+ end
162
+ end
163
+
164
+ class OpenSSLDSAKeyType < OpenSSLKeyTypeBase
165
+ def self.open_ssl_class
166
+ OpenSSL::PKey::DSA
167
+ end
168
+
169
+ def self.error_classes
170
+ [OpenSSL::PKey::DSAError]
171
+ end
172
+ end
173
+
174
+ class OpenSSLRSAKeyType < OpenSSLKeyTypeBase
175
+ def self.open_ssl_class
176
+ OpenSSL::PKey::RSA
177
+ end
178
+
179
+ def self.error_classes
180
+ [OpenSSL::PKey::RSAError]
181
+ end
182
+ end
183
+
184
+ class OpenSSLECKeyType < OpenSSLKeyTypeBase
185
+ def self.open_ssl_class
186
+ OpenSSL::PKey::EC
187
+ end
188
+
189
+ def self.error_classes
190
+ [OpenSSL::PKey::ECError]
191
+ end
192
+ end
193
+ # rubocop:enable Style/Documentation, Lint/DuplicateMethods
194
+
195
+ # Determine whether the file describes an RSA or DSA key, and return how load it
196
+ # appropriately.
197
+ def classify_key(data, filename)
198
+ if data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/)
199
+ Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("OpenSSH keys only supported if ED25519 is available")
200
+ return OpenSSHPrivateKeyType
201
+ elsif OpenSSL::PKey.respond_to?(:read)
202
+ return OpenSSLPKeyType
203
+ elsif data.match(/-----BEGIN DSA PRIVATE KEY-----/)
204
+ return OpenSSLDSAKeyType
205
+ elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
206
+ return OpenSSLRSAKeyType
207
+ elsif data.match(/-----BEGIN EC PRIVATE KEY-----/)
208
+ return OpenSSLECKeyType
209
+ elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/)
210
+ raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'"
211
+ else
212
+ raise OpenSSL::PKey::PKeyError, "not a private key (#{filename})"
213
+ end
126
214
  end
127
215
  end
128
216
  end
129
-
130
217
  end
131
-
132
- end; end
218
+ end