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
@@ -1,160 +1,185 @@
1
- gem 'rbnacl', '>= 3.2.0', '< 5.0'
1
+ gem 'ed25519', '~> 1.2'
2
2
  gem 'bcrypt_pbkdf', '~> 1.0' unless RUBY_PLATFORM == "java"
3
3
 
4
- begin
5
- require 'rbnacl/libsodium'
6
- rescue LoadError # rubocop:disable Lint/HandleExceptions
7
- end
8
-
9
- require 'rbnacl'
10
- require 'rbnacl/signatures/ed25519/verify_key'
11
- require 'rbnacl/signatures/ed25519/signing_key'
12
-
13
- require 'rbnacl/hash'
4
+ require 'ed25519'
14
5
 
15
6
  require 'base64'
16
7
 
17
8
  require 'net/ssh/transport/cipher_factory'
9
+ require 'net/ssh/authentication/pub_key_fingerprint'
18
10
  require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java"
19
11
 
20
- module Net; module SSH; module Authentication
21
- module ED25519
22
- class SigningKeyFromFile < RbNaCl::Signatures::Ed25519::SigningKey
23
- def initialize(pk,sk)
24
- @signing_key = sk
25
- @verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(pk)
26
- end
27
- end
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
28
20
 
29
- class PubKey
30
- attr_reader :verify_key
21
+ super(key)
22
+ end
23
+ end
31
24
 
32
- def initialize(data)
33
- @verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(data)
34
- end
25
+ class OpenSSHPrivateKeyLoader
26
+ CipherFactory = Net::SSH::Transport::CipherFactory
35
27
 
36
- def self.read_keyblob(buffer)
37
- PubKey.new(buffer.read_string)
38
- end
28
+ MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
29
+ MEND = "-----END OPENSSH PRIVATE KEY-----"
30
+ MAGIC = "openssh-key-v1"
39
31
 
40
- def to_blob
41
- Net::SSH::Buffer.from(:mstring,"ssh-ed25519",:string,@verify_key.to_bytes).to_s
42
- end
32
+ class DecryptError < ArgumentError
33
+ def initialize(message, encrypted_key: false)
34
+ super(message)
35
+ @encrypted_key = encrypted_key
36
+ end
43
37
 
44
- def ssh_type
45
- "ssh-ed25519"
46
- end
38
+ def encrypted_key?
39
+ return @encrypted_key
40
+ end
41
+ end
47
42
 
48
- def ssh_signature_type
49
- ssh_type
50
- end
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)
51
47
 
52
- def ssh_do_verify(sig,data)
53
- @verify_key.verify(sig,data)
54
- end
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)
55
51
 
56
- def to_pem
57
- # TODO this is not pem
58
- ssh_type + Base64.encode64(@verify_key.to_bytes)
59
- end
52
+ buffer = Net::SSH::Buffer.new(data[MAGIC.size + 1..-1])
60
53
 
61
- def fingerprint
62
- @fingerprint ||= OpenSSL::Digest::MD5.hexdigest(to_blob).scan(/../).join(":")
63
- end
64
- end
54
+ ciphername = buffer.read_string
55
+ raise ArgumentError.new("#{ciphername} in private key is not supported") unless
56
+ CipherFactory.supported?(ciphername)
65
57
 
66
- class PrivKey
67
- CipherFactory = Net::SSH::Transport::CipherFactory
58
+ kdfname = buffer.read_string
59
+ raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w[none bcrypt].include?(kdfname)
68
60
 
69
- MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
70
- MEND = "-----END OPENSSH PRIVATE KEY-----\n"
71
- MAGIC = "openssh-key-v1"
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
72
64
 
73
- attr_reader :sign_key
65
+ _pubkey = buffer.read_string
74
66
 
75
- def initialize(datafull,password)
76
- raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
77
- raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND)
78
- datab64 = datafull[MBEGIN.size ... -MEND.size]
79
- data = Base64.decode64(datab64)
80
- raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)
81
- buffer = Net::SSH::Buffer.new(data[MAGIC.size+1 .. -1])
67
+ len = buffer.read_long
82
68
 
