net-ssh 5.2.0 → 6.0.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 (69) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +7 -4
  6. data/.rubocop_todo.yml +392 -379
  7. data/.travis.yml +16 -17
  8. data/CHANGES.txt +11 -0
  9. data/Manifest +0 -1
  10. data/README.md +286 -0
  11. data/Rakefile +1 -2
  12. data/appveyor.yml +4 -2
  13. data/lib/net/ssh.rb +7 -2
  14. data/lib/net/ssh/authentication/certificate.rb +10 -1
  15. data/lib/net/ssh/authentication/ed25519.rb +2 -1
  16. data/lib/net/ssh/authentication/ed25519_loader.rb +1 -1
  17. data/lib/net/ssh/authentication/key_manager.rb +34 -5
  18. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -1
  19. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +0 -1
  20. data/lib/net/ssh/authentication/session.rb +9 -6
  21. data/lib/net/ssh/buffer.rb +1 -10
  22. data/lib/net/ssh/buffered_io.rb +0 -1
  23. data/lib/net/ssh/config.rb +51 -32
  24. data/lib/net/ssh/connection/channel.rb +17 -5
  25. data/lib/net/ssh/connection/event_loop.rb +0 -1
  26. data/lib/net/ssh/connection/session.rb +7 -4
  27. data/lib/net/ssh/key_factory.rb +6 -8
  28. data/lib/net/ssh/known_hosts.rb +27 -29
  29. data/lib/net/ssh/loggable.rb +2 -2
  30. data/lib/net/ssh/proxy/command.rb +0 -1
  31. data/lib/net/ssh/proxy/socks5.rb +0 -1
  32. data/lib/net/ssh/service/forward.rb +2 -1
  33. data/lib/net/ssh/test.rb +3 -2
  34. data/lib/net/ssh/transport/algorithms.rb +67 -42
  35. data/lib/net/ssh/transport/cipher_factory.rb +11 -27
  36. data/lib/net/ssh/transport/constants.rb +10 -6
  37. data/lib/net/ssh/transport/ctr.rb +1 -7
  38. data/lib/net/ssh/transport/hmac.rb +15 -13
  39. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  40. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  41. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  42. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  43. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  44. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  45. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  46. data/lib/net/ssh/transport/kex.rb +14 -11
  47. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  48. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  49. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  50. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  51. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +1 -15
  52. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +9 -118
  53. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +0 -6
  54. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  55. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +18 -79
  56. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
  57. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
  58. data/lib/net/ssh/transport/openssl.rb +104 -107
  59. data/lib/net/ssh/transport/packet_stream.rb +44 -11
  60. data/lib/net/ssh/transport/state.rb +1 -1
  61. data/lib/net/ssh/version.rb +2 -2
  62. data/net-ssh-public_cert.pem +8 -8
  63. data/net-ssh.gemspec +9 -7
  64. metadata +46 -29
  65. metadata.gz.sig +2 -3
  66. data/Gemfile.noed25519.lock +0 -41
  67. data/README.rdoc +0 -194
  68. data/lib/net/ssh/ruby_compat.rb +0 -13
  69. data/support/arcfour_check.rb +0 -20
@@ -7,6 +7,8 @@ require 'net/ssh/transport/hmac/sha2_256'
7
7
  require 'net/ssh/transport/hmac/sha2_256_96'
8
8
  require 'net/ssh/transport/hmac/sha2_512'
9
9
  require 'net/ssh/transport/hmac/sha2_512_96'
10
+ require 'net/ssh/transport/hmac/sha2_256_etm'
11
+ require 'net/ssh/transport/hmac/sha2_512_etm'
10
12
  require 'net/ssh/transport/hmac/ripemd160'
11
13
  require 'net/ssh/transport/hmac/none'
12
14
 
@@ -15,21 +17,21 @@ require 'net/ssh/transport/hmac/none'
15
17
  module Net::SSH::Transport::HMAC
16
18
  # The mapping of SSH hmac algorithms to their implementations
17
19
  MAP = {
18
- 'hmac-md5' => MD5,
19
- 'hmac-md5-96' => MD5_96,
20
- 'hmac-sha1' => SHA1,
21
- 'hmac-sha1-96' => SHA1_96,
22
- 'hmac-ripemd160' => RIPEMD160,
23
- 'hmac-ripemd160@openssh.com' => RIPEMD160,
24
- 'none' => None
20
+ 'hmac-md5' => MD5,
21
+ 'hmac-md5-96' => MD5_96,
22
+ 'hmac-sha1' => SHA1,
23
+ 'hmac-sha1-96' => SHA1_96,
24
+ 'hmac-sha2-256' => SHA2_256,
25
+ 'hmac-sha2-256-96' => SHA2_256_96,
26
+ 'hmac-sha2-512' => SHA2_512,
27
+ 'hmac-sha2-512-96' => SHA2_512_96,
28
+ 'hmac-sha2-256-etm@openssh.com' => SHA2_256_Etm,
29
+ 'hmac-sha2-512-etm@openssh.com' => SHA2_512_Etm,
30
+ 'hmac-ripemd160' => RIPEMD160,
31
+ 'hmac-ripemd160@openssh.com' => RIPEMD160,
32
+ 'none' => None
25
33
  }
