net-ssh 4.2.0 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
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