net-ssh 3.2.0.rc2 → 7.1.0

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 (204) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +2 -2
  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 +16 -0
  8. data/.gitignore +13 -0
  9. data/.rubocop.yml +22 -0
  10. data/.rubocop_todo.yml +1081 -0
  11. data/CHANGES.txt +228 -7
  12. data/Dockerfile +27 -0
  13. data/Dockerfile.openssl3 +17 -0
  14. data/Gemfile +13 -0
  15. data/Gemfile.noed25519 +12 -0
  16. data/ISSUE_TEMPLATE.md +30 -0
  17. data/Manifest +4 -5
  18. data/README.md +297 -0
  19. data/Rakefile +125 -74
  20. data/SECURITY.md +4 -0
  21. data/appveyor.yml +58 -0
  22. data/docker-compose.yml +23 -0
  23. data/lib/net/ssh/authentication/agent.rb +279 -18
  24. data/lib/net/ssh/authentication/certificate.rb +183 -0
  25. data/lib/net/ssh/authentication/constants.rb +17 -15
  26. data/lib/net/ssh/authentication/ed25519.rb +186 -0
  27. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  28. data/lib/net/ssh/authentication/key_manager.rb +86 -39
  29. data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
  30. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  31. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +13 -13
  32. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  33. data/lib/net/ssh/authentication/methods/password.rb +27 -17
  34. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  35. data/lib/net/ssh/authentication/pageant.rb +471 -367
  36. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  37. data/lib/net/ssh/authentication/session.rb +131 -121
  38. data/lib/net/ssh/buffer.rb +399 -300
  39. data/lib/net/ssh/buffered_io.rb +154 -150
  40. data/lib/net/ssh/config.rb +308 -185
  41. data/lib/net/ssh/connection/channel.rb +635 -613
  42. data/lib/net/ssh/connection/constants.rb +29 -29
  43. data/lib/net/ssh/connection/event_loop.rb +123 -0
  44. data/lib/net/ssh/connection/keepalive.rb +55 -51
  45. data/lib/net/ssh/connection/session.rb +620 -551
  46. data/lib/net/ssh/connection/term.rb +125 -123
  47. data/lib/net/ssh/errors.rb +101 -99
  48. data/lib/net/ssh/key_factory.rb +197 -105
  49. data/lib/net/ssh/known_hosts.rb +214 -127
  50. data/lib/net/ssh/loggable.rb +50 -49
  51. data/lib/net/ssh/packet.rb +83 -79
  52. data/lib/net/ssh/prompt.rb +50 -81
  53. data/lib/net/ssh/proxy/command.rb +105 -90
  54. data/lib/net/ssh/proxy/errors.rb +12 -10
  55. data/lib/net/ssh/proxy/http.rb +82 -79
  56. data/lib/net/ssh/proxy/https.rb +50 -0
  57. data/lib/net/ssh/proxy/jump.rb +54 -0
  58. data/lib/net/ssh/proxy/socks4.rb +2 -6
  59. data/lib/net/ssh/proxy/socks5.rb +14 -17
  60. data/lib/net/ssh/service/forward.rb +370 -317
  61. data/lib/net/ssh/test/channel.rb +145 -136
  62. data/lib/net/ssh/test/extensions.rb +131 -110
  63. data/lib/net/ssh/test/kex.rb +34 -32
  64. data/lib/net/ssh/test/local_packet.rb +46 -44
  65. data/lib/net/ssh/test/packet.rb +89 -70
  66. data/lib/net/ssh/test/remote_packet.rb +32 -30
  67. data/lib/net/ssh/test/script.rb +156 -142
  68. data/lib/net/ssh/test/socket.rb +49 -48
  69. data/lib/net/ssh/test.rb +82 -77
  70. data/lib/net/ssh/transport/algorithms.rb +441 -360
  71. data/lib/net/ssh/transport/cipher_factory.rb +96 -98
  72. data/lib/net/ssh/transport/constants.rb +32 -24
  73. data/lib/net/ssh/transport/ctr.rb +42 -22
  74. data/lib/net/ssh/transport/hmac/abstract.rb +81 -63
  75. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  76. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  77. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  78. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  79. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  80. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  81. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  82. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  83. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  84. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  85. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  86. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  87. data/lib/net/ssh/transport/hmac.rb +14 -12
  88. data/lib/net/ssh/transport/identity_cipher.rb +54 -52
  89. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  90. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  91. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  92. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  93. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  94. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  95. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
  96. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
  97. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  98. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  99. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  100. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  101. data/lib/net/ssh/transport/kex.rb +15 -12
  102. data/lib/net/ssh/transport/key_expander.rb +24 -20
  103. data/lib/net/ssh/transport/openssl.rb +161 -124
  104. data/lib/net/ssh/transport/packet_stream.rb +225 -185
  105. data/lib/net/ssh/transport/server_version.rb +55 -56
  106. data/lib/net/ssh/transport/session.rb +306 -255
  107. data/lib/net/ssh/transport/state.rb +178 -176
  108. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  109. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  110. data/lib/net/ssh/verifiers/always.rb +58 -0
  111. data/lib/net/ssh/verifiers/never.rb +19 -0
  112. data/lib/net/ssh/version.rb +55 -53
  113. data/lib/net/ssh.rb +110 -47
  114. data/net-ssh-public_cert.pem +18 -18
  115. data/net-ssh.gemspec +36 -205
  116. data/support/ssh_tunnel_bug.rb +5 -5
  117. data.tar.gz.sig +0 -0
  118. metadata +153 -118
  119. metadata.gz.sig +0 -0
  120. data/.travis.yml +0 -18
  121. data/README.rdoc +0 -182
  122. data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
  123. data/lib/net/ssh/authentication/agent/socket.rb +0 -178
  124. data/lib/net/ssh/ruby_compat.rb +0 -46
  125. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  126. data/lib/net/ssh/verifiers/null.rb +0 -12
  127. data/lib/net/ssh/verifiers/secure.rb +0 -52
  128. data/lib/net/ssh/verifiers/strict.rb +0 -24
  129. data/setup.rb +0 -1585
  130. data/support/arcfour_check.rb +0 -20
  131. data/test/README.txt +0 -18
  132. data/test/authentication/methods/common.rb +0 -28
  133. data/test/authentication/methods/test_abstract.rb +0 -51
  134. data/test/authentication/methods/test_hostbased.rb +0 -114
  135. data/test/authentication/methods/test_keyboard_interactive.rb +0 -121
  136. data/test/authentication/methods/test_none.rb +0 -41
  137. data/test/authentication/methods/test_password.rb +0 -95
  138. data/test/authentication/methods/test_publickey.rb +0 -148
  139. data/test/authentication/test_agent.rb +0 -232
  140. data/test/authentication/test_key_manager.rb +0 -240
  141. data/test/authentication/test_session.rb +0 -107
  142. data/test/common.rb +0 -125
  143. data/test/configs/auth_off +0 -5
  144. data/test/configs/auth_on +0 -4
  145. data/test/configs/empty +0 -0
  146. data/test/configs/eqsign +0 -3
  147. data/test/configs/exact_match +0 -8
  148. data/test/configs/host_plus +0 -10
  149. data/test/configs/multihost +0 -4
  150. data/test/configs/negative_match +0 -6
  151. data/test/configs/nohost +0 -19
  152. data/test/configs/numeric_host +0 -4
  153. data/test/configs/proxy_remote_user +0 -2
  154. data/test/configs/send_env +0 -2
  155. data/test/configs/substitutes +0 -8
  156. data/test/configs/wild_cards +0 -14
  157. data/test/connection/test_channel.rb +0 -487
  158. data/test/connection/test_session.rb +0 -564
  159. data/test/integration/README.txt +0 -17
  160. data/test/integration/Vagrantfile +0 -12
  161. data/test/integration/common.rb +0 -63
  162. data/test/integration/playbook.yml +0 -56
  163. data/test/integration/test_forward.rb +0 -637
  164. data/test/integration/test_id_rsa_keys.rb +0 -96
  165. data/test/integration/test_proxy.rb +0 -93
  166. data/test/known_hosts/github +0 -1
  167. data/test/known_hosts/github_hash +0 -1
  168. data/test/manual/test_pageant.rb +0 -37
  169. data/test/start/test_connection.rb +0 -53
  170. data/test/start/test_options.rb +0 -57
  171. data/test/start/test_transport.rb +0 -28
  172. data/test/start/test_user_nil.rb +0 -27
  173. data/test/test_all.rb +0 -12
  174. data/test/test_buffer.rb +0 -433
  175. data/test/test_buffered_io.rb +0 -63
  176. data/test/test_config.rb +0 -268
  177. data/test/test_key_factory.rb +0 -191
  178. data/test/test_known_hosts.rb +0 -66
  179. data/test/transport/hmac/test_md5.rb +0 -41
  180. data/test/transport/hmac/test_md5_96.rb +0 -27
  181. data/test/transport/hmac/test_none.rb +0 -34
  182. data/test/transport/hmac/test_ripemd160.rb +0 -36
  183. data/test/transport/hmac/test_sha1.rb +0 -36
  184. data/test/transport/hmac/test_sha1_96.rb +0 -27
  185. data/test/transport/hmac/test_sha2_256.rb +0 -37
  186. data/test/transport/hmac/test_sha2_256_96.rb +0 -27
  187. data/test/transport/hmac/test_sha2_512.rb +0 -37
  188. data/test/transport/hmac/test_sha2_512_96.rb +0 -27
  189. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
  190. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -150
  191. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -96
  192. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -19
  193. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
  194. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
  195. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
  196. data/test/transport/test_algorithms.rb +0 -328
  197. data/test/transport/test_cipher_factory.rb +0 -443
  198. data/test/transport/test_hmac.rb +0 -34
  199. data/test/transport/test_identity_cipher.rb +0 -40
  200. data/test/transport/test_packet_stream.rb +0 -1762
  201. data/test/transport/test_server_version.rb +0 -74
  202. data/test/transport/test_session.rb +0 -331
  203. data/test/transport/test_state.rb +0 -181
  204. data/test/verifiers/test_secure.rb +0 -40