26
34
 
27
- # add mapping to sha2 hmac algorithms if they're available
28
- MAP['hmac-sha2-256'] = SHA2_256 if defined?(::Net::SSH::Transport::HMAC::SHA2_256)
29
- MAP['hmac-sha2-256-96'] = SHA2_256_96 if defined?(::Net::SSH::Transport::HMAC::SHA2_256_96)
30
- MAP['hmac-sha2-512'] = SHA2_512 if defined?(::Net::SSH::Transport::HMAC::SHA2_512)
31
- MAP['hmac-sha2-512-96'] = SHA2_512_96 if defined?(::Net::SSH::Transport::HMAC::SHA2_512_96)
32
-
33
35
  # Retrieves a new hmac instance of the given SSH type (+name+). If +key+ is
34
36
  # given, the new instance will be initialized with that key.
35
37
  def self.get(name, key="", parameters = {})
@@ -9,6 +9,18 @@ module Net
9
9
  # The base class of all OpenSSL-based HMAC algorithm wrappers.
10
10
  class Abstract
11
11
  class <<self
12
+ def etm(*v)
13
+ @etm = false if !defined?(@etm)
14
+ if v.empty?
15
+ @etm = superclass.etm if @etm.nil? && superclass.respond_to?(:etm)
16
+ return @etm
17
+ elsif v.length == 1
18
+ @etm = v.first
19
+ else
20
+ raise ArgumentError, "wrong number of arguments (#{v.length} for 1)"
21
+ end
22
+ end
23
+
12
24
  def key_length(*v)
13
25
  @key_length = nil if !defined?(@key_length)
14
26
  if v.empty?
@@ -46,6 +58,10 @@ module Net
46
58
  end
47
59
  end
48
60
 
61
+ def etm
62
+ self.class.etm
63
+ end
64
+
49
65
  def key_length
50
66
  self.class.key_length
51
67
  end
@@ -1,15 +1,11 @@
1
1
  require 'net/ssh/transport/hmac/abstract'
2
2
 
3
- if defined?(OpenSSL::Digest::SHA256) # need openssl support
4
- module Net::SSH::Transport::HMAC
5
-
6
- # The SHA-256 HMAC algorithm. This has a mac and key length of 32, and
7
- # uses the SHA-256 digest algorithm.
8
- class SHA2_256 < Abstract
9
- mac_length 32
10
- key_length 32
11
- digest_class OpenSSL::Digest::SHA256
12
- end
13
-
3
+ module Net::SSH::Transport::HMAC
4
+ # The SHA-256 HMAC algorithm. This has a mac and key length of 32, and
5
+ # uses the SHA-256 digest algorithm.
6
+ class SHA2_256 < Abstract
7
+ mac_length 32
8
+ key_length 32
9
+ digest_class OpenSSL::Digest::SHA256
14
10
  end
15
11
  end
@@ -1,13 +1,9 @@
1
1
  require 'net/ssh/transport/hmac/abstract'
2
2
 
3
3
  module Net::SSH::Transport::HMAC
4
-
5
- if defined?(SHA2_256) # need openssl support
6
- # The SHA256-96 HMAC algorithm. This returns only the first 12 bytes of
7
- # the digest.
8
- class SHA2_256_96 < SHA2_256
9
- mac_length 12
10
- end
4
+ # The SHA256-96 HMAC algorithm. This returns only the first 12 bytes of
5
+ # the digest.
6
+ class SHA2_256_96 < SHA2_256
7
+ mac_length 12
11
8
  end
12
-
13
9
  end
@@ -0,0 +1,12 @@
1
+ require 'net/ssh/transport/hmac/abstract'
2
+
3
+ module Net::SSH::Transport::HMAC
4
+ # The SHA-256 Encrypt-Then-Mac HMAC algorithm. This has a mac and
5
+ # key length of 32, and uses the SHA-256 digest algorithm.
6
+ class SHA2_256_Etm < Abstract
7
+ etm true
8
+ mac_length 32
9
+ key_length 32
10
+ digest_class OpenSSL::Digest::SHA256
11
+ end
12
+ end
@@ -1,14 +1,11 @@
1
1
  require 'net/ssh/transport/hmac/abstract'
