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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +371 -0
  5. data/ext/poly1305/Cargo.toml +13 -0
  6. data/ext/poly1305/extconf.rb +6 -0
  7. data/ext/poly1305/src/lib.rs +75 -0
  8. data/lib/crussh/auth.rb +46 -0
  9. data/lib/crussh/channel/key_parser.rb +125 -0
  10. data/lib/crussh/channel.rb +381 -0
  11. data/lib/crussh/cipher/algorithm.rb +31 -0
  12. data/lib/crussh/cipher/chacha20poly1305.rb +98 -0
  13. data/lib/crussh/cipher.rb +25 -0
  14. data/lib/crussh/compression.rb +42 -0
  15. data/lib/crussh/gatekeeper.rb +50 -0
  16. data/lib/crussh/handler/line_buffer.rb +131 -0
  17. data/lib/crussh/handler.rb +128 -0
  18. data/lib/crussh/heartbeat.rb +68 -0
  19. data/lib/crussh/kex/algorithm.rb +86 -0
  20. data/lib/crussh/kex/curve25519.rb +30 -0
  21. data/lib/crussh/kex/exchange.rb +234 -0
  22. data/lib/crussh/kex.rb +42 -0
  23. data/lib/crussh/keys/key_pair.rb +61 -0
  24. data/lib/crussh/keys/public_key.rb +35 -0
  25. data/lib/crussh/keys.rb +70 -0
  26. data/lib/crussh/limits.rb +45 -0
  27. data/lib/crussh/logger.rb +95 -0
  28. data/lib/crussh/mac/algorithm.rb +23 -0
  29. data/lib/crussh/mac/crypto.rb +60 -0
  30. data/lib/crussh/mac/none.rb +9 -0
  31. data/lib/crussh/mac.rb +28 -0
  32. data/lib/crussh/negotiator.rb +41 -0
  33. data/lib/crussh/preferred.rb +16 -0
  34. data/lib/crussh/protocol/channel_close.rb +11 -0
  35. data/lib/crussh/protocol/channel_data.rb +12 -0
  36. data/lib/crussh/protocol/channel_eof.rb +11 -0
  37. data/lib/crussh/protocol/channel_extended_data.rb +13 -0
  38. data/lib/crussh/protocol/channel_failure.rb +11 -0
  39. data/lib/crussh/protocol/channel_open.rb +69 -0
  40. data/lib/crussh/protocol/channel_open_confirmation.rb +15 -0
  41. data/lib/crussh/protocol/channel_open_failure.rb +14 -0
  42. data/lib/crussh/protocol/channel_request.rb +146 -0
  43. data/lib/crussh/protocol/channel_success.rb +11 -0
  44. data/lib/crussh/protocol/channel_window_adjust.rb +12 -0
  45. data/lib/crussh/protocol/debug.rb +15 -0
  46. data/lib/crussh/protocol/disconnect.rb +39 -0
  47. data/lib/crussh/protocol/ext_info.rb +48 -0
  48. data/lib/crussh/protocol/global_request.rb +46 -0
  49. data/lib/crussh/protocol/ignore.rb +11 -0
  50. data/lib/crussh/protocol/kex_ecdh_init.rb +11 -0
  51. data/lib/crussh/protocol/kex_ecdh_reply.rb +13 -0
  52. data/lib/crussh/protocol/kex_init.rb +38 -0
  53. data/lib/crussh/protocol/new_keys.rb +9 -0
  54. data/lib/crussh/protocol/ping.rb +11 -0
  55. data/lib/crussh/protocol/pong.rb +11 -0
  56. data/lib/crussh/protocol/request_failure.rb +9 -0
  57. data/lib/crussh/protocol/request_success.rb +11 -0
  58. data/lib/crussh/protocol/service_accept.rb +11 -0
  59. data/lib/crussh/protocol/service_request.rb +11 -0
  60. data/lib/crussh/protocol/unimplemented.rb +11 -0
  61. data/lib/crussh/protocol/userauth_banner.rb +12 -0
  62. data/lib/crussh/protocol/userauth_failure.rb +12 -0
  63. data/lib/crussh/protocol/userauth_pk_ok.rb +12 -0
  64. data/lib/crussh/protocol/userauth_request.rb +52 -0
  65. data/lib/crussh/protocol/userauth_success.rb +9 -0
  66. data/lib/crussh/protocol.rb +135 -0
  67. data/lib/crussh/server/auth_handler.rb +18 -0
  68. data/lib/crussh/server/config.rb +157 -0
  69. data/lib/crussh/server/layers/connection.rb +363 -0
  70. data/lib/crussh/server/layers/transport.rb +49 -0
  71. data/lib/crussh/server/layers/userauth.rb +232 -0
  72. data/lib/crussh/server/request_rule.rb +76 -0
  73. data/lib/crussh/server/session.rb +192 -0
  74. data/lib/crussh/server.rb +214 -0
  75. data/lib/crussh/ssh_id.rb +44 -0
  76. data/lib/crussh/transport/packet_stream.rb +245 -0
  77. data/lib/crussh/transport/reader.rb +98 -0
  78. data/lib/crussh/transport/version_exchange.rb +26 -0
  79. data/lib/crussh/transport/writer.rb +72 -0
  80. data/lib/crussh/version.rb +5 -0
  81. data/lib/crussh.rb +61 -0
  82. data/sig/crussh.rbs +4 -0
  83. 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelSuccess < Message