83
- ciphername = buffer.read_string
84
- raise ArgumentError.new("#{ciphername} in private key is not supported") unless
85
- CipherFactory.supported?(ciphername)
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))
86
72
 
87
- kdfname = buffer.read_string
88
- raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w(none bcrypt).include?(kdfname)
73
+ if kdfname == 'bcrypt'
74
+ salt = kdfopts.read_string
75
+ rounds = kdfopts.read_long
89
76
 
90
- kdfopts = Net::SSH::Buffer.new(buffer.read_string)
91
- num_keys = buffer.read_long
92
- raise ArgumentError.new("Only 1 key is supported in ssh keys #{num_keys} was in private key") unless num_keys == 1
93
- _pubkey = buffer.read_string
77
+ raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
94
78
 
95
- len = buffer.read_long
79
+ key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
80
+ else
81
+ key = '\x00' * (keylen + ivlen)
82
+ end
96
83
 
97
- keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
98
- raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
99
- ((len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0))
84
+ cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv: key[keylen...keylen + ivlen], decrypt: true)
100
85
 
101
- if kdfname == 'bcrypt'
102
- salt = kdfopts.read_string
103
- rounds = kdfopts.read_long
86
+ decoded = cipher.update(buffer.remainder_as_buffer.to_s)
87
+ decoded << cipher.final
104
88
 
105
- raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
106
- key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
107
- else
108
- key = '\x00' * (keylen + ivlen)
109
- end
89
+ decoded = Net::SSH::Buffer.new(decoded)
90
+ check1 = decoded.read_long
91
+ check2 = decoded.read_long
110
92
 
111
- cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv:key[keylen...keylen+ivlen], decrypt: true)
93
+ raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2)
112
94
 
113
- decoded = cipher.update(buffer.remainder_as_buffer.to_s)
114
- decoded << cipher.final
95
+ type_name = decoded.read_string
96
+ case type_name
97
+ when "ssh-ed25519"
98
+ PrivKey.new(decoded)
99
+ else
100
+ decoded.read_private_keyblob(type_name)
101
+ end
102
+ end
103
+ end
115
104
 
116
- decoded = Net::SSH::Buffer.new(decoded)
117
- check1 = decoded.read_long
118
- check2 = decoded.read_long
105
+ class PubKey
106
+ include Net::SSH::Authentication::PubKeyFingerprint
119
107
 
120
- raise ArgumentError, "Decrypt failed on private key" if (check1 != check2)
108
+ attr_reader :verify_key
121
109
 
122
- _type_name = decoded.read_string
123
- pk = decoded.read_string
124
- sk = decoded.read_string
125
- _comment = decoded.read_string
110
+ def initialize(data)
111
+ @verify_key = ::Ed25519::VerifyKey.new(data)
112
+ end
126
113
 
127
- @pk = pk
128
- @sign_key = SigningKeyFromFile.new(pk,sk)
129
- end
114
+ def self.read_keyblob(buffer)
115
+ PubKey.new(buffer.read_string)
116
+ end
130
117
 
131
- def to_blob
132
- public_key.to_blob
133
- end
118
+ def to_blob
119
+ Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s
120
+ end
134
121
 
135
- def ssh_type
136
- "ssh-ed25519"
137
- end
122
+ def ssh_type
123
+ "ssh-ed25519"
124
+ end
138
125
 
139
- def ssh_signature_type
140
- ssh_type
141
- end
126
+ def ssh_signature_type
127
+ ssh_type
128
+ end
142
129
 
143
- def public_key
144
- PubKey.new(@pk)
145
- end
130
+ def ssh_do_verify(sig, data, options = {})
131
+ @verify_key.verify(sig, data)
132
+ end
146
133
 
147
- def ssh_do_sign(data)
148
- @sign_key.sign(data)
149
- end
134
+ def to_pem
135
+ # TODO this is not pem
136
+ ssh_type + Base64.encode64(@verify_key.to_bytes)
137
+ end
138
+ end
150
139
 