2
2
 
3
3
  module Net::SSH::Transport::HMAC
4
-
5
- if defined?(OpenSSL::Digest::SHA512) # need openssl support
6
- # The SHA-512 HMAC algorithm. This has a mac and key length of 64, and
7
- # uses the SHA-512 digest algorithm.
8
- class SHA2_512 < Abstract
9
- mac_length 64
10
- key_length 64
11
- digest_class OpenSSL::Digest::SHA512
12
- end
4
+ # The SHA-512 HMAC algorithm. This has a mac and key length of 64, and
5
+ # uses the SHA-512 digest algorithm.
6
+ class SHA2_512 < Abstract
7
+ mac_length 64
8
+ key_length 64
9
+ digest_class OpenSSL::Digest::SHA512
13
10
  end
14
11
  end
@@ -1,13 +1,9 @@
1
1
  require 'net/ssh/transport/hmac/abstract'
2
2
 
3
3
  module Net::SSH::Transport::HMAC
4
-
5
- if defined?(SHA2_512) # need openssl support
6
- # The SHA2-512-96 HMAC algorithm. This returns only the first 12 bytes of
7
- # the digest.
8
- class SHA2_512_96 < SHA2_512
9
- mac_length 12
10
- end
4
+ # The SHA2-512-96 HMAC algorithm. This returns only the first 12 bytes of
5
+ # the digest.
6
+ class SHA2_512_96 < SHA2_512
7
+ mac_length 12
11
8
  end
12
-
13
9
  end
@@ -0,0 +1,12 @@
1
+ require 'net/ssh/transport/hmac/abstract'
2
+
3
+ module Net::SSH::Transport::HMAC
4
+ # The SHA-512 Encrypt-Then-Mac HMAC algorithm. This has a mac and
5
+ # key length of 64, and uses the SHA-512 digest algorithm.
6
+ class SHA2_512_Etm < Abstract
7
+ etm true
8
+ mac_length 64
9
+ key_length 64
10
+ digest_class OpenSSL::Digest::SHA512
11
+ end
12
+ end
@@ -2,25 +2,28 @@ require 'net/ssh/transport/kex/diffie_hellman_group1_sha1'
2
2
  require 'net/ssh/transport/kex/diffie_hellman_group14_sha1'
3
3
  require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1'
4
4
  require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha256'
5
+ require 'net/ssh/transport/kex/ecdh_sha2_nistp256'
6
+ require 'net/ssh/transport/kex/ecdh_sha2_nistp384'
7
+ require 'net/ssh/transport/kex/ecdh_sha2_nistp521'
8
+ require 'net/ssh/transport/kex/curve25519_sha256_loader'
5
9
 
6
10
  module Net::SSH::Transport
7
11
  module Kex
8
12
  # Maps the supported key-exchange algorithms as named by the SSH protocol
9
13
  # to their corresponding implementors.
10
14
  MAP = {
11
- 'diffie-hellman-group-exchange-sha1' => DiffieHellmanGroupExchangeSHA1,
12
- 'diffie-hellman-group1-sha1' => DiffieHellmanGroup1SHA1,
13
- 'diffie-hellman-group14-sha1' => DiffieHellmanGroup14SHA1
15
+ 'diffie-hellman-group1-sha1' => DiffieHellmanGroup1SHA1,
16
+ 'diffie-hellman-group14-sha1' => DiffieHellmanGroup14SHA1,
17
+ 'diffie-hellman-group-exchange-sha1' => DiffieHellmanGroupExchangeSHA1,
18
+ 'diffie-hellman-group-exchange-sha256' => DiffieHellmanGroupExchangeSHA256,
19
+ 'ecdh-sha2-nistp256' => EcdhSHA2NistP256,
20
+ 'ecdh-sha2-nistp384' => EcdhSHA2NistP384,
21
+ 'ecdh-sha2-nistp521' => EcdhSHA2NistP521
14
22
  }
15
- MAP['diffie-hellman-group-exchange-sha256'] = DiffieHellmanGroupExchangeSHA256 if defined?(DiffieHellmanGroupExchangeSHA256)
16
- if defined?(OpenSSL::PKey::EC)
17
- require 'net/ssh/transport/kex/ecdh_sha2_nistp256'
18
- require 'net/ssh/transport/kex/ecdh_sha2_nistp384'
19
- require 'net/ssh/transport/kex/ecdh_sha2_nistp521'
20
23
 
