net-ssh 4.1.0 → 6.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 (111) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +5 -0
  5. data/.rubocop.yml +8 -2
  6. data/.rubocop_todo.yml +405 -552
  7. data/.travis.yml +23 -22
  8. data/CHANGES.txt +112 -1
  9. data/Gemfile +1 -7
  10. data/{Gemfile.norbnacl → Gemfile.noed25519} +1 -1
  11. data/Manifest +4 -5
  12. data/README.md +287 -0
  13. data/Rakefile +40 -29
  14. data/appveyor.yml +12 -6
  15. data/lib/net/ssh.rb +68 -32
  16. data/lib/net/ssh/authentication/agent.rb +234 -222
  17. data/lib/net/ssh/authentication/certificate.rb +175 -164
  18. data/lib/net/ssh/authentication/constants.rb +17 -14
  19. data/lib/net/ssh/authentication/ed25519.rb +162 -141
  20. data/lib/net/ssh/authentication/ed25519_loader.rb +32 -29
  21. data/lib/net/ssh/authentication/key_manager.rb +40 -9
  22. data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
  23. data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
  24. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +1 -1
  25. data/lib/net/ssh/authentication/methods/none.rb +10 -10
  26. data/lib/net/ssh/authentication/methods/password.rb +13 -13
  27. data/lib/net/ssh/authentication/methods/publickey.rb +56 -55
  28. data/lib/net/ssh/authentication/pageant.rb +468 -465
  29. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  30. data/lib/net/ssh/authentication/session.rb +130 -122
  31. data/lib/net/ssh/buffer.rb +345 -312
  32. data/lib/net/ssh/buffered_io.rb +163 -163
  33. data/lib/net/ssh/config.rb +316 -238
  34. data/lib/net/ssh/connection/channel.rb +670 -650
  35. data/lib/net/ssh/connection/constants.rb +30 -26
  36. data/lib/net/ssh/connection/event_loop.rb +108 -105
  37. data/lib/net/ssh/connection/keepalive.rb +54 -50
  38. data/lib/net/ssh/connection/session.rb +682 -671
  39. data/lib/net/ssh/connection/term.rb +180 -176
  40. data/lib/net/ssh/errors.rb +101 -99
  41. data/lib/net/ssh/key_factory.rb +195 -108
  42. data/lib/net/ssh/known_hosts.rb +161 -152
  43. data/lib/net/ssh/loggable.rb +57 -55
  44. data/lib/net/ssh/packet.rb +82 -78
  45. data/lib/net/ssh/prompt.rb +55 -53
  46. data/lib/net/ssh/proxy/command.rb +104 -89
  47. data/lib/net/ssh/proxy/errors.rb +12 -8
  48. data/lib/net/ssh/proxy/http.rb +93 -91
  49. data/lib/net/ssh/proxy/https.rb +42 -39
  50. data/lib/net/ssh/proxy/jump.rb +50 -47
  51. data/lib/net/ssh/proxy/socks4.rb +0 -2
  52. data/lib/net/ssh/proxy/socks5.rb +11 -12
  53. data/lib/net/ssh/service/forward.rb +370 -317
  54. data/lib/net/ssh/test.rb +83 -77
  55. data/lib/net/ssh/test/channel.rb +146 -142
  56. data/lib/net/ssh/test/extensions.rb +150 -146
  57. data/lib/net/ssh/test/kex.rb +35 -31
  58. data/lib/net/ssh/test/local_packet.rb +48 -44
  59. data/lib/net/ssh/test/packet.rb +87 -84
  60. data/lib/net/ssh/test/remote_packet.rb +35 -31
  61. data/lib/net/ssh/test/script.rb +173 -171
  62. data/lib/net/ssh/test/socket.rb +59 -55
  63. data/lib/net/ssh/transport/algorithms.rb +430 -364
  64. data/lib/net/ssh/transport/cipher_factory.rb +95 -91
  65. data/lib/net/ssh/transport/constants.rb +33 -25
  66. data/lib/net/ssh/transport/ctr.rb +33 -11
  67. data/lib/net/ssh/transport/hmac.rb +15 -13
  68. data/lib/net/ssh/transport/hmac/abstract.rb +82 -63
  69. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  70. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  71. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  72. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  73. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  74. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  75. data/lib/net/ssh/transport/identity_cipher.rb +55 -51
  76. data/lib/net/ssh/transport/kex.rb +14 -13
  77. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  78. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  79. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  80. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  81. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  82. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
  83. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -62
  84. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  85. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  86. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  87. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  88. data/lib/net/ssh/transport/key_expander.rb +29 -25
  89. data/lib/net/ssh/transport/openssl.rb +116 -116
  90. data/lib/net/ssh/transport/packet_stream.rb +223 -190
  91. data/lib/net/ssh/transport/server_version.rb +64 -66
  92. data/lib/net/ssh/transport/session.rb +306 -257
  93. data/lib/net/ssh/transport/state.rb +198 -196
  94. data/lib/net/ssh/verifiers/accept_new.rb +35 -0
  95. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +34 -0
  96. data/lib/net/ssh/verifiers/always.rb +56 -0
  97. data/lib/net/ssh/verifiers/never.rb +21 -0
  98. data/lib/net/ssh/version.rb +55 -53
  99. data/net-ssh-public_cert.pem +18 -19
  100. data/net-ssh.gemspec +12 -11
  101. data/support/ssh_tunnel_bug.rb +2 -2
  102. metadata +86 -75
  103. metadata.gz.sig +0 -0
  104. data/Gemfile.norbnacl.lock +0 -41
  105. data/README.rdoc +0 -169
  106. data/lib/net/ssh/ruby_compat.rb +0 -24
  107. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  108. data/lib/net/ssh/verifiers/null.rb +0 -12
  109. data/lib/net/ssh/verifiers/secure.rb +0 -52
  110. data/lib/net/ssh/verifiers/strict.rb +0 -24
  111. data/support/arcfour_check.rb +0 -20