@@ -0,0 +1,183 @@
1
+ require 'securerandom'
2
+
3
+ module Net
4
+ module SSH
5
+ module Authentication
6
+ # Class for representing an SSH certificate.
7
+ #
8
+ # http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.10&content-type=text/plain
9
+ class Certificate
10
+ attr_accessor :nonce
11
+ attr_accessor :key
12
+ attr_accessor :serial
13
+ attr_accessor :type
14
+ attr_accessor :key_id
15
+ attr_accessor :valid_principals
16
+ attr_accessor :valid_after
17
+ attr_accessor :valid_before
18
+ attr_accessor :critical_options
19
+ attr_accessor :extensions
20
+ attr_accessor :reserved
21
+ attr_accessor :signature_key
22
+ attr_accessor :signature
23
+
24
+ # Read a certificate blob associated with a key of the given type.
25
+ def self.read_certblob(buffer, type)
26
+ cert = Certificate.new
27
+ cert.nonce = buffer.read_string
28
+ cert.key = buffer.read_keyblob(type)
29
+ cert.serial = buffer.read_int64
30
+ cert.type = type_symbol(buffer.read_long)
31
+ cert.key_id = buffer.read_string
32
+ cert.valid_principals = buffer.read_buffer.read_all(&:read_string)
33
+ cert.valid_after = Time.at(buffer.read_int64)
34
+
35
+ cert.valid_before = if RUBY_PLATFORM == "java"
36
+ # 0x20c49ba5e353f7 = 0x7fffffffffffffff/1000, the largest value possible for JRuby
37
+ # JRuby Time.at multiplies the arg by 1000, and then stores it in a signed long.
38
+ # 0x20c49ba2d52500 = 292278993-01-01 00:00:00 +0000
39
+ # JRuby 9.1 does not accept the year 292278994 because of edge cases (https://github.com/JodaOrg/joda-time/issues/190)
40
+ Time.at([0x20c49ba2d52500, buffer.read_int64].min)
41
+ else
42
+ Time.at(buffer.read_int64)
43
+ end
44
+
45
+ cert.critical_options = read_options(buffer)
46
+ cert.extensions = read_options(buffer)
47
+ cert.reserved = buffer.read_string
48
+ cert.signature_key = buffer.read_buffer.read_key
49
+ cert.signature = buffer.read_string
50
+ cert
51
+ end
52
+
53
+ def ssh_type
54
+ key.ssh_type + "-cert-v01@openssh.com"
55
+ end
56
+
57
+ def ssh_signature_type
58
+ key.ssh_type
59
+ end
60
+
61
+ # Serializes the certificate (and key).
62
+ def to_blob
63
+ Buffer.from(
64
+ :raw, to_blob_without_signature,
65
+ :string, signature
66
+ ).to_s
67
+ end
68
+
69
+ def ssh_do_sign(data, sig_alg = nil)
70
+ key.ssh_do_sign(data, sig_alg)
71
+ end
72
+
73
+ def ssh_do_verify(sig, data, options = {})
74
+ key.ssh_do_verify(sig, data, options)
75
+ end
76
+
77
+ def to_pem
78
+ key.to_pem
79
+ end
80
+
81
+ def fingerprint
82
+ key.fingerprint
83
+ end
84
+
85
+ # Signs the certificate with key.
86
+ def sign!(key, sign_nonce = nil)
87
+ # ssh-keygen uses 32 bytes of nonce.
88
+ self.nonce = sign_nonce || SecureRandom.random_bytes(32)
89
+ self.signature_key = key
90
+ self.signature = Net::SSH::Buffer.from(
91
+ :string, key.ssh_signature_type,
92
+ :mstring, key.ssh_do_sign(to_blob_without_signature)
93
+ ).to_s
94
+ self
95
+ end
96
+
97
+ def sign(key, sign_nonce = nil)
98
+ cert = clone
99
+ cert.sign!(key, sign_nonce)
100
+ end
101
+
102
+ # Checks whether the certificate's signature was signed by signature key.
103
+ def signature_valid?
104
+ buffer = Buffer.new(signature)
105
+ sig_format = buffer.read_string
106
+ signature_key.ssh_do_verify(buffer.read_string, to_blob_without_signature, host_key: sig_format)
107
+ end
108
+
109
+ def self.read_options(buffer)
110
+ names = []
111
+ options = buffer.read_buffer.read_all do |b|
112
+ name = b.read_string
113
+ names << name
114
+ data = b.read_string
115
+ data = Buffer.new(data).read_string unless data.empty?
116
+ [name, data]
117
+ end
118
+
119
+ raise ArgumentError, "option/extension names must be in sorted order" if names.sort != names
120
+
121
+ Hash[options]
122
+ end
123
+ private_class_method :read_options
124
+
125
+ def self.type_symbol(type)
126
+ types = { 1 => :user, 2 => :host }
127
+ raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
128
+
129
+ types.fetch(type)
130
+ end
131
+ private_class_method :type_symbol
132
+
133
+ private
134
+
135
+ def type_value(type)
136
+ types = { user: 1, host: 2 }
137
+ raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
138
+
139
+ types.fetch(type)
140
+ end
141
+
142
+ def ssh_time(t)
143
+ # Times in certificates are represented as a uint64.
144
+ [[t.to_i, 0].max, 2 << 64 - 1].min
145
+ end
146
+
147
+ def to_blob_without_signature
148
+ Buffer.from(
149
+ :string, ssh_type,
150
+ :string, nonce,
151
+ :raw, key_without_type,
152
+ :int64, serial,
153
+ :long, type_value(type),
154
+ :string, key_id,
155
+ :string, valid_principals.inject(Buffer.new) { |acc, elem| acc.write_string(elem) }.to_s,
156
+ :int64, ssh_time(valid_after),
157
+ :int64, ssh_time(valid_before),
158
+ :string, options_to_blob(critical_options),
159
+ :string, options_to_blob(extensions),
160
+ :string, reserved,
161
+ :string, signature_key.to_blob
162
+ ).to_s
163
+ end
164
+
165
+ def key_without_type
166
+ # key.to_blob gives us e.g. "ssh-rsa,<key>" but we just want "<key>".
167
+ tmp = Buffer.new(key.to_blob)
168
+ tmp.read_string # skip the underlying key type
169
+ tmp.read
170
+ end
171
+
172
+ def options_to_blob(options)
173
+ options.keys.sort.inject(Buffer.new) do |b, name|
174
+ b.write_string(name)
175
+ data = options.fetch(name)
176
+ data = Buffer.from(:string, data).to_s unless data.empty?
177
+ b.write_string(data)
178
+ end.to_s
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -1,18 +1,20 @@
1
- module Net; module SSH; module Authentication
1
+ module Net
2
+ module SSH
3
+ module Authentication
4
+ # Describes the constants used by the Net::SSH::Authentication components
5
+ # of the Net::SSH library. Individual authentication method implemenations
6
+ # may define yet more constants that are specific to their implementation.
7
+ module Constants
8
+ USERAUTH_REQUEST = 50
9
+ USERAUTH_FAILURE = 51
10
+ USERAUTH_SUCCESS = 52
11
+ USERAUTH_BANNER = 53
2
12
 
