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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +8 -2
- data/.rubocop_todo.yml +405 -552
- data/.travis.yml +23 -22
- data/CHANGES.txt +112 -1
- data/Gemfile +1 -7
- data/{Gemfile.norbnacl → Gemfile.noed25519} +1 -1
- data/Manifest +4 -5
- data/README.md +287 -0
- data/Rakefile +40 -29
- data/appveyor.yml +12 -6
- data/lib/net/ssh.rb +68 -32
- data/lib/net/ssh/authentication/agent.rb +234 -222
- data/lib/net/ssh/authentication/certificate.rb +175 -164
- data/lib/net/ssh/authentication/constants.rb +17 -14
- data/lib/net/ssh/authentication/ed25519.rb +162 -141
- data/lib/net/ssh/authentication/ed25519_loader.rb +32 -29
- data/lib/net/ssh/authentication/key_manager.rb +40 -9
- data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
- data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +1 -1
- data/lib/net/ssh/authentication/methods/none.rb +10 -10
- data/lib/net/ssh/authentication/methods/password.rb +13 -13
- data/lib/net/ssh/authentication/methods/publickey.rb +56 -55
- data/lib/net/ssh/authentication/pageant.rb +468 -465
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +130 -122
- data/lib/net/ssh/buffer.rb +345 -312
- data/lib/net/ssh/buffered_io.rb +163 -163
- data/lib/net/ssh/config.rb +316 -238
- data/lib/net/ssh/connection/channel.rb +670 -650
- data/lib/net/ssh/connection/constants.rb +30 -26
- data/lib/net/ssh/connection/event_loop.rb +108 -105
- data/lib/net/ssh/connection/keepalive.rb +54 -50
- data/lib/net/ssh/connection/session.rb +682 -671
- data/lib/net/ssh/connection/term.rb +180 -176
- data/lib/net/ssh/errors.rb +101 -99
- data/lib/net/ssh/key_factory.rb +195 -108
- data/lib/net/ssh/known_hosts.rb +161 -152
- data/lib/net/ssh/loggable.rb +57 -55
- data/lib/net/ssh/packet.rb +82 -78
- data/lib/net/ssh/prompt.rb +55 -53
- data/lib/net/ssh/proxy/command.rb +104 -89
- data/lib/net/ssh/proxy/errors.rb +12 -8
- data/lib/net/ssh/proxy/http.rb +93 -91
- data/lib/net/ssh/proxy/https.rb +42 -39
- data/lib/net/ssh/proxy/jump.rb +50 -47
- data/lib/net/ssh/proxy/socks4.rb +0 -2
- data/lib/net/ssh/proxy/socks5.rb +11 -12
- data/lib/net/ssh/service/forward.rb +370 -317
- data/lib/net/ssh/test.rb +83 -77
- data/lib/net/ssh/test/channel.rb +146 -142
- data/lib/net/ssh/test/extensions.rb +150 -146
- data/lib/net/ssh/test/kex.rb +35 -31
- data/lib/net/ssh/test/local_packet.rb +48 -44
- data/lib/net/ssh/test/packet.rb +87 -84
- data/lib/net/ssh/test/remote_packet.rb +35 -31
- data/lib/net/ssh/test/script.rb +173 -171
- data/lib/net/ssh/test/socket.rb +59 -55
- data/lib/net/ssh/transport/algorithms.rb +430 -364
- data/lib/net/ssh/transport/cipher_factory.rb +95 -91
- data/lib/net/ssh/transport/constants.rb +33 -25
- data/lib/net/ssh/transport/ctr.rb +33 -11
- data/lib/net/ssh/transport/hmac.rb +15 -13
- data/lib/net/ssh/transport/hmac/abstract.rb +82 -63
- data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/identity_cipher.rb +55 -51
- data/lib/net/ssh/transport/kex.rb +14 -13
- data/lib/net/ssh/transport/kex/abstract.rb +123 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -62
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
- data/lib/net/ssh/transport/key_expander.rb +29 -25
- data/lib/net/ssh/transport/openssl.rb +116 -116
- data/lib/net/ssh/transport/packet_stream.rb +223 -190
- data/lib/net/ssh/transport/server_version.rb +64 -66
- data/lib/net/ssh/transport/session.rb +306 -257
- data/lib/net/ssh/transport/state.rb +198 -196
- data/lib/net/ssh/verifiers/accept_new.rb +35 -0
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +34 -0
- data/lib/net/ssh/verifiers/always.rb +56 -0
- data/lib/net/ssh/verifiers/never.rb +21 -0
- data/lib/net/ssh/version.rb +55 -53
- data/net-ssh-public_cert.pem +18 -19
- data/net-ssh.gemspec +12 -11
- data/support/ssh_tunnel_bug.rb +2 -2
- metadata +86 -75
- metadata.gz.sig +0 -0
- data/Gemfile.norbnacl.lock +0 -41
- data/README.rdoc +0 -169
- data/lib/net/ssh/ruby_compat.rb +0 -24
- data/lib/net/ssh/verifiers/lenient.rb +0 -30
- data/lib/net/ssh/verifiers/null.rb +0 -12
- data/lib/net/ssh/verifiers/secure.rb +0 -52
- data/lib/net/ssh/verifiers/strict.rb +0 -24
- data/support/arcfour_check.rb +0 -20
@@ -1,169 +1,180 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
|
3
|
-
module Net
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
180
|
+
end
|
@@ -1,18 +1,21 @@
|
|
1
|
-
module Net
|
1
|
+
module Net
|
2
|
+
module SSH
|
3
|
+
module Authentication
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
14
|
+
USERAUTH_PASSWD_CHANGEREQ = 60
|
15
|
+
USERAUTH_PK_OK = 60
|
14
16
|
|
15
|
-
|
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 '
|
1
|
+
gem 'ed25519', '~> 1.2'
|
2
2
|
gem 'bcrypt_pbkdf', '~> 1.0' unless RUBY_PLATFORM == "java"
|
3
3
|
|
4
|
-
|
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
|
21
|
-
module
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
150
|
+
@pk = pk
|
151
|
+
@sign_key = SigningKeyFromFile.new(pk,sk)
|
152
|
+
end
|
72
153
|
|
73
|
-
|
154
|
+
def to_blob
|
155
|
+
public_key.to_blob
|
156
|
+
end
|
74
157
|
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
162
|
+
def ssh_signature_type
|
163
|
+
ssh_type
|
164
|
+
end
|
86
165
|
|
87
|
-
|
88
|
-
|
166
|
+
def public_key
|
167
|
+
PubKey.new(@pk)
|
168
|
+
end
|
89
169
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
_pubkey = buffer.read_string
|
170
|
+
def ssh_do_sign(data)
|
171
|
+
@sign_key.sign(data)
|
172
|
+
end
|
94
173
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|