6
+ message_type CHANNEL_SUCCESS
7
+
8
+ field :recipient_channel, :uint32
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelWindowAdjust < Message
6
+ message_type CHANNEL_WINDOW_ADJUST
7
+
8
+ field :recipient_channel, :uint32
9
+ field :bytes_to_add, :uint32
10
+ end
11
+ end
12
+ 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class Ignore < Message
6
+ message_type IGNORE
7
+
8
+ field :data, :string, default: ""
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class KexEcdhInit < Message
6
+ message_type KEX_ECDH_INIT
7
+
8
+ field :public_key, :string
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class KexEcdhReply < Message
6
+ message_type KEX_ECDH_REPLY
7
+
8
+ field :public_host_key, :string
9
+ field :public_key, :string
10
+ field :signature, :string
11
+ end
12
+ end
13
+ 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class NewKeys < Message
6
+ message_type NEWKEYS
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class Ping < Message
6
+ message_type PING
7
+
8
+ field :data, :string, default: ""
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class Pong < Message
6
+ message_type PONG
7
+
8
+ field :data, :string, default: ""
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class RequestFailure < Message
6
+ message_type REQUEST_FAILURE
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class RequestSuccess < Message
6
+ message_type REQUEST_SUCCESS
7
+
8
+ field :response_data, :remaining
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ServiceAccept < Message
6
+ message_type SERVICE_ACCEPT
7
+
8
+ field :service_name, :string
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ServiceRequest < Message
6
+ message_type SERVICE_REQUEST
7
+
8
+ field :service_name, :string
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class Unimplemented < Message
6
+ message_type UNIMPLEMENTED
7
+
8
+ field :sequence_number, :uint32
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class UserauthBanner < Message
6
+ message_type USERAUTH_BANNER
7
+
8
+ field :message, :string
9
+ field :language, :string, default: ""
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class UserauthFailure < Message
6
+ message_type USERAUTH_FAILURE
7
+
8
+ field :authentications, :name_list
9
+ field :partial_success, :boolean, default: false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class UserauthPkOk < Message
6
+ message_type USERAUTH_PK_OK
7
+
8
+ field :algorithm, :string
9
+ field :key_blob, :string
10
+ end
11
+ end
12
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class UserauthSuccess < Message
6
+ message_type USERAUTH_SUCCESS
7
+ end
8
+ end
9
+ end