3
- # Describes the constants used by the Net::SSH::Authentication components
4
- # of the Net::SSH library. Individual authentication method implemenations
5
- # may define yet more constants that are specific to their implementation.
6
- module Constants
7
- USERAUTH_REQUEST = 50
8
- USERAUTH_FAILURE = 51
9
- USERAUTH_SUCCESS = 52
10
- USERAUTH_BANNER = 53
13
+ USERAUTH_PASSWD_CHANGEREQ = 60
14
+ USERAUTH_PK_OK = 60
11
15
 
12
- USERAUTH_PASSWD_CHANGEREQ = 60
13
- USERAUTH_PK_OK = 60
14
-
15
- USERAUTH_METHOD_RANGE = 60..79
16
+ USERAUTH_METHOD_RANGE = 60..79
17
+ end
18
+ end
16
19
  end
17
-
18
- end; end; end
20
+ end
@@ -0,0 +1,186 @@
1
+ gem 'ed25519', '~> 1.2'
2
+ gem 'bcrypt_pbkdf', '~> 1.0' unless RUBY_PLATFORM == "java"
3
+
4
+ require 'ed25519'
5
+
6
+ require 'base64'
7
+
8
+ require 'net/ssh/transport/cipher_factory'
9
+ require 'net/ssh/authentication/pub_key_fingerprint'
10
+ require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java"
11
+
12
+ module Net
13
+ module SSH
14
+ module Authentication
15
+ module ED25519
16
+ class SigningKeyFromFile < SimpleDelegator
17
+ def initialize(pk, sk)
18
+ key = ::Ed25519::SigningKey.from_keypair(sk)
19
+ raise ArgumentError, "pk does not match sk" unless pk == key.verify_key.to_bytes
20
+
21
+ super(key)
22
+ end
23
+ end
24
+
25
+ class OpenSSHPrivateKeyLoader
26
+ CipherFactory = Net::SSH::Transport::CipherFactory
27
+
28
+ MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
29
+ MEND = "-----END OPENSSH PRIVATE KEY-----"
30
+ MAGIC = "openssh-key-v1"
31
+
32
+ class DecryptError < ArgumentError
33
+ def initialize(message, encrypted_key: false)
34
+ super(message)
35
+ @encrypted_key = encrypted_key
36
+ end
37
+
38
+ def encrypted_key?
39
+ return @encrypted_key
40
+ end
41
+ end
42
+
43
+ def self.read(datafull, password)
44
+ datafull = datafull.strip
45
+ raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
46
+ raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND)
47
+
48
+ datab64 = datafull[MBEGIN.size...-MEND.size]
49
+ data = Base64.decode64(datab64)
50
+ raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)
51
+
52
+ buffer = Net::SSH::Buffer.new(data[MAGIC.size + 1..-1])
53
+
54
+ ciphername = buffer.read_string
55
+ raise ArgumentError.new("#{ciphername} in private key is not supported") unless
56
+ CipherFactory.supported?(ciphername)
57
+
58
+ kdfname = buffer.read_string
59
+ raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w[none bcrypt].include?(kdfname)
60
+
61
+ kdfopts = Net::SSH::Buffer.new(buffer.read_string)
62
+ num_keys = buffer.read_long
63
+ raise ArgumentError.new("Only 1 key is supported in ssh keys #{num_keys} was in private key") unless num_keys == 1
64
+
65
+ _pubkey = buffer.read_string
66
+
67
+ len = buffer.read_long
68
+
69
+ keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
70
+ raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
71
+ ((len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0))
72
+
73
+ if kdfname == 'bcrypt'
74
+ salt = kdfopts.read_string
75
+ rounds = kdfopts.read_long
76
+
77
+ raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
78
+
79
+ key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
80
+ raise DecryptError.new("BCyryptPbkdf failed", encrypted_key: true) unless key
81
+ else
82
+ key = '\x00' * (keylen + ivlen)
83
+ end
84
+
85
+ cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv: key[keylen...keylen + ivlen], decrypt: true)
86
+
87
+ decoded = cipher.update(buffer.remainder_as_buffer.to_s)
88
+ decoded << cipher.final
89
+
90
+ decoded = Net::SSH::Buffer.new(decoded)
91
+ check1 = decoded.read_long
92
+ check2 = decoded.read_long
93
+
94
+ raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2)
95
+
96
+ type_name = decoded.read_string
97
+ case type_name
98
+ when "ssh-ed25519"
99
+ PrivKey.new(decoded)
100
+ else
101
+ decoded.read_private_keyblob(type_name)
102
+ end
103
+ end
104
+ end
105
+
106
+ class PubKey
107
+ include Net::SSH::Authentication::PubKeyFingerprint
108
+
109
+ attr_reader :verify_key
110
+
111
+ def initialize(data)
112
+ @verify_key = ::Ed25519::VerifyKey.new(data)
113
+ end
114
+
115
+ def self.read_keyblob(buffer)
116
+ PubKey.new(buffer.read_string)
117
+ end
118
+
119
+ def to_blob
120
+ Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s
121
+ end
122
+
123
+ def ssh_type
124
+ "ssh-ed25519"
125
+ end
126
+
127
+ def ssh_signature_type
128
+ ssh_type
129
+ end
130
+
131
+ def ssh_do_verify(sig, data, options = {})
132
+ @verify_key.verify(sig, data)
133
+ end
134
+
135
+ def to_pem
136
+ # TODO this is not pem
137
+ ssh_type + Base64.encode64(@verify_key.to_bytes)
138
+ end
139
+ end
140
+
141
+ class PrivKey
142
+ CipherFactory = Net::SSH::Transport::CipherFactory
143
+
144
+ MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
145
+ MEND = "-----END OPENSSH PRIVATE KEY-----\n"
146
+ MAGIC = "openssh-key-v1"
147
+
148
+ attr_reader :sign_key
149
+
150
+ def initialize(buffer)
151
+ pk = buffer.read_string
152
+ sk = buffer.read_string
153
+ _comment = buffer.read_string
154
+
155
+ @pk = pk
156
+ @sign_key = SigningKeyFromFile.new(pk, sk)
157
+ end
158
+
159
+ def to_blob
160
+ public_key.to_blob
161
+ end
162
+
163
+ def ssh_type
164
+ "ssh-ed25519"
165
+ end
166
+
167
+ def ssh_signature_type
168
+ ssh_type
169
+ end
170
+
171
+ def public_key
172
+ PubKey.new(@pk)
173
+ end
174
+
175
+ def ssh_do_sign(data, sig_alg = nil)
176
+ @sign_key.sign(data)
177
+ end
178
+
179
+ def self.read(data, password)
180
+ OpenSSHPrivateKeyLoader.read(data, password)
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,31 @@
1
+ module Net
2
+ module SSH
3
+ module Authentication
4
+ # Loads ED25519 support which requires optinal dependecies like
5
+ # ed25519, bcrypt_pbkdf
6
+ module ED25519Loader
7
+ begin
8
+ require 'net/ssh/authentication/ed25519'
9
+ LOADED = true
10
+ ERROR = nil
11
+ rescue LoadError => e
12
+ ERROR = e
13
+ LOADED = false
14
+ end
15
+
16
+ def self.raiseUnlessLoaded(message)
17
+ description = ERROR.is_a?(LoadError) ? dependenciesRequiredForED25519 : ''
18
+ description << "#{ERROR.class} : \"#{ERROR.message}\"\n" if ERROR
19
+ raise NotImplementedError, "#{message}\n#{description}" unless LOADED
20
+ end
21
+
22
+ def self.dependenciesRequiredForED25519
23
+ result = "net-ssh requires the following gems for ed25519 support:\n"
24
+ result << " * ed25519 (>= 1.2, < 2.0)\n"
25
+ result << " * bcrypt_pbkdf (>= 1.0, < 2.0)\n" unless RUBY_PLATFORM == "java"
26
+ result << "See https://github.com/net-ssh/net-ssh/issues/565 for more information\n"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,7 +6,6 @@ require 'net/ssh/authentication/agent'
6
6
  module Net
