net-ssh-backports 6.3.0.backports
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 +7 -0
- data/.github/workflows/ci.yml +93 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +1074 -0
- data/.travis.yml +51 -0
- data/CHANGES.txt +698 -0
- data/Gemfile +13 -0
- data/Gemfile.noed25519 +12 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/LICENSE.txt +19 -0
- data/Manifest +132 -0
- data/README.md +287 -0
- data/Rakefile +105 -0
- data/THANKS.txt +110 -0
- data/appveyor.yml +58 -0
- data/lib/net/ssh/authentication/agent.rb +284 -0
- data/lib/net/ssh/authentication/certificate.rb +183 -0
- data/lib/net/ssh/authentication/constants.rb +20 -0
- data/lib/net/ssh/authentication/ed25519.rb +185 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
- data/lib/net/ssh/authentication/key_manager.rb +297 -0
- data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
- data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
- data/lib/net/ssh/authentication/methods/none.rb +34 -0
- data/lib/net/ssh/authentication/methods/password.rb +80 -0
- data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
- data/lib/net/ssh/authentication/pageant.rb +497 -0
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +163 -0
- data/lib/net/ssh/buffer.rb +434 -0
- data/lib/net/ssh/buffered_io.rb +202 -0
- data/lib/net/ssh/config.rb +406 -0
- data/lib/net/ssh/connection/channel.rb +695 -0
- data/lib/net/ssh/connection/constants.rb +33 -0
- data/lib/net/ssh/connection/event_loop.rb +123 -0
- data/lib/net/ssh/connection/keepalive.rb +59 -0
- data/lib/net/ssh/connection/session.rb +712 -0
- data/lib/net/ssh/connection/term.rb +180 -0
- data/lib/net/ssh/errors.rb +106 -0
- data/lib/net/ssh/key_factory.rb +218 -0
- data/lib/net/ssh/known_hosts.rb +264 -0
- data/lib/net/ssh/loggable.rb +62 -0
- data/lib/net/ssh/packet.rb +106 -0
- data/lib/net/ssh/prompt.rb +62 -0
- data/lib/net/ssh/proxy/command.rb +123 -0
- data/lib/net/ssh/proxy/errors.rb +16 -0
- data/lib/net/ssh/proxy/http.rb +98 -0
- data/lib/net/ssh/proxy/https.rb +50 -0
- data/lib/net/ssh/proxy/jump.rb +54 -0
- data/lib/net/ssh/proxy/socks4.rb +67 -0
- data/lib/net/ssh/proxy/socks5.rb +140 -0
- data/lib/net/ssh/service/forward.rb +426 -0
- data/lib/net/ssh/test/channel.rb +147 -0
- data/lib/net/ssh/test/extensions.rb +173 -0
- data/lib/net/ssh/test/kex.rb +46 -0
- data/lib/net/ssh/test/local_packet.rb +53 -0
- data/lib/net/ssh/test/packet.rb +101 -0
- data/lib/net/ssh/test/remote_packet.rb +40 -0
- data/lib/net/ssh/test/script.rb +180 -0
- data/lib/net/ssh/test/socket.rb +65 -0
- data/lib/net/ssh/test.rb +94 -0
- data/lib/net/ssh/transport/algorithms.rb +502 -0
- data/lib/net/ssh/transport/cipher_factory.rb +103 -0
- data/lib/net/ssh/transport/constants.rb +40 -0
- data/lib/net/ssh/transport/ctr.rb +115 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
- data/lib/net/ssh/transport/hmac/md5.rb +10 -0
- data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/none.rb +13 -0
- data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +47 -0
- data/lib/net/ssh/transport/identity_cipher.rb +57 -0
- data/lib/net/ssh/transport/kex/abstract.rb +130 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
- data/lib/net/ssh/transport/kex.rb +31 -0
- data/lib/net/ssh/transport/key_expander.rb +30 -0
- data/lib/net/ssh/transport/openssl.rb +253 -0
- data/lib/net/ssh/transport/packet_stream.rb +280 -0
- data/lib/net/ssh/transport/server_version.rb +77 -0
- data/lib/net/ssh/transport/session.rb +354 -0
- data/lib/net/ssh/transport/state.rb +208 -0
- data/lib/net/ssh/verifiers/accept_new.rb +33 -0
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
- data/lib/net/ssh/verifiers/always.rb +58 -0
- data/lib/net/ssh/verifiers/never.rb +19 -0
- data/lib/net/ssh/version.rb +68 -0
- data/lib/net/ssh.rb +330 -0
- data/net-ssh-public_cert.pem +20 -0
- data/net-ssh.gemspec +44 -0
- data/support/ssh_tunnel_bug.rb +65 -0
- metadata +271 -0
@@ -0,0 +1,497 @@
|
|
1
|
+
if RUBY_VERSION < "1.9"
|
2
|
+
require 'dl/import'
|
3
|
+
require 'dl/struct'
|
4
|
+
elsif RUBY_VERSION < "2.1"
|
5
|
+
require 'dl/import'
|
6
|
+
require 'dl/types'
|
7
|
+
require 'dl'
|
8
|
+
else
|
9
|
+
require 'fiddle'
|
10
|
+
require 'fiddle/types'
|
11
|
+
require 'fiddle/import'
|
12
|
+
|
13
|
+
# For now map DL to Fiddler versus updating all the code below
|
14
|
+
module DL
|
15
|
+
CPtr ||= Fiddle::Pointer
|
16
|
+
if RUBY_PLATFORM != "java"
|
17
|
+
RUBY_FREE ||= Fiddle::RUBY_FREE
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'net/ssh/errors'
|
23
|
+
|
24
|
+
module Net
|
25
|
+
module SSH
|
26
|
+
module Authentication
|
27
|
+
# This module encapsulates the implementation of a socket factory that
|
28
|
+
# uses the PuTTY "pageant" utility to obtain information about SSH
|
29
|
+
# identities.
|
30
|
+
#
|
31
|
+
# This code is a slightly modified version of the original implementation
|
32
|
+
# by Guillaume Marçais (guillaume.marcais@free.fr). It is used and
|
33
|
+
# relicensed by permission.
|
34
|
+
module Pageant
|
35
|
+
# From Putty pageant.c
|
36
|
+
AGENT_MAX_MSGLEN = 8192
|
37
|
+
AGENT_COPYDATA_ID = 0x804e50ba
|
38
|
+
|
39
|
+
# The definition of the Windows methods and data structures used in
|
40
|
+
# communicating with the pageant process.
|
41
|
+
module Win # rubocop:disable Metrics/ModuleLength
|
42
|
+
# Compatibility on initialization
|
43
|
+
if RUBY_VERSION < "1.9"
|
44
|
+
extend DL::Importable
|
45
|
+
|
46
|
+
dlload 'user32.dll'
|
47
|
+
dlload 'kernel32.dll'
|
48
|
+
dlload 'advapi32.dll'
|
49
|
+
|
50
|
+
SIZEOF_DWORD = DL.sizeof('L')
|
51
|
+
elsif RUBY_VERSION < "2.1"
|
52
|
+
extend DL::Importer
|
53
|
+
dlload 'user32.dll','kernel32.dll', 'advapi32.dll'
|
54
|
+
include DL::Win32Types
|
55
|
+
|
56
|
+
SIZEOF_DWORD = DL::SIZEOF_LONG
|
57
|
+
else
|
58
|
+
extend Fiddle::Importer
|
59
|
+
dlload 'user32.dll','kernel32.dll', 'advapi32.dll'
|
60
|
+
include Fiddle::Win32Types
|
61
|
+
SIZEOF_DWORD = Fiddle::SIZEOF_LONG
|
62
|
+
end
|
63
|
+
|
64
|
+
if RUBY_ENGINE == "jruby"
|
65
|
+
typealias("HANDLE", "void *") # From winnt.h
|
66
|
+
typealias("PHANDLE", "void *") # From winnt.h
|
67
|
+
typealias("ULONG_PTR", "unsigned long*")
|
68
|
+
end
|
69
|
+
typealias("LPCTSTR", "char *") # From winnt.h
|
70
|
+
typealias("LPVOID", "void *") # From winnt.h
|
71
|
+
typealias("LPCVOID", "const void *") # From windef.h
|
72
|
+
typealias("LRESULT", "long") # From windef.h
|
73
|
+
typealias("WPARAM", "unsigned int *") # From windef.h
|
74
|
+
typealias("LPARAM", "long *") # From windef.h
|
75
|
+
typealias("PDWORD_PTR", "long *") # From basetsd.h
|
76
|
+
typealias("USHORT", "unsigned short") # From windef.h
|
77
|
+
|
78
|
+
# From winbase.h, winnt.h
|
79
|
+
INVALID_HANDLE_VALUE = -1
|
80
|
+
NULL = nil
|
81
|
+
PAGE_READWRITE = 0x0004
|
82
|
+
FILE_MAP_WRITE = 2
|
83
|
+
WM_COPYDATA = 74
|
84
|
+
|
85
|
+
SMTO_NORMAL = 0 # From winuser.h
|
86
|
+
|
87
|
+
SUFFIX = if RUBY_ENGINE == "jruby"
|
88
|
+
"A"
|
89
|
+
else
|
90
|
+
""
|
91
|
+
end
|
92
|
+
|
93
|
+
# args: lpClassName, lpWindowName
|
94
|
+
extern "HWND FindWindow#{SUFFIX}(LPCTSTR, LPCTSTR)"
|
95
|
+
|
96
|
+
# args: none
|
97
|
+
extern 'DWORD GetCurrentThreadId()'
|
98
|
+
|
99
|
+
# args: hFile, (ignored), flProtect, dwMaximumSizeHigh,
|
100
|
+
# dwMaximumSizeLow, lpName
|
101
|
+
extern "HANDLE CreateFileMapping#{SUFFIX}(HANDLE, void *, DWORD, " +
|
102
|
+
"DWORD, DWORD, LPCTSTR)"
|
103
|
+
|
104
|
+
# args: hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
|
105
|
+
# dwfileOffsetLow, dwNumberOfBytesToMap
|
106
|
+
extern 'LPVOID MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, DWORD)'
|
107
|
+
|
108
|
+
# args: lpBaseAddress
|
109
|
+
extern 'BOOL UnmapViewOfFile(LPCVOID)'
|
110
|
+
|
111
|
+
# args: hObject
|
112
|
+
extern 'BOOL CloseHandle(HANDLE)'
|
113
|
+
|
114
|
+
# args: hWnd, Msg, wParam, lParam, fuFlags, uTimeout, lpdwResult
|
115
|
+
extern "LRESULT SendMessageTimeout#{SUFFIX}(HWND, UINT, WPARAM, LPARAM, " +
|
116
|
+
"UINT, UINT, PDWORD_PTR)"
|
117
|
+
|
118
|
+
# args: none
|
119
|
+
extern 'DWORD GetLastError()'
|
120
|
+
|
121
|
+
# args: none
|
122
|
+
extern 'HANDLE GetCurrentProcess()'
|
123
|
+
|
124
|
+
# args: hProcessHandle, dwDesiredAccess, (out) phNewTokenHandle
|
125
|
+
extern 'BOOL OpenProcessToken(HANDLE, DWORD, PHANDLE)'
|
126
|
+
|
127
|
+
# args: hTokenHandle, uTokenInformationClass,
|
128
|
+
# (out) lpTokenInformation, dwTokenInformationLength
|
129
|
+
# (out) pdwInfoReturnLength
|
130
|
+
extern 'BOOL GetTokenInformation(HANDLE, UINT, LPVOID, DWORD, ' +
|
131
|
+
'PDWORD)'
|
132
|
+
|
133
|
+
# args: (out) lpSecurityDescriptor, dwRevisionLevel
|
134
|
+
extern 'BOOL InitializeSecurityDescriptor(LPVOID, DWORD)'
|
135
|
+
|
136
|
+
# args: (out) lpSecurityDescriptor, lpOwnerSid, bOwnerDefaulted
|
137
|
+
extern 'BOOL SetSecurityDescriptorOwner(LPVOID, LPVOID, BOOL)'
|
138
|
+
|
139
|
+
# args: pSecurityDescriptor
|
140
|
+
extern 'BOOL IsValidSecurityDescriptor(LPVOID)'
|
141
|
+
|
142
|
+
# Constants needed for security attribute retrieval.
|
143
|
+
# Specifies the access mask corresponding to the desired access
|
144
|
+
# rights.
|
145
|
+
TOKEN_QUERY = 0x8
|
146
|
+
|
147
|
+
# The value of TOKEN_USER from the TOKEN_INFORMATION_CLASS enum.
|
148
|
+
TOKEN_USER_INFORMATION_CLASS = 1
|
149
|
+
|
150
|
+
# The initial revision level assigned to the security descriptor.
|
151
|
+
REVISION = 1
|
152
|
+
|
153
|
+
# Structs for security attribute functions.
|
154
|
+
# Holds the retrieved user access token.
|
155
|
+
TOKEN_USER = struct ['void * SID', 'DWORD ATTRIBUTES']
|
156
|
+
|
157
|
+
# Contains the security descriptor, this gets passed to the
|
158
|
+
# function that constructs the shared memory map.
|
159
|
+
SECURITY_ATTRIBUTES = struct ['DWORD nLength',
|
160
|
+
'LPVOID lpSecurityDescriptor',
|
161
|
+
'BOOL bInheritHandle']
|
162
|
+
|
163
|
+
# The security descriptor holds security information.
|
164
|
+
SECURITY_DESCRIPTOR = struct ['UCHAR Revision', 'UCHAR Sbz1',
|
165
|
+
'USHORT Control', 'LPVOID Owner',
|
166
|
+
'LPVOID Group', 'LPVOID Sacl',
|
167
|
+
'LPVOID Dacl']
|
168
|
+
|
169
|
+
# The COPYDATASTRUCT is used to send WM_COPYDATA messages
|
170
|
+
COPYDATASTRUCT = if RUBY_ENGINE == "jruby"
|
171
|
+
struct ['ULONG_PTR dwData', 'DWORD cbData', 'LPVOID lpData']
|
172
|
+
else
|
173
|
+
struct ['uintptr_t dwData', 'DWORD cbData', 'LPVOID lpData']
|
174
|
+
end
|
175
|
+
|
176
|
+
# Compatibility for security attribute retrieval.
|
177
|
+
if RUBY_VERSION < "1.9"
|
178
|
+
# Alias functions to > 1.9 capitalization
|
179
|
+
%w[findWindow
|
180
|
+
getCurrentProcess
|
181
|
+
initializeSecurityDescriptor
|
182
|
+
setSecurityDescriptorOwner
|
183
|
+
isValidSecurityDescriptor
|
184
|
+
openProcessToken
|
185
|
+
getTokenInformation
|
186
|
+
getLastError
|
187
|
+
getCurrentThreadId
|
188
|
+
createFileMapping
|
189
|
+
mapViewOfFile
|
190
|
+
sendMessageTimeout
|
191
|
+
unmapViewOfFile
|
192
|
+
closeHandle].each do |name|
|
193
|
+
new_name = name[0].chr.upcase + name[1..name.length]
|
194
|
+
alias_method new_name, name
|
195
|
+
module_function new_name
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.malloc_ptr(size)
|
199
|
+
return DL.malloc(size)
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.get_ptr(data)
|
203
|
+
return data.to_ptr
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.set_ptr_data(ptr, data)
|
207
|
+
ptr[0] = data
|
208
|
+
end
|
209
|
+
elsif RUBY_ENGINE == "jruby"
|
210
|
+
%w[FindWindow CreateFileMapping SendMessageTimeout].each do |name|
|
211
|
+
alias_method name, name + "A"
|
212
|
+
module_function name
|
213
|
+
end
|
214
|
+
# :nodoc:
|
215
|
+
module LibC
|
216
|
+
extend FFI::Library
|
217
|
+
ffi_lib FFI::Library::LIBC
|
218
|
+
attach_function :malloc, [:size_t], :pointer
|
219
|
+
attach_function :free, [:pointer], :void
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.malloc_ptr(size)
|
223
|
+
Fiddle::Pointer.new(LibC.malloc(size), size, LibC.method(:free))
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.get_ptr(ptr)
|
227
|
+
return data.address
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.set_ptr_data(ptr, data)
|
231
|
+
ptr.write_string_length(data, data.size)
|
232
|
+
end
|
233
|
+
else
|
234
|
+
def self.malloc_ptr(size)
|
235
|
+
return DL::CPtr.malloc(size, DL::RUBY_FREE)
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.get_ptr(data)
|
239
|
+
return DL::CPtr.to_ptr data
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.set_ptr_data(ptr, data)
|
243
|
+
DL::CPtr.new(ptr)[0,data.size] = data
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def self.get_security_attributes_for_user
|
248
|
+
user = get_current_user
|
249
|
+
|
250
|
+
psd_information = malloc_ptr(Win::SECURITY_DESCRIPTOR.size)
|
251
|
+
raise_error_if_zero(
|
252
|
+
Win.InitializeSecurityDescriptor(psd_information,
|
253
|
+
Win::REVISION)
|
254
|
+
)
|
255
|
+
raise_error_if_zero(
|
256
|
+
Win.SetSecurityDescriptorOwner(psd_information, get_sid_ptr(user),
|
257
|
+
0)
|
258
|
+
)
|
259
|
+
raise_error_if_zero(
|
260
|
+
Win.IsValidSecurityDescriptor(psd_information)
|
261
|
+
)
|
262
|
+
|
263
|
+
sa = Win::SECURITY_ATTRIBUTES.new(to_struct_ptr(malloc_ptr(Win::SECURITY_ATTRIBUTES.size)))
|
264
|
+
sa.nLength = Win::SECURITY_ATTRIBUTES.size
|
265
|
+
sa.lpSecurityDescriptor = psd_information.to_i
|
266
|
+
sa.bInheritHandle = 1
|
267
|
+
|
268
|
+
return sa
|
269
|
+
end
|
270
|
+
|
271
|
+
if RUBY_ENGINE == "jruby"
|
272
|
+
def self.ptr_to_s(ptr, size)
|
273
|
+
ret = ptr.to_s(size)
|
274
|
+
ret << "\x00" while ret.size < size
|
275
|
+
ret
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.ptr_to_handle(phandle)
|
279
|
+
phandle.ptr
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.ptr_to_dword(ptr)
|
283
|
+
first = ptr.ptr.to_i
|
284
|
+
second = ptr_to_s(ptr,Win::SIZEOF_DWORD).unpack('L')[0]
|
285
|
+
raise "Error" unless first == second
|
286
|
+
|
287
|
+
first
|
288
|
+
end
|
289
|
+
|
290
|
+
def self.to_token_user(ptoken_information)
|
291
|
+
TOKEN_USER.new(ptoken_information.to_ptr)
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.to_struct_ptr(ptr)
|
295
|
+
ptr.to_ptr
|
296
|
+
end
|
297
|
+
|
298
|
+
def self.get_sid(user)
|
299
|
+
ptr_to_s(user.to_ptr.ptr,Win::SIZEOF_DWORD).unpack('L')[0]
|
300
|
+
end
|
301
|
+
|
302
|
+
def self.get_sid_ptr(user)
|
303
|
+
user.to_ptr.ptr
|
304
|
+
end
|
305
|
+
else
|
306
|
+
def self.get_sid(user)
|
307
|
+
user.SID
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.ptr_to_handle(phandle)
|
311
|
+
phandle.ptr.to_i
|
312
|
+
end
|
313
|
+
|
314
|
+
def self.to_struct_ptr(ptr)
|
315
|
+
ptr
|
316
|
+
end
|
317
|
+
|
318
|
+
def self.ptr_to_dword(ptr)
|
319
|
+
ptr.to_s(Win::SIZEOF_DWORD).unpack('L')[0]
|
320
|
+
end
|
321
|
+
|
322
|
+
def self.to_token_user(ptoken_information)
|
323
|
+
TOKEN_USER.new(ptoken_information)
|
324
|
+
end
|
325
|
+
|
326
|
+
def self.get_sid_ptr(user)
|
327
|
+
user.SID
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def self.get_current_user
|
332
|
+
token_handle = open_process_token(Win.GetCurrentProcess,
|
333
|
+
Win::TOKEN_QUERY)
|
334
|
+
token_user = get_token_information(token_handle,
|
335
|
+
Win::TOKEN_USER_INFORMATION_CLASS)
|
336
|
+
return token_user
|
337
|
+
end
|
338
|
+
|
339
|
+
def self.open_process_token(process_handle, desired_access)
|
340
|
+
ptoken_handle = malloc_ptr(Win::SIZEOF_DWORD)
|
341
|
+
|
342
|
+
raise_error_if_zero(
|
343
|
+
Win.OpenProcessToken(process_handle, desired_access,
|
344
|
+
ptoken_handle)
|
345
|
+
)
|
346
|
+
token_handle = ptr_to_handle(ptoken_handle)
|
347
|
+
return token_handle
|
348
|
+
end
|
349
|
+
|
350
|
+
def self.get_token_information(token_handle,
|
351
|
+
token_information_class)
|
352
|
+
# Hold the size of the information to be returned
|
353
|
+
preturn_length = malloc_ptr(Win::SIZEOF_DWORD)
|
354
|
+
|
355
|
+
# Going to throw an INSUFFICIENT_BUFFER_ERROR, but that is ok
|
356
|
+
# here. This is retrieving the size of the information to be
|
357
|
+
# returned.
|
358
|
+
Win.GetTokenInformation(token_handle,
|
359
|
+
token_information_class,
|
360
|
+
Win::NULL, 0, preturn_length)
|
361
|
+
ptoken_information = malloc_ptr(ptr_to_dword(preturn_length))
|
362
|
+
|
363
|
+
# This call is going to write the requested information to
|
364
|
+
# the memory location referenced by token_information.
|
365
|
+
raise_error_if_zero(
|
366
|
+
Win.GetTokenInformation(token_handle,
|
367
|
+
token_information_class,
|
368
|
+
ptoken_information,
|
369
|
+
ptoken_information.size,
|
370
|
+
preturn_length)
|
371
|
+
)
|
372
|
+
|
373
|
+
return to_token_user(ptoken_information)
|
374
|
+
end
|
375
|
+
|
376
|
+
def self.raise_error_if_zero(result)
|
377
|
+
if result == 0
|
378
|
+
raise "Windows error: #{Win.GetLastError}"
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# Get a null-terminated string given a string.
|
383
|
+
def self.get_cstr(str)
|
384
|
+
return str + "\000"
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# This is the pseudo-socket implementation that mimics the interface of
|
389
|
+
# a socket, translating each request into a Windows messaging call to
|
390
|
+
# the pageant daemon. This allows pageant support to be implemented
|
391
|
+
# simply by replacing the socket factory used by the Agent class.
|
392
|
+
class Socket
|
393
|
+
private_class_method :new
|
394
|
+
|
395
|
+
# The factory method for creating a new Socket instance.
|
396
|
+
def self.open
|
397
|
+
new
|
398
|
+
end
|
399
|
+
|
400
|
+
# Create a new instance that communicates with the running pageant
|
401
|
+
# instance. If no such instance is running, this will cause an error.
|
402
|
+
def initialize
|
403
|
+
@win = Win.FindWindow("Pageant", "Pageant")
|
404
|
+
|
405
|
+
if @win.to_i == 0
|
406
|
+
raise Net::SSH::Exception,
|
407
|
+
"pageant process not running"
|
408
|
+
end
|
409
|
+
|
410
|
+
@input_buffer = Net::SSH::Buffer.new
|
411
|
+
@output_buffer = Net::SSH::Buffer.new
|
412
|
+
end
|
413
|
+
|
414
|
+
# Forwards the data to #send_query, ignoring any arguments after
|
415
|
+
# the first.
|
416
|
+
def send(data, *args)
|
417
|
+
@input_buffer.append(data)
|
418
|
+
|
419
|
+
ret = data.length
|
420
|
+
|
421
|
+
while true
|
422
|
+
return ret if @input_buffer.length < 4
|
423
|
+
|
424
|
+
msg_length = @input_buffer.read_long + 4
|
425
|
+
@input_buffer.reset!
|
426
|
+
|
427
|
+
return ret if @input_buffer.length < msg_length
|
428
|
+
|
429
|
+
msg = @input_buffer.read!(msg_length)
|
430
|
+
@output_buffer.append(send_query(msg))
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
# Reads +n+ bytes from the cached result of the last query. If +n+
|
435
|
+
# is +nil+, returns all remaining data from the last query.
|
436
|
+
def read(n = nil)
|
437
|
+
@output_buffer.read(n)
|
438
|
+
end
|
439
|
+
|
440
|
+
def close; end
|
441
|
+
|
442
|
+
# Packages the given query string and sends it to the pageant
|
443
|
+
# process via the Windows messaging subsystem. The result is
|
444
|
+
# cached, to be returned piece-wise when #read is called.
|
445
|
+
def send_query(query)
|
446
|
+
res = nil
|
447
|
+
filemap = 0
|
448
|
+
ptr = nil
|
449
|
+
id = Win.malloc_ptr(Win::SIZEOF_DWORD)
|
450
|
+
|
451
|
+
mapname = "PageantRequest%08x" % Win.GetCurrentThreadId()
|
452
|
+
security_attributes = Win.get_ptr Win.get_security_attributes_for_user
|
453
|
+
|
454
|
+
filemap = Win.CreateFileMapping(Win::INVALID_HANDLE_VALUE,
|
455
|
+
security_attributes,
|
456
|
+
Win::PAGE_READWRITE, 0,
|
457
|
+
AGENT_MAX_MSGLEN, mapname)
|
458
|
+
|
459
|
+
if filemap == 0 || filemap == Win::INVALID_HANDLE_VALUE
|
460
|
+
raise Net::SSH::Exception,
|
461
|
+
"Creation of file mapping failed with error: #{Win.GetLastError}"
|
462
|
+
end
|
463
|
+
|
464
|
+
ptr = Win.MapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0,
|
465
|
+
0)
|
466
|
+
|
467
|
+
if ptr.nil? || ptr.null?
|
468
|
+
raise Net::SSH::Exception, "Mapping of file failed"
|
469
|
+
end
|
470
|
+
|
471
|
+
Win.set_ptr_data(ptr, query)
|
472
|
+
|
473
|
+
# using struct to achieve proper alignment and field size on 64-bit platform
|
474
|
+
cds = Win::COPYDATASTRUCT.new(Win.malloc_ptr(Win::COPYDATASTRUCT.size))
|
475
|
+
cds.dwData = AGENT_COPYDATA_ID
|
476
|
+
cds.cbData = mapname.size + 1
|
477
|
+
cds.lpData = Win.get_cstr(mapname)
|
478
|
+
succ = Win.SendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
|
479
|
+
cds.to_ptr, Win::SMTO_NORMAL, 5000, id)
|
480
|
+
|
481
|
+
if succ > 0
|
482
|
+
retlen = 4 + ptr.to_s(4).unpack("N")[0]
|
483
|
+
res = ptr.to_s(retlen)
|
484
|
+
else
|
485
|
+
raise Net::SSH::Exception, "Message failed with error: #{Win.GetLastError}"
|
486
|
+
end
|
487
|
+
|
488
|
+
return res
|
489
|
+
ensure
|
490
|
+
Win.UnmapViewOfFile(ptr) unless ptr.nil? || ptr.null?
|
491
|
+
Win.CloseHandle(filemap) if filemap != 0
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Net
|
4
|
+
module SSH
|
5
|
+
module Authentication
|
6
|
+
# Public key fingerprinting utility module - internal not part of API.
|
7
|
+
# This is included in pubkey classes and called from there. All RSA, DSA, and ECC keys
|
8
|
+
# are supported.
|
9
|
+
#
|
10
|
+
# require 'net/ssh'
|
11
|
+
# my_pubkey_text = File.read('/path/to/id_ed25519.pub')
|
12
|
+
# #=> "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDB2NBh4GJPPUN1kXPMu8b633Xcv55WoKC3OkBjFAbzJ alice@example.com"
|
13
|
+
# my_pubkey = Net::SSH::KeyFactory.load_data_public_key(my_pubkey_text)
|
14
|
+
# #=> #<Net::SSH::Authentication::ED25519::PubKey:0x00007fc8e91819b0
|
15
|
+
# my_pubkey.fingerprint
|
16
|
+
# #=> "2f:7f:97:21:76:a4:0f:38:c4:fe:d8:b4:6a:39:72:30"
|
17
|
+
# my_pubkey.fingerprint('SHA256')
|
18
|
+
# #=> "SHA256:u6mXnY8P1b0FODGp8mckqOB33u8+jvkSCtJbD5Q9klg"
|
19
|
+
module PubKeyFingerprint # :nodoc:
|
20
|
+
# Return the key's fingerprint. Algorithm may be either +MD5+ (default),
|
21
|
+
# or +SHA256+. For +SHA256+, fingerprints are in the same format
|
22
|
+
# returned by OpenSSH's <tt>`ssh-add -l -E SHA256`</tt>, i.e.,
|
23
|
+
# trailing base64 padding '=' characters are stripped and the
|
24
|
+
# literal string +SHA256:+ is prepended.
|
25
|
+
def fingerprint(algorithm='MD5')
|
26
|
+
@fingerprint ||= {}
|
27
|
+
@fingerprint[algorithm] ||= PubKeyFingerprint.fingerprint(to_blob, algorithm)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.fingerprint(blob, algorithm='MD5')
|
31
|
+
case algorithm.to_s.upcase
|
32
|
+
when 'MD5'
|
33
|
+
OpenSSL::Digest.hexdigest(algorithm, blob).scan(/../).join(":")
|
34
|
+
when 'SHA256'
|
35
|
+
"SHA256:#{Base64.encode64(OpenSSL::Digest.digest(algorithm, blob)).chomp.gsub(/=+\z/, '')}"
|
36
|
+
else
|
37
|
+
raise OpenSSL::Digest::DigestError, "unsupported ssh key digest #{algorithm}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'net/ssh/loggable'
|
2
|
+
require 'net/ssh/transport/constants'
|
3
|
+
require 'net/ssh/authentication/constants'
|
4
|
+
require 'net/ssh/authentication/key_manager'
|
5
|
+
require 'net/ssh/authentication/methods/none'
|
6
|
+
require 'net/ssh/authentication/methods/publickey'
|
7
|
+
require 'net/ssh/authentication/methods/hostbased'
|
8
|
+
require 'net/ssh/authentication/methods/password'
|
9
|
+
require 'net/ssh/authentication/methods/keyboard_interactive'
|
10
|
+
|
11
|
+
module Net
|
12
|
+
module SSH
|
13
|
+
module Authentication
|
14
|
+
# Raised if the current authentication method is not allowed
|
15
|
+
class DisallowedMethod < Net::SSH::Exception
|
16
|
+
end
|
17
|
+
|
18
|
+
# Represents an authentication session. It manages the authentication of
|
19
|
+
# a user over an established connection (the "transport" object, see
|
20
|
+
# Net::SSH::Transport::Session).
|
21
|
+
#
|
22
|
+
# The use of an authentication session to manage user authentication is
|
23
|
+
# internal to Net::SSH (specifically Net::SSH.start). Consumers of the
|
24
|
+
# Net::SSH library will never need to access this class directly.
|
25
|
+
class Session
|
26
|
+
include Loggable
|
27
|
+
include Constants
|
28
|
+
include Transport::Constants
|
29
|
+
|
30
|
+
# transport layer abstraction
|
31
|
+
attr_reader :transport
|
32
|
+
|
33
|
+
# the list of authentication methods to try
|
34
|
+
attr_reader :auth_methods
|
35
|
+
|
36
|
+
# the list of authentication methods that are allowed
|
37
|
+
attr_reader :allowed_auth_methods
|
38
|
+
|
39
|
+
# a hash of options, given at construction time
|
40
|
+
attr_reader :options
|
41
|
+
|
42
|
+
# Instantiates a new Authentication::Session object over the given
|
43
|
+
# transport layer abstraction.
|
44
|
+
def initialize(transport, options={})
|
45
|
+
self.logger = transport.logger
|
46
|
+
@transport = transport
|
47
|
+
|
48
|
+
@auth_methods = options[:auth_methods] || Net::SSH::Config.default_auth_methods
|
49
|
+
@options = options
|
50
|
+
|
51
|
+
@allowed_auth_methods = @auth_methods
|
52
|
+
end
|
53
|
+
|
54
|
+
# Attempts to authenticate the given user, in preparation for the next
|
55
|
+
# service request. Returns true if an authentication method succeeds in
|
56
|
+
# authenticating the user, and false otherwise.
|
57
|
+
def authenticate(next_service, username, password=nil)
|
58
|
+
debug { "beginning authentication of `#{username}'" }
|
59
|
+
|
60
|
+
transport.send_message(transport.service_request("ssh-userauth"))
|
61
|
+
expect_message(SERVICE_ACCEPT)
|
62
|
+
|
63
|
+
key_manager = KeyManager.new(logger, options)
|
64
|
+
keys.each { |key| key_manager.add(key) } unless keys.empty?
|
65
|
+
keycerts.each { |keycert| key_manager.add_keycert(keycert) } unless keycerts.empty?
|
66
|
+
key_data.each { |key2| key_manager.add_key_data(key2) } unless key_data.empty?
|
67
|
+
default_keys.each { |key| key_manager.add(key) } unless options.key?(:keys) || options.key?(:key_data)
|
68
|
+
|
69
|
+
attempted = []
|
70
|
+
|
71
|
+
@auth_methods.each do |name|
|
72
|
+
next unless @allowed_auth_methods.include?(name)
|
73
|
+
|
74
|
+
attempted << name
|
75
|
+
|
76
|
+
debug { "trying #{name}" }
|
77
|
+
begin
|
78
|
+
auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join)
|
79
|
+
method = auth_class.new(self, key_manager: key_manager, password_prompt: options[:password_prompt])
|
80
|
+
rescue NameError
|
81
|
+
debug {"Mechanism #{name} was requested, but isn't a known type. Ignoring it."}
|
82
|
+
next
|
83
|
+
end
|
84
|
+
|
85
|
+
return true if method.authenticate(next_service, username, password)
|
86
|
+
rescue Net::SSH::Authentication::DisallowedMethod
|
87
|
+
end
|
88
|
+
|
89
|
+
error { "all authorization methods failed (tried #{attempted.join(', ')})" }
|
90
|
+
return false
|
91
|
+
ensure
|
92
|
+
key_manager.finish if key_manager
|
93
|
+
end
|
94
|
+
|
95
|
+
# Blocks until a packet is received. It silently handles USERAUTH_BANNER
|
96
|
+
# packets, and will raise an error if any packet is received that is not
|
97
|
+
# valid during user authentication.
|
98
|
+
def next_message
|
99
|
+
loop do
|
100
|
+
packet = transport.next_message
|
101
|
+
|
102
|
+
case packet.type
|
103
|
+
when USERAUTH_BANNER
|
104
|
+
info { packet[:message] }
|
105
|
+
# TODO add a hook for people to retrieve the banner when it is sent
|
106
|
+
|
107
|
+
when USERAUTH_FAILURE
|
108
|
+
@allowed_auth_methods = packet[:authentications].split(/,/)
|
109
|
+
debug { "allowed methods: #{packet[:authentications]}" }
|
110
|
+
return packet
|
111
|
+
|
112
|
+
when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT
|
113
|
+
return packet
|
114
|
+
|
115
|
+
when USERAUTH_SUCCESS
|
116
|
+
transport.hint :authenticated
|
117
|
+
return packet
|
118
|
+
|
119
|
+
else
|
120
|
+
raise Net::SSH::Exception, "unexpected message #{packet.type} (#{packet})"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Blocks until a packet is received, and returns it if it is of the given
|
126
|
+
# type. If it is not, an exception is raised.
|
127
|
+
def expect_message(type)
|
128
|
+
message = next_message
|
129
|
+
raise Net::SSH::Exception, "expected #{type}, got #{message.type} (#{message})" unless message.type == type
|
130
|
+
|
131
|
+
message
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# Returns an array of paths to the key files usually defined
|
137
|
+
# by system default.
|
138
|
+
def default_keys
|
139
|
+
%w[~/.ssh/id_ed25519 ~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa
|
140
|
+
~/.ssh2/id_ed25519 ~/.ssh2/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_ecdsa]
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns an array of paths to the key files that should be used when
|
144
|
+
# attempting any key-based authentication mechanism.
|
145
|
+
def keys
|
146
|
+
Array(options[:keys])
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns an array of paths to the keycert files that should be used when
|
150
|
+
# attempting any key-based authentication mechanism.
|
151
|
+
def keycerts
|
152
|
+
Array(options[:keycerts])
|
153
|
+
end
|
154
|
+
|
155
|
+
# Returns an array of the key data that should be used when
|
156
|
+
# attempting any key-based authentication mechanism.
|
157
|
+
def key_data
|
158
|
+
Array(options[:key_data])
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|