21
- MAP['ecdh-sha2-nistp256'] = EcdhSHA2NistP256
22
- MAP['ecdh-sha2-nistp384'] = EcdhSHA2NistP384
23
- MAP['ecdh-sha2-nistp521'] = EcdhSHA2NistP521
24
+ if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED
25
+ MAP['curve25519-sha256'] = Curve25519Sha256
26
+ MAP['curve25519-sha256@libssh.org'] = Curve25519Sha256
24
27
  end
25
28
  end
26
29
  end
@@ -0,0 +1,123 @@
1
+ require 'net/ssh/buffer'
2
+ require 'net/ssh/errors'
3
+ require 'net/ssh/loggable'
4
+ require 'net/ssh/transport/openssl'
5
+ require 'net/ssh/transport/constants'
6
+
7
+ module Net
8
+ module SSH
9
+ module Transport
10
+ module Kex
11
+ # Abstract class that implement Diffie-Hellman Key Exchange
12
+ # See https://tools.ietf.org/html/rfc4253#page-21
13
+ class Abstract
14
+ include Loggable
15
+ include Constants
16
+
17
+ attr_reader :algorithms
18
+ attr_reader :connection
19
+ attr_reader :data
20
+ attr_reader :dh
21
+
22
+ # Create a new instance of the Diffie-Hellman Key Exchange algorithm.
23
+ # The Diffie-Hellman (DH) key exchange provides a shared secret that
24
+ # cannot be determined by either party alone. The key exchange is
25
+ # combined with a signature with the host key to provide host
26
+ # authentication.
27
+ def initialize(algorithms, connection, data)
28
+ @algorithms = algorithms
29
+ @connection = connection
30
+
31
+ @data = data.dup
32
+ @dh = generate_key
33
+ @logger = @data.delete(:logger)
34
+ end
35
+
36
+ # Perform the key-exchange for the given session, with the given
37
+ # data. This method will return a hash consisting of the
38
+ # following keys:
39
+ #
40
+ # * :session_id
41
+ # * :server_key
42
+ # * :shared_secret
43
+ # * :hashing_algorithm
44
+ #
45
+ # The caller is expected to be able to understand how to use these
46
+ # deliverables.
47
+ def exchange_keys
48
+ result = send_kexinit
49
+ verify_server_key(result[:server_key])
50
+ session_id = verify_signature(result)
51
+ confirm_newkeys
52
+
53
+ {
54
+ session_id: session_id,
55
+ server_key: result[:server_key],
56
+ shared_secret: result[:shared_secret],
57
+ hashing_algorithm: digester
58
+ }
59
+ end
60
+
61
+ def digester
62
+ raise NotImplementedError, 'abstract class: digester not implemented'
63
+ end
64
+
65
+ private
66
+
67
+ # Verify that the given key is of the expected type, and that it
68
+ # really is the key for the session's host. Raise Net::SSH::Exception
69
+ # if it is not.
70
+ def verify_server_key(key) #:nodoc:
71
+ if key.ssh_type != algorithms.host_key
72
+ raise Net::SSH::Exception, "host key algorithm mismatch '#{key.ssh_type}' != '#{algorithms.host_key}'"
73
+ end
74
+
75
+ blob, fingerprint = generate_key_fingerprint(key)
76
+
77
+ unless connection.host_key_verifier.verify(key: key, key_blob: blob, fingerprint: fingerprint, session: connection)
78
+ raise Net::SSH::Exception, 'host key verification failed'
79
+ end
80
+ end
81
+
82
+ def generate_key_fingerprint(key)
83
+ blob = Net::SSH::Buffer.from(:key, key).to_s
84
+
85
+ fingerprint = Net::SSH::Authentication::PubKeyFingerprint.fingerprint(blob, @connection.options[:fingerprint_hash] || 'SHA256')
86
+
87
+ [blob, fingerprint]
88
+ rescue StandardError => e
89
+ [nil, "(could not generate fingerprint: #{e.message})"]
90
+ end
91
+
92
+ # Verify the signature that was received. Raise Net::SSH::Exception
93
+ # if the signature could not be verified. Otherwise, return the new
94
+ # session-id.
95
+ def verify_signature(result) #:nodoc:
96
+ response = build_signature_buffer(result)
97
+
98
+ hash = digester.digest(response.to_s)
99
+
100
+ unless connection.host_key_verifier.verify_signature { result[:server_key].ssh_do_verify(result[:server_sig], hash) }
101
+ raise Net::SSH::Exception, 'could not verify server signature'
102
+ end
103
+
104
+ hash
105
+ end
106
+
107
+ # Send the NEWKEYS message, and expect the NEWKEYS message in
108
+ # reply.
109
+ def confirm_newkeys #:nodoc:
110
+ # send own NEWKEYS message first (the wodSSHServer won't send first)
111
+ response = Net::SSH::Buffer.new
112
+ response.write_byte(NEWKEYS)
113
+ connection.send_message(response)
114
+
115
+ # wait for the server's NEWKEYS message
116
+ buffer = connection.next_message
117
+ raise Net::SSH::Exception, 'expected NEWKEYS' unless buffer.type == NEWKEYS
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,72 @@
1
+ require 'net/ssh/transport/kex/abstract'
2
+
3
+ module Net
4
+ module SSH
5
+ module Transport
6
+ module Kex
7
+ # Implement key-exchange algorithm from Elliptic Curve Algorithm Integration
8
+ # in the Secure Shell Transport Layer (RFC 5656)
9
+ class Abstract5656 < Abstract
10
+ alias ecdh dh
11
+
12
+ def curve_name
13
+ raise NotImplementedError, 'abstract class: curve_name not implemented'
14
+ end
15
+
16
+ private
17
+
18
+ def get_message_types
19
+ [KEXECDH_INIT, KEXECDH_REPLY]
20
+ end
21
+
22
+ def build_signature_buffer(result)
23
+ response = Net::SSH::Buffer.new
24
+ response.write_string data[:client_version_string],
25
+ data[:server_version_string],
26
+ data[:client_algorithm_packet],
27
+ data[:server_algorithm_packet],
28
+ result[:key_blob],
29
+ ecdh_public_key_bytes,
30
+ result[:server_ecdh_pubkey]
31
+ response.write_bignum result[:shared_secret]
32
+ response
33
+ end
34
+
35
+ def send_kexinit #:nodoc:
36
+ init, reply = get_message_types
37
+
38
+ # send the KEXECDH_INIT message
39
+ ## byte SSH_MSG_KEX_ECDH_INIT
40
+ ## string Q_C, client's ephemeral public key octet string
41
+ buffer = Net::SSH::Buffer.from(:byte, init, :mstring, ecdh_public_key_bytes)
42
+ connection.send_message(buffer)
43
+
44
+ # expect the following KEXECDH_REPLY message
45
+ ## byte SSH_MSG_KEX_ECDH_REPLY
46
+ ## string K_S, server's public host key
47
+ ## string Q_S, server's ephemeral public key octet string
48
+ ## string the signature on the exchange hash
49
+ buffer = connection.next_message
50
+ raise Net::SSH::Exception, 'expected REPLY' unless buffer.type == reply
51
+
52
+ result = {}
53
+ result[:key_blob] = buffer.read_string
54
+ result[:server_key] = Net::SSH::Buffer.new(result[:key_blob]).read_key
55
+ result[:server_ecdh_pubkey] = buffer.read_string
56
+ result[:shared_secret] = compute_shared_secret(result[:server_ecdh_pubkey])
57
+
58
+ sig_buffer = Net::SSH::Buffer.new(buffer.read_string)
59
+ sig_type = sig_buffer.read_string
60
+ if sig_type != algorithms.host_key_format
61
+ raise Net::SSH::Exception, "host key algorithm mismatch for signature '#{sig_type}' != '#{algorithms.host_key_format}'"
62
+ end
63
+
64
+ result[:server_sig] = sig_buffer.read_string
65
+
66
+ result
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,38 @@
1
+ gem 'x25519' # raise if the gem x25519 is not installed
2
+
3
+ require 'x25519'
4
+ require 'net/ssh/transport/constants'
5
+ require 'net/ssh/transport/kex/abstract5656'
6
+
7
+ module Net
8
+ module SSH
9
+ module Transport
10
+ module Kex
11
+ # A key-exchange service implementing the "curve25519-sha256@libssh.org"
12
+ # key-exchange algorithm. (defined in https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-06)
13
+ class Curve25519Sha256 < Abstract5656
14
+ def digester
15
+ OpenSSL::Digest::SHA256
16
+ end
17
+
18
+ private
19
+
20
+ def generate_key #:nodoc:
21
+ ::X25519::Scalar.generate
22
+ end
23
+
24
+ ## string Q_C, client's ephemeral public key octet string
25
+ def ecdh_public_key_bytes
26
+ ecdh.public_key.to_bytes
27
+ end
28
+
29
+ # compute shared secret from server's public key and client's private key
30
+ def compute_shared_secret(server_ecdh_pubkey)
31
+ pk = ::X25519::MontgomeryU.new(server_ecdh_pubkey)
32
+ OpenSSL::BN.new(ecdh.diffie_hellman(pk).to_bytes, 2)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end