7
7
  module SSH
8
8
  module Authentication
9
-
10
9
  # A trivial exception class used to report errors in the key manager.
11
10
  class KeyManagerError < Net::SSH::Exception; end
12
11
 
@@ -30,6 +29,9 @@ module Net
30
29
  # The list of user key data that will be examined
31
30
  attr_reader :key_data
32
31
 
32
+ # The list of user key certificate files that will be examined
33
+ attr_reader :keycert_files
34
+
33
35
  # The map of loaded identities
34
36
  attr_reader :known_identities
35
37
 
@@ -39,11 +41,12 @@ module Net
39
41
  # Create a new KeyManager. By default, the manager will
40
42
  # use the ssh-agent if it is running and the `:use_agent` option
41
43
  # is not false.
42
- def initialize(logger, options={})
44
+ def initialize(logger, options = {})
43
45
  self.logger = logger
44
46
  @key_files = []
45
47
  @key_data = []
46
- @use_agent = !(options[:use_agent] == false)
48
+ @keycert_files = []
49
+ @use_agent = options[:use_agent] != false
47
50
  @known_identities = {}
48
51
  @agent = nil
49
52
  @options = options
@@ -66,6 +69,12 @@ module Net
66
69
  self
67
70
  end
68
71
 
72
+ # Add the given keycert_file to the list of keycert files that will be used.
73
+ def add_keycert(keycert_file)
74
+ keycert_files.push(File.expand_path(keycert_file)).uniq!
75
+ self
76
+ end
77
+
69
78
  # Add the given key_file to the list of keys that will be used.