151
- def self.read(data,password)
152
- self.new(data,password)
153
- end
140
+ class PrivKey
141
+ CipherFactory = Net::SSH::Transport::CipherFactory
142
+
143
+ MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
144
+ MEND = "-----END OPENSSH PRIVATE KEY-----\n"
145
+ MAGIC = "openssh-key-v1"
146
+
147
+ attr_reader :sign_key
154
148
 
155
- def self.read_keyblob(buffer)
156
- ED25519::PubKey.read_keyblob(buffer)
149
+ def initialize(buffer)
150
+ pk = buffer.read_string
151
+ sk = buffer.read_string
152
+ _comment = buffer.read_string
153
+
154
+ @pk = pk
155
+ @sign_key = SigningKeyFromFile.new(pk, sk)
156
+ end
157
+
158
+ def to_blob
159
+ public_key.to_blob
160
+ end
161
+
162
+ def ssh_type
163
+ "ssh-ed25519"
164
+ end
165
+
166
+ def ssh_signature_type
167
+ ssh_type
168
+ end
169
+
170
+ def public_key
171
+ PubKey.new(@pk)
172
+ end
173
+
174
+ def ssh_do_sign(data, sig_alg = nil)
175
+ @sign_key.sign(data)
176
+ end
177
+
178
+ def self.read(data, password)
179
+ OpenSSHPrivateKeyLoader.read(data, password)
180
+ end
181
+ end
182
+ end
157
183
  end
158
184
  end
159
185
  end
160
- end; end; end
@@ -1,31 +1,31 @@
1
- module Net; module SSH; module Authentication
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
2
15
 
3
- # Loads ED25519 support which requires optinal dependecies like
4
- # rbnacl, bcrypt_pbkdf
5
- module ED25519Loader
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
6
21
 
7
- begin
8
- require 'net/ssh/authentication/ed25519'
9
- LOADED = true
10
- ERROR = nil
11
- rescue LoadError => e
12
- ERROR = e
13
- LOADED = false
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
14
31
  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 << " * rbnacl (>= 3.2, < 5.0)\n"
25
- result << " * rbnacl-libsodium, if your system doesn't have libsodium installed.\n"
26
- result << " * bcrypt_pbkdf (>= 1.0, < 2.0)\n" unless RUBY_PLATFORM == "java"
27
- result << "See https://github.com/net-ssh/net-ssh/issues/478 for more information\n"
28
- end
29
-
30
- end
31
- end; end; 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])
166
+ info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive], options[:password_prompt])
143
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_signature_type,
150
- :mstring, 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.
@@ -219,34 +262,35 @@ module Net
219
262
  # Load prepared identities. Private key decryption errors ignored if ignore_decryption_errors
220
263
  def load_identities(identities, ask_passphrase, ignore_decryption_errors)
221
264
  identities.map do |identity|
222
- begin
223
- case identity[:load_from]
224
- when :pubkey_file
225
- key = KeyFactory.load_public_key(identity[:pubkey_file])
226
- { public_key: key, from: :file, file: identity[:privkey_file] }
227
- when :privkey_file
228
- private_key = KeyFactory.load_private_key(identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt])
229
- key = private_key.send(:public_key)
230
- { public_key: key, from: :file, file: identity[:privkey_file], key: private_key }
231
- when :data
232
- private_key = KeyFactory.load_data_private_key(identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt])
233
- key = private_key.send(:public_key)
234
- { public_key: key, from: :key_data, data: identity[:data], key: private_key }
235
- else
236
- identity
237
- end
238
-
239
- rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e
240
- if ignore_decryption_errors
241
- identity
242
- else
243
- process_identity_loading_error(identity, e)
244
- nil
245
- end
246
- 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
247
288
  process_identity_loading_error(identity, e)
248
289
  nil
249
290
  end
291
+ rescue Exception => e
292
+ process_identity_loading_error(identity, e)
293
+ nil
250
294
  end.compact
251
295
  end
252
296
 
@@ -260,7 +304,6 @@ module Net
260
304
  raise e
261
305
  end
262
306
  end
263
-
264
307
  end
265
308
  end
266
309
  end