@@ -1,169 +1,180 @@
1
1
  require 'securerandom'
2
2
 
3
- module Net; module SSH; module Authentication
4
- # Class for representing an SSH certificate.
5
- #
6
- # http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.10&content-type=text/plain
7
- class Certificate
8
- attr_accessor :nonce
9
- attr_accessor :key
10
- attr_accessor :serial
11
- attr_accessor :type
12
- attr_accessor :key_id
13
- attr_accessor :valid_principals
14
- attr_accessor :valid_after
15
- attr_accessor :valid_before
16
- attr_accessor :critical_options
17
- attr_accessor :extensions
18
- attr_accessor :reserved
19
- attr_accessor :signature_key
20
- attr_accessor :signature
21
-
22
- # Read a certificate blob associated with a key of the given type.
23
- def self.read_certblob(buffer, type)
24
- cert = Certificate.new
25
- cert.nonce = buffer.read_string
26
- cert.key = buffer.read_keyblob(type)
27
- cert.serial = buffer.read_int64
28
- cert.type = type_symbol(buffer.read_long)
29
- cert.key_id = buffer.read_string
30
- cert.valid_principals = buffer.read_buffer.read_all(&:read_string)
31
- cert.valid_after = Time.at(buffer.read_int64)
32
- cert.valid_before = Time.at(buffer.read_int64)
33
- cert.critical_options = read_options(buffer)
34
- cert.extensions = read_options(buffer)
35
- cert.reserved = buffer.read_string
36
- cert.signature_key = buffer.read_buffer.read_key
37
- cert.signature = buffer.read_string
38
- cert
39
- end
40
-
41
- def ssh_type
42
- key.ssh_type + "-cert-v01@openssh.com"
43
- end
44
-
45
- def ssh_signature_type
46
- key.ssh_type
47
- end
48
-
49
- # Serializes the certificate (and key).
50
- def to_blob
51
- Buffer.from(
52
- :raw, to_blob_without_signature,
53
- :string, signature
54
- ).to_s
55
- end
56
-
57
- def ssh_do_sign(data)
58
- key.ssh_do_sign(data)
59
- end
60
-
61
- def ssh_do_verify(sig, data)
62
- key.ssh_do_verify(sig, data)
63
- end
64
-
65
- def to_pem
66
- key.to_pem
67
- end
68
-
69
- def fingerprint
70
- key.fingerprint
71
- end
72
-
73
- # Signs the certificate with key.
74
- def sign!(key, sign_nonce=nil)
75
- # ssh-keygen uses 32 bytes of nonce.
76
- self.nonce = sign_nonce || SecureRandom.random_bytes(32)
77
- self.signature_key = key
78
- self.signature = Net::SSH::Buffer.from(
79
- :string, key.ssh_signature_type,
80
- :mstring, key.ssh_do_sign(to_blob_without_signature)
81
- ).to_s
82
- self
83
- end
84
-
85
- def sign(key, sign_nonce=nil)
86
- cert = clone
87
- cert.sign!(key, sign_nonce)
88
- end
89
-
90
- # Checks whether the certificate's signature was signed by signature key.
91
- def signature_valid?
92
- buffer = Buffer.new(signature)
93
- buffer.read_string # skip signature format
94
- signature_key.ssh_do_verify(buffer.read_string, to_blob_without_signature)
95
- end
96
-
97
- def self.read_options(buffer)
98
- names = []
99
- options = buffer.read_buffer.read_all do |b|
100
- name = b.read_string
101
- names << name
102
- data = b.read_string
103
- data = Buffer.new(data).read_string unless data.empty?
104
- [name, data]
105
- end
106
-
107
- if names.sort != names
108
- raise ArgumentError, "option/extension names must be in sorted order"
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
+ # 0x20c49ba5e353f7 = 292278994-08-17 01:12:55 -0600
39
+ Time.at([0x20c49ba5e353f7, buffer.read_int64].min)
40
+ else
41
+ Time.at(buffer.read_int64)
42
+ end
43
+
44
+ cert.critical_options = read_options(buffer)
45
+ cert.extensions = read_options(buffer)
46
+ cert.reserved = buffer.read_string
47
+ cert.signature_key = buffer.read_buffer.read_key
48
+ cert.signature = buffer.read_string
49
+ cert
50
+ end
51
+
52
+ def ssh_type
53
+ key.ssh_type + "-cert-v01@openssh.com"
54
+ end
55
+
56
+ def ssh_signature_type
57
+ key.ssh_type
58
+ end
59
+
60
+ # Serializes the certificate (and key).
61
+ def to_blob
62
+ Buffer.from(
63
+ :raw, to_blob_without_signature,
64
+ :string, signature
65
+ ).to_s
66
+ end
67
+
68
+ def ssh_do_sign(data)
69
+ key.ssh_do_sign(data)
70
+ end
71
+
72
+ def ssh_do_verify(sig, data)
73
+ key.ssh_do_verify(sig, data)
74
+ end
75
+
76
+ def to_pem
77
+ key.to_pem
78
+ end
79
+
80
+ def fingerprint
81
+ key.fingerprint
82
+ end
83
+
84
+ # Signs the certificate with key.
85
+ def sign!(key, sign_nonce=nil)
86
+ # ssh-keygen uses 32 bytes of nonce.
87
+ self.nonce = sign_nonce || SecureRandom.random_bytes(32)
88
+ self.signature_key = key
89
+ self.signature = Net::SSH::Buffer.from(
90
+ :string, key.ssh_signature_type,
91
+ :mstring, key.ssh_do_sign(to_blob_without_signature)
92
+ ).to_s
93
+ self
94
+ end
95
+
96
+ def sign(key, sign_nonce=nil)
97
+ cert = clone
98
+ cert.sign!(key, sign_nonce)
99
+ end
100
+
101
+ # Checks whether the certificate's signature was signed by signature key.
102
+ def signature_valid?
103
+ buffer = Buffer.new(signature)
104
+ buffer.read_string # skip signature format
105
+ signature_key.ssh_do_verify(buffer.read_string, to_blob_without_signature)
106
+ end
107
+
108
+ def self.read_options(buffer)
109
+ names = []
110
+ options = buffer.read_buffer.read_all do |b|
111
+ name = b.read_string
112
+ names << name
113
+ data = b.read_string
114
+ data = Buffer.new(data).read_string unless data.empty?
115
+ [name, data]
116
+ end
117
+
118
+ raise ArgumentError, "option/extension names must be in sorted order" if names.sort != names
119
+
120
+ Hash[options]
121
+ end
122
+ private_class_method :read_options
123
+
124
+ def self.type_symbol(type)
125
+ types = { 1 => :user, 2 => :host }
126
+ raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
127
+ types.fetch(type)
128
+ end
129
+ private_class_method :type_symbol
130
+
131
+ private
132
+
133
+ def type_value(type)
134
+ types = { user: 1, host: 2 }
135
+ raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
136
+ types.fetch(type)
137
+ end
138
+
139
+ def ssh_time(t)
140
+ # Times in certificates are represented as a uint64.
141
+ [[t.to_i, 0].max, 2 << 64 - 1].min
142
+ end
143
+
144
+ def to_blob_without_signature
145
+ Buffer.from(
146
+ :string, ssh_type,
147
+ :string, nonce,
148
+ :raw, key_without_type,
149
+ :int64, serial,
150
+ :long, type_value(type),
151
+ :string, key_id,
152
+ :string, valid_principals.inject(Buffer.new) { |acc, elem| acc.write_string(elem) }.to_s,
153
+ :int64, ssh_time(valid_after),
154
+ :int64, ssh_time(valid_before),
155
+ :string, options_to_blob(critical_options),
156
+ :string, options_to_blob(extensions),
157
+ :string, reserved,
158
+ :string, signature_key.to_blob
159
+ ).to_s
160
+ end
161
+
162
+ def key_without_type
163
+ # key.to_blob gives us e.g. "ssh-rsa,<key>" but we just want "<key>".
164
+ tmp = Buffer.new(key.to_blob)
165
+ tmp.read_string # skip the underlying key type
166
+ tmp.read
167
+ end
168
+
169
+ def options_to_blob(options)
170
+ options.keys.sort.inject(Buffer.new) do |b, name|
171
+ b.write_string(name)
172
+ data = options.fetch(name)
173
+ data = Buffer.from(:string, data).to_s unless data.empty?
174
+ b.write_string(data)
175
+ end.to_s
176
+ end
109
177
  end