70
79
  def add_key_data(key_data_)
71
80
  key_data.push(key_data_).uniq!
@@ -108,7 +117,7 @@ module Net
108
117
  user_identities.delete(corresponding_user_identity) if corresponding_user_identity
109
118
 
110
119
  if !options[:keys_only] || corresponding_user_identity
111
- known_identities[key] = { :from => :agent }
120
+ known_identities[key] = { from: :agent, identity: key }
112
121
  yield key
113
122
  end
114
123
  end
@@ -122,6 +131,21 @@ module Net
122
131
  yield key
123
132
  end
124
133
 
134
+ known_identity_blobs = known_identities.keys.map(&:to_blob)
135
+ keycert_files.each do |keycert_file|
136
+ keycert = KeyFactory.load_public_key(keycert_file)
137
+ next if known_identity_blobs.include?(keycert.to_blob)
138
+
139
+ (_, corresponding_identity) = known_identities.detect { |public_key, _|
140
+ public_key.to_pem == keycert.to_pem
141
+ }
142
+
143
+ if corresponding_identity
144
+ known_identities[keycert] = corresponding_identity
145
+ yield keycert
146
+ end
147
+ end
148
+
125
149
  self
126
150
  end
127
151
 
@@ -134,25 +158,39 @@ module Net
134
158
  # Regardless of the identity's origin or who does the signing, this
