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
data/lib/crussh/kex.rb ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Kex
5
+ STRICT_SERVER = "kex-strict-s-v00@openssh.com"
6
+ STRICT_CLIENT = "kex-strict-c-v00@openssh.com"
7
+
8
+ EXT_INFO_SERVER = "ext-info-s"
9
+ EXT_INFO_CLIENT = "ext-info-c"
10
+
11
+ CURVE25519_SHA256 = "curve25519-sha256"
12
+ CURVE25519_SHA256_LIBSSH = "curve25519-sha256@libssh.org"
13
+
14
+ DEFAULT = [CURVE25519_SHA256, CURVE25519_SHA256_LIBSSH, STRICT_SERVER, EXT_INFO_SERVER]
15
+
16
+ REGISTRY = {
17
+ CURVE25519_SHA256 => Curve25519,
18
+ CURVE25519_SHA256_LIBSSH => Curve25519,
19
+ }
20
+
21
+ class << self
22
+ def from_name(name)
23
+ algorithm_class = REGISTRY[name]
24
+
25
+ raise UnknownAlgorithm, "Unknown KEX algorithm: #{name}" if algorithm_class.nil?
26
+
27
+ algorithm_class.new
28
+ end
29
+ end
30
+
31
+ Parameters = Data.define(
32
+ :client_id,
33
+ :server_id,
34
+ :client_kexinit,
35
+ :server_kexinit,
36
+ :server_host_key,
37
+ :client_public,
38
+ :server_public,
39
+ :shared_secret,
40
+ )
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Keys
5
+ class KeyPair
6
+ def initialize(private_key, signature_algorithm: nil)
7
+ @private_key = private_key
8
+ @signature_algorithm = signature_algorithm || default_signature_algorithm
9
+ end
10
+
11
+ attr_reader :signature_algorithm
12
+
13
+ def algorithm
14
+ @private_key.public_key.algo
15
+ end
16
+
17
+ def public_key_blob
18
+ @private_key.public_key.rfc4253
19
+ end
20
+
21
+ def sign(data)
22
+ @private_key.sign(data, algo: @signature_algorithm)
23
+ end
24
+
25
+ def verify(data, signature)
26
+ @private_key.public_key.verify(data, signature)
27
+ end
28
+
29
+ def public_key
30
+ @public_key ||= PublicKey.new(@private_key.public_key)
31
+ end
32
+
33
+ def fingerprint
34
+ public_key.fingerprint
35
+ end
36
+
37
+ def to_authorized_key(comment: nil)
38
+ public_key.to_authorized_key(comment:)
39
+ end
40
+
41
+ def to_openssh(passphrase: nil, comment: "")
42
+ @private_key.openssh(comment: comment)
43
+ end
44
+
45
+ private
46
+
47
+ def default_signature_algorithm
48
+ case @private_key
49
+ when SSHData::PrivateKey::ED25519
50
+ ED25519
51
+ when SSHData::PrivateKey::RSA
52
+ RSA_SHA512
53
+ when SSHData::PrivateKey::ECDSA
54
+ @private_key.public_key.algo
55
+ else
56
+ @private_key.public_key.algo
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Keys
5
+ class PublicKey
6
+ def initialize(public_key)
7
+ @public_key = public_key
8
+ end
9
+
10
+ def algorithm
11
+ @public_key.algo
12
+ end
13
+
14
+ def encode
15
+ @public_key.rfc4253
16
+ end
17
+
18
+ def verify(data, signature)
19
+ @public_key.verify(data, signature)
20
+ rescue SSHData::VerifyError
21
+ false
22
+ end
23
+
24
+ def fingerprint
25
+ @public_key.fingerprint
26
+ end
27
+
28
+ def to_authorized_key(comment: nil)
29
+ parts = [@public_key.algo, Base64.strict_encode64(@public_key.encode)]
30
+ parts << comment if comment
31
+ parts.join(" ")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ed25519"
4
+ require "ssh_data"
5
+
6
+ module Crussh
7
+ module Keys
8
+ ED25519 = "ssh-ed25519"
9
+ RSA_SHA256 = "rsa-sha2-256"
10
+ RSA_SHA512 = "rsa-sha2-512"
11
+ ECDSA_P256 = "ecdsa-sha2-nistp256"
12
+ ECDSA_P384 = "ecdsa-sha2-nistp384"
13
+ ECDSA_P521 = "ecdsa-sha2-nistp521"
14
+
15
+ DEFAULT = [
16
+ ED25519,
17
+ RSA_SHA512,
18
+ RSA_SHA256,
19
+ ]
20
+
21
+ class << self
22
+ def generate(algorithm = ED25519)
23
+ private_key = case algorithm
24
+ when ED25519
25
+ SSHData::PrivateKey::ED25519.generate
26
+ when RSA_SHA256, RSA_SHA512
27
+ SSHData::PrivateKey::RSA.generate
28
+ when ECDSA_P256
29
+ SSHData::PrivateKey::ECDSA.generate("nistp256")
30
+ when ECDSA_P384
31
+ SSHData::PrivateKey::ECDSA.generate("nistp384")
32
+ when ECDSA_P521
33
+ SSHData::PrivateKey::ECDSA.generate("nistp521")
34
+ else
35
+ raise KeyError, "Unknown algorithm: #{algorithm}"
36
+ end
37
+
38
+ KeyPair.new(private_key, signature_algorithm: algorithm)
39
+ end
40
+
41
+ def from_openssh(data, passphrase: nil)
42
+ private_key = SSHData::PrivateKey.parse_openssh(data, passphrase:)
43
+ KeyPair.new(private_key)
44
+ rescue SSHData::DecryptError
45
+ raise KeyError, "Invalid passphrase or corrupted key"
46
+ rescue SSHData::DecodeError => e
47
+ raise KeyError, "Failed to parse key: #{e.message}"
48
+ end
49
+
50
+ def from_file(path, passphrase: nil)
51
+ from_openssh(File.read(path), passphrase:)
52
+ end
53
+
54
+ def parse_public_blob(data)
55
+ public_key = SSHData::PublicKey.parse(data)
56
+
57
+ PublicKey.new(public_key)
58
+ rescue SSHData::DecodeError => e
59
+ raise KeyError, "Failed to parse public key: #{e.message}"
60
+ end
61
+
62
+ def parse_authorized_key(data)
63
+ public_key = SSHData::PublicKey.parse_openssh(data)
64
+ PublicKey.new(public_key)
65
+ rescue SSHData::DecodeError => e
66
+ raise KeyError, "Failed to parse public key: #{e.message}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ # Thresholds for when to perform key re-exchange.
5
+ # Rekeying is important for long-lived connections to limit
6
+ # the amount of data encrypted under a single key.
7
+ class Limits
8
+ ONE_GB = 1 << 30
9
+ ONE_HOUR = 3600
10
+
11
+ attr_accessor :rekey_write_limit,
12
+ :rekey_read_limit,
13
+ :rekey_time_limit
14
+
15
+ # defaults from
16
+ # https://datatracker.ietf.org/doc/html/rfc4253#section-9
17
+ def initialize(
18
+ rekey_write_limit: ONE_GB,
19
+ rekey_read_limit: ONE_GB,
20
+ rekey_time_limit: ONE_HOUR
21
+ )
22
+ @rekey_write_limit = rekey_write_limit
23
+ @rekey_read_limit = rekey_read_limit
24
+ @rekey_time_limit = rekey_time_limit
25
+ end
26
+
27
+ def over?(read:, written:, time:)
28
+ over_read?(read) || over_write?(written) || over_time?(time)
29
+ end
30
+
31
+ private
32
+
33
+ def over_read?(read)
34
+ read >= rekey_read_limit
35
+ end
36
+
37
+ def over_write?(written)
38
+ written >= rekey_write_limit
39
+ end
40
+
41
+ def over_time?(time)
42
+ (Time.now - time) >= rekey_time_limit
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Logger
5
+ class Filter
6
+ DEFAULT_FILTERED_KEYS = [
7
+ :password,
8
+ :passphrase,
9
+ :private_key,
10
+ :secret,
11
+ :key_blob,
12
+ :signature,
13
+ :shared_secret,
14
+ :session_id,
15
+ :mac_key,
16
+ :encryption_key,
17
+ :iv,
18
+ :nonce,
19
+ :token,
20
+ :credential,
21
+ ].freeze
22
+
23
+ FILTERED = "[FILTERED]"
24
+ BINARY_PREVIEW_BYTES = 4
25
+
26
+ def initialize(keys: DEFAULT_FILTERED_KEYS)
27
+ @keys = keys.map(&:to_s)
28
+ end
29
+
30
+ def apply(value, key: nil)
31
+ return FILTERED if key && filter_key?(key)
32
+
33
+ case value
34
+ when Hash
35
+ value.to_h { |k, v| [k, apply(v, key: k)] }
36
+ when Array
37
+ value.map { |v| apply(v) }
38
+ when String
39
+ binary?(value) ? format_binary(value) : value
40
+ else
41
+ value
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def filter_key?(key)
48
+ key_s = key.to_s.downcase
49
+ @keys.any? { |filtered| key_s.include?(filtered) }
50
+ end
51
+
52
+ def binary?(value)
53
+ return false if value.empty?
54
+ return false if value.valid_encoding? && value.match?(/\A[[:print:]\s]*\z/)
55
+
56
+ true
57
+ end
58
+
59
+ def format_binary(value)
60
+ preview = value.bytes.first(BINARY_PREVIEW_BYTES)
61
+ .map { |b| format("%02x", b) }
62
+ .join
63
+ "<#{value.bytesize} bytes: 0x#{preview}...>"
64
+ end
65
+ end
66
+
67
+ class << self
68
+ attr_writer :filter
69
+
70
+ def filter
71
+ @filter ||= Filter.new
72
+ end
73
+
74
+ def debug(subject, message = nil, *args, **params)
75
+ Console.debug(subject, message, *args, **filter.apply(params))
76
+ end
77
+
78
+ def info(subject, message = nil, *args, **params)
79
+ Console.info(subject, message, *args, **filter.apply(params))
80
+ end
81
+
82
+ def warn(subject, message = nil, *args, **params)
83
+ Console.warn(subject, message, *args, **filter.apply(params))
84
+ end
85
+
86
+ def error(subject, message = nil, *args, **params)
87
+ Console.error(subject, message, *args, **filter.apply(params))
88
+ end
89
+
90
+ def fatal(subject, message = nil, *args, **params)
91
+ Console.fatal(subject, message, *args, **filter.apply(params))
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Mac
5
+ class Algorithm
6
+ def initialize(key)
7
+ @key = key
8
+ end
9
+
10
+ def key_length = 0
11
+ def mac_length = 0
12
+ def etm? = false
13
+
14
+ def compute(sequence, data)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def verify?(sequence, data, mac)
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Mac
5
+ class HmacShaAlgorithm < Algorithm
6
+ def initialize(key)
7
+ super
8
+
9
+ @key = key
10
+ end
11
+
12
+ def compute(sequence, data)
13
+ OpenSSL::HMAC.digest(digest_name, @key, [sequence].pack("N") + data)
14
+ end
15
+
16
+ def verify?(sequence, data, mac)
17
+ expected = OpenSSL::HMAC.digest(digest_name, @key, [sequence].pack("N") + data)
18
+ OpenSSL.secure_compare(expected, mac)
19
+ end
20
+
21
+ private
22
+
23
+ def digest_name
24
+ raise NotImplementedError
25
+ end
26
+ end
27
+
28
+ class HmacSha1 < HmacShaAlgorithm
29
+ NAME = "hmac-sha1"
30
+
31
+ def key_length = 20
32
+ def mac_length = 20
33
+ def digest_name = "SHA1"
34
+ end
35
+
36
+ class HmacSha256 < HmacShaAlgorithm
37
+ def key_length = 32
38
+ def mac_length = 32
39
+ def digest_name = "SHA256"
40
+ end
41
+
42
+ class HmacSha256Etm < HmacSha256
43
+ def etm? = true
44
+ end
45
+
46
+ class HmacSha512 < HmacShaAlgorithm
47
+ NAME = "hmac-sha2-512"
48
+
49
+ def key_length = 64
50
+ def mac_length = 64
51
+ def digest_name = "SHA512"
52
+ end
53
+
54
+ class HmacSha512Etm < HmacSha512
55
+ NAME = "hmac-sha2-512-etm@openssh.com"
56
+
57
+ def etm? = true
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Mac
5
+ class None < Algorithm
6
+ def compute(sequence, data) = nil
7
+ end
8
+ end
9
+ end
data/lib/crussh/mac.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Mac
5
+ HMAC_SHA256 = "hmac-sha2-256"
6
+ HMAC_SHA256_ETM = "hmac-sha2-256-etm@openssh.com"
7
+ HMAC_SHA512 = "hmac-sha2-512"
8
+ HMAC_SHA512_ETM = "hmac-sha2-512-etm@openssh.com"
9
+ NONE = "none"
10
+
11
+ DEFAULT = [
12
+ HMAC_SHA512_ETM,
13
+ HMAC_SHA256_ETM,
14
+ ].freeze
15
+
16
+ REGISTRY = {}
17
+
18
+ class << self
19
+ def from_name(name, key)
20
+ algorithm_class = REGISTRY[name]
21
+
22
+ raise UnknownAlgorithm, "Unknown MAC algorithm: #{name}" if algorithm_class.nil?
23
+
24
+ algorithm_class.new(key)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ class Negotiator
5
+ def initialize(client_kexinit, server_kexinit)
6
+ @client_kexinit = client_kexinit
7
+ @server_kexinit = server_kexinit
8
+ end
9
+
10
+ def negotiate
11
+ Algorithms.new(
12
+ kex: pick!(:kex_algorithms),
13
+ host_key: pick!(:server_host_key_algorithms),
14
+ cipher_client_to_server: pick!(:cipher_client_to_server),
15
+ cipher_server_to_client: pick!(:cipher_server_to_client),
16
+ mac_client_to_server: pick(:mac_client_to_server),
17
+ mac_server_to_client: pick(:mac_server_to_client),
18
+ compression_client_to_server: pick!(:compression_client_to_server),
19
+ compression_server_to_client: pick!(:compression_server_to_client),
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ def pick!(category)
26
+ pick(category) || raise(NegotiationError, "No supported common algorithm for #{category}")
27
+ end
28
+
29
+ def pick(category)
30
+ server_list, client_list = extract_lists(category)
31
+
32
+ return if client_list.empty? || server_list.empty?
33
+
34
+ client_list.find { |algorithm| server_list.include?(algorithm) }
35
+ end
36
+
37
+ def extract_lists(category)
38
+ [@server_kexinit.send(category), @client_kexinit.send(category)]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ # Algorithm preferences - ordered by preference (first = most preferred)
5
+ class Preferred
6
+ def initialize
7
+ @kex = Kex::DEFAULT
8
+ @host_key = Keys::DEFAULT
9
+ @cipher = Cipher::DEFAULT
10
+ @mac = Mac::DEFAULT
11
+ @compression = Compression::DEFAULT
12
+ end
13
+
14
+ attr_accessor :kex, :host_key, :cipher, :mac, :compression
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelClose < Message
6
+ message_type CHANNEL_CLOSE
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 ChannelData < Message
6
+ message_type CHANNEL_DATA
7
+
8
+ field :recipient_channel, :uint32
9
+ field :data, :string
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelEof < Message
6
+ message_type CHANNEL_EOF
7
+
8
+ field :recipient_channel, :uint32
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelExtendedData < Message
6
+ message_type CHANNEL_EXTENDED_DATA
7
+
8
+ field :recipient_channel, :uint32
9
+ field :data_type_code, :uint32
10
+ field :data, :string
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelFailure < Message
6
+ message_type CHANNEL_FAILURE
7
+
8
+ field :recipient_channel, :uint32
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelOpen < Message
6
+ message_type CHANNEL_OPEN
7
+
8
+ field :channel_type, :string
9
+ field :sender_channel, :uint32
10
+ field :initial_window_size, :uint32
11
+ field :maximum_packet_size, :uint32
12
+ field :channel_data, :remaining
13
+
14
+ def x11?
15
+ channel_type == "x11"
16
+ end
17
+
18
+ def forwarded_tcpip?
19
+ channel_type == "forwarded-tcpip"
20
+ end
21
+
22
+ def direct_tcpip?
23
+ channel_type == "direct-tcpip"
24
+ end
25
+
26
+ def x11
27
+ return @x11 if @x11
28
+ return unless x11?
29
+
30
+ reader = Transport::Reader.new(channel_data)
31
+
32
+ originator_address = reader.string
33
+ originator_port = reader.uint32
34
+
35
+ @x11 = X11.new(originator_address:, originator_port:)
36
+ end
37
+ X11 = Data.define(:originator_address, :originator_port)
38
+
39
+ def forwarded_tcpip
40
+ return @forwarded_tcpip if @forwarded_tcpip
41
+ return unless forwarded_tcpip?
42
+
43
+ reader = Transport::Reader.new(channel_data)
44
+
45
+ address = reader.string
46
+ port = reader.uint32
47
+ originator_address = reader.string
48
+ originator_port = reader.uint32
49
+
50
+ @forwarded_tcpip = Tcpip.new(address:, port:, originator_address:, originator_port:)
51
+ end
52
+ Tcpip = Data.define(:address, :port, :originator_address, :originator_port)
53
+
54
+ def direct_tcpip
55
+ return @direct_tcpip if @direct_tcpip
56
+ return unless direct_tcpip?
57
+
58
+ reader = Transport::Reader.new(channel_data)
59
+
60
+ address = reader.string
61
+ port = reader.uint32
62
+ originator_address = reader.string
63
+ originator_port = reader.uint32
64
+
65
+ @direct_tcpip = Tcpip.new(address:, port:, originator_address:, originator_port:)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelOpenConfirmation < Message
6
+ message_type CHANNEL_OPEN_CONFIRMATION
7
+
8
+ field :recipient_channel, :uint32
9
+ field :sender_channel, :uint32
10
+ field :initial_window_size, :uint32
11
+ field :maximum_packet_size, :uint32
12
+ field :channel_data, :remaining, default: ""
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crussh
4
+ module Protocol
5
+ class ChannelOpenFailure < Message
6
+ message_type CHANNEL_OPEN_FAILURE
7
+
8
+ field :recipient_channel, :uint32
9
+ field :reason_code, :uint32
10
+ field :description, :string
11
+ field :language, :string, default: ""
12
+ end
13
+ end
14
+ end