110
-
111
- Hash[options]
112
- end
113
- private_class_method :read_options
114
-
115
- def self.type_symbol(type)
116
- types = {1 => :user, 2 => :host}
117
- raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
118
- types.fetch(type)
119
- end
120
- private_class_method :type_symbol
121
-
122
- private
123
-
124
- def type_value(type)
125
- types = {user: 1, host: 2}
126
- raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
127
- types.fetch(type)
128
- end
129
-
130
- def ssh_time(t)
131
- # Times in certificates are represented as a uint64.
132
- [[t.to_i, 0].max, 2<<64 - 1].min
133
- end
134
-
135
- def to_blob_without_signature
136
- Buffer.from(
137
- :string, ssh_type,
138
- :string, nonce,
139
- :raw, key_without_type,
140
- :int64, serial,
141
- :long, type_value(type),
142
- :string, key_id,
143
- :string, valid_principals.inject(Buffer.new) { |acc, elem| acc.write_string(elem) }.to_s,
144
- :int64, ssh_time(valid_after),
145
- :int64, ssh_time(valid_before),
146
- :string, options_to_blob(critical_options),
147
- :string, options_to_blob(extensions),
148
- :string, reserved,
149
- :string, signature_key.to_blob
150
- ).to_s
151
- end
152
-
153
- def key_without_type
154
- # key.to_blob gives us e.g. "ssh-rsa,<key>" but we just want "<key>".
155
- tmp = Buffer.new(key.to_blob)
156
- tmp.read_string # skip the underlying key type
157
- tmp.read
158
- end
159
-
160
- def options_to_blob(options)
161
- options.keys.sort.inject(Buffer.new) do |b, name|
162
- b.write_string(name)
163
- data = options.fetch(name)
164
- data = Buffer.from(:string, data).to_s unless data.empty?
165
- b.write_string(data)
166
- end.to_s
167
178
  end