135
159
  # will always return the signature in an SSH2-specified "signature
136
160
  # blob" format.
137
- def sign(identity, data)
161
+ def sign(identity, data, sig_alg = nil)
138
162
  info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager"
139
163
 
140
164
  if info[:key].nil? && info[:from] == :file
141
165
  begin
142
- info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive])
143
- rescue Exception, OpenSSL::OpenSSLError => e
166
+ info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive], options[:password_prompt])
167
+ rescue OpenSSL::OpenSSLError, Exception => e
144
168
  raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
145
169
  end
146
170
  end
147
171
 
148
172
  if info[:key]
149
- return Net::SSH::Buffer.from(:string, identity.ssh_type,
150
- :string, info[:key].ssh_do_sign(data.to_s)).to_s
173
+ if sig_alg.nil?
174
+ signed = info[:key].ssh_do_sign(data.to_s)
175
+ sig_alg = identity.ssh_signature_type
176
+ else
177
+ signed = info[:key].ssh_do_sign(data.to_s, sig_alg)
178
+ end
179
+ return Net::SSH::Buffer.from(:string, sig_alg,
180
+ :mstring, signed).to_s
151
181
  end
152
182
 
153
183
  if info[:from] == :agent
154
184
  raise KeyManagerError, "the agent is no longer available" unless agent
