crussh 0.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +371 -0
- data/ext/poly1305/Cargo.toml +13 -0
- data/ext/poly1305/extconf.rb +6 -0
- data/ext/poly1305/src/lib.rs +75 -0
- data/lib/crussh/auth.rb +46 -0
- data/lib/crussh/channel/key_parser.rb +125 -0
- data/lib/crussh/channel.rb +381 -0
- data/lib/crussh/cipher/algorithm.rb +31 -0
- data/lib/crussh/cipher/chacha20poly1305.rb +98 -0
- data/lib/crussh/cipher.rb +25 -0
- data/lib/crussh/compression.rb +42 -0
- data/lib/crussh/gatekeeper.rb +50 -0
- data/lib/crussh/handler/line_buffer.rb +131 -0
- data/lib/crussh/handler.rb +128 -0
- data/lib/crussh/heartbeat.rb +68 -0
- data/lib/crussh/kex/algorithm.rb +86 -0
- data/lib/crussh/kex/curve25519.rb +30 -0
- data/lib/crussh/kex/exchange.rb +234 -0
- data/lib/crussh/kex.rb +42 -0
- data/lib/crussh/keys/key_pair.rb +61 -0
- data/lib/crussh/keys/public_key.rb +35 -0
- data/lib/crussh/keys.rb +70 -0
- data/lib/crussh/limits.rb +45 -0
- data/lib/crussh/logger.rb +95 -0
- data/lib/crussh/mac/algorithm.rb +23 -0
- data/lib/crussh/mac/crypto.rb +60 -0
- data/lib/crussh/mac/none.rb +9 -0
- data/lib/crussh/mac.rb +28 -0
- data/lib/crussh/negotiator.rb +41 -0
- data/lib/crussh/preferred.rb +16 -0
- data/lib/crussh/protocol/channel_close.rb +11 -0
- data/lib/crussh/protocol/channel_data.rb +12 -0
- data/lib/crussh/protocol/channel_eof.rb +11 -0
- data/lib/crussh/protocol/channel_extended_data.rb +13 -0
- data/lib/crussh/protocol/channel_failure.rb +11 -0
- data/lib/crussh/protocol/channel_open.rb +69 -0
- data/lib/crussh/protocol/channel_open_confirmation.rb +15 -0
- data/lib/crussh/protocol/channel_open_failure.rb +14 -0
- data/lib/crussh/protocol/channel_request.rb +146 -0
- data/lib/crussh/protocol/channel_success.rb +11 -0
- data/lib/crussh/protocol/channel_window_adjust.rb +12 -0
- data/lib/crussh/protocol/debug.rb +15 -0
- data/lib/crussh/protocol/disconnect.rb +39 -0
- data/lib/crussh/protocol/ext_info.rb +48 -0
- data/lib/crussh/protocol/global_request.rb +46 -0
- data/lib/crussh/protocol/ignore.rb +11 -0
- data/lib/crussh/protocol/kex_ecdh_init.rb +11 -0
- data/lib/crussh/protocol/kex_ecdh_reply.rb +13 -0
- data/lib/crussh/protocol/kex_init.rb +38 -0
- data/lib/crussh/protocol/new_keys.rb +9 -0
- data/lib/crussh/protocol/ping.rb +11 -0
- data/lib/crussh/protocol/pong.rb +11 -0
- data/lib/crussh/protocol/request_failure.rb +9 -0
- data/lib/crussh/protocol/request_success.rb +11 -0
- data/lib/crussh/protocol/service_accept.rb +11 -0
- data/lib/crussh/protocol/service_request.rb +11 -0
- data/lib/crussh/protocol/unimplemented.rb +11 -0
- data/lib/crussh/protocol/userauth_banner.rb +12 -0
- data/lib/crussh/protocol/userauth_failure.rb +12 -0
- data/lib/crussh/protocol/userauth_pk_ok.rb +12 -0
- data/lib/crussh/protocol/userauth_request.rb +52 -0
- data/lib/crussh/protocol/userauth_success.rb +9 -0
- data/lib/crussh/protocol.rb +135 -0
- data/lib/crussh/server/auth_handler.rb +18 -0
- data/lib/crussh/server/config.rb +157 -0
- data/lib/crussh/server/layers/connection.rb +363 -0
- data/lib/crussh/server/layers/transport.rb +49 -0
- data/lib/crussh/server/layers/userauth.rb +232 -0
- data/lib/crussh/server/request_rule.rb +76 -0
- data/lib/crussh/server/session.rb +192 -0
- data/lib/crussh/server.rb +214 -0
- data/lib/crussh/ssh_id.rb +44 -0
- data/lib/crussh/transport/packet_stream.rb +245 -0
- data/lib/crussh/transport/reader.rb +98 -0
- data/lib/crussh/transport/version_exchange.rb +26 -0
- data/lib/crussh/transport/writer.rb +72 -0
- data/lib/crussh/version.rb +5 -0
- data/lib/crussh.rb +61 -0
- data/sig/crussh.rbs +4 -0
- metadata +249 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
module Protocol
|
|
5
|
+
class ChannelRequest < Message
|
|
6
|
+
message_type CHANNEL_REQUEST
|
|
7
|
+
|
|
8
|
+
field :recipient_channel, :uint32
|
|
9
|
+
field :request_type, :string
|
|
10
|
+
field :want_reply, :boolean
|
|
11
|
+
field :request_data, :remaining
|
|
12
|
+
|
|
13
|
+
def want_reply? = want_reply
|
|
14
|
+
def pty? = request_type == "pty-req"
|
|
15
|
+
def x11? = request_type == "x11-req"
|
|
16
|
+
def env? = request_type == "env"
|
|
17
|
+
def shell? = request_type == "shell"
|
|
18
|
+
def exec? = request_type == "exec"
|
|
19
|
+
def subsystem? = request_type == "subsystem"
|
|
20
|
+
def window_change? = request_type == "window-change"
|
|
21
|
+
def local_flow_control? = request_type == "xon-xoff"
|
|
22
|
+
def signal? = request_type == "signal"
|
|
23
|
+
def exit_status? = request_type == "exit-status"
|
|
24
|
+
def exit_signal? = request_type == "exit-signal"
|
|
25
|
+
|
|
26
|
+
def pty
|
|
27
|
+
return @pty_data if @pty_data
|
|
28
|
+
return unless pty?
|
|
29
|
+
|
|
30
|
+
reader = Transport::Reader.new(request_data)
|
|
31
|
+
|
|
32
|
+
term = reader.string
|
|
33
|
+
width = reader.uint32
|
|
34
|
+
height = reader.uint32
|
|
35
|
+
pixel_width = reader.uint32
|
|
36
|
+
pixel_height = reader.uint32
|
|
37
|
+
modes = reader.string
|
|
38
|
+
|
|
39
|
+
@pty_data = Channel::Pty.new(term:, width:, height:, pixel_width:, pixel_height:, modes:)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def x11
|
|
43
|
+
return @x11_data if @x11_data
|
|
44
|
+
return unless x11?
|
|
45
|
+
|
|
46
|
+
reader = Transport::Reader.new(request_data)
|
|
47
|
+
|
|
48
|
+
single_connection = reader.boolean
|
|
49
|
+
x11_auth_protocol = reader.string
|
|
50
|
+
x11_auth_cookie = reader.string
|
|
51
|
+
x11_screen_number = reader.uint32
|
|
52
|
+
|
|
53
|
+
@x11_data = X11.new(single_connection:, auth_protocol: x11_auth_protocol, auth_cookie: x11_auth_cookie, screen_number: x11_screen_number)
|
|
54
|
+
end
|
|
55
|
+
X11 = Data.define(:single_connection, :auth_protocol, :auth_cookie, :screen_number) do
|
|
56
|
+
def single_connection? = single_connection
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def env
|
|
60
|
+
return @env_data if @env_data
|
|
61
|
+
return unless env?
|
|
62
|
+
|
|
63
|
+
reader = Transport::Reader.new(request_data)
|
|
64
|
+
|
|
65
|
+
variable_name = reader.string
|
|
66
|
+
variable_value = reader.string
|
|
67
|
+
|
|
68
|
+
@env_data = Env.new(variable_name:, variable_value:)
|
|
69
|
+
end
|
|
70
|
+
Env = Data.define(:variable_name, :variable_value)
|
|
71
|
+
|
|
72
|
+
def command
|
|
73
|
+
return @command if @command
|
|
74
|
+
return unless exec?
|
|
75
|
+
|
|
76
|
+
reader = Transport::Reader.new(request_data)
|
|
77
|
+
@command = reader.string
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def subsystem_name
|
|
81
|
+
return @subsystem_name if @subsystem_name
|
|
82
|
+
return unless subsystem?
|
|
83
|
+
|
|
84
|
+
reader = Transport::Reader.new(request_data)
|
|
85
|
+
@subsystem_name = reader.string
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def window_change
|
|
89
|
+
return @window_change_data if @window_change_data
|
|
90
|
+
return unless window_change?
|
|
91
|
+
|
|
92
|
+
reader = Transport::Reader.new(request_data)
|
|
93
|
+
|
|
94
|
+
width = reader.uint32
|
|
95
|
+
height = reader.uint32
|
|
96
|
+
pixel_width = reader.uint32
|
|
97
|
+
pixel_height = reader.uint32
|
|
98
|
+
|
|
99
|
+
@window_change_data = Channel::WindowChange.new(width:, height:, pixel_width:, pixel_height:)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def can_do_local_flow_control?
|
|
103
|
+
return @can_do_local_flow_control unless @can_do_local_flow_control.nil?
|
|
104
|
+
return false unless local_flow_control?
|
|
105
|
+
|
|
106
|
+
reader = Transport::Reader.new(request_data)
|
|
107
|
+
|
|
108
|
+
@can_do_local_flow_control = reader.boolean
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def signal
|
|
112
|
+
return @signal if @signal
|
|
113
|
+
return false unless signal?
|
|
114
|
+
|
|
115
|
+
reader = Transport::Reader.new(request_data)
|
|
116
|
+
|
|
117
|
+
@signal = Channel::Signal.new(name: reader.string)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def exit_status
|
|
121
|
+
return @exit_status if @exit_status
|
|
122
|
+
return unless exit_status?
|
|
123
|
+
|
|
124
|
+
reader = Transport::Reader.new(request_data)
|
|
125
|
+
|
|
126
|
+
@exit_status = reader.uint32
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def exit_signal
|
|
130
|
+
return @exit_signal if @exit_signal
|
|
131
|
+
return unless exit_signal?
|
|
132
|
+
|
|
133
|
+
reader = Transport::Reader.new(request_data)
|
|
134
|
+
|
|
135
|
+
core_dumped = reader.boolean
|
|
136
|
+
error_message = reader.string
|
|
137
|
+
language_tag = reader.string
|
|
138
|
+
|
|
139
|
+
@exit_signal = ExitSignal.new(core_dumped:, error_message:, language_tag:)
|
|
140
|
+
end
|
|
141
|
+
ExitSignal = Data.define(:core_dumped, :error_message, :language_tag) do
|
|
142
|
+
def core_dumped? = core_dumped
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
module Protocol
|
|
5
|
+
class Debug < Message
|
|
6
|
+
message_type DEBUG
|
|
7
|
+
|
|
8
|
+
field :always_display, :boolean
|
|
9
|
+
field :message, :string
|
|
10
|
+
field :language, :string, default: ""
|
|
11
|
+
|
|
12
|
+
def always_display? = @always_display
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
module Protocol
|
|
5
|
+
class Disconnect < Message
|
|
6
|
+
message_type DISCONNECT
|
|
7
|
+
|
|
8
|
+
field :reason_code, :uint32
|
|
9
|
+
field :description, :string, default: ""
|
|
10
|
+
field :language, :string, default: ""
|
|
11
|
+
|
|
12
|
+
REASONS_MAP = {
|
|
13
|
+
host_not_allowed_to_connect: 1,
|
|
14
|
+
protocol_error: 2,
|
|
15
|
+
key_exchange_failed: 3,
|
|
16
|
+
reserved: 4,
|
|
17
|
+
mac_error: 5,
|
|
18
|
+
compression_error: 6,
|
|
19
|
+
service_not_available: 7,
|
|
20
|
+
protocol_version_not_supported: 8,
|
|
21
|
+
host_key_not_verifiable: 9,
|
|
22
|
+
connection_lost: 10,
|
|
23
|
+
by_application: 11,
|
|
24
|
+
too_many_connections: 12,
|
|
25
|
+
auth_cancelled_by_user: 13,
|
|
26
|
+
no_more_auth_methods_available: 14,
|
|
27
|
+
illegal_user_name: 15,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def build(reason, description = "")
|
|
32
|
+
reason_code = REASONS_MAP[reason]
|
|
33
|
+
|
|
34
|
+
new(reason_code:, description:)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
module Protocol
|
|
5
|
+
class ExtInfo
|
|
6
|
+
def initialize(extensions: {})
|
|
7
|
+
@extensions = extensions
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :extensions
|
|
11
|
+
|
|
12
|
+
def serialize
|
|
13
|
+
writer = Transport::Writer.new
|
|
14
|
+
writer.byte(EXT_INFO)
|
|
15
|
+
writer.uint32(@extensions.size)
|
|
16
|
+
|
|
17
|
+
@extensions.each do |name, value|
|
|
18
|
+
writer.string(name)
|
|
19
|
+
writer.string(value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
writer.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
def parse(data)
|
|
27
|
+
reader = Transport::Reader.new(data)
|
|
28
|
+
wire_message_type = reader.byte
|
|
29
|
+
|
|
30
|
+
unless wire_message_type == EXT_INFO
|
|
31
|
+
raise ProtocolError, "Expected EXT_INFO, got message type #{wire_message_type}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
count = reader.uint32
|
|
35
|
+
extensions = {}
|
|
36
|
+
|
|
37
|
+
count.times do
|
|
38
|
+
name = reader.string
|
|
39
|
+
value = reader.string
|
|
40
|
+
extensions[name] = value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
new(extensions: extensions)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
module Protocol
|
|
5
|
+
class GlobalRequest < Message
|
|
6
|
+
message_type GLOBAL_REQUEST
|
|
7
|
+
|
|
8
|
+
field :request_type, :string
|
|
9
|
+
field :want_reply, :boolean, default: false
|
|
10
|
+
field :request_data, :remaining, default: ""
|
|
11
|
+
|
|
12
|
+
def tcpip_forward?
|
|
13
|
+
request_type == "tcpip-forward"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def cancel_tcpip_forward?
|
|
17
|
+
request_type == "cancel-tcpip-forward"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tcpip_forward
|
|
21
|
+
return @tcpip_forward if @tcpip_forward
|
|
22
|
+
return unless tcpip_forward?
|
|
23
|
+
|
|
24
|
+
reader = Transport::Reader.new(request_data)
|
|
25
|
+
|
|
26
|
+
address = reader.string
|
|
27
|
+
port = reader.uint32
|
|
28
|
+
|
|
29
|
+
@tcpip_forward = TcpipForward.new(address:, port:)
|
|
30
|
+
end
|
|
31
|
+
TcpipForward = Data.define(:address, :port)
|
|
32
|
+
|
|
33
|
+
def cancel_tcpip_forward
|
|
34
|
+
return @cancel_tcpip_forward if @cancel_tcpip_forward
|
|
35
|
+
return unless cancel_tcpip_forward?
|
|
36
|
+
|
|
37
|
+
reader = Transport::Reader.new(request_data)
|
|
38
|
+
|
|
39
|
+
address = reader.string
|
|
40
|
+
port = reader.uint32
|
|
41
|
+
|
|
42
|
+
@cancel_tcpip_forward = TcpipForward.new(address:, port:)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
module Protocol
|
|
5
|
+
class KexInit < Message
|
|
6
|
+
message_type KEXINIT
|
|
7
|
+
|
|
8
|
+
field :cookie, :raw, length: 16, default: -> { SecureRandom.random_bytes(16) }
|
|
9
|
+
field :kex_algorithms, :name_list, default: []
|
|
10
|
+
field :server_host_key_algorithms, :name_list, default: []
|
|
11
|
+
field :cipher_client_to_server, :name_list, default: []
|
|
12
|
+
field :cipher_server_to_client, :name_list, default: []
|
|
13
|
+
field :mac_client_to_server, :name_list, default: []
|
|
14
|
+
field :mac_server_to_client, :name_list, default: []
|
|
15
|
+
field :compression_client_to_server, :name_list, default: []
|
|
16
|
+
field :compression_server_to_client, :name_list, default: []
|
|
17
|
+
field :languages_client_to_server, :name_list, default: []
|
|
18
|
+
field :languages_server_to_client, :name_list, default: []
|
|
19
|
+
field :first_kex_packet_follows, :boolean, default: false
|
|
20
|
+
field :reserved, :uint32, default: 0
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
def from_preferred(preferred)
|
|
24
|
+
new(
|
|
25
|
+
kex_algorithms: preferred.kex,
|
|
26
|
+
server_host_key_algorithms: preferred.host_key,
|
|
27
|
+
cipher_client_to_server: preferred.cipher,
|
|
28
|
+
cipher_server_to_client: preferred.cipher,
|
|
29
|
+
mac_client_to_server: preferred.mac,
|
|
30
|
+
mac_server_to_client: preferred.mac,
|
|
31
|
+
compression_client_to_server: preferred.compression,
|
|
32
|
+
compression_server_to_client: preferred.compression,
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crussh
|
|
4
|
+
module Protocol
|
|
5
|
+
class UserauthRequest < Message
|
|
6
|
+
message_type USERAUTH_REQUEST
|
|
7
|
+
|
|
8
|
+
field :username, :string
|
|
9
|
+
field :service_name, :string
|
|
10
|
+
field :method_name, :string
|
|
11
|
+
field :method_data, :remaining
|
|
12
|
+
|
|
13
|
+
def none?
|
|
14
|
+
method_name == "none"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def password?
|
|
18
|
+
method_name == "password"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def publickey?
|
|
22
|
+
method_name == "publickey"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def password
|
|
26
|
+
return unless password?
|
|
27
|
+
return @password if @password
|
|
28
|
+
|
|
29
|
+
reader = Transport::Reader.new(method_data)
|
|
30
|
+
reader.boolean
|
|
31
|
+
@password = reader.string
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def public_key_data
|
|
35
|
+
return @public_key_data if @public_key_data
|
|
36
|
+
return unless publickey?
|
|
37
|
+
|
|
38
|
+
reader = Transport::Reader.new(method_data)
|
|
39
|
+
has_signature = reader.boolean
|
|
40
|
+
algorithm = reader.string
|
|
41
|
+
key_blob = reader.string
|
|
42
|
+
signature = has_signature && !reader.eof? ? reader.string : nil
|
|
43
|
+
|
|
44
|
+
@public_key_data = PublicKeyData.new(has_signature, algorithm, key_blob, signature)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
PublicKeyData = Data.define(:has_signature, :algorithm, :key_blob, :signature) do
|
|
48
|
+
def has_signature? = has_signature
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|