168
179
  end
169
- end; end; end
180
+ end
@@ -1,18 +1,21 @@
1
- module Net; module SSH; module Authentication
1
+ module Net
2
+ module SSH
3
+ module Authentication
2
4
 
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
5
+ # Describes the constants used by the Net::SSH::Authentication components
6
+ # of the Net::SSH library. Individual authentication method implemenations
7
+ # may define yet more constants that are specific to their implementation.
8
+ module Constants
9
+ USERAUTH_REQUEST = 50
10
+ USERAUTH_FAILURE = 51
11
+ USERAUTH_SUCCESS = 52
12
+ USERAUTH_BANNER = 53
11
13
 
12
- USERAUTH_PASSWD_CHANGEREQ = 60
13
- USERAUTH_PK_OK = 60
14
+ USERAUTH_PASSWD_CHANGEREQ = 60
15
+ USERAUTH_PK_OK = 60
14
16
 
15
- USERAUTH_METHOD_RANGE = 60..79
17
+ USERAUTH_METHOD_RANGE = 60..79
18
+ end
19
+ end
16
20
  end
17
-
18
- end; end; end
21
+ end
@@ -1,160 +1,181 @@
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
28
-
29
- class PubKey
30
- attr_reader :verify_key
31
-
32
- def initialize(data)
33
- @verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(data)
34
- end
35
-
36
- def self.read_keyblob(buffer)
37
- PubKey.new(buffer.read_string)
38
- end
39
-
40
- def to_blob
41
- Net::SSH::Buffer.from(:mstring,"ssh-ed25519",:string,@verify_key.to_bytes).to_s
42
- end
43
-
44
- def ssh_type
45
- "ssh-ed25519"
46
- end
47
-
48
- def ssh_signature_type
49
- ssh_type
50
- end
51
-
52
- def ssh_do_verify(sig,data)
53
- @verify_key.verify(sig,data)
54
- end
55
-
56
- def to_pem
57
- # TODO this is not pem
58
- ssh_type + Base64.encode64(@verify_key.to_bytes)
59
- end
60
-
61
- def fingerprint
62
- @fingerprint ||= OpenSSL::Digest::MD5.hexdigest(to_blob).scan(/../).join(":")
63
- end
64
- end
65
-
66
- class PrivKey
67
- CipherFactory = Net::SSH::Transport::CipherFactory
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
+ datab64 = datafull[MBEGIN.size...-MEND.size]
48
+ data = Base64.decode64(datab64)
49
+ raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)
50
+ buffer = Net::SSH::Buffer.new(data[MAGIC.size + 1..-1])
51
+
52
+ ciphername = buffer.read_string
53
+ raise ArgumentError.new("#{ciphername} in private key is not supported") unless
54
+ CipherFactory.supported?(ciphername)
55
+
56
+ kdfname = buffer.read_string
57
+ raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w[none bcrypt].include?(kdfname)
58
+
59
+ kdfopts = Net::SSH::Buffer.new(buffer.read_string)
60
+ num_keys = buffer.read_long
61
+ raise ArgumentError.new("Only 1 key is supported in ssh keys #{num_keys} was in private key") unless num_keys == 1
62
+ _pubkey = buffer.read_string
63
+
64
+ len = buffer.read_long
65
+
66
+ keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
67
+ raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
68
+ ((len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0))
69
+
70
+ if kdfname == 'bcrypt'
71
+ salt = kdfopts.read_string
72
+ rounds = kdfopts.read_long
73
+
74
+ raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
75
+ key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
76
+ else
77
+ key = '\x00' * (keylen + ivlen)
78
+ end
79
+
80
+ cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv:key[keylen...keylen + ivlen], decrypt: true)
81
+
82
+ decoded = cipher.update(buffer.remainder_as_buffer.to_s)
83
+ decoded << cipher.final
84
+
85
+ decoded = Net::SSH::Buffer.new(decoded)
86
+ check1 = decoded.read_long
87
+ check2 = decoded.read_long
88
+
89
+ raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2)
90
+
91
+ type_name = decoded.read_string
92
+ case type_name
93
+ when "ssh-ed25519"
94
+ PrivKey.new(decoded)
95
+ else
96
+ decoded.read_private_keyblob(type_name)
97
+ end
98
+ end
99
+ end
100
+
101
+ class PubKey
102
+ include Net::SSH::Authentication::PubKeyFingerprint
103
+
104
+ attr_reader :verify_key
105
+
106
+ def initialize(data)
107
+ @verify_key = ::Ed25519::VerifyKey.new(data)
108
+ end
109
+
110
+ def self.read_keyblob(buffer)
111
+ PubKey.new(buffer.read_string)
112
+ end
113
+
114
+ def to_blob
115
+ Net::SSH::Buffer.from(:mstring,"ssh-ed25519",:string,@verify_key.to_bytes).to_s
116
+ end
117
+
118
+ def ssh_type
119
+ "ssh-ed25519"
120
+ end
121
+
122
+ def ssh_signature_type
123
+ ssh_type
124
+ end
125
+
126
+ def ssh_do_verify(sig,data)
127
+ @verify_key.verify(sig,data)
128
+ end
129
+
130
+ def to_pem
131
+ # TODO this is not pem
132
+ ssh_type + Base64.encode64(@verify_key.to_bytes)
133
+ end
134
+ end
135
+
136
+ class PrivKey
137
+ CipherFactory = Net::SSH::Transport::CipherFactory
138
+
139
+ MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
140
+ MEND = "-----END OPENSSH PRIVATE KEY-----\n"
141
+ MAGIC = "openssh-key-v1"
142
+
143
+ attr_reader :sign_key
144
+
145
+ def initialize(buffer)
146
+ pk = buffer.read_string
147
+ sk = buffer.read_string
148
+ _comment = buffer.read_string
68
149
 