155
- return agent.sign(identity, data.to_s)
185
+
186
+ case sig_alg
187
+ when "rsa-sha2-512"
188
+ return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_512)
189
+ when "rsa-sha2-256"
190
+ return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_256)
191
+ else
192
+ return agent.sign(info[:identity], data.to_s)
193
+ end
156
194
  end
157
195
 
158
196
  raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
@@ -176,12 +214,17 @@ module Net
176
214
  # or if the agent is otherwise not available.
177
215
  def agent
178
216
  return unless use_agent?
179
- @agent ||= Agent.connect(logger, options[:agent_socket_factory])
217
+
218
+ @agent ||= Agent.connect(logger, options[:agent_socket_factory], options[:identity_agent])
180
219
  rescue AgentNotAvailable
181
220
  @use_agent = false
182
221
  nil
183
222
  end
184
223
 
224
+ def no_keys?
225
+ key_files.empty? && key_data.empty?
226
+ end
227
+
185
228
  private
186
229
 
187
230
  # Prepares identities from user key_files for loading, preserving their order and sources.
@@ -189,8 +232,12 @@ module Net
189
232
  key_files.map do |file|
190
233
  if readable_file?(file)
191
234
  identity = {}
235
+ cert_file = file + "-cert.pub"
192
236
  public_key_file = file + ".pub"