69
- MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
70
- MEND = "-----END OPENSSH PRIVATE KEY-----\n"
71
- MAGIC = "openssh-key-v1"
150
+ @pk = pk
151
+ @sign_key = SigningKeyFromFile.new(pk,sk)
152
+ end
72
153
 
73
- attr_reader :sign_key
154
+ def to_blob
155
+ public_key.to_blob
156
+ end
74
157
 
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])
158
+ def ssh_type
159
+ "ssh-ed25519"
160
+ end
82
161
 
83
- ciphername = buffer.read_string
84
- raise ArgumentError.new("#{ciphername} in private key is not supported") unless
85
- CipherFactory.supported?(ciphername)
162
+ def ssh_signature_type
163
+ ssh_type
164
+ end
86
165
 
87
- kdfname = buffer.read_string
88
- raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w(none bcrypt).include?(kdfname)
166
+ def public_key
167
+ PubKey.new(@pk)
168
+ end
89
169
 
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
170
+ def ssh_do_sign(data)
171
+ @sign_key.sign(data)
172
+ end
94
173
 
95
- len = buffer.read_long
96
-
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))
100
-
101
- if kdfname == 'bcrypt'
102
- salt = kdfopts.read_string
103
- rounds = kdfopts.read_long
104
-
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)
174
+ def self.read(data, password)
175
+ OpenSSHPrivateKeyLoader.read(data, password)
176
+ end
177
+ end
109
178
  end