193
- if readable_file?(public_key_file)
237
+ if readable_file?(cert_file)
238
+ identity[:load_from] = :pubkey_file
239
+ identity[:pubkey_file] = cert_file
240
+ elsif readable_file?(public_key_file)
194
241
  identity[:load_from] = :pubkey_file
195
242
  identity[:pubkey_file] = public_key_file
196
243
  else
@@ -208,41 +255,42 @@ module Net
208
255
  # Prepared identities from user key_data, preserving their order and sources.
209
256
  def prepare_identities_from_data
210
257
  key_data.map do |data|
211
- { :load_from => :data, :data => data }
258
+ { load_from: :data, data: data }
212
259
  end
213
260
  end
214
261
 
215
262
  # Load prepared identities. Private key decryption errors ignored if ignore_decryption_errors
216
263
  def load_identities(identities, ask_passphrase, ignore_decryption_errors)
217
264
  identities.map do |identity|
218
- begin
219
- case identity[:load_from]
220
- when :pubkey_file
221
- key = KeyFactory.load_public_key(identity[:pubkey_file])
222
- { :public_key => key, :from => :file, :file => identity[:privkey_file] }
223
- when :privkey_file
224
- private_key = KeyFactory.load_private_key(identity[:privkey_file], options[:passphrase], ask_passphrase)
225
- key = private_key.send(:public_key)
226
- { :public_key => key, :from => :file, :file => identity[:privkey_file], :key => private_key }
227
- when :data
228
- private_key = KeyFactory.load_data_private_key(identity[:data], options[:passphrase], ask_passphrase)
229
- key = private_key.send(:public_key)
230
- { :public_key => key, :from => :key_data, :data => identity[:data], :key => private_key }
231
- else
232
- identity
233
- end
234
-
235
- rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, ArgumentError => e
236
- if ignore_decryption_errors
237
- identity
238
- else
239
- process_identity_loading_error(identity, e)
240
- nil
241
- end
242
- rescue Exception => e
265
+ case identity[:load_from]
266
+ when :pubkey_file
267
+ key = KeyFactory.load_public_key(identity[:pubkey_file])
268
+ { public_key: key, from: :file, file: identity[:privkey_file] }
269
+ when :privkey_file
270
+ private_key = KeyFactory.load_private_key(
271
+ identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt]
272
+ )
273
+ key = private_key.send(:public_key)
274
+ { public_key: key, from: :file, file: identity[:privkey_file], key: private_key }
275
+ when :data
276
+ private_key = KeyFactory.load_data_private_key(
277
+ identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt]
278
+ )
279
+ key = private_key.send(:public_key)
280
+ { public_key: key, from: :key_data, data: identity[:data], key: private_key }
281
+ else
282
+ identity
283
+ end
284
+ rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e
285
+ if ignore_decryption_errors
286
+ identity
287
+ else
243
288
  process_identity_loading_error(identity, e)
244
289
  nil
245
290
  end
291
+ rescue Exception => e
292
+ process_identity_loading_error(identity, e)
293
+ nil
246
294
  end.compact
247
295
  end
248
296
 
@@ -256,7 +304,6 @@ module Net
256
304
  raise e
257
305
  end
258
306
  end
259
-
260
307
  end
261
308
  end
262
309
  end