110
-
111
- cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv:key[keylen...keylen+ivlen], decrypt: true)
112
-
113
- decoded = cipher.update(buffer.remainder_as_buffer.to_s)
114
- decoded << cipher.final
115
-
116
- decoded = Net::SSH::Buffer.new(decoded)
117
- check1 = decoded.read_long
118
- check2 = decoded.read_long
119
-
120
- raise ArgumentError, "Decrypt failed on private key" if (check1 != check2)
121
-
122
- _type_name = decoded.read_string
123
- pk = decoded.read_string
124
- sk = decoded.read_string
125
- _comment = decoded.read_string
126
-
127
- @pk = pk
128
- @sign_key = SigningKeyFromFile.new(pk,sk)
129
- end
130
-
131
- def to_blob
132
- public_key.to_blob
133
- end
134
-
135
- def ssh_type
136
- "ssh-ed25519"
137
- end
138
-
139
- def ssh_signature_type
140
- ssh_type
141
- end
142
-
143
- def public_key
144
- PubKey.new(@pk)
145
- end
146
-
147
- def ssh_do_sign(data)
148
- @sign_key.sign(data)
149
- end
150
-
151
- def self.read(data,password)
152
- self.new(data,password)
153
- end
154
-
155
- def self.read_keyblob(buffer)
156
- ED25519::PubKey.read_keyblob(buffer)
157
179
  end
158
180
  end
159
181
  end
160
- end; end; end