diff options
166 files changed, 2726 insertions, 2205 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..363814f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: CI +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-18.04 + continue-on-error: ${{ matrix.flaky }} + strategy: + matrix: + ruby-version: [2.7.2, 2.6.6, 3.0.1] + flaky: [false] + include: + - ruby-version: 2.5.8 + flaky: true + steps: + - uses: actions/checkout@v1 + + - name: Set up Ruby ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Cache bundler + uses: actions/cache@v1 + id: bundler-cache + with: + path: vendor/bundle + key: ${{ runner.os }}-${{ matrix.ruby-version }}-gem-v3-${{ hashFiles('**/Gemfile') }}-${{ hashFiles('**/net-ssh.gemspec') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.ruby-version }}-gem-v3- + + - name: Cache pip + uses: actions/cache@v1 + id: pip-cache + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-v1 + restore-keys: | + ${{ runner.os }}-pip-v1 + - name: Bundle install + run: | + gem install bundler + bundle config set path 'vendor/bundle' + bundle config set --local path 'vendor/bundle' + bundle install --jobs 4 --retry 3 --path vendor/bundle + BUNDLE_GEMFILE=./Gemfile.noed25519 bundle install --jobs 4 --retry 3 --path vendor/bundle + env: + BUNDLE_PATH: vendor/bundle + + - name: Add to etc/hosts + run: | + sudo echo "127.0.0.1 gateway.netssh" | sudo tee -a /etc/hosts + - name: Check sshd_config + run: sudo cat '/etc/ssh/sshd_config' || true + - name: Check sshd_config2 + run: sudo cat /etc/ssh/sshd_config.d/*.conf || true + - name: Check sshd pid + run: sudo ps aux | grep sshd + - name: Ansible install + run: | + python -m pip install --upgrade pip + pip install ansible urllib3 pyOpenSSL ndg-httpsclient pyasn1 + ansible-galaxy install rvm.ruby + pwd + uname -a + export + who am i + ansible-playbook ./test/integration/playbook.yml -i "localhost," --become -c local -e 'no_rvm=true' -e 'myuser=runner' -e 'mygroup=runner' -e 'homedir=/home/runner' + - name: Check sshd_config + run: sudo cat '/etc/ssh/sshd_config' || true + - name: Check sshd pid + run: sudo ps aux | grep sshd + - name: Check sshd_config2 + run: sudo cat /etc/ssh/sshd_config.d/*.conf || true + - name: Run Tests + run: bundle exec rake test + env: + NET_SSH_RUN_INTEGRATION_TESTS: 1 + CI: 1 + - name: Run Tests (without ed25519) + run: bundle exec rake test + env: + BUNDLE_GEMFILE: ./Gemfile.noed25519 + NET_SSH_RUN_INTEGRATION_TESTS: 1 + CI: 1 + - name: Run test helper test + run: bundle exec rake test_test + - name: Rubocop + if: matrix.ruby-version == '3.0.1' + run: bundle exec rubocop --fail-level C -f s @@ -9,3 +9,5 @@ test/integration/.vagrant test/integration/playbook.retry .byebug_history + +tryout diff --git a/.rubocop.yml b/.rubocop.yml index 41e4915..aa0c0cb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,11 +1,22 @@ +AllCops: + Exclude: + - 'tryout/**/*' + - "vendor/**/.*" + - "vendor/**/*" + NewCops: enable + TargetRubyVersion: 2.5 + inherit_from: .rubocop_todo.yml Style/DoubleNegation: Exclude: - 'lib/net/ssh/key_factory.rb' -Metrics/LineLength: +Layout/LineLength: Max: 150 Exclude: - 'test/**/*.rb' - 'net-ssh.gemspec' + +Style/EmptyLiteral: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a1b7067..0cec116 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,71 +1,20 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-09-10 17:13:26 +0200 using RuboCop version 0.74.0. +# on 2021-10-25 13:17:23 UTC using RuboCop version 1.22.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 76 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: with_first_argument, with_fixed_indentation -Layout/AlignArguments: - Enabled: false - -# Offense count: 57 -# Cop supports --auto-correct. -# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. -# SupportedHashRocketStyles: key, separator, table -# SupportedColonStyles: key, separator, table -# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit -Layout/AlignHash: - Exclude: - - 'lib/net/ssh/key_factory.rb' - - 'lib/net/ssh/transport/cipher_factory.rb' - - 'lib/net/ssh/transport/hmac.rb' - - 'lib/net/ssh/transport/kex.rb' - - 'test/test_config.rb' - -# Offense count: 70 -# Cop supports --auto-correct. -Layout/EmptyLineAfterGuardClause: - Enabled: false - -# Offense count: 170 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines -Layout/EmptyLinesAroundModuleBody: - Enabled: false - -# Offense count: 2 +# Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_brackets -Layout/IndentFirstArrayElement: - Exclude: - - 'lib/net/ssh/transport/openssl.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -Layout/LeadingBlankLines: - Exclude: - - 'Rakefile' - - 'lib/net/ssh/authentication/pub_key_fingerprint.rb' - - 'support/arcfour_check.rb' - -# Offense count: 14 -# Cop supports --auto-correct. -# Configuration parameters: AllowDoxygenCommentStyle. -Layout/LeadingCommentSpace: +# SupportedStyles: aligned, indented +Layout/LineEndStringConcatenationIndentation: Exclude: - - 'test/integration/test_ed25519_pkeys.rb' - - 'test/integration/test_forward.rb' - - 'test/integration/test_id_rsa_keys.rb' - - 'test/integration/test_proxy.rb' + - 'lib/net/ssh/transport/algorithms.rb' -# Offense count: 23 +# Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented @@ -75,31 +24,8 @@ Layout/MultilineOperationIndentation: - 'lib/net/ssh/proxy/https.rb' - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/state.rb' - - 'lib/net/ssh/verifiers/always.rb' - - 'test/authentication/methods/test_hostbased.rb' - - 'test/authentication/methods/test_publickey.rb' -# Offense count: 16 -# Cop supports --auto-correct. -Layout/SpaceAfterColon: - Exclude: - - 'lib/net/ssh/authentication/ed25519.rb' - - 'test/integration/test_ed25519_pkeys.rb' - - 'test/verifiers/test_always.rb' - -# Offense count: 281 -# Cop supports --auto-correct. -Layout/SpaceAfterComma: - Enabled: false - -# Offense count: 136 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceAroundEqualsInParameterDefault: - Enabled: false - -# Offense count: 10 +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space @@ -109,9 +35,6 @@ Layout/SpaceInsideBlockBraces: - 'lib/net/ssh/authentication/session.rb' - 'lib/net/ssh/transport/ctr.rb' - 'support/ssh_tunnel_bug.rb' - - 'test/authentication/test_agent.rb' - - 'test/authentication/test_key_manager.rb' - - 'test/start/test_user_nil.rb' # Offense count: 6 # Cop supports --auto-correct. @@ -122,18 +45,18 @@ Layout/SpaceInsideReferenceBrackets: Exclude: - 'lib/net/ssh/transport/algorithms.rb' -# Offense count: 17 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: final_newline, final_blank_line -Layout/TrailingBlankLines: - Enabled: false - -# Offense count: 739 +# Offense count: 11 # Cop supports --auto-correct. -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Enabled: false +Lint/AmbiguousOperatorPrecedence: + Exclude: + - 'lib/net/ssh/authentication/certificate.rb' + - 'lib/net/ssh/config.rb' + - 'lib/net/ssh/loggable.rb' + - 'lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb' + - 'lib/net/ssh/transport/openssl.rb' + - 'lib/net/ssh/transport/state.rb' + - 'lib/net/ssh/version.rb' + - 'test/integration/test_proxy.rb' # Offense count: 4 # Configuration parameters: AllowSafeAssignment. @@ -144,76 +67,124 @@ Lint/AssignmentInCondition: - 'lib/net/ssh/proxy/command.rb' # Offense count: 1 -Lint/EmptyWhen: +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: Exclude: - - 'lib/net/ssh/config.rb' + - 'test/transport/test_cipher_factory.rb' -# Offense count: 9 -# Configuration parameters: AllowComments. -Lint/HandleExceptions: +# Offense count: 1 +# Cop supports --auto-correct. +Lint/DeprecatedClassMethods: + Exclude: + - 'lib/net/ssh/transport/packet_stream.rb' + +# Offense count: 10 +# Cop supports --auto-correct. +Lint/DeprecatedOpenSSLConstant: Exclude: - - 'lib/net/ssh/authentication/session.rb' - - 'lib/net/ssh/known_hosts.rb' - 'lib/net/ssh/transport/openssl.rb' - - 'test/integration/common.rb' - - 'test/integration/test_forward.rb' - - 'test/start/test_connection.rb' + +# Offense count: 2 +# Configuration parameters: AllowComments, AllowEmptyLambdas. +Lint/EmptyBlock: + Exclude: + - 'test/common.rb' - 'test/start/test_transport.rb' +# Offense count: 1 +# Configuration parameters: AllowComments. +Lint/EmptyWhen: + Exclude: + - 'lib/net/ssh/config.rb' + # Offense count: 72 Lint/ImplicitStringConcatenation: Exclude: - 'lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' -# Offense count: 1 -Lint/LiteralAsCondition: +# Offense count: 4 +# Cop supports --auto-correct. +Lint/IncompatibleIoSelectWithFiberScheduler: Exclude: - - 'lib/net/ssh/authentication/pageant.rb' + - 'lib/net/ssh/transport/packet_stream.rb' + - 'lib/net/ssh/transport/server_version.rb' # Offense count: 2 +# Cop supports --auto-correct. Lint/Loop: Exclude: - 'lib/net/ssh/authentication/methods/password.rb' - 'lib/net/ssh/key_factory.rb' # Offense count: 3 -# Configuration parameters: MaximumRangeSize. -Lint/MissingCopEnableDirective: +Lint/MissingSuper: Exclude: - - 'test/authentication/test_agent.rb' - - 'test/authentication/test_certificate.rb' - - 'test/integration/test_agent.rb' + - 'lib/net/ssh/proxy/jump.rb' + - 'test/common.rb' + - 'test/integration/mitm_server.rb' # Offense count: 1 Lint/NonLocalExitFromIterator: Exclude: - 'lib/net/ssh/known_hosts.rb' +# Offense count: 2 +# Cop supports --auto-correct. +Lint/OrAssignmentToConstant: + Exclude: + - 'lib/net/ssh/authentication/pageant.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: AllowedImplicitNamespaces. +# AllowedImplicitNamespaces: Gem +Lint/RaiseException: + Exclude: + - 'Rakefile' + - 'lib/net/ssh/buffer.rb' + - 'lib/net/ssh/key_factory.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Lint/RedundantCopDisableDirective: + Exclude: + - 'lib/net/ssh/key_factory.rb' + # Offense count: 3 Lint/RescueException: Exclude: - 'lib/net/ssh/authentication/key_manager.rb' - 'lib/net/ssh/service/forward.rb' +# Offense count: 4 +# Cop supports --auto-correct. +Lint/SendWithMixinArgument: + Exclude: + - 'lib/net/ssh/test/extensions.rb' + # Offense count: 2 Lint/ShadowedException: Exclude: - 'lib/net/ssh/authentication/key_manager.rb' +# Offense count: 5 +# Configuration parameters: AllowComments, AllowNil. +Lint/SuppressedException: + Exclude: + - 'lib/net/ssh/authentication/session.rb' + - 'lib/net/ssh/transport/openssl.rb' + - 'test/integration/common.rb' + - 'test/integration/test_forward.rb' + # Offense count: 1 # Configuration parameters: AllowKeywordBlockArguments. Lint/UnderscorePrefixedVariableName: Exclude: - 'lib/net/ssh/test/local_packet.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Lint/UnneededRequireStatement: - Exclude: - - 'lib/net/ssh/ruby_compat.rb' - -# Offense count: 60 +# Offense count: 15 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: @@ -221,21 +192,15 @@ Lint/UnusedBlockArgument: - 'lib/net/ssh/connection/keepalive.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/service/forward.rb' - - 'test/authentication/methods/test_password.rb' - - 'test/authentication/test_agent.rb' - - 'test/common.rb' - - 'test/connection/test_channel.rb' - - 'test/connection/test_session.rb' - - 'test/transport/test_algorithms.rb' - - 'test/transport/test_hmac.rb' -# Offense count: 62 +# Offense count: 71 # Cop supports --auto-correct. -# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. Lint/UnusedMethodArgument: Enabled: false # Offense count: 3 +# Cop supports --auto-correct. # Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: @@ -251,13 +216,20 @@ Lint/UselessAssignment: - 'test/integration/common.rb' - 'test/integration/test_forward.rb' -# Offense count: 229 +# Offense count: 1 +# Cop supports --auto-correct. +Lint/UselessTimes: + Exclude: + - 'test/integration/test_forward.rb' + +# Offense count: 202 +# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: - Max: 89 + Max: 74 -# Offense count: 15 -# Configuration parameters: CountComments, ExcludedMethods. -# ExcludedMethods: refine +# Offense count: 16 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. +# IgnoredMethods: refine Metrics/BlockLength: Max: 59 @@ -266,37 +238,37 @@ Metrics/BlockLength: Metrics/BlockNesting: Max: 4 -# Offense count: 31 -# Configuration parameters: CountComments. +# Offense count: 33 +# Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 488 -# Offense count: 38 +# Offense count: 37 +# Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: - Max: 27 - Exclude: - - 'lib/net/ssh/config.rb' + Max: 32 -# Offense count: 211 -# Configuration parameters: CountComments, ExcludedMethods. +# Offense count: 229 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: - Max: 82 + Max: 72 -# Offense count: 2 -# Configuration parameters: CountComments. +# Offense count: 3 +# Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: Max: 160 -# Offense count: 1 -# Configuration parameters: CountKeywordArgs. +# Offense count: 2 +# Configuration parameters: Max, CountKeywordArgs. Metrics/ParameterLists: - Max: 6 + MaxOptionalParameters: 4 -# Offense count: 30 +# Offense count: 32 +# Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: - Max: 20 + Max: 32 -# Offense count: 9 +# Offense count: 10 Naming/AccessorMethodName: Exclude: - 'lib/net/ssh/authentication/methods/password.rb' @@ -308,30 +280,17 @@ Naming/AccessorMethodName: - 'lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb' # Offense count: 2 +# Cop supports --auto-correct. Naming/BinaryOperatorParameterName: Exclude: - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/version.rb' -# Offense count: 12 +# Offense count: 16 +# Configuration parameters: AllowedNames. +# AllowedNames: module_parent Naming/ClassAndModuleCamelCase: - Exclude: - - 'lib/net/ssh/transport/hmac/md5_96.rb' - - 'lib/net/ssh/transport/hmac/sha1_96.rb' - - 'lib/net/ssh/transport/hmac/sha2_256.rb' - - 'lib/net/ssh/transport/hmac/sha2_256_96.rb' - - 'lib/net/ssh/transport/hmac/sha2_256_etm.rb' - - 'lib/net/ssh/transport/hmac/sha2_512.rb' - - 'lib/net/ssh/transport/hmac/sha2_512_96.rb' - - 'lib/net/ssh/transport/hmac/sha2_512_etm.rb' - - 'test/transport/hmac/test_md5_96.rb' - - 'test/transport/hmac/test_sha1_96.rb' - - 'test/transport/hmac/test_sha2_256.rb' - - 'test/transport/hmac/test_sha2_256_96.rb' - - 'test/transport/hmac/test_sha2_256_etm.rb' - - 'test/transport/hmac/test_sha2_512.rb' - - 'test/transport/hmac/test_sha2_512_96.rb' - - 'test/transport/hmac/test_sha2_512_etm.rb' + Enabled: false # Offense count: 4 Naming/ConstantName: @@ -340,9 +299,9 @@ Naming/ConstantName: - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' - 'lib/net/ssh/transport/openssl.rb' -# Offense count: 11 -# Configuration parameters: Blacklist. -# Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) +# Offense count: 12 +# Configuration parameters: ForbiddenDelimiters. +# ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: Exclude: - 'test/authentication/test_agent.rb' @@ -352,7 +311,7 @@ Naming/HeredocDelimiterNaming: - 'test/integration/test_agent.rb' - 'test/test_key_factory.rb' -# Offense count: 4 +# Offense count: 5 # Configuration parameters: EnforcedStyleForLeadingUnderscores. # SupportedStylesForLeadingUnderscores: disallowed, required, optional Naming/MemoizedInstanceVariableName: @@ -361,7 +320,7 @@ Naming/MemoizedInstanceVariableName: - 'test/authentication/test_key_manager.rb' # Offense count: 32 -# Configuration parameters: EnforcedStyle. +# Configuration parameters: EnforcedStyle, IgnoredPatterns. # SupportedStyles: snake_case, camelCase Naming/MethodName: Exclude: @@ -374,19 +333,10 @@ Naming/MethodName: - 'test/test_config.rb' - 'test/test_key_factory.rb' -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: PreferredName. -Naming/RescuedExceptionsVariableName: - Exclude: - - 'lib/net/ssh/connection/session.rb' - - 'lib/net/ssh/service/forward.rb' - - 'lib/net/ssh/verifiers/accept_new.rb' - # Offense count: 23 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id, to, by, on, in, at, ip, db -Naming/UncommunicativeMethodParamName: +# AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to +Naming/MethodParameterName: Exclude: - 'lib/net/ssh/authentication/certificate.rb' - 'lib/net/ssh/authentication/ed25519.rb' @@ -394,21 +344,56 @@ Naming/UncommunicativeMethodParamName: - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/buffered_io.rb' - - 'lib/net/ssh/ruby_compat.rb' - 'lib/net/ssh/test/socket.rb' - 'lib/net/ssh/transport/ctr.rb' - 'lib/net/ssh/transport/hmac/abstract.rb' - 'lib/net/ssh/transport/identity_cipher.rb' - 'test/connection/test_session.rb' -# Offense count: 8 -# Configuration parameters: EnforcedStyle. +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: PreferredName. +Naming/RescuedExceptionsVariableName: + Exclude: + - 'lib/net/ssh/connection/session.rb' + - 'lib/net/ssh/service/forward.rb' + - 'lib/net/ssh/verifiers/accept_new.rb' + +# Offense count: 5 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 +Naming/VariableNumber: + Exclude: + - 'test/test_buffer.rb' + - 'test/test_known_hosts.rb' + - 'test/transport/test_identity_cipher.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Security/IoMethods: + Exclude: + - 'lib/net/ssh/config.rb' + +# Offense count: 2 +# Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols. # SupportedStyles: inline, group Style/AccessModifierDeclarations: Exclude: - 'lib/net/ssh/authentication/pageant.rb' - - 'lib/net/ssh/transport/openssl.rb' - - 'test/test_key_factory.rb' + +# Offense count: 31 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: separated, grouped +Style/AccessorGrouping: + Exclude: + - 'lib/net/ssh/authentication/certificate.rb' + - 'lib/net/ssh/transport/kex/abstract.rb' + - 'test/common.rb' + - 'test/connection/test_channel.rb' + - 'test/integration/mitm_server.rb' + - 'test/start/test_transport.rb' # Offense count: 2 # Cop supports --auto-correct. @@ -419,36 +404,19 @@ Style/Alias: - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/service/forward.rb' -# Offense count: 31 +# Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: always, conditionals Style/AndOr: Exclude: - - 'lib/net/ssh/authentication/key_manager.rb' - - 'lib/net/ssh/buffer.rb' - - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/connection/channel.rb' - 'lib/net/ssh/connection/session.rb' - - 'lib/net/ssh/key_factory.rb' - 'lib/net/ssh/service/forward.rb' - - 'lib/net/ssh/test/channel.rb' - - 'lib/net/ssh/test/script.rb' - - 'lib/net/ssh/transport/cipher_factory.rb' - - 'lib/net/ssh/transport/hmac.rb' - - 'lib/net/ssh/transport/key_expander.rb' - - 'test/common.rb' - -# Offense count: 2 -# Configuration parameters: AllowedChars. -Style/AsciiComments: - Exclude: - - 'lib/net/ssh/authentication/pageant.rb' - - 'lib/net/ssh/buffered_io.rb' -# Offense count: 8 +# Offense count: 9 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners. +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object # FunctionalMethods: let, let!, subject, watch @@ -463,53 +431,41 @@ Style/BlockDelimiters: - 'lib/net/ssh/transport/ctr.rb' - 'test/verifiers/test_always.rb' -# Offense count: 7 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: braces, no_braces, context_dependent -Style/BracesAroundHashParameters: - Exclude: - - 'lib/net/ssh/config.rb' - - 'test/integration/test_ed25519_pkeys.rb' - - 'test/integration/test_id_rsa_keys.rb' - - 'test/transport/test_hmac.rb' - - 'test/transport/test_session.rb' - # Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowOnConstant. Style/CaseEquality: Exclude: - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/connection/session.rb' +# Offense count: 2 +# Cop supports --auto-correct. +Style/CaseLikeIf: + Exclude: + - 'lib/net/ssh/transport/openssl.rb' + - 'test/connection/test_session.rb' + # Offense count: 1 # Cop supports --auto-correct. Style/CharacterLiteral: Exclude: - 'test/test_buffer.rb' -# Offense count: 15 +# Offense count: 18 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle. +# Configuration parameters: EnforcedStyle. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: ==, equal?, eql? +Style/ClassEqualityComparison: Exclude: - - 'lib/net/ssh/transport/ctr.rb' - - 'lib/net/ssh/transport/hmac.rb' - - 'lib/net/ssh/transport/hmac/md5.rb' - - 'lib/net/ssh/transport/hmac/md5_96.rb' - - 'lib/net/ssh/transport/hmac/none.rb' - - 'lib/net/ssh/transport/hmac/ripemd160.rb' - - 'lib/net/ssh/transport/hmac/sha1.rb' - - 'lib/net/ssh/transport/hmac/sha1_96.rb' - - 'lib/net/ssh/transport/hmac/sha2_256.rb' - - 'lib/net/ssh/transport/hmac/sha2_256_96.rb' - - 'lib/net/ssh/transport/hmac/sha2_256_etm.rb' - - 'lib/net/ssh/transport/hmac/sha2_512.rb' - - 'lib/net/ssh/transport/hmac/sha2_512_96.rb' - - 'lib/net/ssh/transport/hmac/sha2_512_etm.rb' - - 'lib/net/ssh/transport/kex.rb' - - 'lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb' - - 'lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb' + - 'lib/net/ssh/service/forward.rb' # Offense count: 7 Style/ClassVars: @@ -525,10 +481,16 @@ Style/ColonMethodCall: Exclude: - 'lib/net/ssh/authentication/ed25519.rb' +# Offense count: 2 +Style/CombinableLoops: + Exclude: + - 'lib/net/ssh/connection/channel.rb' + - 'test/integration/test_hmac_etm.rb' + # Offense count: 4 # Cop supports --auto-correct. -# Configuration parameters: Keywords. -# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW +# Configuration parameters: Keywords, RequireColon. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE Style/CommentAnnotation: Exclude: - 'lib/net/ssh/authentication/ed25519.rb' @@ -537,6 +499,7 @@ Style/CommentAnnotation: - 'lib/net/ssh/config.rb' # Offense count: 3 +# Cop supports --auto-correct. Style/CommentedKeyword: Exclude: - 'test/connection/test_session.rb' @@ -554,13 +517,8 @@ Style/ConditionalAssignment: - 'lib/net/ssh/transport/state.rb' - 'test/test_key_factory.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/DefWithParentheses: - Exclude: - - 'test/integration/test_forward.rb' - -# Offense count: 14 +# Offense count: 12 +# Configuration parameters: AllowedConstants. Style/Documentation: Exclude: - 'spec/**/*' @@ -568,7 +526,6 @@ Style/Documentation: - 'lib/net/ssh/authentication/ed25519.rb' - 'lib/net/ssh/connection/keepalive.rb' - 'lib/net/ssh/connection/session.rb' - - 'lib/net/ssh/ruby_compat.rb' - 'lib/net/ssh/test/extensions.rb' - 'lib/net/ssh/transport/kex.rb' - 'lib/net/ssh/transport/key_expander.rb' @@ -576,15 +533,19 @@ Style/Documentation: # Offense count: 1 # Cop supports --auto-correct. -Style/EmptyLiteral: +Style/EvenOdd: Exclude: - - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' + - 'lib/net/ssh/buffer.rb' -# Offense count: 1 +# Offense count: 9 # Cop supports --auto-correct. -Style/EvenOdd: +Style/ExplicitBlockArgument: Exclude: - - 'lib/net/ssh/buffer.rb' + - 'lib/net/ssh/loggable.rb' + - 'lib/net/ssh/test.rb' + - 'test/integration/common.rb' + - 'test/integration/mitm_server.rb' + - 'test/integration/test_forward.rb' # Offense count: 2 # Cop supports --auto-correct. @@ -595,32 +556,40 @@ Style/FormatString: - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/loggable.rb' -# Offense count: 1 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: annotated, template, unannotated -Style/FormatStringToken: - Exclude: - - 'lib/net/ssh/loggable.rb' - -# Offense count: 166 +# Offense count: 173 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. -# SupportedStyles: always, never +# SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false -# Offense count: 34 +# Offense count: 1 +# Cop supports --auto-correct. +Style/GlobalStdStream: + Exclude: + - 'lib/net/ssh.rb' + +# Offense count: 35 # Configuration parameters: MinBodyLength. Style/GuardClause: Enabled: false +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: AllowSplatArgument. +Style/HashConversion: + Exclude: + - 'lib/net/ssh/authentication/certificate.rb' + - 'test/test_config.rb' + # Offense count: 1 +# Cop supports --auto-correct. # Configuration parameters: AllowIfModifier. Style/IfInsideElse: Exclude: - 'lib/net/ssh/connection/session.rb' -# Offense count: 16 +# Offense count: 13 # Cop supports --auto-correct. Style/IfUnlessModifier: Exclude: @@ -629,8 +598,6 @@ Style/IfUnlessModifier: - 'lib/net/ssh/proxy/command.rb' - 'lib/net/ssh/service/forward.rb' - 'lib/net/ssh/transport/ctr.rb' - - 'lib/net/ssh/transport/kex.rb' - - 'lib/net/ssh/transport/kex/abstract5656.rb' - 'lib/net/ssh/transport/key_expander.rb' - 'test/integration/test_proxy.rb' - 'test/test_key_factory.rb' @@ -651,11 +618,6 @@ Style/LineEndConcatenation: - 'lib/net/ssh/verifiers/always.rb' # Offense count: 1 -Style/MethodMissingSuper: - Exclude: - - 'lib/net/ssh/connection/session.rb' - -# Offense count: 1 Style/MissingRespondToMissing: Exclude: - 'lib/net/ssh/connection/session.rb' @@ -667,15 +629,16 @@ Style/MultilineIfThen: - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/service/forward.rb' -# Offense count: 6 +# Offense count: 7 # Cop supports --auto-correct. Style/MultilineWhenThen: Exclude: - - 'lib/net/ssh/test/packet.rb' - 'lib/net/ssh/transport/packet_stream.rb' - 'lib/net/ssh/transport/session.rb' # Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: AllowMethodComparison. Style/MultipleComparison: Exclude: - 'lib/net/ssh/authentication/agent.rb' @@ -683,14 +646,14 @@ Style/MultipleComparison: - 'lib/net/ssh/known_hosts.rb' - 'lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb' -# Offense count: 41 +# Offense count: 42 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: Enabled: false -# Offense count: 13 +# Offense count: 14 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: both, prefix, postfix @@ -706,6 +669,13 @@ Style/NegatedIf: - 'test/test_key_factory.rb' - 'test/transport/test_state.rb' +# Offense count: 2 +# Cop supports --auto-correct. +Style/NegatedIfElseCondition: + Exclude: + - 'lib/net/ssh/transport/algorithms.rb' + - 'lib/net/ssh/transport/cipher_factory.rb' + # Offense count: 1 # Cop supports --auto-correct. Style/NegatedWhile: @@ -734,7 +704,7 @@ Style/Not: Exclude: - 'lib/net/ssh/connection/channel.rb' -# Offense count: 8 +# Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: Strict. Style/NumericLiterals: @@ -742,11 +712,27 @@ Style/NumericLiterals: # Offense count: 29 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. +# Configuration parameters: EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Enabled: false +# Offense count: 16 +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'lib/net/ssh/connection/session.rb' + - 'lib/net/ssh/key_factory.rb' + - 'lib/net/ssh/prompt.rb' + - 'lib/net/ssh/test/channel.rb' + - 'lib/net/ssh/test/script.rb' + - 'lib/net/ssh/transport/algorithms.rb' + - 'lib/net/ssh/transport/session.rb' + - 'lib/net/ssh/transport/state.rb' + - 'test/common.rb' + - 'test/transport/test_server_version.rb' + # Offense count: 15 # Cop supports --auto-correct. Style/ParallelAssignment: @@ -771,7 +757,7 @@ Style/ParenthesesAroundCondition: - 'lib/net/ssh/transport/ctr.rb' - 'test/integration/test_proxy.rb' -# Offense count: 33 +# Offense count: 23 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: @@ -779,7 +765,7 @@ Style/PercentLiteralDelimiters: - 'net-ssh.gemspec' - 'test/test_config.rb' -# Offense count: 15 +# Offense count: 16 # Cop supports --auto-correct. Style/PerlBackrefs: Exclude: @@ -805,33 +791,70 @@ Style/Proc: # Offense count: 7 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. +# Configuration parameters: EnforcedStyle, AllowedCompactTypes. # SupportedStyles: compact, exploded Style/RaiseArgs: Exclude: - 'lib/net/ssh/authentication/ed25519.rb' -# Offense count: 4 +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: Methods. +Style/RedundantArgument: + Exclude: + - 'lib/net/ssh/known_hosts.rb' + - 'test/authentication/test_ed25519.rb' + +# Offense count: 5 # Cop supports --auto-correct. Style/RedundantBegin: Exclude: - 'lib/net/ssh/buffered_io.rb' + - 'lib/net/ssh/service/forward.rb' - 'lib/net/ssh/verifiers/accept_new.rb' - 'test/manual/test_pageant.rb' # Offense count: 1 # Cop supports --auto-correct. -Style/RedundantParentheses: +Style/RedundantCondition: + Exclude: + - 'lib/net/ssh/proxy/command.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantFileExtensionInRequire: + Exclude: + - 'lib/net/ssh/transport/cipher_factory.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/RedundantInterpolation: + Exclude: + - 'lib/net/ssh/proxy/socks5.rb' + - 'lib/net/ssh/transport/session.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/RedundantPercentQ: Exclude: - - 'support/arcfour_check.rb' + - 'net-ssh.gemspec' -# Offense count: 59 +# Offense count: 9 +# Cop supports --auto-correct. +Style/RedundantRegexpEscape: + Exclude: + - 'lib/net/ssh/authentication/agent.rb' + - 'lib/net/ssh/buffer.rb' + - 'lib/net/ssh/config.rb' + - 'lib/net/ssh/transport/cipher_factory.rb' + +# Offense count: 86 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Enabled: false -# Offense count: 14 +# Offense count: 18 # Cop supports --auto-correct. Style/RedundantSelf: Exclude: @@ -848,8 +871,8 @@ Style/RescueModifier: # Offense count: 25 # Cop supports --auto-correct. -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, Whitelist. -# Whitelist: present?, blank?, presence, try, try! +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. +# AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: Exclude: - 'lib/net/ssh/authentication/key_manager.rb' @@ -865,6 +888,12 @@ Style/SafeNavigation: - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/packet_stream.rb' +# Offense count: 3 +# Cop supports --auto-correct. +Style/SelectByRegexp: + Exclude: + - 'test/test_all.rb' + # Offense count: 2 # Cop supports --auto-correct. Style/SelfAssignment: @@ -896,9 +925,18 @@ Style/SingleLineMethods: Exclude: - 'lib/net/ssh/buffered_io.rb' +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: AllowModifier. +Style/SoleNestedConditional: + Exclude: + - 'lib/net/ssh/transport/packet_stream.rb' + - 'test/common.rb' + - 'test/integration/test_proxy.rb' + # Offense count: 18 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. +# Configuration parameters: RequireEnglish, EnforcedStyle. # SupportedStyles: use_perl_names, use_english_names Style/SpecialGlobalVars: Exclude: @@ -910,16 +948,39 @@ Style/SpecialGlobalVars: - 'test/manual/test_pageant.rb' - 'test/test_all.rb' -# Offense count: 1754 +# Offense count: 1 +# Cop supports --auto-correct. +Style/StringChars: + Exclude: + - 'test/transport/test_server_version.rb' + +# Offense count: 27 +# Cop supports --auto-correct. +# Configuration parameters: Mode. +Style/StringConcatenation: + Exclude: + - 'lib/net/ssh/authentication/certificate.rb' + - 'lib/net/ssh/authentication/key_manager.rb' + - 'lib/net/ssh/authentication/pageant.rb' + - 'lib/net/ssh/config.rb' + - 'lib/net/ssh/transport/algorithms.rb' + - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' + - 'test/authentication/test_key_manager.rb' + - 'test/integration/common.rb' + - 'test/integration/test_proxy.rb' + - 'test/test_buffer.rb' + - 'test/test_key_factory.rb' + +# Offense count: 1816 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Enabled: false -# Offense count: 11 +# Offense count: 6 # Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. +# Configuration parameters: AllowMethodsWithArguments, IgnoredMethods. # IgnoredMethods: respond_to, define_method Style/SymbolProc: Exclude: @@ -927,30 +988,17 @@ Style/SymbolProc: - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/test/extensions.rb' - - 'lib/net/ssh/transport/algorithms.rb' - - 'test/integration/test_forward.rb' - - 'test/test/test_test.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/UnneededCondition: - Exclude: - - 'lib/net/ssh/proxy/command.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -Style/UnneededInterpolation: - Exclude: - - 'lib/net/ssh/proxy/socks5.rb' - - 'lib/net/ssh/transport/session.rb' - - 'test/integration/test_forward.rb' -# Offense count: 15 +# Offense count: 14 # Cop supports --auto-correct. -Style/UnneededPercentQ: +Style/UnpackFirst: Exclude: - - 'net-ssh.gemspec' - - 'test/test_config.rb' + - 'lib/net/ssh/authentication/pageant.rb' + - 'lib/net/ssh/buffer.rb' + - 'lib/net/ssh/key_factory.rb' + - 'lib/net/ssh/known_hosts.rb' + - 'lib/net/ssh/transport/openssl.rb' + - 'lib/net/ssh/transport/packet_stream.rb' # Offense count: 2 # Cop supports --auto-correct. @@ -959,7 +1007,7 @@ Style/WhileUntilDo: - 'lib/net/ssh/config.rb' - 'test/integration/common.rb' -# Offense count: 3 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: WordRegex. # SupportedStyles: percent, brackets @@ -973,10 +1021,3 @@ Style/ZeroLengthPredicate: Exclude: - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/connection/channel.rb' - -# Offense count: 1636 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 932 diff --git a/.travis.yml b/.travis.yml index c314b65..2c2bdd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,16 @@ language: ruby sudo: true -dist: trusty +dist: focal addon: hosts: gateway.netssh rvm: - - 2.3.8 - - 2.4.8 - 2.5.7 - 2.6.5 - 2.7.0 + - 3.0.0 - jruby-9.2.11.1 - rbx-3.107 - ruby-head @@ -35,18 +34,18 @@ matrix: install: - export JRUBY_OPTS='--client -J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -Xcext.enabled=false -J-Xss2m -Xcompile.invokedynamic=false' - sudo pip install ansible urllib3 pyOpenSSL ndg-httpsclient pyasn1 - - gem install bundler -v "= 1.17" + - gem install bundler - gem list bundler - - bundle _1.17_ install - - bundle _1.17_ -v - - BUNDLE_GEMFILE=./Gemfile.noed25519 bundle _1.17_ install + - bundle install + - bundle -v + - BUNDLE_GEMFILE=./Gemfile.noed25519 bundle install - sudo ansible-galaxy install rvm.ruby - sudo chown -R travis:travis /home/travis/.ansible - ansible-playbook ./test/integration/playbook.yml -i "localhost," --become -c local -e 'no_rvm=true' -e 'myuser=travis' -e 'mygroup=travis' -e 'homedir=/home/travis' script: - ssh -V - - bundle _1.17_ exec rake test - - BUNDLE_GEMFILE=./Gemfile.noed25519 bundle _1.17_ exec rake test - - bundle _1.17_ exec rake test_test - - bundle _1.17_ exec rubocop + - bundle exec rake test + - BUNDLE_GEMFILE=./Gemfile.noed25519 bundle exec rake test + - bundle exec rake test_test + - bundle exec rubocop diff --git a/CHANGES.txt b/CHANGES.txt index f973490..86c5bd1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,19 @@ +=== 6.3.0 beta1 + + * Support cert based host key auth, fix asterisk in known_hosts [#833] + * Support kex dh-group14-sha256 [#795] + * Fix StrictHostKeyChecking ssh config parameter translation [#765] + +=== 6.2.0 rc1 + +=== 6.2.0 beta1 + + * rsa-sha2-512, rsa-sha2-256 host_key algs [#771] + * JRuby aes*-ctr suppport [#767] + === 6.1.0 - * adapt to ssh's default bahaviors when no username is provided. + * Adapt to ssh's default behaviors when no username is provided. When Net::SSH.start user is nil and config has no entry we default to Etc.getpwuid.name() instead of Etc.getlogin(). [#749] @@ -36,7 +49,7 @@ === 5.2.0.rc3 * Fix check_host_ip read from config - * Support ssh-ed25519 in kown hosts + * Support ssh-ed25519 in known hosts === 5.2.0.rc2 @@ -59,7 +72,7 @@ === 5.0.2 - * fix ctr for jruby [#612] + * Fix ctr for jruby [#612] === 5.0.1 @@ -9,3 +9,5 @@ if ENV["CI"] gem 'codecov', require: false, group: :test gem 'simplecov', require: false, group: :test end + +gem 'webrick', group: %i[development test] if RUBY_VERSION.split(".")[0].to_i >= 3 diff --git a/Gemfile.noed25519 b/Gemfile.noed25519 index b6c3576..f13c7c3 100644 --- a/Gemfile.noed25519 +++ b/Gemfile.noed25519 @@ -8,3 +8,5 @@ if ENV["CI"] && !Gem.win_platform? gem 'simplecov', require: false, group: :test gem 'codecov', require: false, group: :test end + +gem 'webrick', group: %i[development test] if RUBY_VERSION.split(".")[0].to_i >= 3 @@ -1,13 +1,13 @@ [![Gem Version](https://badge.fury.io/rb/net-ssh.svg)](https://badge.fury.io/rb/net-ssh) [![Join the chat at https://gitter.im/net-ssh/net-ssh](https://badges.gitter.im/net-ssh/net-ssh.svg)](https://gitter.im/net-ssh/net-ssh?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/net-ssh/net-ssh.svg?branch=master)](https://travis-ci.org/net-ssh/net-ssh) +[![Build status](https://github.com/net-ssh/net-ssh/actions/workflows/ci.yml/badge.svg)](https://github.com/net-ssh/net-ssh/actions/workflows/ci.yml) [![Coverage status](https://codecov.io/gh/net-ssh/net-ssh/branch/master/graph/badge.svg)](https://codecov.io/gh/net-ssh/net-ssh) [![Backers on Open Collective](https://opencollective.com/net-ssh/backers/badge.svg)](#backers]) [![Sponsors on Open Collective](https://opencollective.com/net-ssh/sponsors/badge.svg)](#sponsors) # Net::SSH 6.x -* Docs: http://net-ssh.github.com/net-ssh +* Docs: http://net-ssh.github.io/net-ssh * Issues: https://github.com/net-ssh/net-ssh/issues * Codes: https://github.com/net-ssh/net-ssh * Email: net-ssh@solutious.com @@ -48,6 +48,7 @@ namespace :cert do raw = File.read "net-ssh-public_cert.pem" certificate = OpenSSL::X509::Certificate.new raw raise Exception, "Not yet expired: #{certificate.not_after}" unless certificate.not_after < Time.now + sh "gem cert --build netssh@solutious.com --days 365*5 --private-key /mnt/gem/net-ssh-private_key.pem" sh "mv gem-public_cert.pem net-ssh-public_cert.pem" sh "gem cert --add net-ssh-public_cert.pem" diff --git a/lib/net/ssh.rb b/lib/net/ssh.rb index 292d6c6..b72ab7f 100644 --- a/lib/net/ssh.rb +++ b/lib/net/ssh.rb @@ -15,7 +15,6 @@ require 'net/ssh/connection/session' require 'net/ssh/prompt' module Net - # Net::SSH is a library for interacting, programmatically, with remote # processes via the SSH2 protocol. Sessions are always initiated via # Net::SSH.start. From there, a program interacts with the new SSH session @@ -122,7 +121,7 @@ module Net # * :forward_agent => set to true if you want the SSH agent connection to # be forwarded # * :known_hosts => a custom object holding known hosts records. - # It must implement #search_for and add in a similiar manner as KnownHosts. + # It must implement #search_for and `add` in a similiar manner as KnownHosts. # * :global_known_hosts_file => the location of the global known hosts # file. Set to an array if you want to specify multiple global known # hosts files. Defaults to %w(/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2). @@ -215,7 +214,7 @@ module Net # * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256' # If +user+ parameter is nil it defaults to USER from ssh_config, or # local username - def self.start(host, user=nil, options={}, &block) + def self.start(host, user = nil, options = {}, &block) invalid_options = options.keys - VALID_OPTIONS if invalid_options.any? raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}" @@ -302,9 +301,9 @@ module Net end def self._sanitize_options(options) - invalid_option_values = [nil,[nil]] + invalid_option_values = [nil, [nil]] unless (options.values & invalid_option_values).empty? - nil_options = options.select { |_k,v| invalid_option_values.include?(v) }.map(&:first) + nil_options = options.select { |_k, v| invalid_option_values.include?(v) }.map(&:first) Kernel.warn "#{caller_locations(2, 1)[0]}: Passing nil, or [nil] to Net::SSH.start is deprecated for keys: #{nil_options.join(', ')}" end end diff --git a/lib/net/ssh/authentication/agent.rb b/lib/net/ssh/authentication/agent.rb index 15c75ea..a344626 100644 --- a/lib/net/ssh/authentication/agent.rb +++ b/lib/net/ssh/authentication/agent.rb @@ -13,6 +13,7 @@ module Net module Authentication # Class for representing agent-specific errors. class AgentError < Net::SSH::Exception; end + # An exception for indicating that the SSH agent is not available. class AgentNotAvailable < AgentError; end @@ -39,6 +40,8 @@ module Net SSH2_AGENT_ADD_IDENTITY = 17 SSH2_AGENT_REMOVE_IDENTITY = 18 SSH2_AGENT_REMOVE_ALL_IDENTITIES = 19 + SSH2_AGENT_LOCK = 22 + SSH2_AGENT_UNLOCK = 23 SSH2_AGENT_ADD_ID_CONSTRAINED = 25 SSH2_AGENT_FAILURE = 30 SSH2_AGENT_VERSION_RESPONSE = 103 @@ -62,7 +65,7 @@ module Net # Instantiates a new agent object, connects to a running SSH agent, # negotiates the agent protocol version, and returns the agent object. - def self.connect(logger=nil, agent_socket_factory = nil, identity_agent = nil) + def self.connect(logger = nil, agent_socket_factory = nil, identity_agent = nil) agent = new(logger) agent.connect!(agent_socket_factory, identity_agent) agent.negotiate! @@ -71,7 +74,7 @@ module Net # Creates a new Agent object, using the optional logger instance to # report status. - def initialize(logger=nil) + def initialize(logger = nil) self.logger = logger end @@ -105,6 +108,7 @@ module Net type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION) raise AgentNotAvailable, "SSH2 agents are not yet supported" if type == SSH2_AGENT_VERSION_RESPONSE + if type == SSH2_AGENT_FAILURE debug { "Unexpected response type==#{type}, this will be ignored" } elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2 @@ -173,7 +177,7 @@ module Net req_type = constraints.empty? ? SSH2_AGENT_ADD_IDENTITY : SSH2_AGENT_ADD_ID_CONSTRAINED type, = send_and_wait(req_type, :string, priv_key.ssh_type, :raw, blob_for_add(priv_key), - :string, comment, :raw, constraints) + :string, comment, :raw, constraints) raise AgentError, "could not add identity to agent" if type != SSH_AGENT_SUCCESS end @@ -189,6 +193,18 @@ module Net raise AgentError, "could not remove all identity from agent" if type != SSH_AGENT_SUCCESS end + # lock the ssh agent with password + def lock(password) + type, = send_and_wait(SSH2_AGENT_LOCK, :string, password) + raise AgentError, "could not lock agent" if type != SSH_AGENT_SUCCESS + end + + # unlock the ssh agent with password + def unlock(password) + type, = send_and_wait(SSH2_AGENT_UNLOCK, :string, password) + raise AgentError, "could not unlock agent" if type != SSH_AGENT_SUCCESS + end + private def unix_socket_class @@ -235,31 +251,31 @@ module Net case priv_key.ssh_type when /^ssh-dss$/ Net::SSH::Buffer.from(:bignum, priv_key.p, :bignum, priv_key.q, :bignum, priv_key.g, - :bignum, priv_key.pub_key, :bignum, priv_key.priv_key).to_s + :bignum, priv_key.pub_key, :bignum, priv_key.priv_key).to_s when /^ssh-dss-cert-v01@openssh\.com$/ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.priv_key).to_s when /^ecdsa\-sha2\-(\w*)$/ curve_name = OpenSSL::PKey::EC::CurveNameAliasInv[priv_key.group.curve_name] Net::SSH::Buffer.from(:string, curve_name, :mstring, priv_key.public_key.to_bn.to_s(2), - :bignum, priv_key.private_key).to_s + :bignum, priv_key.private_key).to_s when /^ecdsa\-sha2\-(\w*)-cert-v01@openssh\.com$/ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.private_key).to_s when /^ssh-ed25519$/ Net::SSH::Buffer.from(:string, priv_key.public_key.verify_key.to_bytes, - :string, priv_key.sign_key.keypair).to_s + :string, priv_key.sign_key.keypair).to_s when /^ssh-ed25519-cert-v01@openssh\.com$/ # Unlike the other certificate types, the public key is included after the certifiate. Net::SSH::Buffer.from(:string, priv_key.to_blob, - :string, priv_key.key.public_key.verify_key.to_bytes, - :string, priv_key.key.sign_key.keypair).to_s + :string, priv_key.key.public_key.verify_key.to_bytes, + :string, priv_key.key.sign_key.keypair).to_s when /^ssh-rsa$/ # `n` and `e` are reversed compared to the ordering in `OpenSSL::PKey::RSA#to_blob`. Net::SSH::Buffer.from(:bignum, priv_key.n, :bignum, priv_key.e, :bignum, priv_key.d, - :bignum, priv_key.iqmp, :bignum, priv_key.p, :bignum, priv_key.q).to_s + :bignum, priv_key.iqmp, :bignum, priv_key.p, :bignum, priv_key.q).to_s when /^ssh-rsa-cert-v01@openssh\.com$/ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.d, - :bignum, priv_key.key.iqmp, :bignum, priv_key.key.p, - :bignum, priv_key.key.q).to_s + :bignum, priv_key.key.iqmp, :bignum, priv_key.key.p, + :bignum, priv_key.key.q).to_s end end end diff --git a/lib/net/ssh/authentication/certificate.rb b/lib/net/ssh/authentication/certificate.rb index 82e37e9..5250789 100644 --- a/lib/net/ssh/authentication/certificate.rb +++ b/lib/net/ssh/authentication/certificate.rb @@ -31,12 +31,13 @@ module Net cert.key_id = buffer.read_string cert.valid_principals = buffer.read_buffer.read_all(&:read_string) cert.valid_after = Time.at(buffer.read_int64) - + cert.valid_before = if RUBY_PLATFORM == "java" # 0x20c49ba5e353f7 = 0x7fffffffffffffff/1000, the largest value possible for JRuby # JRuby Time.at multiplies the arg by 1000, and then stores it in a signed long. - # 0x20c49ba5e353f7 = 292278994-08-17 01:12:55 -0600 - Time.at([0x20c49ba5e353f7, buffer.read_int64].min) + # 0x20c49ba2d52500 = 292278993-01-01 00:00:00 +0000 + # JRuby 9.1 does not accept the year 292278994 because of edge cases (https://github.com/JodaOrg/joda-time/issues/190) + Time.at([0x20c49ba2d52500, buffer.read_int64].min) else Time.at(buffer.read_int64) end @@ -65,12 +66,12 @@ module Net ).to_s end - def ssh_do_sign(data) - key.ssh_do_sign(data) + def ssh_do_sign(data, sig_alg = nil) + key.ssh_do_sign(data, sig_alg) end - def ssh_do_verify(sig, data) - key.ssh_do_verify(sig, data) + def ssh_do_verify(sig, data, options = {}) + key.ssh_do_verify(sig, data, options) end def to_pem @@ -82,7 +83,7 @@ module Net end # Signs the certificate with key. - def sign!(key, sign_nonce=nil) + def sign!(key, sign_nonce = nil) # ssh-keygen uses 32 bytes of nonce. self.nonce = sign_nonce || SecureRandom.random_bytes(32) self.signature_key = key @@ -93,7 +94,7 @@ module Net self end - def sign(key, sign_nonce=nil) + def sign(key, sign_nonce = nil) cert = clone cert.sign!(key, sign_nonce) end @@ -124,6 +125,7 @@ module Net def self.type_symbol(type) types = { 1 => :user, 2 => :host } raise ArgumentError("unsupported type: #{type}") unless types.include?(type) + types.fetch(type) end private_class_method :type_symbol @@ -133,6 +135,7 @@ module Net def type_value(type) types = { user: 1, host: 2 } raise ArgumentError("unsupported type: #{type}") unless types.include?(type) + types.fetch(type) end diff --git a/lib/net/ssh/authentication/constants.rb b/lib/net/ssh/authentication/constants.rb index d0b88b0..8976aed 100644 --- a/lib/net/ssh/authentication/constants.rb +++ b/lib/net/ssh/authentication/constants.rb @@ -1,7 +1,6 @@ module Net module SSH module Authentication - # Describes the constants used by the Net::SSH::Authentication components # of the Net::SSH library. Individual authentication method implemenations # may define yet more constants that are specific to their implementation. diff --git a/lib/net/ssh/authentication/ed25519.rb b/lib/net/ssh/authentication/ed25519.rb index 0c5530c..dccc64f 100644 --- a/lib/net/ssh/authentication/ed25519.rb +++ b/lib/net/ssh/authentication/ed25519.rb @@ -14,7 +14,7 @@ module Net module Authentication module ED25519 class SigningKeyFromFile < SimpleDelegator - def initialize(pk,sk) + def initialize(pk, sk) key = ::Ed25519::SigningKey.from_keypair(sk) raise ArgumentError, "pk does not match sk" unless pk == key.verify_key.to_bytes @@ -44,9 +44,11 @@ module Net datafull = datafull.strip raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN) raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND) + datab64 = datafull[MBEGIN.size...-MEND.size] data = Base64.decode64(datab64) raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC) + buffer = Net::SSH::Buffer.new(data[MAGIC.size + 1..-1]) ciphername = buffer.read_string @@ -59,6 +61,7 @@ module Net kdfopts = Net::SSH::Buffer.new(buffer.read_string) num_keys = buffer.read_long raise ArgumentError.new("Only 1 key is supported in ssh keys #{num_keys} was in private key") unless num_keys == 1 + _pubkey = buffer.read_string len = buffer.read_long @@ -72,12 +75,13 @@ module Net rounds = kdfopts.read_long raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java" + key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds) else key = '\x00' * (keylen + ivlen) end - cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv:key[keylen...keylen + ivlen], decrypt: true) + cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv: key[keylen...keylen + ivlen], decrypt: true) decoded = cipher.update(buffer.remainder_as_buffer.to_s) decoded << cipher.final @@ -112,7 +116,7 @@ module Net end def to_blob - Net::SSH::Buffer.from(:mstring,"ssh-ed25519",:string,@verify_key.to_bytes).to_s + Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s end def ssh_type @@ -123,8 +127,8 @@ module Net ssh_type end - def ssh_do_verify(sig,data) - @verify_key.verify(sig,data) + def ssh_do_verify(sig, data, options = {}) + @verify_key.verify(sig, data) end def to_pem @@ -148,7 +152,7 @@ module Net _comment = buffer.read_string @pk = pk - @sign_key = SigningKeyFromFile.new(pk,sk) + @sign_key = SigningKeyFromFile.new(pk, sk) end def to_blob @@ -167,7 +171,7 @@ module Net PubKey.new(@pk) end - def ssh_do_sign(data) + def ssh_do_sign(data, sig_alg = nil) @sign_key.sign(data) end diff --git a/lib/net/ssh/authentication/ed25519_loader.rb b/lib/net/ssh/authentication/ed25519_loader.rb index bcf920d..94f87aa 100644 --- a/lib/net/ssh/authentication/ed25519_loader.rb +++ b/lib/net/ssh/authentication/ed25519_loader.rb @@ -1,11 +1,9 @@ -module Net - module SSH +module Net + module SSH module Authentication - # Loads ED25519 support which requires optinal dependecies like # ed25519, bcrypt_pbkdf module ED25519Loader - begin require 'net/ssh/authentication/ed25519' LOADED = true @@ -14,20 +12,19 @@ module Net ERROR = e LOADED = false end - + def self.raiseUnlessLoaded(message) description = ERROR.is_a?(LoadError) ? dependenciesRequiredForED25519 : '' description << "#{ERROR.class} : \"#{ERROR.message}\"\n" if ERROR raise NotImplementedError, "#{message}\n#{description}" unless LOADED end - + def self.dependenciesRequiredForED25519 result = "net-ssh requires the following gems for ed25519 support:\n" result << " * ed25519 (>= 1.2, < 2.0)\n" result << " * bcrypt_pbkdf (>= 1.0, < 2.0)\n" unless RUBY_PLATFORM == "java" result << "See https://github.com/net-ssh/net-ssh/issues/565 for more information\n" end - end end end diff --git a/lib/net/ssh/authentication/key_manager.rb b/lib/net/ssh/authentication/key_manager.rb index 242d5d5..563702c 100644 --- a/lib/net/ssh/authentication/key_manager.rb +++ b/lib/net/ssh/authentication/key_manager.rb @@ -6,7 +6,6 @@ require 'net/ssh/authentication/agent' module Net module SSH module Authentication - # A trivial exception class used to report errors in the key manager. class KeyManagerError < Net::SSH::Exception; end @@ -42,7 +41,7 @@ module Net # Create a new KeyManager. By default, the manager will # use the ssh-agent if it is running and the `:use_agent` option # is not false. - def initialize(logger, options={}) + def initialize(logger, options = {}) self.logger = logger @key_files = [] @key_data = [] @@ -159,7 +158,7 @@ module Net # Regardless of the identity's origin or who does the signing, this # will always return the signature in an SSH2-specified "signature # blob" format. - def sign(identity, data) + def sign(identity, data, sig_alg = nil) info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager" if info[:key].nil? && info[:from] == :file @@ -171,13 +170,27 @@ module Net end if info[:key] - return Net::SSH::Buffer.from(:string, identity.ssh_signature_type, - :mstring, info[:key].ssh_do_sign(data.to_s)).to_s + if sig_alg.nil? + signed = info[:key].ssh_do_sign(data.to_s) + sig_alg = identity.ssh_signature_type + else + signed = info[:key].ssh_do_sign(data.to_s, sig_alg) + end + return Net::SSH::Buffer.from(:string, sig_alg, + :mstring, signed).to_s end if info[:from] == :agent raise KeyManagerError, "the agent is no longer available" unless agent - return agent.sign(info[:identity], data.to_s) + + case sig_alg + when "rsa-sha2-512" + return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_512) + when "rsa-sha2-256" + return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_256) + else + return agent.sign(info[:identity], data.to_s) + end end raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})" @@ -201,6 +214,7 @@ module Net # or if the agent is otherwise not available. def agent return unless use_agent? + @agent ||= Agent.connect(logger, options[:agent_socket_factory], options[:identity_agent]) rescue AgentNotAvailable @use_agent = false @@ -248,37 +262,35 @@ module Net # Load prepared identities. Private key decryption errors ignored if ignore_decryption_errors def load_identities(identities, ask_passphrase, ignore_decryption_errors) identities.map do |identity| - begin - case identity[:load_from] - when :pubkey_file - key = KeyFactory.load_public_key(identity[:pubkey_file]) - { public_key: key, from: :file, file: identity[:privkey_file] } - when :privkey_file - private_key = KeyFactory.load_private_key( - identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt] - ) - key = private_key.send(:public_key) - { public_key: key, from: :file, file: identity[:privkey_file], key: private_key } - when :data - private_key = KeyFactory.load_data_private_key( - identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt] - ) - key = private_key.send(:public_key) - { public_key: key, from: :key_data, data: identity[:data], key: private_key } - else - identity - end - rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e - if ignore_decryption_errors - identity - else - process_identity_loading_error(identity, e) - nil - end - rescue Exception => e + case identity[:load_from] + when :pubkey_file + key = KeyFactory.load_public_key(identity[:pubkey_file]) + { public_key: key, from: :file, file: identity[:privkey_file] } + when :privkey_file + private_key = KeyFactory.load_private_key( + identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt] + ) + key = private_key.send(:public_key) + { public_key: key, from: :file, file: identity[:privkey_file], key: private_key } + when :data + private_key = KeyFactory.load_data_private_key( + identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt] + ) + key = private_key.send(:public_key) + { public_key: key, from: :key_data, data: identity[:data], key: private_key } + else + identity + end + rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e + if ignore_decryption_errors + identity + else process_identity_loading_error(identity, e) nil end + rescue Exception => e + process_identity_loading_error(identity, e) + nil end.compact end diff --git a/lib/net/ssh/authentication/methods/abstract.rb b/lib/net/ssh/authentication/methods/abstract.rb index bcddd4f..f023011 100644 --- a/lib/net/ssh/authentication/methods/abstract.rb +++ b/lib/net/ssh/authentication/methods/abstract.rb @@ -7,7 +7,6 @@ module Net module SSH module Authentication module Methods - # The base class of all user authentication methods. It provides a few # bits of common functionality. class Abstract @@ -21,12 +20,22 @@ module Net # this. attr_reader :key_manager + # So far only affects algorithms used for rsa keys, but can be + # extended to other keys, e.g after reading of + # PubkeyAcceptedAlgorithms option from ssh_config file is implemented. + attr_reader :pubkey_algorithms + # Instantiates a new authentication method. - def initialize(session, options={}) + def initialize(session, options = {}) @session = session @key_manager = options[:key_manager] @options = options @prompt = options[:password_prompt] + @pubkey_algorithms = options[:pubkey_algorithms] \ + || %w[rsa-sha2-256-cert-v01@openssh.com + ssh-rsa-cert-v01@openssh.com + rsa-sha2-256 + ssh-rsa] self.logger = session.logger end @@ -47,7 +56,7 @@ module Net # of the packet. The new packet is returned, ready for sending. def userauth_request(username, next_service, auth_method, *others) buffer = Net::SSH::Buffer.from(:byte, USERAUTH_REQUEST, - :string, username, :string, next_service, :string, auth_method) + :string, username, :string, next_service, :string, auth_method) others.each do |value| case value diff --git a/lib/net/ssh/authentication/methods/hostbased.rb b/lib/net/ssh/authentication/methods/hostbased.rb index a4afbb4..6c92ef4 100644 --- a/lib/net/ssh/authentication/methods/hostbased.rb +++ b/lib/net/ssh/authentication/methods/hostbased.rb @@ -4,19 +4,18 @@ module Net module SSH module Authentication module Methods - # Implements the host-based SSH authentication method. class Hostbased < Abstract include Constants # Attempts to perform host-based authorization of the user by trying # all known keys. - def authenticate(next_service, username, password=nil) + def authenticate(next_service, username, password = nil) return false unless key_manager key_manager.each_identity do |identity| return true if authenticate_with(identity, next_service, - username, key_manager) + username, key_manager) end return false @@ -64,10 +63,9 @@ module Net # Build the "core" hostbased request string. def build_request(identity, next_service, username, hostname, client_username) userauth_request(username, next_service, "hostbased", identity.ssh_type, - Buffer.from(:key, identity).to_s, hostname, client_username).to_s + Buffer.from(:key, identity).to_s, hostname, client_username).to_s end end - end end end diff --git a/lib/net/ssh/authentication/methods/keyboard_interactive.rb b/lib/net/ssh/authentication/methods/keyboard_interactive.rb index 7652b21..db195d9 100644 --- a/lib/net/ssh/authentication/methods/keyboard_interactive.rb +++ b/lib/net/ssh/authentication/methods/keyboard_interactive.rb @@ -5,14 +5,13 @@ module Net module SSH module Authentication module Methods - # Implements the "keyboard-interactive" SSH authentication method. class KeyboardInteractive < Abstract USERAUTH_INFO_REQUEST = 60 USERAUTH_INFO_RESPONSE = 61 # Attempt to authenticate the given user for the given service. - def authenticate(next_service, username, password=nil) + def authenticate(next_service, username, password = nil) debug { "trying keyboard-interactive" } send_message(userauth_request(username, next_service, "keyboard-interactive", "", "")) @@ -32,6 +31,7 @@ module Net message[:authentications].split(/,/).include? 'keyboard-interactive' return false unless interactive? + password = nil debug { "retrying keyboard-interactive" } send_message(userauth_request(username, next_service, "keyboard-interactive", "", "")) diff --git a/lib/net/ssh/authentication/methods/none.rb b/lib/net/ssh/authentication/methods/none.rb index d583b7d..d38e877 100644 --- a/lib/net/ssh/authentication/methods/none.rb +++ b/lib/net/ssh/authentication/methods/none.rb @@ -5,32 +5,29 @@ module Net module SSH module Authentication module Methods - # Implements the "none" SSH authentication method. class None < Abstract # Attempt to authenticate as "none" - def authenticate(next_service, user="", password="") - send_message(userauth_request(user, next_service, "none")) + def authenticate(next_service, user = "", password = "") + send_message(userauth_request(user, next_service, "none")) message = session.next_message - + case message.type when USERAUTH_SUCCESS debug { "none succeeded" } return true when USERAUTH_FAILURE debug { "none failed" } - + raise Net::SSH::Authentication::DisallowedMethod unless message[:authentications].split(/,/).include? 'none' - + return false else raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})" - end - + end end end - end end end diff --git a/lib/net/ssh/authentication/methods/password.rb b/lib/net/ssh/authentication/methods/password.rb index e18edee..ffb881c 100644 --- a/lib/net/ssh/authentication/methods/password.rb +++ b/lib/net/ssh/authentication/methods/password.rb @@ -6,12 +6,11 @@ module Net module SSH module Authentication module Methods - # Implements the "password" SSH authentication method. class Password < Abstract # Attempt to authenticate the given user for the given service. If # the password parameter is nil, this will ask for password - def authenticate(next_service, username, password=nil) + def authenticate(next_service, username, password = nil) clear_prompter! retries = 0 max_retries = get_max_retries @@ -29,6 +28,7 @@ module Net raise Net::SSH::Authentication::DisallowedMethod unless message[:authentications].split(/,/).include? 'password' + password = nil end end until (message.type != USERAUTH_FAILURE || retries >= max_retries) @@ -74,7 +74,6 @@ module Net options[:non_interactive] ? 0 : result end end - end end end diff --git a/lib/net/ssh/authentication/methods/publickey.rb b/lib/net/ssh/authentication/methods/publickey.rb index bff9ffd..48a56ab 100644 --- a/lib/net/ssh/authentication/methods/publickey.rb +++ b/lib/net/ssh/authentication/methods/publickey.rb @@ -6,14 +6,13 @@ module Net module SSH module Authentication module Methods - # Implements the "publickey" SSH authentication method. class Publickey < Abstract # Attempts to perform public-key authentication for the given # username, trying each identity known to the key manager. If any of # them succeed, returns +true+, otherwise returns +false+. This # requires the presence of a key manager. - def authenticate(next_service, username, password=nil) + def authenticate(next_service, username, password = nil) return false unless key_manager key_manager.each_identity do |identity| @@ -27,41 +26,40 @@ module Net # Builds a packet that contains the request formatted for sending # a public-key request to the server. - def build_request(pub_key, username, next_service, has_sig) + def build_request(pub_key, username, next_service, alg, has_sig) blob = Net::SSH::Buffer.new blob.write_key pub_key userauth_request(username, next_service, "publickey", has_sig, - pub_key.ssh_type, blob.to_s) + alg, blob.to_s) end # Builds and sends a request formatted for a public-key # authentication request. - def send_request(pub_key, username, next_service, signature=nil) - msg = build_request(pub_key, username, next_service, !signature.nil?) + def send_request(pub_key, username, next_service, alg, signature = nil) + msg = build_request(pub_key, username, next_service, alg, + !signature.nil?) msg.write_string(signature) if signature send_message(msg) end - # Attempts to perform public-key authentication for the given - # username, with the given identity (public key). Returns +true+ if - # successful, or +false+ otherwise. - def authenticate_with(identity, next_service, username) + def authenticate_with_alg(identity, next_service, username, alg, sig_alg = nil) debug { "trying publickey (#{identity.fingerprint})" } - send_request(identity, username, next_service) + send_request(identity, username, next_service, alg) message = session.next_message case message.type when USERAUTH_PK_OK - buffer = build_request(identity, username, next_service, true) + buffer = build_request(identity, username, next_service, alg, + true) sig_data = Net::SSH::Buffer.new sig_data.write_string(session_id) sig_data.append(buffer.to_s) - sig_blob = key_manager.sign(identity, sig_data) + sig_blob = key_manager.sign(identity, sig_data, sig_alg) - send_request(identity, username, next_service, sig_blob.to_s) + send_request(identity, username, next_service, alg, sig_blob.to_s) message = session.next_message case message.type @@ -77,7 +75,7 @@ module Net return false else raise Net::SSH::Exception, - "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})" + "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})" end when USERAUTH_FAILURE @@ -89,8 +87,50 @@ module Net raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})" end end - end + # Attempts to perform public-key authentication for the given + # username, with the given identity (public key). Returns +true+ if + # successful, or +false+ otherwise. + def authenticate_with(identity, next_service, username) + type = identity.ssh_type + if type == "ssh-rsa" + pubkey_algorithms.each do |pk_alg| + case pk_alg + when "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa" + if authenticate_with_alg(identity, next_service, username, pk_alg, pk_alg) + # success + return true + end + end + end + elsif type == "ssh-rsa-cert-v01@openssh.com" + pubkey_algorithms.each do |pk_alg| + case pk_alg + when "rsa-sha2-512-cert-v01@openssh.com" + if authenticate_with_alg(identity, next_service, username, pk_alg, "rsa-sha2-512") + # success + return true + end + when "rsa-sha2-256-cert-v01@openssh.com" + if authenticate_with_alg(identity, next_service, username, pk_alg, "rsa-sha2-256") + # success + return true + end + when "ssh-rsa-cert-v01@openssh.com" + if authenticate_with_alg(identity, next_service, username, pk_alg) + # success + return true + end + end + end + elsif authenticate_with_alg(identity, next_service, username, type) + # success + return true + end + # failure + return false + end + end end end end diff --git a/lib/net/ssh/authentication/pageant.rb b/lib/net/ssh/authentication/pageant.rb index a79802b..a186c17 100644 --- a/lib/net/ssh/authentication/pageant.rb +++ b/lib/net/ssh/authentication/pageant.rb @@ -21,10 +21,9 @@ end require 'net/ssh/errors' -module Net - module SSH +module Net + module SSH module Authentication - # This module encapsulates the implementation of a socket factory that # uses the PuTTY "pageant" utility to obtain information about SSH # identities. @@ -33,36 +32,35 @@ module Net # by Guillaume Marçais (guillaume.marcais@free.fr). It is used and # relicensed by permission. module Pageant - # From Putty pageant.c AGENT_MAX_MSGLEN = 8192 AGENT_COPYDATA_ID = 0x804e50ba - + # The definition of the Windows methods and data structures used in # communicating with the pageant process. module Win # rubocop:disable Metrics/ModuleLength # Compatibility on initialization if RUBY_VERSION < "1.9" extend DL::Importable - + dlload 'user32.dll' dlload 'kernel32.dll' dlload 'advapi32.dll' - + SIZEOF_DWORD = DL.sizeof('L') elsif RUBY_VERSION < "2.1" extend DL::Importer - dlload 'user32.dll','kernel32.dll', 'advapi32.dll' + dlload 'user32.dll', 'kernel32.dll', 'advapi32.dll' include DL::Win32Types - + SIZEOF_DWORD = DL::SIZEOF_LONG else extend Fiddle::Importer - dlload 'user32.dll','kernel32.dll', 'advapi32.dll' + dlload 'user32.dll', 'kernel32.dll', 'advapi32.dll' include Fiddle::Win32Types SIZEOF_DWORD = Fiddle::SIZEOF_LONG end - + if RUBY_ENGINE == "jruby" typealias("HANDLE", "void *") # From winnt.h typealias("PHANDLE", "void *") # From winnt.h @@ -76,105 +74,105 @@ module Net typealias("LPARAM", "long *") # From windef.h typealias("PDWORD_PTR", "long *") # From basetsd.h typealias("USHORT", "unsigned short") # From windef.h - + # From winbase.h, winnt.h INVALID_HANDLE_VALUE = -1 NULL = nil PAGE_READWRITE = 0x0004 FILE_MAP_WRITE = 2 WM_COPYDATA = 74 - + SMTO_NORMAL = 0 # From winuser.h - + SUFFIX = if RUBY_ENGINE == "jruby" "A" else "" end - + # args: lpClassName, lpWindowName extern "HWND FindWindow#{SUFFIX}(LPCTSTR, LPCTSTR)" - + # args: none extern 'DWORD GetCurrentThreadId()' - + # args: hFile, (ignored), flProtect, dwMaximumSizeHigh, # dwMaximumSizeLow, lpName extern "HANDLE CreateFileMapping#{SUFFIX}(HANDLE, void *, DWORD, " + "DWORD, DWORD, LPCTSTR)" - + # args: hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh, # dwfileOffsetLow, dwNumberOfBytesToMap extern 'LPVOID MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, DWORD)' - + # args: lpBaseAddress extern 'BOOL UnmapViewOfFile(LPCVOID)' - + # args: hObject extern 'BOOL CloseHandle(HANDLE)' - + # args: hWnd, Msg, wParam, lParam, fuFlags, uTimeout, lpdwResult extern "LRESULT SendMessageTimeout#{SUFFIX}(HWND, UINT, WPARAM, LPARAM, " + "UINT, UINT, PDWORD_PTR)" - + # args: none extern 'DWORD GetLastError()' - + # args: none extern 'HANDLE GetCurrentProcess()' - + # args: hProcessHandle, dwDesiredAccess, (out) phNewTokenHandle extern 'BOOL OpenProcessToken(HANDLE, DWORD, PHANDLE)' - + # args: hTokenHandle, uTokenInformationClass, # (out) lpTokenInformation, dwTokenInformationLength # (out) pdwInfoReturnLength extern 'BOOL GetTokenInformation(HANDLE, UINT, LPVOID, DWORD, ' + 'PDWORD)' - + # args: (out) lpSecurityDescriptor, dwRevisionLevel extern 'BOOL InitializeSecurityDescriptor(LPVOID, DWORD)' - + # args: (out) lpSecurityDescriptor, lpOwnerSid, bOwnerDefaulted extern 'BOOL SetSecurityDescriptorOwner(LPVOID, LPVOID, BOOL)' - + # args: pSecurityDescriptor extern 'BOOL IsValidSecurityDescriptor(LPVOID)' - + # Constants needed for security attribute retrieval. # Specifies the access mask corresponding to the desired access # rights. TOKEN_QUERY = 0x8 - + # The value of TOKEN_USER from the TOKEN_INFORMATION_CLASS enum. TOKEN_USER_INFORMATION_CLASS = 1 - + # The initial revision level assigned to the security descriptor. REVISION = 1 - + # Structs for security attribute functions. # Holds the retrieved user access token. TOKEN_USER = struct ['void * SID', 'DWORD ATTRIBUTES'] - + # Contains the security descriptor, this gets passed to the # function that constructs the shared memory map. SECURITY_ATTRIBUTES = struct ['DWORD nLength', 'LPVOID lpSecurityDescriptor', 'BOOL bInheritHandle'] - + # The security descriptor holds security information. SECURITY_DESCRIPTOR = struct ['UCHAR Revision', 'UCHAR Sbz1', 'USHORT Control', 'LPVOID Owner', 'LPVOID Group', 'LPVOID Sacl', 'LPVOID Dacl'] - + # The COPYDATASTRUCT is used to send WM_COPYDATA messages COPYDATASTRUCT = if RUBY_ENGINE == "jruby" struct ['ULONG_PTR dwData', 'DWORD cbData', 'LPVOID lpData'] else struct ['uintptr_t dwData', 'DWORD cbData', 'LPVOID lpData'] end - + # Compatibility for security attribute retrieval. if RUBY_VERSION < "1.9" # Alias functions to > 1.9 capitalization @@ -196,15 +194,15 @@ module Net alias_method new_name, name module_function new_name end - + def self.malloc_ptr(size) return DL.malloc(size) end - + def self.get_ptr(data) return data.to_ptr end - + def self.set_ptr_data(ptr, data) ptr[0] = data end @@ -220,15 +218,15 @@ module Net attach_function :malloc, [:size_t], :pointer attach_function :free, [:pointer], :void end - + def self.malloc_ptr(size) Fiddle::Pointer.new(LibC.malloc(size), size, LibC.method(:free)) end - + def self.get_ptr(ptr) return data.address end - + def self.set_ptr_data(ptr, data) ptr.write_string_length(data, data.size) end @@ -236,19 +234,19 @@ module Net def self.malloc_ptr(size) return DL::CPtr.malloc(size, DL::RUBY_FREE) end - + def self.get_ptr(data) return DL::CPtr.to_ptr data end - + def self.set_ptr_data(ptr, data) - DL::CPtr.new(ptr)[0,data.size] = data + DL::CPtr.new(ptr)[0, data.size] = data end end - + def self.get_security_attributes_for_user user = get_current_user - + psd_information = malloc_ptr(Win::SECURITY_DESCRIPTOR.size) raise_error_if_zero( Win.InitializeSecurityDescriptor(psd_information, @@ -261,45 +259,46 @@ module Net raise_error_if_zero( Win.IsValidSecurityDescriptor(psd_information) ) - + sa = Win::SECURITY_ATTRIBUTES.new(to_struct_ptr(malloc_ptr(Win::SECURITY_ATTRIBUTES.size))) sa.nLength = Win::SECURITY_ATTRIBUTES.size sa.lpSecurityDescriptor = psd_information.to_i sa.bInheritHandle = 1 - + return sa end - + if RUBY_ENGINE == "jruby" def self.ptr_to_s(ptr, size) ret = ptr.to_s(size) ret << "\x00" while ret.size < size ret end - + def self.ptr_to_handle(phandle) phandle.ptr end - + def self.ptr_to_dword(ptr) first = ptr.ptr.to_i - second = ptr_to_s(ptr,Win::SIZEOF_DWORD).unpack('L')[0] + second = ptr_to_s(ptr, Win::SIZEOF_DWORD).unpack('L')[0] raise "Error" unless first == second + first end - + def self.to_token_user(ptoken_information) TOKEN_USER.new(ptoken_information.to_ptr) end - + def self.to_struct_ptr(ptr) ptr.to_ptr end - + def self.get_sid(user) - ptr_to_s(user.to_ptr.ptr,Win::SIZEOF_DWORD).unpack('L')[0] + ptr_to_s(user.to_ptr.ptr, Win::SIZEOF_DWORD).unpack('L')[0] end - + def self.get_sid_ptr(user) user.to_ptr.ptr end @@ -307,39 +306,39 @@ module Net def self.get_sid(user) user.SID end - + def self.ptr_to_handle(phandle) phandle.ptr.to_i end - + def self.to_struct_ptr(ptr) ptr end - + def self.ptr_to_dword(ptr) ptr.to_s(Win::SIZEOF_DWORD).unpack('L')[0] end - + def self.to_token_user(ptoken_information) TOKEN_USER.new(ptoken_information) end - + def self.get_sid_ptr(user) user.SID end end - + def self.get_current_user token_handle = open_process_token(Win.GetCurrentProcess, Win::TOKEN_QUERY) token_user = get_token_information(token_handle, - Win::TOKEN_USER_INFORMATION_CLASS) + Win::TOKEN_USER_INFORMATION_CLASS) return token_user end - + def self.open_process_token(process_handle, desired_access) ptoken_handle = malloc_ptr(Win::SIZEOF_DWORD) - + raise_error_if_zero( Win.OpenProcessToken(process_handle, desired_access, ptoken_handle) @@ -347,12 +346,12 @@ module Net token_handle = ptr_to_handle(ptoken_handle) return token_handle end - + def self.get_token_information(token_handle, token_information_class) # Hold the size of the information to be returned preturn_length = malloc_ptr(Win::SIZEOF_DWORD) - + # Going to throw an INSUFFICIENT_BUFFER_ERROR, but that is ok # here. This is retrieving the size of the information to be # returned. @@ -360,7 +359,7 @@ module Net token_information_class, Win::NULL, 0, preturn_length) ptoken_information = malloc_ptr(ptr_to_dword(preturn_length)) - + # This call is going to write the requested information to # the memory location referenced by token_information. raise_error_if_zero( @@ -370,74 +369,76 @@ module Net ptoken_information.size, preturn_length) ) - + return to_token_user(ptoken_information) end - + def self.raise_error_if_zero(result) if result == 0 raise "Windows error: #{Win.GetLastError}" end end - + # Get a null-terminated string given a string. def self.get_cstr(str) return str + "\000" end end - + # This is the pseudo-socket implementation that mimics the interface of # a socket, translating each request into a Windows messaging call to # the pageant daemon. This allows pageant support to be implemented # simply by replacing the socket factory used by the Agent class. class Socket private_class_method :new - + # The factory method for creating a new Socket instance. def self.open new end - + # Create a new instance that communicates with the running pageant # instance. If no such instance is running, this will cause an error. def initialize @win = Win.FindWindow("Pageant", "Pageant") - + if @win.to_i == 0 raise Net::SSH::Exception, - "pageant process not running" + "pageant process not running" end - + @input_buffer = Net::SSH::Buffer.new @output_buffer = Net::SSH::Buffer.new end - + # Forwards the data to #send_query, ignoring any arguments after # the first. def send(data, *args) @input_buffer.append(data) - + ret = data.length - + while true return ret if @input_buffer.length < 4 + msg_length = @input_buffer.read_long + 4 @input_buffer.reset! - + return ret if @input_buffer.length < msg_length + msg = @input_buffer.read!(msg_length) @output_buffer.append(send_query(msg)) end end - + # Reads +n+ bytes from the cached result of the last query. If +n+ # is +nil+, returns all remaining data from the last query. def read(n = nil) @output_buffer.read(n) end - + def close; end - + # Packages the given query string and sends it to the pageant # process via the Windows messaging subsystem. The result is # cached, to be returned piece-wise when #read is called. @@ -446,29 +447,29 @@ module Net filemap = 0 ptr = nil id = Win.malloc_ptr(Win::SIZEOF_DWORD) - + mapname = "PageantRequest%08x" % Win.GetCurrentThreadId() security_attributes = Win.get_ptr Win.get_security_attributes_for_user - + filemap = Win.CreateFileMapping(Win::INVALID_HANDLE_VALUE, security_attributes, Win::PAGE_READWRITE, 0, AGENT_MAX_MSGLEN, mapname) - + if filemap == 0 || filemap == Win::INVALID_HANDLE_VALUE raise Net::SSH::Exception, - "Creation of file mapping failed with error: #{Win.GetLastError}" + "Creation of file mapping failed with error: #{Win.GetLastError}" end - + ptr = Win.MapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0, 0) - + if ptr.nil? || ptr.null? raise Net::SSH::Exception, "Mapping of file failed" end - + Win.set_ptr_data(ptr, query) - + # using struct to achieve proper alignment and field size on 64-bit platform cds = Win::COPYDATASTRUCT.new(Win.malloc_ptr(Win::COPYDATASTRUCT.size)) cds.dwData = AGENT_COPYDATA_ID @@ -476,14 +477,14 @@ module Net cds.lpData = Win.get_cstr(mapname) succ = Win.SendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL, cds.to_ptr, Win::SMTO_NORMAL, 5000, id) - + if succ > 0 retlen = 4 + ptr.to_s(4).unpack("N")[0] res = ptr.to_s(retlen) else raise Net::SSH::Exception, "Message failed with error: #{Win.GetLastError}" end - + return res ensure Win.UnmapViewOfFile(ptr) unless ptr.nil? || ptr.null? @@ -491,7 +492,6 @@ module Net end end end - end end end diff --git a/lib/net/ssh/authentication/pub_key_fingerprint.rb b/lib/net/ssh/authentication/pub_key_fingerprint.rb index a776006..42f2f16 100644 --- a/lib/net/ssh/authentication/pub_key_fingerprint.rb +++ b/lib/net/ssh/authentication/pub_key_fingerprint.rb @@ -22,12 +22,12 @@ module Net # returned by OpenSSH's <tt>`ssh-add -l -E SHA256`</tt>, i.e., # trailing base64 padding '=' characters are stripped and the # literal string +SHA256:+ is prepended. - def fingerprint(algorithm='MD5') + def fingerprint(algorithm = 'MD5') @fingerprint ||= {} @fingerprint[algorithm] ||= PubKeyFingerprint.fingerprint(to_blob, algorithm) end - def self.fingerprint(blob, algorithm='MD5') + def self.fingerprint(blob, algorithm = 'MD5') case algorithm.to_s.upcase when 'MD5' OpenSSL::Digest.hexdigest(algorithm, blob).scan(/../).join(":") diff --git a/lib/net/ssh/authentication/session.rb b/lib/net/ssh/authentication/session.rb index dfc5c06..773d704 100644 --- a/lib/net/ssh/authentication/session.rb +++ b/lib/net/ssh/authentication/session.rb @@ -11,7 +11,6 @@ require 'net/ssh/authentication/methods/keyboard_interactive' module Net module SSH module Authentication - # Raised if the current authentication method is not allowed class DisallowedMethod < Net::SSH::Exception end @@ -42,7 +41,7 @@ module Net # Instantiates a new Authentication::Session object over the given # transport layer abstraction. - def initialize(transport, options={}) + def initialize(transport, options = {}) self.logger = transport.logger @transport = transport @@ -55,7 +54,7 @@ module Net # Attempts to authenticate the given user, in preparation for the next # service request. Returns true if an authentication method succeeds in # authenticating the user, and false otherwise. - def authenticate(next_service, username, password=nil) + def authenticate(next_service, username, password = nil) debug { "beginning authentication of `#{username}'" } transport.send_message(transport.service_request("ssh-userauth")) @@ -70,22 +69,23 @@ module Net attempted = [] @auth_methods.each do |name| + next unless @allowed_auth_methods.include?(name) + + attempted << name + + debug { "trying #{name}" } begin - next unless @allowed_auth_methods.include?(name) - attempted << name - - debug { "trying #{name}" } - begin - auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join) - method = auth_class.new(self, key_manager: key_manager, password_prompt: options[:password_prompt]) - rescue NameError - debug {"Mechanism #{name} was requested, but isn't a known type. Ignoring it."} - next - end - - return true if method.authenticate(next_service, username, password) - rescue Net::SSH::Authentication::DisallowedMethod + auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join) + method = auth_class.new(self, + key_manager: key_manager, password_prompt: options[:password_prompt], + pubkey_algorithms: options[:pubkey_algorithms] || nil) + rescue NameError + debug {"Mechanism #{name} was requested, but isn't a known type. Ignoring it."} + next end + + return true if method.authenticate(next_service, username, password) + rescue Net::SSH::Authentication::DisallowedMethod end error { "all authorization methods failed (tried #{attempted.join(', ')})" } @@ -129,6 +129,7 @@ module Net def expect_message(type) message = next_message raise Net::SSH::Exception, "expected #{type}, got #{message.type} (#{message})" unless message.type == type + message end diff --git a/lib/net/ssh/buffer.rb b/lib/net/ssh/buffer.rb index 0fe4e56..b68d656 100644 --- a/lib/net/ssh/buffer.rb +++ b/lib/net/ssh/buffer.rb @@ -5,7 +5,6 @@ require 'net/ssh/authentication/ed25519_loader' module Net module SSH - # Net::SSH::Buffer is a flexible class for building and parsing binary # data packets. It provides a stream-like interface for sequentially # reading data items from the buffer, as well as a useful helper method @@ -71,7 +70,7 @@ module Net # Creates a new buffer, initialized to the given content. The position # is initialized to the beginning of the buffer. - def initialize(content="") + def initialize(content = String.new) @content = content.to_s @position = 0 end @@ -118,7 +117,7 @@ module Net # Resets the buffer, making it empty. Also, resets the read position to # 0. def clear! - @content = "" + @content = String.new @position = 0 end @@ -129,12 +128,12 @@ module Net # would otherwise tend to grow without bound. # # Returns the buffer object itself. - def consume!(n=position) + def consume!(n = position) if n >= length # optimize for a fairly common case clear! elsif n > 0 - @content = @content[n..-1] || "" + @content = @content[n..-1] || String.new @position -= n @position = 0 if @position < 0 end @@ -172,7 +171,7 @@ module Net # Reads and returns the next +count+ bytes from the buffer, starting from # the read position. If +count+ is +nil+, this will return all remaining # text in the buffer. This method will increment the pointer. - def read(count=nil) + def read(count = nil) count ||= length count = length - @position if @position + count > length @position += count @@ -181,7 +180,7 @@ module Net # Reads (as #read) and returns the given number of bytes from the buffer, # and then consumes (as #consume!) all data up to the new read position. - def read!(count=nil) + def read!(count = nil) data = read(count) consume! data @@ -237,6 +236,7 @@ module Net def read_bignum data = read_string return unless data + OpenSSL::BN.new(data, 2) end @@ -346,7 +346,12 @@ module Net # Optimized version of write where the caller gives up ownership of string # to the method. This way we can mutate the string. def write_moved(string) - @content << string.force_encoding('BINARY') + @content << + if string.frozen? + string.dup.force_encoding('BINARY') + else + string.force_encoding('BINARY') + end self end diff --git a/lib/net/ssh/buffered_io.rb b/lib/net/ssh/buffered_io.rb index 54a4889..c2d34e2 100644 --- a/lib/net/ssh/buffered_io.rb +++ b/lib/net/ssh/buffered_io.rb @@ -1,9 +1,8 @@ require 'net/ssh/buffer' require 'net/ssh/loggable' -module Net +module Net module SSH - # This module is used to extend sockets and other IO objects, to allow # them to be buffered for both read and write. This abstraction makes it # quite easy to write a select-based event loop @@ -48,19 +47,19 @@ module Net # end module BufferedIo include Loggable - + # Called when the #extend is called on an object, with this module as the # argument. It ensures that the modules instance variables are all properly # initialized. - def self.extended(object) #:nodoc: + def self.extended(object) # :nodoc: # need to use __send__ because #send is overridden in Socket object.__send__(:initialize_buffered_io) end - + # Tries to read up to +n+ bytes of data from the remote end, and appends # the data to the input buffer. It returns the number of bytes read, or 0 # if no data was available to be read. - def fill(n=8192) + def fill(n = 8192) input.consume! data = recv(n) debug { "read #{data.length} bytes" } @@ -70,31 +69,31 @@ module Net @input_errors << e return 0 end - + # Read up to +length+ bytes from the input buffer. If +length+ is nil, # all available data is read from the buffer. (See #available.) - def read_available(length=nil) + def read_available(length = nil) input.read(length || available) end - + # Returns the number of bytes available to be read from the input buffer. # (See #read_available.) def available input.available end - + # Enqueues data in the output buffer, to be written when #send_pending # is called. Note that the data is _not_ sent immediately by this method! def enqueue(data) output.append(data) end - + # Returns +true+ if there is data waiting in the output buffer, and # +false+ otherwise. def pending_write? output.length > 0 end - + # Sends as much of the pending output as possible. Returns +true+ if any # data was sent, and +false+ otherwise. def send_pending @@ -107,7 +106,7 @@ module Net return false end end - + # Calls #send_pending repeatedly, if necessary, blocking until the output # buffer is empty. def wait_for_pending_sends @@ -115,31 +114,32 @@ module Net while output.length > 0 result = IO.select(nil, [self]) or next next unless result[1].any? + send_pending end end - + public # these methods are primarily for use in tests - - def write_buffer #:nodoc: + + def write_buffer # :nodoc: output.to_s end - - def read_buffer #:nodoc: + + def read_buffer # :nodoc: input.to_s end - + private - + #-- # Can't use attr_reader here (after +private+) without incurring the # wrath of "ruby -w". We hates it. #++ - + def input; @input; end def output; @output; end - + # Initializes the intput and output buffers for this object. This method # is called automatically when the module is mixed into an object via # Object#extend (see Net::SSH::BufferedIo.extended), but must be called @@ -166,7 +166,7 @@ module Net # http://github.com/net-ssh/net-ssh/tree/portfwfix # module ForwardedBufferedIo - def fill(n=8192) + def fill(n = 8192) begin super(n) rescue Errno::ECONNRESET => e @@ -181,7 +181,7 @@ module Net end end end - + def send_pending begin super @@ -198,6 +198,5 @@ module Net end end end - end end diff --git a/lib/net/ssh/config.rb b/lib/net/ssh/config.rb index a40262e..86c75f5 100644 --- a/lib/net/ssh/config.rb +++ b/lib/net/ssh/config.rb @@ -1,6 +1,5 @@ module Net module SSH - # The Net::SSH::Config class is used to parse OpenSSH configuration files, # and translates that syntax into the configuration syntax that Net::SSH # understands. This lets Net::SSH scripts read their configuration (to @@ -34,7 +33,7 @@ module Net # * ProxyJump => maps to the :proxy option # * PubKeyAuthentication => maps to the :auth_methods option # * RekeyLimit => :rekey_limit - # * StrictHostKeyChecking => :strict_host_key_checking + # * StrictHostKeyChecking => :verify_host_key # * User => :user # * UserKnownHostsFile => :user_known_hosts_file # * NumberOfPasswordPrompts => :number_of_password_prompts @@ -66,7 +65,7 @@ module Net # given +files+ (defaulting to the list of files returned by # #default_files), translates the resulting hash into the options # recognized by Net::SSH, and returns them. - def for(host, files=expandable_default_files) + def for(host, files = expandable_default_files) translate(files.inject({}) { |settings, file| load(file, host, settings) }) @@ -78,7 +77,7 @@ module Net # ones. Returns a hash containing the OpenSSH options. (See # #translate for how to convert the OpenSSH options into Net::SSH # options.) - def load(path, host, settings={}, base_dir = nil) + def load(path, host, settings = {}, base_dir = nil) file = File.expand_path(path) base_dir ||= File.dirname(file) return settings unless File.readable?(file) @@ -186,17 +185,35 @@ module Net # Filters default_files down to the files that are expandable. def expandable_default_files default_files.keep_if do |path| - begin - File.expand_path(path) - true - rescue ArgumentError - false - end + File.expand_path(path) + true + rescue ArgumentError + false end end private + def translate_verify_host_key(value) + case value + when false + :never + when true + :always + when 'accept-new' + :accept_new + end + end + + def translate_keepalive(hash, value) + if value && value.to_i > 0 + hash[:keepalive] = true + hash[:keepalive_interval] = value.to_i + else + hash[:keepalive] = false + end + end + TRANSLATE_CONFIG_KEY_RENAME_MAP = { bindaddress: :bind_address, compression: :compression, @@ -211,13 +228,14 @@ module Net identityfile: :keys, fingerprinthash: :fingerprint_hash, port: :port, - stricthostkeychecking: :strict_host_key_checking, user: :user, userknownhostsfile: :user_known_hosts_file, checkhostip: :check_host_ip }.freeze def translate_config_key(hash, key, value, settings) case key + when :stricthostkeychecking + hash[:verify_host_key] = translate_verify_host_key(value) when :ciphers hash[:encryption] = value.split(/,/) when :hostbasedauthentication @@ -235,12 +253,7 @@ module Net when :serveralivecountmax hash[:keepalive_maxcount] = value.to_i if value when :serveraliveinterval - if value && value.to_i > 0 - hash[:keepalive] = true - hash[:keepalive_interval] = value.to_i - else - hash[:keepalive] = false - end + translate_keepalive(hash, value) when :passwordauthentication if value (hash[:auth_methods] << 'password').uniq! @@ -302,9 +315,9 @@ module Net # host names. def pattern2regex(pattern) tail = pattern - prefix = "" + prefix = String.new while !tail.empty? do - head,sep,tail = tail.partition(/[\*\?]/) + head, sep, tail = tail.partition(/[\*\?]/) prefix = prefix + Regexp.quote(head) case sep when '*' @@ -358,7 +371,7 @@ module Net conditions = conditions.each_slice(2) condition_matches = [] - conditions.each do |(kind,exprs)| + conditions.each do |(kind, exprs)| exprs = unquote(exprs) case kind.downcase diff --git a/lib/net/ssh/connection/channel.rb b/lib/net/ssh/connection/channel.rb index 7bfee72..6fcf790 100644 --- a/lib/net/ssh/connection/channel.rb +++ b/lib/net/ssh/connection/channel.rb @@ -5,7 +5,6 @@ require 'net/ssh/connection/term' module Net module SSH module Connection - # The channel abstraction. Multiple "channels" can be multiplexed onto a # single SSH channel, each operating independently and seemingly in parallel. # This class represents a single such channel. Most operations performed @@ -55,55 +54,55 @@ module Net class Channel include Loggable include Constants - + # The local id for this channel, assigned by the Net::SSH::Connection::Session instance. attr_reader :local_id - + # The remote id for this channel, assigned by the remote host. attr_reader :remote_id - + # The type of this channel, usually "session". attr_reader :type - + # The underlying Net::SSH::Connection::Session instance that supports this channel. attr_reader :connection - + # The maximum packet size that the local host can receive. attr_reader :local_maximum_packet_size - + # The maximum amount of data that the local end of this channel can # receive. This is a total, not per-packet. attr_reader :local_maximum_window_size - + # The maximum packet size that the remote host can receive. attr_reader :remote_maximum_packet_size - + # The maximum amount of data that the remote end of this channel can # receive. This is a total, not per-packet. attr_reader :remote_maximum_window_size - + # This is the remaining window size on the local end of this channel. When # this reaches zero, no more data can be received. attr_reader :local_window_size - + # This is the remaining window size on the remote end of this channel. When # this reaches zero, no more data can be sent. attr_reader :remote_window_size - + # A hash of properties for this channel. These can be used to store state # information about this channel. See also #[] and #[]=. attr_reader :properties - + # The output buffer for this channel. Data written to the channel is # enqueued here, to be written as CHANNEL_DATA packets during each pass of # the event loop. See Connection::Session#process and #enqueue_pending_output. - attr_reader :output #:nodoc: - + attr_reader :output # :nodoc: + # The list of pending requests. Each time a request is sent which requires # a reply, the corresponding callback is pushed onto this queue. As responses # arrive, they are shifted off the front and handled. - attr_reader :pending_requests #:nodoc: - + attr_reader :pending_requests # :nodoc: + # Instantiates a new channel on the given connection, of the given type, # and with the given id. If a block is given, it will be remembered until # the channel is confirmed open by the server, and will be invoked at @@ -112,36 +111,36 @@ module Net # This also sets the default maximum packet size and maximum window size. def initialize(connection, type, local_id, max_pkt_size = 0x8000, max_win_size = 0x20000, &on_confirm_open) self.logger = connection.logger - + @connection = connection @type = type @local_id = local_id - + @local_maximum_packet_size = max_pkt_size @local_window_size = @local_maximum_window_size = max_win_size - + @on_confirm_open = on_confirm_open - + @output = Buffer.new - + @properties = {} - + @pending_requests = [] @on_open_failed = @on_data = @on_extended_data = @on_process = @on_close = @on_eof = nil @on_request = {} @closing = @eof = @sent_eof = @local_closed = @remote_closed = false end - + # A shortcut for accessing properties of the channel (see #properties). def [](name) @properties[name] end - + # A shortcut for setting properties of the channel (see #properties). def []=(name, value) @properties[name] = value end - + # Syntactic sugar for executing a command. Sends a channel request asking # that the given command be invoked. If the block is given, it will be # called when the server responds. The first parameter will be the @@ -161,7 +160,7 @@ module Net def exec(command, &block) send_channel_request("exec", :string, command, &block) end - + # Syntactic sugar for requesting that a subsystem be started. Subsystems # are a way for other protocols (like SFTP) to be run, using SSH as # the transport. Generally, you'll never need to call this directly unless @@ -178,7 +177,7 @@ module Net def subsystem(subsystem, &block) send_channel_request("subsystem", :string, subsystem, &block) end - + # Syntactic sugar for setting an environment variable in the remote # process' environment. Note that for security reasons, the server may # refuse to set certain environment variables, or all, at the server's @@ -190,7 +189,7 @@ module Net def env(variable_name, variable_value, &block) send_channel_request("env", :string, variable_name, :string, variable_value, &block) end - + # A hash of the valid PTY options (see #request_pty). VALID_PTY_OPTIONS = { term: "xterm", chars_wide: 80, @@ -198,7 +197,7 @@ module Net pixels_wide: 640, pixels_high: 480, modes: {} } - + # Requests that a pseudo-tty (or "pty") be made available for this channel. # This is useful when you want to invoke and interact with some kind of # screen-based program (e.g., vim, or some menuing system). @@ -218,24 +217,24 @@ module Net # puts "could not obtain pty" # end # end - def request_pty(opts={}, &block) + def request_pty(opts = {}, &block) extra = opts.keys - VALID_PTY_OPTIONS.keys raise ArgumentError, "invalid option(s) to request_pty: #{extra.inspect}" if extra.any? - + opts = VALID_PTY_OPTIONS.merge(opts) - + modes = opts[:modes].inject(Buffer.new) do |memo, (mode, data)| memo.write_byte(mode).write_long(data) end # mark the end of the mode opcode list with a 0 byte modes.write_byte(0) - + send_channel_request("pty-req", :string, opts[:term], - :long, opts[:chars_wide], :long, opts[:chars_high], - :long, opts[:pixels_wide], :long, opts[:pixels_high], - :string, modes.to_s, &block) + :long, opts[:chars_wide], :long, opts[:chars_high], + :long, opts[:pixels_wide], :long, opts[:pixels_high], + :string, modes.to_s, &block) end - + # Sends data to the channel's remote endpoint. This usually has the # effect of sending the given string to the remote process' stdin stream. # Note that it does not immediately send the data across the channel, @@ -251,9 +250,10 @@ module Net # channel.send_data("the password\n") def send_data(data) raise EOFError, "cannot send data if channel has declared eof" if eof? + output.append(data.to_s) end - + # Returns true if the channel exists in the channel list of the session, # and false otherwise. This can be used to determine whether a channel has # been closed or not. @@ -262,7 +262,7 @@ module Net def active? connection.channels.key?(local_id) end - + # Runs the SSH event loop until the channel is no longer active. This is # handy for blocking while you wait for some channel to finish. # @@ -271,7 +271,7 @@ module Net def wait connection.loop { active? } end - + # True if close() has been called; NOTE: if the channel has data waiting to # be sent then the channel will close after all the data is sent. See # closed?() to determine if we have actually sent CHANNEL_CLOSE to server. @@ -280,61 +280,63 @@ module Net def closing? @closing end - + # True if we have sent CHANNEL_CLOSE to the remote server. def local_closed? @local_closed end - + def remote_closed? @remote_closed end - + def remote_closed! @remote_closed = true end - + # Requests that the channel be closed. It only marks the channel to be closed # the CHANNEL_CLOSE message will be sent from event loop def close return if @closing + @closing = true end - + # Returns true if the local end of the channel has declared that no more # data is forthcoming (see #eof!). Trying to send data via #send_data when # this is true will result in an exception being raised. def eof? @eof end - + # Tells the remote end of the channel that no more data is forthcoming # from this end of the channel. The remote end may still send data. # The CHANNEL_EOF packet will be sent once the output buffer is empty. def eof! return if eof? + @eof = true end - + # If an #on_process handler has been set up, this will cause it to be # invoked (passing the channel itself as an argument). It also causes all # pending output to be enqueued as CHANNEL_DATA packets (see #enqueue_pending_output). def process @on_process.call(self) if @on_process enqueue_pending_output - + if @eof and not @sent_eof and output.empty? and remote_id and not @local_closed connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id)) @sent_eof = true end - + if @closing and not @local_closed and output.empty? and remote_id connection.send_message(Buffer.from(:byte, CHANNEL_CLOSE, :long, remote_id)) @local_closed = true connection.cleanup_channel(self) end end - + # Registers a callback to be invoked when data packets are received by the # channel. The callback is called with the channel as the first argument, # and the data as the second. @@ -349,7 +351,7 @@ module Net old, @on_data = @on_data, block old end - + # Registers a callback to be invoked when extended data packets are received # by the channel. The callback is called with the channel as the first # argument, the data type (as an integer) as the second, and the data as @@ -364,7 +366,7 @@ module Net old, @on_extended_data = @on_extended_data, block old end - + # Registers a callback to be invoked for each pass of the event loop for # this channel. There are no guarantees on timeliness in the event loop, # but it will be called roughly once for each packet received by the @@ -391,7 +393,7 @@ module Net old, @on_process = @on_process, block old end - + # Registers a callback to be invoked when the server acknowledges that a # channel is closed. This is invoked with the channel as the sole argument. # @@ -402,7 +404,7 @@ module Net old, @on_close = @on_close, block old end - + # Registers a callback to be invoked when the server indicates that no more # data will be sent to the channel (although the channel can still send # data to the server). The channel is the sole argument to the callback. @@ -414,7 +416,7 @@ module Net old, @on_eof = @on_eof, block old end - + # Registers a callback to be invoked when the server was unable to open # the requested channel. The channel itself will be passed to the block, # along with the integer "reason code" for the failure, and a textual @@ -429,7 +431,7 @@ module Net old, @on_open_failed = @on_open_failed, block old end - + # Registers a callback to be invoked when a channel request of the given # type is received. The callback will receive the channel as the first # argument, and the associated (unparsed) data as the second. The data @@ -460,7 +462,7 @@ module Net old, @on_request[type] = @on_request[type], block old end - + # Sends a new channel request with the given name. The extra +data+ # parameter must either be empty, or consist of an even number of # arguments. See Net::SSH::Buffer.from for a description of their format. @@ -486,28 +488,29 @@ module Net def send_channel_request(request_name, *data, &callback) info { "sending channel request #{request_name.inspect}" } fail "Channel open not yet confirmed, please call send_channel_request(or exec) from block of open_channel" unless remote_id + msg = Buffer.from(:byte, CHANNEL_REQUEST, - :long, remote_id, :string, request_name, - :bool, !callback.nil?, *data) + :long, remote_id, :string, request_name, + :bool, !callback.nil?, *data) connection.send_message(msg) pending_requests << callback if callback end - + public # these methods are public, but for Net::SSH internal use only - + # Enqueues pending output at the connection as CHANNEL_DATA packets. This # does nothing if the channel has not yet been confirmed open (see # #do_open_confirmation). This is called automatically by #process, which # is called from the event loop (Connection::Session#process). You will # generally not need to invoke it directly. - def enqueue_pending_output #:nodoc: + def enqueue_pending_output # :nodoc: return unless remote_id - + while output.length > 0 length = output.length length = remote_window_size if length > remote_window_size length = remote_maximum_packet_size if length > remote_maximum_packet_size - + if length > 0 connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length))) output.consume! @@ -517,14 +520,14 @@ module Net end end end - + # Invoked when the server confirms that a channel has been opened. # The remote_id is the id of the channel as assigned by the remote host, # and max_window and max_packet are the maximum window and maximum # packet sizes, respectively. If an open-confirmation callback was # given when the channel was created, it is invoked at this time with # the channel itself as the sole argument. - def do_open_confirmation(remote_id, max_window, max_packet) #:nodoc: + def do_open_confirmation(remote_id, max_window, max_packet) # :nodoc: @remote_id = remote_id @remote_window_size = @remote_maximum_window_size = max_window @remote_maximum_packet_size = max_packet @@ -533,7 +536,7 @@ module Net set_remote_env(connection.options[:set_env]) if connection.options[:set_env] @on_confirm_open.call(self) if @on_confirm_open end - + # Invoked when the server failed to open the channel. If an #on_open_failed # callback was specified, it will be invoked with the channel, reason code, # and description as arguments. Otherwise, a ChannelOpenFailed exception @@ -545,16 +548,16 @@ module Net raise ChannelOpenFailed.new(reason_code, description) end end - + # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and # causes the remote window size to be adjusted upwards by the given # number of bytes. This has the effect of allowing more data to be sent # from the local end to the remote end of the channel. - def do_window_adjust(bytes) #:nodoc: + def do_window_adjust(bytes) # :nodoc: @remote_maximum_window_size += bytes @remote_window_size += bytes end - + # Invoked when the server sends a channel request. If any #on_request # callback has been registered for the specific type of this request, # it is invoked. If +want_reply+ is true, a packet will be sent of @@ -563,32 +566,32 @@ module Net # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The # callback should accept the channel as the first argument, and the # request-specific data as the second. - def do_request(request, want_reply, data) #:nodoc: + def do_request(request, want_reply, data) # :nodoc: result = true - + begin callback = @on_request[request] or raise ChannelRequestFailed callback.call(self, data) rescue ChannelRequestFailed result = false end - + if want_reply msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id) connection.send_message(msg) end end - + # Invokes the #on_data callback when the server sends data to the # channel. This will reduce the available window size on the local end, # but does not actually throttle requests that come in illegally when # the window size is too small. The callback is invoked with the channel # as the first argument, and the data as the second. - def do_data(data) #:nodoc: + def do_data(data) # :nodoc: update_local_window_size(data.length) @on_data.call(self, data) if @on_data end - + # Invokes the #on_extended_data callback when the server sends # extended data to the channel. This will reduce the available window # size on the local end. The callback is invoked with the channel, @@ -597,20 +600,20 @@ module Net update_local_window_size(data.length) @on_extended_data.call(self, type, data) if @on_extended_data end - + # Invokes the #on_eof callback when the server indicates that no # further data is forthcoming. The callback is invoked with the channel # as the argument. def do_eof @on_eof.call(self) if @on_eof end - + # Invokes the #on_close callback when the server closes a channel. # The channel is the only argument. def do_close @on_close.call(self) if @on_close end - + # Invokes the next pending request callback with +false+ as the second # argument. def do_failure @@ -620,7 +623,7 @@ module Net error { "channel failure received with no pending request to handle it (bug?)" } end end - + # Invokes the next pending request callback with +true+ as the second # argument. def do_success @@ -683,10 +686,10 @@ module Net # # channel.set_remote_env foo: 'bar', baz: 'whale' def set_remote_env(env) + env.each { |key, value| puts "E:#{key} V:#{value}" } env.each { |key, value| self.env(key, value) } end end - end end end diff --git a/lib/net/ssh/connection/constants.rb b/lib/net/ssh/connection/constants.rb index a2b0257..5370fa8 100644 --- a/lib/net/ssh/connection/constants.rb +++ b/lib/net/ssh/connection/constants.rb @@ -1,11 +1,9 @@ module Net module SSH module Connection - # Definitions of constants that are specific to the connection layer of the # SSH protocol. module Constants - #-- # Connection protocol generic messages #++ @@ -29,9 +27,7 @@ module Net CHANNEL_REQUEST = 98 CHANNEL_SUCCESS = 99 CHANNEL_FAILURE = 100 - end - end end end diff --git a/lib/net/ssh/connection/event_loop.rb b/lib/net/ssh/connection/event_loop.rb index cd9d3f1..5aac78c 100644 --- a/lib/net/ssh/connection/event_loop.rb +++ b/lib/net/ssh/connection/event_loop.rb @@ -1,7 +1,7 @@ require 'net/ssh/loggable' -module Net - module SSH +module Net + module SSH module Connection # EventLoop can be shared across multiple sessions # @@ -11,81 +11,84 @@ module Net # and we don't pass session. class EventLoop include Loggable - - def initialize(logger=nil) + + def initialize(logger = nil) self.logger = logger @sessions = [] end - + def register(session) @sessions << session end - + # process until timeout # if a block is given a session will be removed from loop # if block returns false for that session def process(wait = nil, &block) return false unless ev_preprocess(&block) - + ev_select_and_postprocess(wait) end - + # process the event loop but only for the sepcified session def process_only(session, wait = nil) orig_sessions = @sessions begin @sessions = [session] return false unless ev_preprocess + ev_select_and_postprocess(wait) ensure @sessions = orig_sessions end end - + # Call preprocess on each session. If block given and that # block retuns false then we exit the processing def ev_preprocess(&block) return false if block_given? && !yield(self) + @sessions.each(&:ev_preprocess) return false if block_given? && !yield(self) + return true end - + def ev_select_and_postprocess(wait) owners = {} r = [] w = [] minwait = nil @sessions.each do |session| - sr,sw,actwait = session.ev_do_calculate_rw_wait(wait) + sr, sw, actwait = session.ev_do_calculate_rw_wait(wait) minwait = actwait if actwait && (minwait.nil? || actwait < minwait) r.push(*sr) w.push(*sw) sr.each { |ri| owners[ri] = session } sw.each { |wi| owners[wi] = session } end - + readers, writers, = IO.select(r, w, nil, minwait) - + fired_sessions = {} - + if readers readers.each do |reader| session = owners[reader] - (fired_sessions[session] ||= { r: [],w: [] })[:r] << reader + (fired_sessions[session] ||= { r: [], w: [] })[:r] << reader end end if writers writers.each do |writer| session = owners[writer] - (fired_sessions[session] ||= { r: [],w: [] })[:w] << writer + (fired_sessions[session] ||= { r: [], w: [] })[:w] << writer end end - - fired_sessions.each do |s,rw| - s.ev_do_handle_events(rw[:r],rw[:w]) + + fired_sessions.each do |s, rw| + s.ev_do_handle_events(rw[:r], rw[:w]) end - + @sessions.each { |s| s.ev_do_postprocess(fired_sessions.key?(s)) } true end @@ -97,18 +100,21 @@ module Net # we call block with session as argument def ev_preprocess(&block) return false if block_given? && !yield(@sessions.first) + @sessions.each(&:ev_preprocess) return false if block_given? && !yield(@sessions.first) + return true end - + def ev_select_and_postprocess(wait) raise "Only one session expected" unless @sessions.count == 1 + session = @sessions.first - sr,sw,actwait = session.ev_do_calculate_rw_wait(wait) + sr, sw, actwait = session.ev_do_calculate_rw_wait(wait) readers, writers, = IO.select(sr, sw, nil, actwait) - - session.ev_do_handle_events(readers,writers) + + session.ev_do_handle_events(readers, writers) session.ev_do_postprocess(!((readers.nil? || readers.empty?) && (writers.nil? || writers.empty?))) end end diff --git a/lib/net/ssh/connection/keepalive.rb b/lib/net/ssh/connection/keepalive.rb index fb9be05..cdbb3e0 100644 --- a/lib/net/ssh/connection/keepalive.rb +++ b/lib/net/ssh/connection/keepalive.rb @@ -1,45 +1,46 @@ require 'net/ssh/loggable' -module Net - module SSH +module Net + module SSH module Connection - class Keepalive include Loggable - + def initialize(session) @last_keepalive_sent_at = nil @unresponded_keepalive_count = 0 @session = session self.logger = session.logger end - + def options @session.options end - + def enabled? options[:keepalive] end - + def interval options[:keepalive_interval] || Session::DEFAULT_IO_SELECT_TIMEOUT end - + def should_send? return false unless enabled? return true unless @last_keepalive_sent_at + Time.now - @last_keepalive_sent_at >= interval end - + def keepalive_maxcount (options[:keepalive_maxcount] || 3).to_i end - + def send_as_needed(was_events) return if was_events return unless should_send? + info { "sending keepalive #{@unresponded_keepalive_count}" } - + @unresponded_keepalive_count += 1 @session.send_global_request("keepalive@openssh.com") { |success, response| debug { "keepalive response successful. Missed #{@unresponded_keepalive_count - 1} keepalives" } @@ -53,7 +54,6 @@ module Net end end end - end end end diff --git a/lib/net/ssh/connection/session.rb b/lib/net/ssh/connection/session.rb index 61abd76..fbfc017 100644 --- a/lib/net/ssh/connection/session.rb +++ b/lib/net/ssh/connection/session.rb @@ -5,10 +5,9 @@ require 'net/ssh/service/forward' require 'net/ssh/connection/keepalive' require 'net/ssh/connection/event_loop' -module Net - module SSH +module Net + module SSH module Connection - # A session class representing the connection service running on top of # the SSH transport layer. It manages the creation of channels (see # #open_channel), and the dispatching of messages to the various channels. @@ -28,50 +27,50 @@ module Net class Session include Loggable include Constants - + # Default IO.select timeout threshold DEFAULT_IO_SELECT_TIMEOUT = 300 - + # The underlying transport layer abstraction (see Net::SSH::Transport::Session). attr_reader :transport - + # The map of options that were used to initialize this instance. attr_reader :options - + # The collection of custom properties for this instance. (See #[] and #[]=). attr_reader :properties - + # The map of channels, each key being the local-id for the channel. - attr_reader :channels #:nodoc: - + attr_reader :channels # :nodoc: + # The map of listeners that the event loop knows about. See #listen_to. - attr_reader :listeners #:nodoc: - + attr_reader :listeners # :nodoc: + # The map of specialized handlers for opening specific channel types. See # #on_open_channel. - attr_reader :channel_open_handlers #:nodoc: - + attr_reader :channel_open_handlers # :nodoc: + # The list of callbacks for pending requests. See #send_global_request. - attr_reader :pending_requests #:nodoc: - + attr_reader :pending_requests # :nodoc: + class NilChannel def initialize(session) @session = session end - + def method_missing(sym, *args) @session.lwarn { "ignoring request #{sym.inspect} for non-existent (closed?) channel; probably ssh server bug" } end end - + # Create a new connection service instance atop the given transport # layer. Initializes the listeners to be only the underlying socket object. - def initialize(transport, options={}) + def initialize(transport, options = {}) self.logger = transport.logger - + @transport = transport @options = options - + @channel_id_counter = -1 @channels = Hash.new(NilChannel.new(self)) @listeners = { transport.socket => nil } @@ -79,34 +78,34 @@ module Net @channel_open_handlers = {} @on_global_request = {} @properties = (options[:properties] || {}).dup - + @max_pkt_size = (options.key?(:max_pkt_size) ? options[:max_pkt_size] : 0x8000) @max_win_size = (options.key?(:max_win_size) ? options[:max_win_size] : 0x20000) - + @keepalive = Keepalive.new(self) - + @event_loop = options[:event_loop] || SingleSessionEventLoop.new @event_loop.register(self) end - + # Retrieves a custom property from this instance. This can be used to # store additional state in applications that must manage multiple # SSH connections. def [](key) @properties[key] end - + # Sets a custom property for this instance. def []=(key, value) @properties[key] = value end - + # Returns the name of the host that was given to the transport layer to # connect to. def host transport.host end - + # Returns true if the underlying transport has been closed. Note that # this can be a little misleading, since if the remote server has # closed the connection, the local end will still think it is open @@ -115,7 +114,7 @@ module Net def closed? transport.closed? end - + # Closes the session gracefully, blocking until all channels have # successfully closed, and then closes the underlying transport layer # connection. @@ -129,7 +128,7 @@ module Net end transport.close end - + # Performs a "hard" shutdown of the connection. In general, this should # never be done, but it might be necessary (in a rescue clause, for instance, # when the connection needs to close but you don't know the status of the @@ -137,10 +136,10 @@ module Net def shutdown! transport.shutdown! end - + # preserve a reference to Kernel#loop alias :loop_forever :loop - + # Returns +true+ if there are any channels currently active on this # session. By default, this will not include "invisible" channels # (such as those created by forwarding ports and such), but if you pass @@ -150,14 +149,14 @@ module Net # to be run. # # ssh.loop { ssh.busy? } - def busy?(include_invisible=false) + def busy?(include_invisible = false) if include_invisible channels.any? else channels.any? { |id, ch| !ch[:invisible] } end end - + # The main event loop. Calls #process until #process returns false. If a # block is given, it is passed to #process, otherwise a default proc is # used that just returns true if there are any channels active (see #busy?). @@ -175,7 +174,7 @@ module Net # int_pressed = false # trap("INT") { int_pressed = true } # ssh.loop(0.1) { not int_pressed } - def loop(wait=nil, &block) + def loop(wait = nil, &block) running = block || Proc.new { busy? } loop_forever { break unless process(wait, &running) } begin @@ -188,7 +187,7 @@ module Net end end end - + # The core of the event loop. It processes a single iteration of the event # loop. If a block is given, it should return false when the processing # should abort, which causes #process to return false. Otherwise, @@ -223,13 +222,13 @@ module Net # connections.delete_if { |ssh| !ssh.process(0.1, &condition) } # break if connections.empty? # end - def process(wait=nil, &block) + def process(wait = nil, &block) @event_loop.process(wait, &block) rescue StandardError force_channel_cleanup_on_close if closed? raise end - + # This is called internally as part of #process. It dispatches any # available incoming packets, and then runs Net::SSH::Connection::Channel#process # for any active channels. If a block is given, it is invoked at the @@ -237,31 +236,33 @@ module Net # false, this method returns false. Otherwise, it returns true. def preprocess(&block) return false if block_given? && !yield(self) + ev_preprocess(&block) return false if block_given? && !yield(self) + return true end - + # Called by event loop to process available data before going to # event multiplexing def ev_preprocess(&block) dispatch_incoming_packets(raise_disconnect_errors: false) each_channel { |id, channel| channel.process unless channel.local_closed? } end - + # Returns the file descriptors the event loop should wait for read/write events, # we also return the max wait def ev_do_calculate_rw_wait(wait) r = listeners.keys w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? } - [r,w,io_select_wait(wait)] + [r, w, io_select_wait(wait)] end - + # This is called internally as part of #process. def postprocess(readers, writers) ev_do_handle_events(readers, writers) end - + # It loops over the given arrays of reader IO's and writer IO's, # processing them as needed, and # then calls Net::SSH::Transport::Session#rekey_as_needed to allow the @@ -277,12 +278,12 @@ module Net end end end - + Array(writers).each do |writer| writer.send_pending end end - + # calls Net::SSH::Transport::Session#rekey_as_needed to allow the # transport layer to rekey def ev_do_postprocess(was_events) @@ -290,7 +291,7 @@ module Net transport.rekey_as_needed true end - + # Send a global request of the given type. The +extra+ parameters must # be even in number, and conform to the same format as described for # Net::SSH::Buffer.from. If a callback is not specified, the request will @@ -314,7 +315,7 @@ module Net pending_requests << callback if callback self end - + # Requests that a new channel be opened. By default, the channel will be # of type "session", but if you know what you're doing you can select any # of the channel types supported by the SSH protocol. The +extra+ parameters @@ -334,27 +335,27 @@ module Net # end # # channel.wait - def open_channel(type="session", *extra, &on_confirm) + def open_channel(type = "session", *extra, &on_confirm) local_id = get_next_channel_id - + channel = Channel.new(self, type, local_id, @max_pkt_size, @max_win_size, &on_confirm) msg = Buffer.from(:byte, CHANNEL_OPEN, :string, type, :long, local_id, - :long, channel.local_maximum_window_size, - :long, channel.local_maximum_packet_size, *extra) + :long, channel.local_maximum_window_size, + :long, channel.local_maximum_packet_size, *extra) send_message(msg) - + channels[local_id] = channel end - + class StringWithExitstatus < String def initialize(str, exitstatus) super(str) @exitstatus = exitstatus end - + attr_reader :exitstatus end - + # A convenience method for executing a command and interacting with it. If # no block is given, all output is printed via $stdout and $stderr. Otherwise, # the block is called for each data and extended data packet, with three @@ -379,17 +380,17 @@ module Net open_channel do |channel| channel.exec(command) do |ch, success| raise "could not execute command: #{command.inspect}" unless success - + if status - channel.on_request("exit-status") do |ch2,data| + channel.on_request("exit-status") do |ch2, data| status[:exit_code] = data.read_long end - + channel.on_request("exit-signal") do |ch2, data| status[:exit_signal] = data.read_long end end - + channel.on_data do |ch2, data| if block block.call(ch2, :stdout, data) @@ -397,7 +398,7 @@ module Net $stdout.print(data) end end - + channel.on_extended_data do |ch2, type, data| if block block.call(ch2, :stderr, data) @@ -408,7 +409,7 @@ module Net end end end - + # Same as #exec, except this will block until the command finishes. Also, # if no block is given, this will return all output (stdout and stderr) # as a single string. @@ -418,20 +419,20 @@ module Net # the returned string has an exitstatus method to query it's exit satus def exec!(command, status: nil, &block) block_or_concat = block || Proc.new do |ch, type, data| - ch[:result] ||= "" + ch[:result] ||= String.new ch[:result] << data end - + status ||= {} channel = exec(command, status: status, &block_or_concat) channel.wait - - channel[:result] ||= "" unless block + + channel[:result] ||= String.new unless block channel[:result] &&= channel[:result].force_encoding("UTF-8") unless block - + StringWithExitstatus.new(channel[:result], status[:exit_code]) if channel[:result] end - + # Enqueues a message to be sent to the server as soon as the socket is # available for writing. Most programs will never need to call this, but # if you are implementing an extension to the SSH protocol, or if you @@ -442,7 +443,7 @@ module Net def send_message(message) transport.enqueue_message(message) end - + # Adds an IO object for the event loop to listen to. If a callback # is given, it will be invoked when the io is ready to be read, otherwise, # the io will merely have its #fill method invoked. @@ -480,19 +481,19 @@ module Net def listen_to(io, &callback) listeners[io] = callback end - + # Removes the given io object from the listeners collection, so that the # event loop will no longer monitor it. def stop_listening_to(io) listeners.delete(io) end - + # Returns a reference to the Net::SSH::Service::Forward service, which can # be used for forwarding ports over SSH. def forward @forward ||= Service::Forward.new(self) end - + # Registers a handler to be invoked when the server wants to open a # channel on the client. The callback receives the connection object, # the new channel object, and the packet itself as arguments, and should @@ -506,7 +507,7 @@ module Net def on_open_channel(type, &block) channel_open_handlers[type] = block end - + # Registers a handler to be invoked when the server sends a global request # of the given type. The callback receives the request data as the first # parameter, and true/false as the second (indicating whether a response @@ -517,61 +518,61 @@ module Net old, @on_global_request[type] = @on_global_request[type], block old end - + def cleanup_channel(channel) if channel.local_closed? and channel.remote_closed? info { "#{host} delete channel #{channel.local_id} which closed locally and remotely" } channels.delete(channel.local_id) end end - + # If the #preprocess and #postprocess callbacks for this session need to run # periodically, this method returns the maximum number of seconds which may # pass between callbacks. def max_select_wait_time @keepalive.interval if @keepalive.enabled? end - + private - + # iterate channels with the posibility of callbacks opening new channels during the iteration def each_channel(&block) channels.dup.each(&block) end - + # Read all pending packets from the connection and dispatch them as # appropriate. Returns as soon as there are no more pending packets. def dispatch_incoming_packets(raise_disconnect_errors: true) while packet = transport.poll_message raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})" unless MAP.key?(packet.type) - + send(MAP[packet.type], packet) end rescue StandardError force_channel_cleanup_on_close if closed? raise if raise_disconnect_errors || !$!.is_a?(Net::SSH::Disconnect) end - + # Returns the next available channel id to be assigned, and increments # the counter. def get_next_channel_id @channel_id_counter += 1 end - + def force_channel_cleanup_on_close channels.each do |id, channel| channel_closed(channel) end end - + def channel_closed(channel) channel.remote_closed! channel.close - + cleanup_channel(channel) channel.do_close end - + # Invoked when a global request is received. The registered global # request callback will be invoked, if one exists, and the necessary # reply returned. @@ -583,41 +584,41 @@ module Net if result != :sent && result != true && result != false raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}" end - + if packet[:want_reply] && result != :sent msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE) send_message(msg) end end - + # Invokes the next pending request callback with +true+. def request_success(packet) info { "global request success" } callback = pending_requests.shift callback.call(true, packet) if callback end - + # Invokes the next pending request callback with +false+. def request_failure(packet) info { "global request failure" } callback = pending_requests.shift callback.call(false, packet) if callback end - + # Called when the server wants to open a channel. If no registered # channel handler exists for the given channel type, CHANNEL_OPEN_FAILURE # is returned, otherwise the callback is invoked and everything proceeds # accordingly. def channel_open(packet) info { "channel open #{packet[:channel_type]}" } - + local_id = get_next_channel_id - + channel = Channel.new(self, packet[:channel_type], local_id, @max_pkt_size, @max_win_size) channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size]) - + callback = channel_open_handlers[packet[:channel_type]] - + if callback begin callback[self, channel, packet] @@ -632,80 +633,80 @@ module Net else failure = [3, "unknown channel type #{channel.type}"] end - + if failure error { failure.inspect } msg = Buffer.from(:byte, CHANNEL_OPEN_FAILURE, :long, channel.remote_id, :long, failure[0], :string, failure[1], :string, "") end - + send_message(msg) end - + def channel_open_confirmation(packet) info { "channel_open_confirmation: #{packet[:local_id]} #{packet[:remote_id]} #{packet[:window_size]} #{packet[:packet_size]}" } channel = channels[packet[:local_id]] channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size]) end - + def channel_open_failure(packet) error { "channel_open_failed: #{packet[:local_id]} #{packet[:reason_code]} #{packet[:description]}" } channel = channels.delete(packet[:local_id]) channel.do_open_failed(packet[:reason_code], packet[:description]) end - + def channel_window_adjust(packet) info { "channel_window_adjust: #{packet[:local_id]} +#{packet[:extra_bytes]}" } channels[packet[:local_id]].do_window_adjust(packet[:extra_bytes]) end - + def channel_request(packet) info { "channel_request: #{packet[:local_id]} #{packet[:request]} #{packet[:want_reply]}" } channels[packet[:local_id]].do_request(packet[:request], packet[:want_reply], packet[:request_data]) end - + def channel_data(packet) info { "channel_data: #{packet[:local_id]} #{packet[:data].length}b" } channels[packet[:local_id]].do_data(packet[:data]) end - + def channel_extended_data(packet) info { "channel_extended_data: #{packet[:local_id]} #{packet[:data_type]} #{packet[:data].length}b" } channels[packet[:local_id]].do_extended_data(packet[:data_type], packet[:data]) end - + def channel_eof(packet) info { "channel_eof: #{packet[:local_id]}" } channels[packet[:local_id]].do_eof end - + def channel_close(packet) info { "channel_close: #{packet[:local_id]}" } - + channel = channels[packet[:local_id]] channel_closed(channel) end - + def channel_success(packet) info { "channel_success: #{packet[:local_id]}" } channels[packet[:local_id]].do_success end - + def channel_failure(packet) info { "channel_failure: #{packet[:local_id]}" } channels[packet[:local_id]].do_failure end - + def io_select_wait(wait) [wait, max_select_wait_time].compact.min end - + MAP = Constants.constants.each_with_object({}) do |name, memo| value = const_get(name) next unless Integer === value + memo[value] = name.downcase.to_sym end end - end end end diff --git a/lib/net/ssh/connection/term.rb b/lib/net/ssh/connection/term.rb index 5b5a7de..d4abad1 100644 --- a/lib/net/ssh/connection/term.rb +++ b/lib/net/ssh/connection/term.rb @@ -1,7 +1,6 @@ -module Net - module SSH +module Net + module SSH module Connection - # These constants are used when requesting a pseudo-terminal (via # Net::SSH::Connection::Channel#request_pty). The descriptions for each are # taken directly from RFC 4254 ("The Secure Shell (SSH) Connection Protocol"), @@ -10,173 +9,172 @@ module Net # Interrupt character; 255 if none. Similarly for the other characters. # Not all of these characters are supported on all systems. VINTR = 1 - + # The quit character (sends SIGQUIT signal on POSIX systems). VQUIT = 2 - + # Erase the character to left of the cursor. VERASE = 3 - + # Kill the current input line. VKILL = 4 - + # End-of-file character (sends EOF from the terminal). VEOF = 5 - + # End-of-line character in addition to carriage return and/or linefeed. VEOL = 6 - + # Additional end-of-line character. VEOL2 = 7 - + # Continues paused output (normally control-Q). VSTART = 8 - + # Pauses output (normally control-S). VSTOP = 9 - + # Suspends the current program. VSUSP = 10 - + # Another suspend character. VDSUSP = 11 - + # Reprints the current input line. VREPRINT = 12 - + # Erases a word left of cursor. VWERASE = 13 - + # Enter the next character typed literally, even if it is a special # character. VLNEXT = 14 - + # Character to flush output. VFLUSH = 15 - + # Switch to a different shell layer. VSWITCH = 16 - + # Prints system status line (load, command, pid, etc). VSTATUS = 17 - + # Toggles the flushing of terminal output. VDISCARD = 18 - + # The ignore parity flag. The parameter SHOULD be 0 if this flag is FALSE, # and 1 if it is TRUE. IGNPAR = 30 - + # Mark parity and framing errors. PARMRK = 31 - + # Enable checking of parity errors. INPCK = 32 - + # Strip 8th bit off characters. ISTRIP = 33 - + # Map NL into CR on input. INCLR = 34 - + # Ignore CR on input. IGNCR = 35 - + # Map CR to NL on input. ICRNL = 36 - + # Translate uppercase characters to lowercase. IUCLC = 37 - + # Enable output flow control. IXON = 38 - + # Any char will restart after stop. IXANY = 39 - + # Enable input flow control. IXOFF = 40 - + # Ring bell on input queue full. IMAXBEL = 41 - + # Enable signals INTR, QUIT, [D]SUSP. ISIG = 50 - + # Canonicalize input lines. ICANON = 51 - + # Enable input and output of uppercase characters by preceding their # lowercase equivalents with "\". XCASE = 52 - + # Enable echoing. ECHO = 53 - + # Visually erase chars. ECHOE = 54 - + # Kill character discards current line. ECHOK = 55 - + # Echo NL even if ECHO is off. ECHONL = 56 - + # Don't flush after interrupt. NOFLSH = 57 - + # Stop background jobs from output. TOSTOP = 58 - + # Enable extensions. IEXTEN = 59 - + # Echo control characters as ^(Char). ECHOCTL = 60 - + # Visual erase for line kill. ECHOKE = 61 - + # Retype pending input. PENDIN = 62 - + # Enable output processing. OPOST = 70 - + # Convert lowercase to uppercase. OLCUC = 71 - + # Map NL to CR-NL. ONLCR = 72 - + # Translate carriage return to newline (output). OCRNL = 73 - + # Translate newline to carriage return-newline (output). ONOCR = 74 - + # Newline performs a carriage return (output). ONLRET = 75 - + # 7 bit mode. CS7 = 90 - + # 8 bit mode. CS8 = 91 - + # Parity enable. PARENB = 92 - + # Odd parity, else even. PARODD = 93 - + # Specifies the input baud rate in bits per second. TTY_OP_ISPEED = 128 - + # Specifies the output baud rate in bits per second. TTY_OP_OSPEED = 129 end - end end end diff --git a/lib/net/ssh/errors.rb b/lib/net/ssh/errors.rb index 8fa8a4b..7455fed 100644 --- a/lib/net/ssh/errors.rb +++ b/lib/net/ssh/errors.rb @@ -1,4 +1,4 @@ -module Net +module Net module SSH # A general exception class, to act as the ancestor of all other Net::SSH # exception classes. @@ -33,7 +33,7 @@ module Net # a "channel open failed" message. class ChannelOpenFailed < Net::SSH::Exception attr_reader :code, :reason - + def initialize(code, reason) @code, @reason = code, reason super "#{reason} (#{code})" @@ -45,43 +45,43 @@ module Net # the remember_host! method on the exception, and then retry. class HostKeyError < Net::SSH::Exception # the callback to use when #remember_host! is called - attr_writer :callback #:nodoc: - + attr_writer :callback # :nodoc: + # situation-specific data describing the host (see #host, #port, etc.) - attr_writer :data #:nodoc: - + attr_writer :data # :nodoc: + # An accessor for getting at the data that was used to look up the host # (see also #fingerprint, #host, #port, #ip, and #key). def [](key) @data && @data[key] end - + # Returns the fingerprint of the key for the host, which either was not # found or did not match. def fingerprint @data && @data[:fingerprint] end - + # Returns the host name for the remote host, as reported by the socket. def host @data && @data[:peer] && @data[:peer][:host] end - + # Returns the port number for the remote host, as reported by the socket. def port @data && @data[:peer] && @data[:peer][:port] end - + # Returns the IP address of the remote host, as reported by the socket. def ip @data && @data[:peer] && @data[:peer][:ip] end - + # Returns the key itself, as reported by the remote host. def key @data && @data[:key] end - + # Tell Net::SSH to record this host and key in the known hosts file, so # that subsequent connections will remember them. def remember_host! diff --git a/lib/net/ssh/key_factory.rb b/lib/net/ssh/key_factory.rb index 04dac98..5d5cfbf 100644 --- a/lib/net/ssh/key_factory.rb +++ b/lib/net/ssh/key_factory.rb @@ -5,7 +5,6 @@ require 'net/ssh/authentication/ed25519_loader' module Net module SSH - # A factory class for returning new Key classes. It is used for obtaining # OpenSSL key instances via their SSH names, and for loading both public and # private keys. It used used primarily by Net::SSH itself, internally, and @@ -18,14 +17,14 @@ module Net class KeyFactory # Specifies the mapping of SSH names to OpenSSL key classes. MAP = { - 'dh' => OpenSSL::PKey::DH, - 'rsa' => OpenSSL::PKey::RSA, - 'dsa' => OpenSSL::PKey::DSA, + 'dh' => OpenSSL::PKey::DH, + 'rsa' => OpenSSL::PKey::RSA, + 'dsa' => OpenSSL::PKey::DSA, 'ecdsa' => OpenSSL::PKey::EC } MAP["ed25519"] = Net::SSH::Authentication::ED25519::PrivKey if defined? Net::SSH::Authentication::ED25519 - class <<self + class << self # Fetch an OpenSSL key instance by its SSH name. It will be a new, # empty key of the given type. def get(name) @@ -37,7 +36,7 @@ module Net # appropriately. The new key is returned. If the key itself is # encrypted (requiring a passphrase to use), the user will be # prompted to enter their password unless passphrase works. - def load_private_key(filename, passphrase=nil, ask_passphrase=true, prompt=Prompt.default) + def load_private_key(filename, passphrase = nil, ask_passphrase = true, prompt = Prompt.default) data = File.read(File.expand_path(filename)) load_data_private_key(data, passphrase, ask_passphrase, filename, prompt) end @@ -47,7 +46,7 @@ module Net # appropriately. The new key is returned. If the key itself is # encrypted (requiring a passphrase to use), the user will be # prompted to enter their password unless passphrase works. - def load_data_private_key(data, passphrase=nil, ask_passphrase=true, filename="", prompt=Prompt.default) + def load_data_private_key(data, passphrase = nil, ask_passphrase = true, filename = "", prompt = Prompt.default) key_type = classify_key(data, filename) encrypted_key = nil @@ -87,7 +86,7 @@ module Net # Loads a public key. It will correctly determine whether # the file describes an RSA or DSA key, and will load it # appropriately. The new public key is returned. - def load_data_public_key(data, filename="") + def load_data_public_key(data, filename = "") fields = data.split(/ /) blob = nil diff --git a/lib/net/ssh/known_hosts.rb b/lib/net/ssh/known_hosts.rb index 23a5606..eeb8635 100644 --- a/lib/net/ssh/known_hosts.rb +++ b/lib/net/ssh/known_hosts.rb @@ -6,6 +6,63 @@ require 'net/ssh/authentication/ed25519_loader' module Net module SSH + module HostKeyEntries + # regular public key entry + class PubKey < Delegator + def initialize(key, comment: nil) # rubocop:disable Lint/MissingSuper + @key = key + @comment = comment + end + + def ssh_type + @key.ssh_type + end + + def ssh_types + [ssh_type] + end + + def to_blob + @key.to_blob + end + + def __getobj__ + Kernel.warn("Calling Net::SSH::Buffer methods on HostKeyEntries PubKey is deprecated") + @key + end + + def matches_key?(server_key) + @key.ssh_type == server_key.ssh_type && @key.to_blob == server_key.to_blob + end + end + + # @cert-authority entry + class CertAuthority + def ssh_types + %w[ + ecdsa-sha2-nistp256-cert-v01@openssh.com + ecdsa-sha2-nistp384-cert-v01@openssh.com + ecdsa-sha2-nistp521-cert-v01@openssh.com + ssh-ed25519-cert-v01@openssh.com + ssh-rsa-cert-v01@openssh.com + ssh-rsa-cert-v00@openssh.com + ] + end + + def initialize(key, comment: nil) + @key = key + @comment = comment + end + + def matches_key?(server_key) + if ssh_types.include?(server_key.ssh_type) + server_key.signature_valid? && (server_key.signature_key.to_blob == @key.to_blob) + else + false + end + end + end + end # Represents the result of a search in known hosts # see search_for @@ -48,10 +105,10 @@ module Net SUPPORTED_TYPE.push('ssh-ed25519') if Net::SSH::Authentication::ED25519Loader::LOADED - class <<self + class << self # Searches all known host files (see KnownHosts.hostfiles) for all keys # of the given host. Returns an enumerable of keys found. - def search_for(host, options={}) + def search_for(host, options = {}) HostKeys.new(search_in(hostfiles(options), host, options), host, self, options) end @@ -70,7 +127,7 @@ module Net # # If you only want the user known host files, you can pass :user as # the second option. - def hostfiles(options, which=:all) + def hostfiles(options, which = :all) files = [] files += Array(options[:user_known_hosts_file] || %w[~/.ssh/known_hosts ~/.ssh/known_hosts2]) if which == :all || which == :user @@ -85,14 +142,12 @@ module Net # Looks in all user known host files (see KnownHosts.hostfiles) and tries to # add an entry for the given host and key to the first file it is able # to. - def add(host, key, options={}) + def add(host, key, options = {}) hostfiles(options, :user).each do |file| - begin - KnownHosts.new(file).add(host, key) - return - rescue SystemCallError - # try the next hostfile - end + KnownHosts.new(file).add(host, key) + return + rescue SystemCallError + # try the next hostfile end end end @@ -130,7 +185,13 @@ module Net File.open(source) do |file| file.each_line do |line| - hosts, type, key_content = line.split(' ') + if line.start_with?('@') + marker, hosts, type, key_content, comment = line.split(' ') + else + marker = nil + hosts, type, key_content, comment = line.split(' ') + end + # Skip empty line or one that is commented next if hosts.nil? || hosts.start_with?('#') @@ -145,7 +206,14 @@ module Net next unless found blob = key_content.unpack("m*").first - keys << Net::SSH::Buffer.new(blob).read_key + raw_key = Net::SSH::Buffer.new(blob).read_key + + keys << + if marker == "@cert-authority" + HostKeyEntries::CertAuthority.new(raw_key, comment: comment) + else + HostKeyEntries::PubKey.new(raw_key, comment: comment) + end end end @@ -155,11 +223,11 @@ module Net def match(host, pattern) if pattern.include?('*') || pattern.include?('?') # see man 8 sshd for pattern details - pattern_regexp = pattern.split('*').map do |x| - x.split('?').map do |y| + pattern_regexp = pattern.split('*', -1).map do |x| + x.split('?', -1).map do |y| Regexp.escape(y) end.join('.') - end.join('[^.]*') + end.join('.*') host =~ Regexp.new("\\A#{pattern_regexp}\\z") else diff --git a/lib/net/ssh/loggable.rb b/lib/net/ssh/loggable.rb index b9df4d5..49ea5dd 100644 --- a/lib/net/ssh/loggable.rb +++ b/lib/net/ssh/loggable.rb @@ -1,6 +1,5 @@ -module Net +module Net module SSH - # A simple module to make logging easier to deal with. It assumes that the # logger instance (if not nil) quacks like a Logger object (in Ruby's # standard library). Although used primarily internally by Net::SSH, it @@ -19,39 +18,39 @@ module Net # The logger instance that will be used to log messages. If nil, nothing # will be logged. attr_accessor :logger - + # Displays the result of yielding if the log level is Logger::DEBUG or # greater. def debug logger.add(Logger::DEBUG, nil, facility) { yield } if logger && logger.debug? end - + # Displays the result of yielding if the log level is Logger::INFO or # greater. def info logger.add(Logger::INFO, nil, facility) { yield } if logger && logger.info? end - + # Displays the result of yielding if the log level is Logger::WARN or # greater. (Called lwarn to avoid shadowing with Kernel#warn.) def lwarn logger.add(Logger::WARN, nil, facility) { yield } if logger && logger.warn? end - + # Displays the result of yielding if the log level is Logger:ERROR or # greater. def error logger.add(Logger::ERROR, nil, facility) { yield } if logger && logger.error? end - + # Displays the result of yielding if the log level is Logger::FATAL or # greater. def fatal logger.add(Logger::FATAL, nil, facility) { yield } if logger && logger.fatal? end - + private - + # Sets the "facility" value, used for reporting where a log message # originates. It defaults to the name of class with the object_id # appended. diff --git a/lib/net/ssh/packet.rb b/lib/net/ssh/packet.rb index a379586..cc762ca 100644 --- a/lib/net/ssh/packet.rb +++ b/lib/net/ssh/packet.rb @@ -5,7 +5,6 @@ require 'net/ssh/connection/constants' module Net module SSH - # A specialization of Buffer that knows the format of certain common # packet types. It auto-parses those packet types, and allows them to # be accessed via the #[] accessor. @@ -85,6 +84,7 @@ module Net def [](name) name = name.to_sym raise ArgumentError, "no such element #{name}" unless @named_elements.key?(name) + @named_elements[name] end diff --git a/lib/net/ssh/prompt.rb b/lib/net/ssh/prompt.rb index bffe458..a52463f 100644 --- a/lib/net/ssh/prompt.rb +++ b/lib/net/ssh/prompt.rb @@ -1,8 +1,7 @@ require 'io/console' -module Net +module Net module SSH - # Default prompt implementation, called for asking password from user. # It will never be instantiated directly, but will instead be created for # you automatically. @@ -13,8 +12,8 @@ module Net # # prompter = options[:password_prompt].start({type:'password'}) # while !ok && max_retries < 3 - # user = prompter.ask("user: ", {}, true) - # password = prompter.ask("password: ", {}, false) + # user = prompter.ask("user: ", true) + # password = prompter.ask("password: ", false) # ok = send(user, password) # prompter.sucess if ok # end @@ -24,9 +23,9 @@ module Net def self.default(options = {}) @default ||= new(options) end - + def initialize(options = {}); end - + # default prompt object implementation. More sophisticated implemenetations # might implement caching. class Prompter @@ -36,22 +35,22 @@ module Net $stdout.puts(info[:instruction]) unless info[:instruction].empty? end end - + # ask input from user, a prompter might ask for multiple inputs # (like user and password) in a single session. - def ask(prompt, echo=true) + def ask(prompt, echo = true) $stdout.print(prompt) $stdout.flush ret = $stdin.noecho(&:gets).chomp $stdout.print("\n") ret end - + # success method will be called when the password was accepted # It's a good time to save password asked to a cache. def success; end end - + # start password session. Multiple questions might be asked multiple times # on the returned object. Info hash tries to uniquely identify the password # session, so caching implementations can save passwords properly. @@ -59,6 +58,5 @@ module Net Prompter.new(info) end end - end end diff --git a/lib/net/ssh/proxy/command.rb b/lib/net/ssh/proxy/command.rb index 1bb2423..d6a89dd 100644 --- a/lib/net/ssh/proxy/command.rb +++ b/lib/net/ssh/proxy/command.rb @@ -5,7 +5,6 @@ require 'net/ssh/proxy/errors' module Net module SSH module Proxy - # An implementation of a command proxy. To use it, instantiate it, # then pass the instantiated object via the :proxy key to # Net::SSH.start: @@ -105,6 +104,7 @@ module Net if IO.select([self], nil, [self], timeout_in_seconds) == nil raise "Unexpected spurious read wakeup" end + retry end result diff --git a/lib/net/ssh/proxy/errors.rb b/lib/net/ssh/proxy/errors.rb index bbaf5dd..f696cf3 100644 --- a/lib/net/ssh/proxy/errors.rb +++ b/lib/net/ssh/proxy/errors.rb @@ -1,9 +1,8 @@ require 'net/ssh/errors' -module Net - module SSH +module Net + module SSH module Proxy - # A general exception class for all Proxy errors. class Error < Net::SSH::Exception; end @@ -12,7 +11,6 @@ module Net # Used when the server doesn't recognize the user's credentials. class UnauthorizedError < Error; end - end end end diff --git a/lib/net/ssh/proxy/http.rb b/lib/net/ssh/proxy/http.rb index 1cfaa62..fd9958e 100644 --- a/lib/net/ssh/proxy/http.rb +++ b/lib/net/ssh/proxy/http.rb @@ -1,10 +1,9 @@ require 'socket' require 'net/ssh/proxy/errors' -module Net - module SSH +module Net + module SSH module Proxy - # An implementation of an HTTP proxy. To use it, instantiate it, then # pass the instantiated object via the :proxy key to Net::SSH.start: # @@ -26,14 +25,14 @@ module Net class HTTP # The hostname or IP address of the HTTP proxy. attr_reader :proxy_host - + # The port number of the proxy. attr_reader :proxy_port - + # The map of additional options that were given to the object at # initialization. attr_reader :options - + # Create a new socket factory that tunnels via the given host and # port. The +options+ parameter is a hash of additional settings that # can be used to tweak this proxy connection. Specifically, the following @@ -41,52 +40,52 @@ module Net # # * :user => the user name to use when authenticating to the proxy # * :password => the password to use when authenticating - def initialize(proxy_host, proxy_port=80, options={}) + def initialize(proxy_host, proxy_port = 80, options = {}) @proxy_host = proxy_host @proxy_port = proxy_port @options = options end - + # Return a new socket connected to the given host and port via the # proxy that was requested when the socket factory was instantiated. def open(host, port, connection_options) socket = establish_connection(connection_options[:timeout]) socket.write "CONNECT #{host}:#{port} HTTP/1.1\r\n" socket.write "Host: #{host}:#{port}\r\n" - + if options[:user] credentials = ["#{options[:user]}:#{options[:password]}"].pack("m*").gsub(/\s/, "") socket.write "Proxy-Authorization: Basic #{credentials}\r\n" end - + socket.write "\r\n" - + resp = parse_response(socket) - + return socket if resp[:code] == 200 - + socket.close raise ConnectError, resp.inspect end - + protected - + def establish_connection(connect_timeout) Socket.tcp(proxy_host, proxy_port, nil, nil, connect_timeout: connect_timeout) end - + def parse_response(socket) version, code, reason = socket.gets.chomp.split(/ /, 3) headers = {} - + while (line = socket.gets) && (line.chomp! != "") name, value = line.split(/:/, 2) headers[name.strip] = value.strip end - + body = socket.read(headers["Content-Length"].to_i) if headers["Content-Length"] - + return { version: version, code: code.to_i, reason: reason, @@ -94,7 +93,6 @@ module Net body: body } end end - end end end diff --git a/lib/net/ssh/proxy/https.rb b/lib/net/ssh/proxy/https.rb index 298d537..2e12017 100644 --- a/lib/net/ssh/proxy/https.rb +++ b/lib/net/ssh/proxy/https.rb @@ -3,10 +3,9 @@ require 'openssl' require 'net/ssh/proxy/errors' require 'net/ssh/proxy/http' -module Net - module SSH +module Net + module SSH module Proxy - # A specialization of the HTTP proxy which encrypts the whole connection # using OpenSSL. This has the advantage that proxy authentication # information is not sent in plaintext. @@ -17,27 +16,27 @@ module Net # taken by Net::SSH::Proxy::HTTP it supports: # # * :ssl_context => the SSL configuration to use for the connection - def initialize(proxy_host, proxy_port=80, options={}) + def initialize(proxy_host, proxy_port = 80, options = {}) @ssl_context = options.delete(:ssl_context) || OpenSSL::SSL::SSLContext.new super(proxy_host, proxy_port, options) end - + protected - + # Shim to make OpenSSL::SSL::SSLSocket behave like a regular TCPSocket # for all intents and purposes of Net::SSH::BufferedIo module SSLSocketCompatibility - def self.extended(object) #:nodoc: + def self.extended(object) # :nodoc: object.define_singleton_method(:recv, object.method(:sysread)) object.sync_close = true end - + def send(data, _opts) syswrite(data) end end - + def establish_connection(connect_timeout) plain_socket = super(connect_timeout) OpenSSL::SSL::SSLSocket.new(plain_socket, @ssl_context).tap do |socket| @@ -46,7 +45,6 @@ module Net end end end - end end end diff --git a/lib/net/ssh/proxy/jump.rb b/lib/net/ssh/proxy/jump.rb index ab67e84..a5de7d9 100644 --- a/lib/net/ssh/proxy/jump.rb +++ b/lib/net/ssh/proxy/jump.rb @@ -1,10 +1,9 @@ require 'uri' require 'net/ssh/proxy/command' -module Net - module SSH +module Net + module SSH module Proxy - # An implementation of a jump proxy. To use it, instantiate it, # then pass the instantiated object via the :proxy key to # Net::SSH.start: @@ -18,39 +17,38 @@ module Net class Jump < Command # The jump proxies attr_reader :jump_proxies - + # Create a new socket factory that tunnels via multiple jump proxes as # [user@]host[:port]. def initialize(jump_proxies) @jump_proxies = jump_proxies end - + # Return a new socket connected to the given host and port via the jump # proxy that was requested when the socket factory was instantiated. def open(host, port, connection_options = nil) build_proxy_command_equivalent(connection_options) super end - + # We cannot build the ProxyCommand template until we know if the :config # option was specified during `Net::SSH.start`. def build_proxy_command_equivalent(connection_options = nil) first_jump, extra_jumps = jump_proxies.split(",", 2) config = connection_options && connection_options[:config] uri = URI.parse("ssh://#{first_jump}") - - template = "ssh" + + template = "ssh".dup template << " -l #{uri.user}" if uri.user template << " -p #{uri.port}" if uri.port template << " -J #{extra_jumps}" if extra_jumps template << " -F #{config}" if config != true && config template << " -W %h:%p " template << uri.host - + @command_line_template = template end end - end end end diff --git a/lib/net/ssh/proxy/socks4.rb b/lib/net/ssh/proxy/socks4.rb index eac35b0..9f4476a 100644 --- a/lib/net/ssh/proxy/socks4.rb +++ b/lib/net/ssh/proxy/socks4.rb @@ -6,7 +6,6 @@ require 'net/ssh/proxy/errors' module Net module SSH module Proxy - # An implementation of a SOCKS4 proxy. To use it, instantiate it, then # pass the instantiated object via the :proxy key to Net::SSH.start: # @@ -38,7 +37,7 @@ module Net # Create a new proxy connection to the given proxy host and port. # Optionally, a :user key may be given to identify the username # with which to authenticate. - def initialize(proxy_host, proxy_port=1080, options={}) + def initialize(proxy_host, proxy_port = 1080, options = {}) @proxy_host = proxy_host @proxy_port = proxy_port @options = options @@ -50,7 +49,7 @@ module Net socket = Socket.tcp(proxy_host, proxy_port, nil, nil, connect_timeout: connection_options[:timeout]) ip_addr = IPAddr.new(Resolv.getaddress(host)) - + packet = [VERSION, CONNECT, port.to_i, ip_addr.to_i, options[:user]].pack("CCnNZ*") socket.send packet, 0 @@ -63,7 +62,6 @@ module Net return socket end end - end end end diff --git a/lib/net/ssh/proxy/socks5.rb b/lib/net/ssh/proxy/socks5.rb index 08f2a3c..1e66318 100644 --- a/lib/net/ssh/proxy/socks5.rb +++ b/lib/net/ssh/proxy/socks5.rb @@ -4,7 +4,6 @@ require 'net/ssh/proxy/errors' module Net module SSH module Proxy - # An implementation of a SOCKS5 proxy. To use it, instantiate it, then # pass the instantiated object via the :proxy key to Net::SSH.start: # @@ -53,7 +52,7 @@ module Net # Create a new proxy connection to the given proxy host and port. # Optionally, :user and :password options may be given to # identify the username and password with which to authenticate. - def initialize(proxy_host, proxy_port=1080, options={}) + def initialize(proxy_host, proxy_port = 1080, options = {}) @proxy_host = proxy_host @proxy_port = proxy_port @options = options @@ -94,7 +93,7 @@ module Net packet << [port].pack("n") socket.send packet, 0 - + version, reply, = socket.recv(2).unpack("C*") socket.recv(1) address_type = socket.recv(1).getbyte(0) @@ -111,7 +110,7 @@ module Net raise ConnectError, "Illegal response type" end portnum = socket.recv(2) - + unless reply == SUCCESS socket.close raise ConnectError, "#{reply}" @@ -136,7 +135,6 @@ module Net end end end - end end end diff --git a/lib/net/ssh/service/forward.rb b/lib/net/ssh/service/forward.rb index 4bb3ae6..8b2f0f9 100644 --- a/lib/net/ssh/service/forward.rb +++ b/lib/net/ssh/service/forward.rb @@ -3,7 +3,6 @@ require 'net/ssh/loggable' module Net module SSH module Service - # This class implements various port forwarding services for use by # Net::SSH clients. The Forward class should never need to be instantiated # directly; instead, it should be accessed via the singleton instance @@ -18,7 +17,7 @@ module Net attr_reader :session # A simple class for representing a requested remote forwarded port. - Remote = Struct.new(:host, :port) #:nodoc: + Remote = Struct.new(:host, :port) # :nodoc: # Instantiates a new Forward service instance atop the given connection # service session. This will register new channel open handlers to handle @@ -106,7 +105,7 @@ module Net # # ssh.forward.cancel_local(1234) # ssh.forward.cancel_local(1234, "0.0.0.0") - def cancel_local(port, bind_address="127.0.0.1") + def cancel_local(port, bind_address = "127.0.0.1") socket = @local_forwarded_ports.delete([port, bind_address]) socket.shutdown rescue nil socket.close rescue nil @@ -215,7 +214,7 @@ module Net # raise Net::SSH::Exception, "remote forwarding request failed" # end # - def remote(port, host, remote_port, remote_host="127.0.0.1") + def remote(port, host, remote_port, remote_host = "127.0.0.1") session.send_global_request("tcpip-forward", :string, remote_host, :long, remote_port) do |success, response| if success remote_port = response.read_long if remote_port == 0 @@ -249,7 +248,7 @@ module Net # # ssh.forward.cancel_remote(1234, "0.0.0.0") # ssh.loop { ssh.forward.active_remotes.include?([1234, "0.0.0.0"]) } - def cancel_remote(port, host="127.0.0.1") + def cancel_remote(port, host = "127.0.0.1") session.send_global_request("cancel-tcpip-forward", :string, host, :long, port) do |success, response| if success @remote_forwarded_ports.delete([port, host]) @@ -290,6 +289,7 @@ module Net # end def agent(channel) return if @agent_forwarded + @agent_forwarded = true channel.send_channel_request("auth-agent-req@openssh.com") do |achannel, success| @@ -388,12 +388,13 @@ module Net originator_address = packet.read_string originator_port = packet.read_long + puts "REMOTE 0: #{connected_port} #{connected_address} #{originator_address} #{originator_port}" remote = @remote_forwarded_ports[[connected_port, connected_address]] - if remote.nil? raise Net::SSH::ChannelOpenFailed.new(1, "unknown request from remote forwarded connection on #{connected_address}:#{connected_port}") end + puts "REMOTE: #{remote.host} #{remote.port}" client = TCPSocket.new(remote.host, remote.port) info { "connected #{connected_address}:#{connected_port} originator #{originator_address}:#{originator_port}" } @@ -420,7 +421,6 @@ module Net end end end - end end end diff --git a/lib/net/ssh/test.rb b/lib/net/ssh/test.rb index 47b5db4..cb727ba 100644 --- a/lib/net/ssh/test.rb +++ b/lib/net/ssh/test.rb @@ -5,7 +5,6 @@ require 'net/ssh/test/socket' module Net module SSH - # This module may be used in unit tests, for when you want to test that your # SSH state machines are really doing what you expect they are doing. You will # typically include this module in your unit test class, and then build a @@ -57,21 +56,21 @@ module Net # Returns the test socket instance to use for these tests (see # Net::SSH::Test::Socket). - def socket(options={}) + def socket(options = {}) @socket ||= Net::SSH::Test::Socket.new end # Returns the connection session (Net::SSH::Connection::Session) for use # in these tests. It is a fully functional SSH session, operating over # a mock socket (#socket). - def connection(options={}) + def connection(options = {}) @connection ||= Net::SSH::Connection::Session.new(transport(options), options) end # Returns the transport session (Net::SSH::Transport::Session) for use # in these tests. It is a fully functional SSH transport session, operating # over a mock socket (#socket). - def transport(options={}) + def transport(options = {}) @transport ||= Net::SSH::Transport::Session.new( options[:host] || "localhost", options.merge(kex: "test", host_key: "ssh-rsa", append_all_supported_algorithms: true, verify_host_key: :never, proxy: socket(options)) @@ -85,11 +84,11 @@ module Net # the block passed to this assertion. def assert_scripted raise "there is no script to be processed" if socket.script.events.empty? + Net::SSH::Test::Extensions::IO.with_test_extension { yield } assert socket.script.events.empty?, "there should not be any remaining scripted events, but there are still" \ "#{socket.script.events.length} pending" end end - end end diff --git a/lib/net/ssh/test/channel.rb b/lib/net/ssh/test/channel.rb index ea17019..c3baa90 100644 --- a/lib/net/ssh/test/channel.rb +++ b/lib/net/ssh/test/channel.rb @@ -1,7 +1,6 @@ -module Net - module SSH +module Net + module SSH module Test - # A mock channel, used for scripting actions in tests. It wraps a # Net::SSH::Test::Script instance, and delegates to it for the most part. # This class has little real functionality on its own, but rather acts as @@ -19,34 +18,34 @@ module Net class Channel # The Net::SSH::Test::Script instance employed by this mock channel. attr_reader :script - + # Sets the local-id of this channel object (the id assigned by the client). attr_writer :local_id - + # Sets the remote-id of this channel object (the id assigned by the mock-server). attr_writer :remote_id - + # Creates a new Test::Channel instance on top of the given +script+ (which # must be a Net::SSH::Test::Script instance). def initialize(script) @script = script @local_id = @remote_id = nil end - + # Returns the local (client-assigned) id for this channel, or a Proc object # that will return the local-id later if the local id has not yet been set. # (See Net::SSH::Test::Packet#instantiate!.) def local_id @local_id || Proc.new { @local_id or raise "local-id has not been set yet!" } end - + # Returns the remote (server-assigned) id for this channel, or a Proc object # that will return the remote-id later if the remote id has not yet been set. # (See Net::SSH::Test::Packet#instantiate!.) def remote_id @remote_id || Proc.new { @remote_id or raise "remote-id has not been set yet!" } end - + # Because adjacent calls to #gets_data will sometimes cause the data packets # to be concatenated (causing expectations in tests to fail), you may # need to separate those calls with calls to #inject_remote_delay! (which @@ -58,62 +57,62 @@ module Net def inject_remote_delay! gets_data("") end - - # Scripts the sending of an "exec" channel request packet to the mock + + # Scripts the sending of an "exec" channel request packet to the mock # server. If +reply+ is true, then the server is expected to reply to the # request, otherwise no response to this request will be sent. If +success+ # is +true+, then the request will be successful, otherwise a failure will # be scripted. # # channel.sends_exec "ls -l" - def sends_exec(command, reply=true, success=true) + def sends_exec(command, reply = true, success = true) script.sends_channel_request(self, "exec", reply, command, success) end - + # Scripts the sending of a "subsystem" channel request packet to the mock # server. See #sends_exec for a discussion of the meaning of the +reply+ # and +success+ arguments. # # channel.sends_subsystem "sftp" - def sends_subsystem(subsystem, reply=true, success=true) + def sends_subsystem(subsystem, reply = true, success = true) script.sends_channel_request(self, "subsystem", reply, subsystem, success) end - + # Scripts the sending of a data packet across the channel. # # channel.sends_data "foo" def sends_data(data) script.sends_channel_data(self, data) end - + # Scripts the sending of an EOF packet across the channel. # # channel.sends_eof def sends_eof script.sends_channel_eof(self) end - + # Scripts the sending of a "channel close" packet across the channel. # # channel.sends_close def sends_close script.sends_channel_close(self) end - + # Scripts the sending of a "request pty" request packet across the channel. # # channel.sends_request_pty def sends_request_pty script.sends_channel_request_pty(self) end - + # Scripts the reception of a channel data packet from the remote end. # # channel.gets_data "bar" def gets_data(data) script.gets_channel_data(self, data) end - + # Scripts the reception of a channel extended data packet from the remote # end. # @@ -121,21 +120,21 @@ module Net def gets_extended_data(data) script.gets_channel_extended_data(self, data) end - + # Scripts the reception of an "exit-status" channel request packet. # # channel.gets_exit_status(127) - def gets_exit_status(status=0) + def gets_exit_status(status = 0) script.gets_channel_request(self, "exit-status", false, status) end - + # Scripts the reception of an EOF packet from the remote end. # # channel.gets_eof def gets_eof script.gets_channel_eof(self) end - + # Scripts the reception of a "channel close" packet from the remote end. # # channel.gets_close @@ -143,7 +142,6 @@ module Net script.gets_channel_close(self) end end - end end -end
\ No newline at end of file +end diff --git a/lib/net/ssh/test/extensions.rb b/lib/net/ssh/test/extensions.rb index 5090f6c..733a531 100644 --- a/lib/net/ssh/test/extensions.rb +++ b/lib/net/ssh/test/extensions.rb @@ -6,16 +6,14 @@ require 'net/ssh/connection/constants' require 'net/ssh/transport/constants' require 'net/ssh/transport/packet_stream' -module Net - module SSH +module Net + module SSH module Test - # A collection of modules used to extend/override the default behavior of # Net::SSH internals for ease of testing. As a consumer of Net::SSH, you'll # never need to use this directly--they're all used under the covers by # the Net::SSH::Test system. module Extensions - # An extension to Net::SSH::BufferedIo (assumes that the underlying IO # is actually a StringIO). Facilitates unit testing. module BufferedIo @@ -24,80 +22,82 @@ module Net def select_for_read? pos < size end - + # Set this to +true+ if you want the IO to pretend to be available for writing attr_accessor :select_for_write - + # Set this to +true+ if you want the IO to pretend to be in an error state attr_accessor :select_for_error - + alias select_for_write? select_for_write alias select_for_error? select_for_error end - + # An extension to Net::SSH::Transport::PacketStream (assumes that the # underlying IO is actually a StringIO). Facilitates unit testing. module PacketStream include BufferedIo # make sure we get the extensions here, too - - def self.included(base) #:nodoc: + + def self.included(base) # :nodoc: base.send :alias_method, :real_available_for_read?, :available_for_read? base.send :alias_method, :available_for_read?, :test_available_for_read? - + base.send :alias_method, :real_enqueue_packet, :enqueue_packet base.send :alias_method, :enqueue_packet, :test_enqueue_packet - + base.send :alias_method, :real_poll_next_packet, :poll_next_packet base.send :alias_method, :poll_next_packet, :test_poll_next_packet end - + # Called when another packet should be inspected from the current # script. If the next packet is a remote packet, it pops it off the # script and shoves it onto this IO object, making it available to # be read. def idle! return false unless script.next(:first) - + if script.next(:first).remote? self.string << script.next.to_s self.pos = pos end - + return true end - + # The testing version of Net::SSH::Transport::PacketStream#available_for_read?. # Returns true if there is data pending to be read. Otherwise calls #idle!. def test_available_for_read? return true if select_for_read? + idle! false end - + # The testing version of Net::SSH::Transport::PacketStream#enqueued_packet. # Simply calls Net::SSH::Test::Script#process on the packet. def test_enqueue_packet(payload) packet = Net::SSH::Buffer.new(payload.to_s) script.process(packet) end - + # The testing version of Net::SSH::Transport::PacketStream#poll_next_packet. # Reads the next available packet from the IO object and returns it. def test_poll_next_packet return nil if available <= 0 + packet = Net::SSH::Buffer.new(read_available(4)) length = packet.read_long Net::SSH::Packet.new(read_available(length)) end end - + # An extension to Net::SSH::Connection::Channel. Facilitates unit testing. module Channel - def self.included(base) #:nodoc: + def self.included(base) # :nodoc: base.send :alias_method, :send_data_for_real, :send_data base.send :alias_method, :send_data, :send_data_for_test end - + # The testing version of Net::SSH::Connection::Channel#send_data. Calls # the original implementation, and then immediately enqueues the data for # output so that scripted sends are properly interpreted as discrete @@ -107,16 +107,16 @@ module Net enqueue_pending_output end end - + # An extension to the built-in ::IO class. Simply redefines IO.select # so that it can be scripted in Net::SSH unit tests. module IO - def self.included(base) #:nodoc: + def self.included(base) # :nodoc: base.extend(ClassMethods) end - + @extension_enabled = false - + def self.with_test_extension(&block) orig_value = @extension_enabled @extension_enabled = true @@ -126,35 +126,36 @@ module Net @extension_enabled = orig_value end end - + def self.extension_enabled? @extension_enabled end - + module ClassMethods - def self.extended(obj) #:nodoc: - class <<obj + def self.extended(obj) # :nodoc: + class << obj alias_method :select_for_real, :select alias_method :select, :select_for_test end end - + # The testing version of ::IO.select. Assumes that all readers, # writers, and errors arrays are either nil, or contain only objects # that mix in Net::SSH::Test::Extensions::BufferedIo. - def select_for_test(readers=nil, writers=nil, errors=nil, wait=nil) + def select_for_test(readers = nil, writers = nil, errors = nil, wait = nil) return select_for_real(readers, writers, errors, wait) unless Net::SSH::Test::Extensions::IO.extension_enabled? + ready_readers = Array(readers).select { |r| r.select_for_read? } ready_writers = Array(writers).select { |r| r.select_for_write? } ready_errors = Array(errors).select { |r| r.select_for_error? } - + return [ready_readers, ready_writers, ready_errors] if ready_readers.any? || ready_writers.any? || ready_errors.any? - + processed = 0 Array(readers).each do |reader| processed += 1 if reader.idle! end - + raise "no readers were ready for reading, and none had any incoming packets" if processed == 0 && wait != 0 [[], [], []] @@ -162,7 +163,6 @@ module Net end end end - end end end diff --git a/lib/net/ssh/test/kex.rb b/lib/net/ssh/test/kex.rb index 415f841..9e6f5be 100644 --- a/lib/net/ssh/test/kex.rb +++ b/lib/net/ssh/test/kex.rb @@ -5,10 +5,9 @@ require 'net/ssh/transport/algorithms' require 'net/ssh/transport/constants' require 'net/ssh/transport/kex' -module Net - module SSH +module Net + module SSH module Test - # An implementation of a key-exchange strategy specifically for unit tests. # (This strategy would never really work against a real SSH server--it makes # too many assumptions about the server's response.) @@ -17,29 +16,28 @@ module Net # "test" algorithm. class Kex include Net::SSH::Transport::Constants - + # Creates a new instance of the testing key-exchange algorithm with the # given arguments. def initialize(algorithms, connection, data) @connection = connection end - + # Exchange keys with the server. This returns a hash of constant values, # and does not actually exchange keys. def exchange_keys result = Net::SSH::Buffer.from(:byte, NEWKEYS) @connection.send_message(result) - + buffer = @connection.next_message raise Net::SSH::Exception, "expected NEWKEYS" unless buffer.type == NEWKEYS - + { session_id: "abc-xyz", server_key: OpenSSL::PKey::RSA.new(512), shared_secret: OpenSSL::BN.new("1234567890", 10), hashing_algorithm: OpenSSL::Digest::SHA1 } end end - end end end diff --git a/lib/net/ssh/test/local_packet.rb b/lib/net/ssh/test/local_packet.rb index 998edb7..edcdd48 100644 --- a/lib/net/ssh/test/local_packet.rb +++ b/lib/net/ssh/test/local_packet.rb @@ -4,7 +4,6 @@ require 'net/ssh/test/packet' module Net module SSH module Test - # This is a specialization of Net::SSH::Test::Packet for representing mock # packets that are sent from the local (client) host. These are created # automatically by Net::SSH::Test::Script and Net::SSH::Test::Channel by any @@ -49,7 +48,6 @@ module Net end end end - end end end diff --git a/lib/net/ssh/test/packet.rb b/lib/net/ssh/test/packet.rb index 8d7dc8c..da5a986 100644 --- a/lib/net/ssh/test/packet.rb +++ b/lib/net/ssh/test/packet.rb @@ -4,7 +4,6 @@ require 'net/ssh/transport/constants' module Net module SSH module Test - # This is an abstract class, not to be instantiated directly, subclassed by # Net::SSH::Test::LocalPacket and Net::SSH::Test::RemotePacket. It implements # functionality common to those subclasses. @@ -70,7 +69,7 @@ module Net # added. Unsupported packet types will otherwise raise an exception. def types @types ||= case @type - when KEXINIT then + when KEXINIT %i[long long long long string string string string string string string string string string bool] @@ -83,13 +82,14 @@ module Net when CHANNEL_REQUEST parts = %i[long string bool] case @data[1] - when "exec", "subsystem","shell" then parts << :string + when "exec", "subsystem", "shell" then parts << :string when "exit-status" then parts << :long when "pty-req" then parts.concat(%i[string long long long long string]) when "env" then parts.contact(%i[string string]) else request = Packet.registered_channel_requests(@data[1]) raise "don't know what to do about #{@data[1]} channel request" unless request + parts.concat(request[:extra_parts]) end else raise "don't know how to parse packet type #{@type}" diff --git a/lib/net/ssh/test/remote_packet.rb b/lib/net/ssh/test/remote_packet.rb index 90f6008..8f7bde2 100644 --- a/lib/net/ssh/test/remote_packet.rb +++ b/lib/net/ssh/test/remote_packet.rb @@ -1,10 +1,9 @@ require 'net/ssh/buffer' require 'net/ssh/test/packet' -module Net - module SSH +module Net + module SSH module Test - # This is a specialization of Net::SSH::Test::Packet for representing mock # packets that are received by the local (client) host. These are created # automatically by Net::SSH::Test::Script and Net::SSH::Test::Channel by any @@ -14,7 +13,7 @@ module Net def remote? true end - + # The #process method should only be called on Net::SSH::Test::LocalPacket # packets; if it is attempted on a remote packet, then it is an expectation # mismatch (a remote packet was received when a local packet was expected @@ -23,8 +22,8 @@ module Net def process(packet) raise "received packet type #{packet.read_byte} and was not expecting any packet" end - - # Returns this remote packet as a string, suitable for parsing by + + # Returns this remote packet as a string, suitable for parsing by # Net::SSH::Transport::PacketStream and friends. When a remote packet is # received, this method is called and the result concatenated onto the # input buffer for the packet stream. @@ -36,7 +35,6 @@ module Net end end end - end end -end
\ No newline at end of file +end diff --git a/lib/net/ssh/test/script.rb b/lib/net/ssh/test/script.rb index 4c51a7d..7eee90a 100644 --- a/lib/net/ssh/test/script.rb +++ b/lib/net/ssh/test/script.rb @@ -2,10 +2,9 @@ require 'net/ssh/test/channel' require 'net/ssh/test/local_packet' require 'net/ssh/test/remote_packet' -module Net - module SSH +module Net + module SSH module Test - # Represents a sequence of scripted events that identify the behavior that # a test expects. Methods named "sends_*" create events for packets being # sent from the local to the remote host, and methods named "gets_*" create @@ -22,41 +21,41 @@ module Net # The list of scripted events. These will be Net::SSH::Test::LocalPacket # and Net::SSH::Test::RemotePacket instances. attr_reader :events - + # Create a new, empty script. def initialize @events = [] end - + # Scripts the opening of a channel by adding a local packet sending the # channel open request, and if +confirm+ is true (the default), also # adding a remote packet confirming the new channel. # # A new Net::SSH::Test::Channel instance is returned, which can be used # to script additional channel operations. - def opens_channel(confirm=true) + def opens_channel(confirm = true) channel = Channel.new(self) channel.remote_id = 5555 - + events << LocalPacket.new(:channel_open) { |p| channel.local_id = p[:remote_id] } - + events << RemotePacket.new(:channel_open_confirmation, channel.local_id, channel.remote_id, 0x20000, 0x10000) if confirm - + channel end - + # A convenience method for adding an arbitrary local packet to the events # list. def sends(type, *args, &block) events << LocalPacket.new(type, *args, &block) end - + # A convenience method for adding an arbitrary remote packet to the events # list. def gets(type, *args) events << RemotePacket.new(type, *args) end - + # Scripts the sending of a new channel request packet to the remote host. # +channel+ should be an instance of Net::SSH::Test::Channel. +request+ # is a string naming the request type to send, +reply+ is a boolean @@ -71,7 +70,7 @@ module Net # # This will typically be called via Net::SSH::Test::Channel#sends_exec or # Net::SSH::Test::Channel#sends_subsystem. - def sends_channel_request(channel, request, reply, data, success=true) + def sends_channel_request(channel, request, reply, data, success = true) if data.is_a? Array events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, *data) else @@ -85,7 +84,7 @@ module Net end end end - + # Scripts the sending of a channel data packet. +channel+ must be a # Net::SSH::Test::Channel object, and +data+ is the (string) data to # expect will be sent. @@ -94,21 +93,21 @@ module Net def sends_channel_data(channel, data) events << LocalPacket.new(:channel_data, channel.remote_id, data) end - + # Scripts the sending of a channel EOF packet from the given # Net::SSH::Test::Channel +channel+. This will typically be called via # Net::SSH::Test::Channel#sends_eof. def sends_channel_eof(channel) events << LocalPacket.new(:channel_eof, channel.remote_id) end - + # Scripts the sending of a channel close packet from the given # Net::SSH::Test::Channel +channel+. This will typically be called via # Net::SSH::Test::Channel#sends_close. def sends_channel_close(channel) events << LocalPacket.new(:channel_close, channel.remote_id) end - + # Scripts the sending of a channel request pty packets from the given # Net::SSH::Test::Channel +channel+. This will typically be called via # Net::SSH::Test::Channel#sends_request_pty. @@ -117,14 +116,14 @@ module Net data += Net::SSH::Connection::Channel::VALID_PTY_OPTIONS.merge(modes: "\0").values events << LocalPacket.new(:channel_request, channel.remote_id, *data) end - + # Scripts the reception of a channel data packet from the remote host by # the given Net::SSH::Test::Channel +channel+. This will typically be # called via Net::SSH::Test::Channel#gets_data. def gets_channel_data(channel, data) events << RemotePacket.new(:channel_data, channel.local_id, data) end - + # Scripts the reception of a channel extended data packet from the remote # host by the given Net::SSH::Test::Channel +channel+. This will typically # be called via Net::SSH::Test::Channel#gets_extended_data. @@ -133,28 +132,28 @@ module Net def gets_channel_extended_data(channel, data) events << RemotePacket.new(:channel_extended_data, channel.local_id, 1, data) end - + # Scripts the reception of a channel request packet from the remote host by # the given Net::SSH::Test::Channel +channel+. This will typically be # called via Net::SSH::Test::Channel#gets_exit_status. def gets_channel_request(channel, request, reply, data) events << RemotePacket.new(:channel_request, channel.local_id, request, reply, data) end - + # Scripts the reception of a channel EOF packet from the remote host by # the given Net::SSH::Test::Channel +channel+. This will typically be # called via Net::SSH::Test::Channel#gets_eof. def gets_channel_eof(channel) events << RemotePacket.new(:channel_eof, channel.local_id) end - + # Scripts the reception of a channel close packet from the remote host by # the given Net::SSH::Test::Channel +channel+. This will typically be # called via Net::SSH::Test::Channel#gets_close. def gets_channel_close(channel) events << RemotePacket.new(:channel_close, channel.local_id) end - + # By default, removes the next event in the list and returns it. However, # this can also be used to non-destructively peek at the next event in the # list, by passing :first as the argument. @@ -164,10 +163,10 @@ module Net # # # peek at the next event # event = script.next(:first) - def next(mode=:shift) + def next(mode = :shift) events.send(mode) end - + # Compare the given packet against the next event in the list. If there is # no next event, an exception will be raised. This is called by # Net::SSH::Test::Extensions::PacketStream#test_enqueue_packet. @@ -176,7 +175,6 @@ module Net event.process(packet) end end - end end -end
\ No newline at end of file +end diff --git a/lib/net/ssh/test/socket.rb b/lib/net/ssh/test/socket.rb index 57fe8f6..543a536 100644 --- a/lib/net/ssh/test/socket.rb +++ b/lib/net/ssh/test/socket.rb @@ -3,66 +3,63 @@ require 'stringio' require 'net/ssh/test/extensions' require 'net/ssh/test/script' -module Net - module SSH +module Net + module SSH module Test - # A mock socket implementation for use in testing. It implements the minimum # necessary interface for interacting with the rest of the Net::SSH::Test # system. class Socket < StringIO attr_reader :host, :port - + # The Net::SSH::Test::Script object in use by this socket. This is the # canonical script instance that should be used for any test depending on # this socket instance. attr_reader :script - + # Create a new test socket. This will also instantiate a new Net::SSH::Test::Script # and seed it with the necessary events to power the initialization of the # connection. def initialize extend(Net::SSH::Transport::PacketStream) super "SSH-2.0-Test\r\n" - + @script = Script.new - + script.sends(:kexinit) script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false) script.sends(:newkeys) script.gets(:newkeys) end - + # This doesn't actually do anything, since we don't really care what gets # written. def write(data) # black hole, because we don't actually care about what gets written end - + # Allows the socket to also mimic a socket factory, simply returning # +self+. - def open(host, port, options={}) + def open(host, port, options = {}) @host, @port = host, port self end - + # Returns a sockaddr struct for the port and host that were used when the # socket was instantiated. def getpeername ::Socket.sockaddr_in(port, host) end - + # Alias to #read, but never returns nil (returns an empty string instead). def recv(n) read(n) || "" end - + def readpartial(n) recv(n) end - end - end end end diff --git a/lib/net/ssh/transport/algorithms.rb b/lib/net/ssh/transport/algorithms.rb index 8126aa6..cc12d95 100644 --- a/lib/net/ssh/transport/algorithms.rb +++ b/lib/net/ssh/transport/algorithms.rb @@ -33,12 +33,15 @@ module Net ecdsa-sha2-nistp256 ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com - ssh-rsa], + ssh-rsa + rsa-sha2-256 + rsa-sha2-512], kex: %w[ecdh-sha2-nistp521 ecdh-sha2-nistp384 ecdh-sha2-nistp256 diffie-hellman-group-exchange-sha256 + diffie-hellman-group14-sha256 diffie-hellman-group14-sha1], encryption: %w[aes256-ctr aes192-ctr aes128-ctr], @@ -143,7 +146,7 @@ module Net # Instantiates a new Algorithms object, and prepares the hash of preferred # algorithms based on the options parameter and the ALGORITHMS constant. - def initialize(session, options={}) + def initialize(session, options = {}) @session = session @logger = session.logger @options = options @@ -275,7 +278,7 @@ module Net # existing known key for the host has preference. existing_keys = session.host_keys - host_keys = existing_keys.map { |key| key.ssh_type }.uniq + host_keys = existing_keys.flat_map { |key| key.respond_to?(:ssh_types) ? key.ssh_types : [key.ssh_type] }.uniq algorithms[:host_key].each do |name| host_keys << name unless host_keys.include?(name) end @@ -366,10 +369,10 @@ module Net language = algorithms[:language].join(",") Net::SSH::Buffer.from(:byte, KEXINIT, - :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)], - :mstring, [kex, host_key, encryption, encryption, hmac, hmac], - :mstring, [compression, compression, language, language], - :bool, false, :long, 0) + :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)], + :mstring, [kex, host_key, encryption, encryption, hmac, hmac], + :mstring, [compression, compression, language, language], + :bool, false, :long, 0) end # Given the parsed server KEX packet, and the client's preferred algorithm @@ -435,13 +438,13 @@ module Net debug { "exchanging keys" } algorithm = Kex::MAP[kex].new(self, session, - client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION, - server_version_string: session.server_version.version, - server_algorithm_packet: @server_packet, - client_algorithm_packet: @client_packet, - need_bytes: kex_byte_requirement, - minimum_dh_bits: options[:minimum_dh_bits], - logger: logger) + client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION, + server_version_string: session.server_version.version, + server_algorithm_packet: @server_packet, + client_algorithm_packet: @client_packet, + need_bytes: kex_byte_requirement, + minimum_dh_bits: options[:minimum_dh_bits], + logger: logger) result = algorithm.exchange_keys secret = result[:shared_secret].to_ssh diff --git a/lib/net/ssh/transport/cipher_factory.rb b/lib/net/ssh/transport/cipher_factory.rb index 13852f3..4dde239 100644 --- a/lib/net/ssh/transport/cipher_factory.rb +++ b/lib/net/ssh/transport/cipher_factory.rb @@ -3,31 +3,30 @@ require 'net/ssh/transport/ctr.rb' require 'net/ssh/transport/key_expander' require 'net/ssh/transport/identity_cipher' -module Net - module SSH +module Net + module SSH module Transport - # Implements a factory of OpenSSL cipher algorithms. class CipherFactory # Maps the SSH name of a cipher to it's corresponding OpenSSL name SSH_TO_OSSL = { - "3des-cbc" => "des-ede3-cbc", - "blowfish-cbc" => "bf-cbc", - "aes256-cbc" => "aes-256-cbc", - "aes192-cbc" => "aes-192-cbc", - "aes128-cbc" => "aes-128-cbc", - "idea-cbc" => "idea-cbc", - "cast128-cbc" => "cast-cbc", + "3des-cbc" => "des-ede3-cbc", + "blowfish-cbc" => "bf-cbc", + "aes256-cbc" => "aes-256-cbc", + "aes192-cbc" => "aes-192-cbc", + "aes128-cbc" => "aes-128-cbc", + "idea-cbc" => "idea-cbc", + "cast128-cbc" => "cast-cbc", "rijndael-cbc@lysator.liu.se" => "aes-256-cbc", - "3des-ctr" => "des-ede3", - "blowfish-ctr" => "bf-ecb", + "3des-ctr" => "des-ede3", + "blowfish-ctr" => "bf-ecb", - 'aes256-ctr' => 'aes-256-ctr', - 'aes192-ctr' => 'aes-192-ctr', - 'aes128-ctr' => 'aes-128-ctr', - 'cast128-ctr' => 'cast5-ecb', + "aes256-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-256-ctr") ? "aes-256-ctr" : "aes-256-ecb", + "aes192-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-192-ctr") ? "aes-192-ctr" : "aes-192-ecb", + "aes128-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-128-ctr") ? "aes-128-ctr" : "aes-128-ecb", + 'cast128-ctr' => 'cast5-ecb', - 'none' => 'none' + 'none' => 'none' } # Returns true if the underlying OpenSSL library supports the given cipher, @@ -35,23 +34,25 @@ module Net def self.supported?(name) ossl_name = SSH_TO_OSSL[name] or raise NotImplementedError, "unimplemented cipher `#{name}'" return true if ossl_name == "none" + return OpenSSL::Cipher.ciphers.include?(ossl_name) end - + # Retrieves a new instance of the named algorithm. The new instance # will be initialized using an iv and key generated from the given # iv, key, shared, hash and digester values. Additionally, the # cipher will be put into encryption or decryption mode, based on the # value of the +encrypt+ parameter. - def self.get(name, options={}) + def self.get(name, options = {}) ossl_name = SSH_TO_OSSL[name] or raise NotImplementedError, "unimplemented cipher `#{name}'" return IdentityCipher if ossl_name == "none" + cipher = OpenSSL::Cipher.new(ossl_name) - + cipher.send(options[:encrypt] ? :encrypt : :decrypt) - + cipher.padding = 0 - + if name =~ /-ctr(@openssh.org)?$/ if ossl_name !~ /-ctr/ cipher.extend(Net::SSH::Transport::CTR) @@ -60,14 +61,14 @@ module Net end end cipher.iv = Net::SSH::Transport::KeyExpander.expand_key(cipher.iv_len, options[:iv], options) - + key_len = cipher.key_len cipher.key_len = key_len cipher.key = Net::SSH::Transport::KeyExpander.expand_key(key_len, options[:key], options) - + return cipher end - + # Returns a two-element array containing the [ key-length, # block-size ] for the named cipher algorithm. If the cipher # algorithm is unknown, or is "none", 0 is returned for both elements @@ -82,7 +83,7 @@ module Net cipher = OpenSSL::Cipher.new(ossl_name) key_len = cipher.key_len cipher.key_len = key_len - + block_size = case ossl_name when /\-ctr/ @@ -90,14 +91,13 @@ module Net else cipher.block_size end - + result = [key_len, block_size] result << cipher.iv_len if options[:iv_len] end result end end - end end end diff --git a/lib/net/ssh/transport/constants.rb b/lib/net/ssh/transport/constants.rb index 26747a6..b3c5fb4 100644 --- a/lib/net/ssh/transport/constants.rb +++ b/lib/net/ssh/transport/constants.rb @@ -1,5 +1,5 @@ -module Net - module SSH +module Net + module SSH module Transport module Constants #-- @@ -12,7 +12,7 @@ module Net DEBUG = 4 SERVICE_REQUEST = 5 SERVICE_ACCEPT = 6 - + #-- # Algorithm negotiation messages #++ diff --git a/lib/net/ssh/transport/ctr.rb b/lib/net/ssh/transport/ctr.rb index d952a86..a1397f4 100644 --- a/lib/net/ssh/transport/ctr.rb +++ b/lib/net/ssh/transport/ctr.rb @@ -2,7 +2,7 @@ require 'openssl' require 'delegate' module Net::SSH::Transport - #:nodoc: + # :nodoc: class OpenSSLAESCTR < SimpleDelegator def initialize(original) super @@ -26,13 +26,13 @@ module Net::SSH::Transport end end - #:nodoc: + # :nodoc: # Pure-Ruby implementation of Stateful Decryption Counter(SDCTR) Mode # for Block Ciphers. See RFC4344 for detail. module CTR def self.extended(orig) orig.instance_eval { - @remaining = "" + @remaining = String.new @counter = nil @counter_len = orig.block_size orig.encrypt @@ -67,13 +67,13 @@ module Net::SSH::Transport end def reset - @remaining = "" + @remaining = String.new end def update(data) @remaining += data - encrypted = "" + encrypted = String.new offset = 0 while (@remaining.bytesize - offset) >= block_size @@ -89,13 +89,13 @@ module Net::SSH::Transport def final s = @remaining.empty? ? '' : xor!(@remaining, _update(@counter)) - @remaining = "" + @remaining = String.new s end def xor!(s1, s2) s = [] - s1.unpack('Q*').zip(s2.unpack('Q*')) {|a,b| s.push(a ^ b) } + s1.unpack('Q*').zip(s2.unpack('Q*')) {|a, b| s.push(a ^ b) } s.pack('Q*') end singleton_class.send(:private, :xor!) diff --git a/lib/net/ssh/transport/hmac.rb b/lib/net/ssh/transport/hmac.rb index 0905050..0742236 100644 --- a/lib/net/ssh/transport/hmac.rb +++ b/lib/net/ssh/transport/hmac.rb @@ -17,24 +17,24 @@ require 'net/ssh/transport/hmac/none' module Net::SSH::Transport::HMAC # The mapping of SSH hmac algorithms to their implementations MAP = { - 'hmac-md5' => MD5, - 'hmac-md5-96' => MD5_96, - 'hmac-sha1' => SHA1, - 'hmac-sha1-96' => SHA1_96, - 'hmac-sha2-256' => SHA2_256, - 'hmac-sha2-256-96' => SHA2_256_96, - 'hmac-sha2-512' => SHA2_512, - 'hmac-sha2-512-96' => SHA2_512_96, + 'hmac-md5' => MD5, + 'hmac-md5-96' => MD5_96, + 'hmac-sha1' => SHA1, + 'hmac-sha1-96' => SHA1_96, + 'hmac-sha2-256' => SHA2_256, + 'hmac-sha2-256-96' => SHA2_256_96, + 'hmac-sha2-512' => SHA2_512, + 'hmac-sha2-512-96' => SHA2_512_96, 'hmac-sha2-256-etm@openssh.com' => SHA2_256_Etm, 'hmac-sha2-512-etm@openssh.com' => SHA2_512_Etm, - 'hmac-ripemd160' => RIPEMD160, - 'hmac-ripemd160@openssh.com' => RIPEMD160, - 'none' => None + 'hmac-ripemd160' => RIPEMD160, + 'hmac-ripemd160@openssh.com' => RIPEMD160, + 'none' => None } # Retrieves a new hmac instance of the given SSH type (+name+). If +key+ is # given, the new instance will be initialized with that key. - def self.get(name, key="", parameters = {}) + def self.get(name, key = "", parameters = {}) impl = MAP[name] or raise ArgumentError, "hmac not found: #{name.inspect}" impl.new(Net::SSH::Transport::KeyExpander.expand_key(impl.key_length, key, parameters)) end diff --git a/lib/net/ssh/transport/hmac/abstract.rb b/lib/net/ssh/transport/hmac/abstract.rb index f8efa3e..8bc2a56 100644 --- a/lib/net/ssh/transport/hmac/abstract.rb +++ b/lib/net/ssh/transport/hmac/abstract.rb @@ -5,10 +5,9 @@ module Net module SSH module Transport module HMAC - # The base class of all OpenSSL-based HMAC algorithm wrappers. class Abstract - class <<self + class << self def etm(*v) @etm = false if !defined?(@etm) if v.empty? @@ -77,19 +76,19 @@ module Net # The key in use for this instance. attr_reader :key - def initialize(key=nil) + def initialize(key = nil) self.key = key end # Sets the key to the given value, truncating it so that it is the correct # length. def key=(value) - @key = value ? value.to_s[0,key_length] : nil + @key = value ? value.to_s[0, key_length] : nil end # Compute the HMAC digest for the given data string. def digest(data) - OpenSSL::HMAC.digest(digest_class.new, key, data)[0,mac_length] + OpenSSL::HMAC.digest(digest_class.new, key, data)[0, mac_length] end end end diff --git a/lib/net/ssh/transport/hmac/md5.rb b/lib/net/ssh/transport/hmac/md5.rb index 66b78ca..549b1aa 100644 --- a/lib/net/ssh/transport/hmac/md5.rb +++ b/lib/net/ssh/transport/hmac/md5.rb @@ -1,12 +1,10 @@ require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC - # The MD5 HMAC algorithm. class MD5 < Abstract mac_length 16 key_length 16 digest_class OpenSSL::Digest::MD5 end - end diff --git a/lib/net/ssh/transport/hmac/md5_96.rb b/lib/net/ssh/transport/hmac/md5_96.rb index 826b70a..6dbebc1 100644 --- a/lib/net/ssh/transport/hmac/md5_96.rb +++ b/lib/net/ssh/transport/hmac/md5_96.rb @@ -1,11 +1,9 @@ require 'net/ssh/transport/hmac/md5' module Net::SSH::Transport::HMAC - # The MD5-96 HMAC algorithm. This returns only the first 12 bytes of # the digest. class MD5_96 < MD5 mac_length 12 end - end diff --git a/lib/net/ssh/transport/hmac/none.rb b/lib/net/ssh/transport/hmac/none.rb index 191373e..a5e3af4 100644 --- a/lib/net/ssh/transport/hmac/none.rb +++ b/lib/net/ssh/transport/hmac/none.rb @@ -1,7 +1,6 @@ require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC - # The "none" algorithm. This has a key and mac length of 0. class None < Abstract key_length 0 @@ -11,5 +10,4 @@ module Net::SSH::Transport::HMAC "" end end - end diff --git a/lib/net/ssh/transport/hmac/ripemd160.rb b/lib/net/ssh/transport/hmac/ripemd160.rb index a77e4cd..4c9cdd7 100644 --- a/lib/net/ssh/transport/hmac/ripemd160.rb +++ b/lib/net/ssh/transport/hmac/ripemd160.rb @@ -1,7 +1,6 @@ require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC - # The RIPEMD-160 HMAC algorithm. This has a mac and key length of 20, and # uses the RIPEMD-160 digest algorithm. class RIPEMD160 < Abstract @@ -9,5 +8,4 @@ module Net::SSH::Transport::HMAC key_length 20 digest_class OpenSSL::Digest::RIPEMD160 end - end diff --git a/lib/net/ssh/transport/hmac/sha1.rb b/lib/net/ssh/transport/hmac/sha1.rb index b40d32f..9208392 100644 --- a/lib/net/ssh/transport/hmac/sha1.rb +++ b/lib/net/ssh/transport/hmac/sha1.rb @@ -1,7 +1,6 @@ require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC - # The SHA1 HMAC algorithm. This has a mac and key length of 20, and # uses the SHA1 digest algorithm. class SHA1 < Abstract @@ -9,5 +8,4 @@ module Net::SSH::Transport::HMAC key_length 20 digest_class OpenSSL::Digest::SHA1 end - end diff --git a/lib/net/ssh/transport/hmac/sha1_96.rb b/lib/net/ssh/transport/hmac/sha1_96.rb index 6b0b3c2..e1631e7 100644 --- a/lib/net/ssh/transport/hmac/sha1_96.rb +++ b/lib/net/ssh/transport/hmac/sha1_96.rb @@ -1,11 +1,9 @@ require 'net/ssh/transport/hmac/sha1' module Net::SSH::Transport::HMAC - # The SHA1-96 HMAC algorithm. This returns only the first 12 bytes of # the digest. class SHA1_96 < SHA1 mac_length 12 end - end diff --git a/lib/net/ssh/transport/identity_cipher.rb b/lib/net/ssh/transport/identity_cipher.rb index c690e9f..ad1a764 100644 --- a/lib/net/ssh/transport/identity_cipher.rb +++ b/lib/net/ssh/transport/identity_cipher.rb @@ -1,59 +1,57 @@ -module Net - module SSH +module Net + module SSH module Transport - # A cipher that does nothing but pass the data through, unchanged. This # keeps things in the code nice and clean when a cipher has not yet been # determined (i.e., during key exchange). class IdentityCipher - class <<self + class << self # A default block size of 8 is required by the SSH2 protocol. def block_size 8 end - + # Returns an arbitrary integer. def iv_len 4 end - + # Does nothing. Returns self. def encrypt self end - + # Does nothing. Returns self. def decrypt self end - + # Passes its single argument through unchanged. def update(text) text end - + # Returns the empty string. def final "" end - + # The name of this cipher, which is "identity". def name "identity" end - + # Does nothing. Returns nil. def iv=(v) nil end - + # Does nothing. Returns self. def reset self end end end - end end end diff --git a/lib/net/ssh/transport/kex.rb b/lib/net/ssh/transport/kex.rb index b3571c3..cf21107 100644 --- a/lib/net/ssh/transport/kex.rb +++ b/lib/net/ssh/transport/kex.rb @@ -1,5 +1,6 @@ require 'net/ssh/transport/kex/diffie_hellman_group1_sha1' require 'net/ssh/transport/kex/diffie_hellman_group14_sha1' +require 'net/ssh/transport/kex/diffie_hellman_group14_sha256' require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1' require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha256' require 'net/ssh/transport/kex/ecdh_sha2_nistp256' @@ -12,13 +13,14 @@ module Net::SSH::Transport # Maps the supported key-exchange algorithms as named by the SSH protocol # to their corresponding implementors. MAP = { - 'diffie-hellman-group1-sha1' => DiffieHellmanGroup1SHA1, - 'diffie-hellman-group14-sha1' => DiffieHellmanGroup14SHA1, - 'diffie-hellman-group-exchange-sha1' => DiffieHellmanGroupExchangeSHA1, + 'diffie-hellman-group1-sha1' => DiffieHellmanGroup1SHA1, + 'diffie-hellman-group14-sha1' => DiffieHellmanGroup14SHA1, + 'diffie-hellman-group14-sha256' => DiffieHellmanGroup14SHA256, + 'diffie-hellman-group-exchange-sha1' => DiffieHellmanGroupExchangeSHA1, 'diffie-hellman-group-exchange-sha256' => DiffieHellmanGroupExchangeSHA256, - 'ecdh-sha2-nistp256' => EcdhSHA2NistP256, - 'ecdh-sha2-nistp384' => EcdhSHA2NistP384, - 'ecdh-sha2-nistp521' => EcdhSHA2NistP521 + 'ecdh-sha2-nistp256' => EcdhSHA2NistP256, + 'ecdh-sha2-nistp384' => EcdhSHA2NistP384, + 'ecdh-sha2-nistp521' => EcdhSHA2NistP521 } if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED diff --git a/lib/net/ssh/transport/kex/abstract.rb b/lib/net/ssh/transport/kex/abstract.rb index c70eb94..27d2613 100644 --- a/lib/net/ssh/transport/kex/abstract.rb +++ b/lib/net/ssh/transport/kex/abstract.rb @@ -64,11 +64,16 @@ module Net private + def matching?(key_ssh_type, host_key_alg) + return true if key_ssh_type == host_key_alg + return true if key_ssh_type == 'ssh-rsa' && ['rsa-sha2-512', 'rsa-sha2-256'].include?(host_key_alg) + end + # Verify that the given key is of the expected type, and that it # really is the key for the session's host. Raise Net::SSH::Exception # if it is not. - def verify_server_key(key) #:nodoc: - if key.ssh_type != algorithms.host_key + def verify_server_key(key) # :nodoc: + unless matching?(key.ssh_type, algorithms.host_key) raise Net::SSH::Exception, "host key algorithm mismatch '#{key.ssh_type}' != '#{algorithms.host_key}'" end @@ -92,12 +97,14 @@ module Net # Verify the signature that was received. Raise Net::SSH::Exception # if the signature could not be verified. Otherwise, return the new # session-id. - def verify_signature(result) #:nodoc: + def verify_signature(result) # :nodoc: response = build_signature_buffer(result) hash = digester.digest(response.to_s) - unless connection.host_key_verifier.verify_signature { result[:server_key].ssh_do_verify(result[:server_sig], hash) } + server_key = result[:server_key] + server_sig = result[:server_sig] + unless connection.host_key_verifier.verify_signature { server_key.ssh_do_verify(server_sig, hash, host_key: algorithms.host_key) } raise Net::SSH::Exception, 'could not verify server signature' end @@ -106,7 +113,7 @@ module Net # Send the NEWKEYS message, and expect the NEWKEYS message in # reply. - def confirm_newkeys #:nodoc: + def confirm_newkeys # :nodoc: # send own NEWKEYS message first (the wodSSHServer won't send first) response = Net::SSH::Buffer.new response.write_byte(NEWKEYS) diff --git a/lib/net/ssh/transport/kex/abstract5656.rb b/lib/net/ssh/transport/kex/abstract5656.rb index 06c874d..5f8d133 100644 --- a/lib/net/ssh/transport/kex/abstract5656.rb +++ b/lib/net/ssh/transport/kex/abstract5656.rb @@ -32,7 +32,7 @@ module Net response end - def send_kexinit #:nodoc: + def send_kexinit # :nodoc: init, reply = get_message_types # send the KEXECDH_INIT message diff --git a/lib/net/ssh/transport/kex/curve25519_sha256.rb b/lib/net/ssh/transport/kex/curve25519_sha256.rb index 9aeba85..e942846 100644 --- a/lib/net/ssh/transport/kex/curve25519_sha256.rb +++ b/lib/net/ssh/transport/kex/curve25519_sha256.rb @@ -1,6 +1,7 @@ gem 'x25519' # raise if the gem x25519 is not installed require 'x25519' + require 'net/ssh/transport/constants' require 'net/ssh/transport/kex/abstract5656' @@ -17,7 +18,7 @@ module Net private - def generate_key #:nodoc: + def generate_key # :nodoc: ::X25519::Scalar.generate end diff --git a/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb b/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb index d560da8..d74c521 100644 --- a/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +++ b/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb @@ -1,8 +1,8 @@ require 'net/ssh/transport/kex/diffie_hellman_group1_sha1' -module Net - module SSH - module Transport +module Net + module SSH + module Transport module Kex # A key-exchange service implementing the "diffie-hellman-group14-sha1" # key-exchange algorithm. (defined in RFC 4253) @@ -24,7 +24,7 @@ module Net "B5C55DF0" "6F4C52C9" "DE2BCBF6" "95581718" + "3995497C" "EA956AE5" "15D22618" "98FA0510" + "15728E5A" "8AACAA68" "FFFFFFFF" "FFFFFFFF" - + # The radix in which P_s represents the value of P P_r = 16 diff --git a/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb b/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb new file mode 100644 index 0000000..7fd985a --- /dev/null +++ b/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb @@ -0,0 +1,11 @@ +require 'net/ssh/transport/kex/diffie_hellman_group14_sha1' + +module Net::SSH::Transport::Kex + # A key-exchange service implementing the "diffie-hellman-group14-sha256" + # key-exchange algorithm. + class DiffieHellmanGroup14SHA256 < DiffieHellmanGroup14SHA1 + def digester + OpenSSL::Digest::SHA256 + end + end +end diff --git a/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb b/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb index 34af18b..9abab2c 100644 --- a/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +++ b/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb @@ -59,7 +59,7 @@ module Net # Generate a DH key with a private key consisting of the given # number of bytes. - def generate_key #:nodoc: + def generate_key # :nodoc: dh = OpenSSL::PKey::DH.new if dh.respond_to?(:set_pqg) @@ -86,7 +86,7 @@ module Net # # Parse the buffer from a KEXDH_REPLY message, returning a hash of # the extracted values. - def send_kexinit #:nodoc: + def send_kexinit # :nodoc: init, reply = get_message_types # send the KEXDH_INIT message @@ -108,8 +108,8 @@ module Net sig_type = sig_buffer.read_string if sig_type != algorithms.host_key_format raise Net::SSH::Exception, - "host key algorithm mismatch for signature " + - "'#{sig_type}' != '#{algorithms.host_key_format}'" + "host key algorithm mismatch for signature " + + "'#{sig_type}' != '#{algorithms.host_key_format}'" end result[:server_sig] = sig_buffer.read_string diff --git a/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb b/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb index bd5e4a9..22178e2 100644 --- a/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +++ b/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb @@ -34,7 +34,7 @@ module Net::SSH::Transport::Kex # request the DH key parameters for the given number of bits. buffer = Net::SSH::Buffer.from(:byte, KEXDH_GEX_REQUEST, :long, data[:minimum_dh_bits], - :long, data[:need_bits], :long, MAXIMUM_BITS) + :long, data[:need_bits], :long, MAXIMUM_BITS) connection.send_message(buffer) buffer = connection.next_message @@ -69,5 +69,4 @@ module Net::SSH::Transport::Kex response end end - end diff --git a/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb b/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb index 84d0e4a..a0c911d 100644 --- a/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +++ b/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb @@ -17,7 +17,7 @@ module Net private - def generate_key #:nodoc: + def generate_key # :nodoc: OpenSSL::PKey::EC.new(curve_name).generate_key end diff --git a/lib/net/ssh/transport/key_expander.rb b/lib/net/ssh/transport/key_expander.rb index 108e9b9..8cff7e9 100644 --- a/lib/net/ssh/transport/key_expander.rb +++ b/lib/net/ssh/transport/key_expander.rb @@ -1,28 +1,27 @@ -module Net - module SSH +module Net + module SSH module Transport module KeyExpander - # Generate a key value in accordance with the SSH2 specification. # (RFC4253 7.2. "Output from Key Exchange") - def self.expand_key(bytes, start, options={}) + def self.expand_key(bytes, start, options = {}) if bytes == 0 return "" end - + k = start[0, bytes] return k if k.length >= bytes - + digester = options[:digester] or raise 'No digester supplied' shared = options[:shared] or raise 'No shared secret supplied' hash = options[:hash] or raise 'No hash supplied' - + while k.length < bytes step = digester.digest(shared + hash + k) bytes_needed = bytes - k.length k << step[0, bytes_needed] end - + return k end end diff --git a/lib/net/ssh/transport/openssl.rb b/lib/net/ssh/transport/openssl.rb index d2d7117..1c23651 100644 --- a/lib/net/ssh/transport/openssl.rb +++ b/lib/net/ssh/transport/openssl.rb @@ -2,7 +2,6 @@ require 'openssl' require 'net/ssh/authentication/pub_key_fingerprint' module OpenSSL - # This class is originally defined in the OpenSSL module. As needed, methods # have been added to it by the Net::SSH module for convenience in dealing with # SSH functionality. @@ -24,7 +23,6 @@ module OpenSSL end module PKey - class PKey include Net::SSH::Authentication::PubKeyFingerprint end @@ -37,6 +35,7 @@ module OpenSSL # lifted more-or-less directly from OpenSSH, dh.c, dh_pub_is_valid.) def valid? return false if pub_key.nil? || pub_key < 0 + bits_set = 0 pub_key.num_bits.times { |i| bits_set += 1 if pub_key.bit_set?(i) } return (bits_set > 1 && pub_key < p) @@ -53,9 +52,7 @@ module OpenSSL "ssh-rsa" end - def ssh_signature_type - ssh_type - end + alias ssh_signature_type ssh_type # Converts the key to a blob, according to the SSH2 protocol. def to_blob @@ -63,13 +60,30 @@ module OpenSSL end # Verifies the given signature matches the given data. - def ssh_do_verify(sig, data) - verify(OpenSSL::Digest::SHA1.new, sig, data) + def ssh_do_verify(sig, data, options = {}) + digester = + if options[:host_key] == "rsa-sha2-512" + OpenSSL::Digest::SHA512.new + elsif options[:host_key] == "rsa-sha2-256" + OpenSSL::Digest::SHA256.new + else + OpenSSL::Digest::SHA1.new + end + + verify(digester, sig, data) end # Returns the signature for the given data. - def ssh_do_sign(data) - sign(OpenSSL::Digest::SHA1.new, data) + def ssh_do_sign(data, sig_alg = nil) + digester = + if sig_alg == "rsa-sha2-512" + OpenSSL::Digest::SHA512.new + elsif sig_alg == "rsa-sha2-256" + OpenSSL::Digest::SHA256.new + else + OpenSSL::Digest::SHA1.new + end + sign(digester, data) end end @@ -83,20 +97,18 @@ module OpenSSL "ssh-dss" end - def ssh_signature_type - ssh_type - end + alias ssh_signature_type ssh_type # Converts the key to a blob, according to the SSH2 protocol. def to_blob @blob ||= Net::SSH::Buffer.from(:string, ssh_type, - :bignum, p, :bignum, q, :bignum, g, :bignum, pub_key).to_s + :bignum, p, :bignum, q, :bignum, g, :bignum, pub_key).to_s end # Verifies the given signature matches the given data. - def ssh_do_verify(sig, data) - sig_r = sig[0,20].unpack("H*")[0].to_i(16) - sig_s = sig[20,20].unpack("H*")[0].to_i(16) + def ssh_do_verify(sig, data, options = {}) + sig_r = sig[0, 20].unpack("H*")[0].to_i(16) + sig_s = sig[20, 20].unpack("H*")[0].to_i(16) a1sig = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(sig_r), OpenSSL::ASN1::Integer(sig_s) @@ -105,7 +117,7 @@ module OpenSSL end # Signs the given data. - def ssh_do_sign(data) + def ssh_do_sign(data, sig_alg = nil) sig = sign(OpenSSL::Digest::SHA1.new, data) a1sig = OpenSSL::ASN1.decode(sig) @@ -163,9 +175,7 @@ module OpenSSL "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}" end - def ssh_signature_type - ssh_type - end + alias ssh_signature_type ssh_type def digester if group.curve_name =~ /^[a-z]+(\d+)\w*\z/ @@ -192,7 +202,7 @@ module OpenSSL end # Verifies the given signature matches the given data. - def ssh_do_verify(sig, data) + def ssh_do_verify(sig, data, options = {}) digest = digester.digest(data) a1sig = nil @@ -218,7 +228,7 @@ module OpenSSL end # Returns the signature for the given data. - def ssh_do_sign(data) + def ssh_do_sign(data, sig_alg = nil) digest = digester.digest(data) sig = dsa_sign_asn1(digest) a1sig = OpenSSL::ASN1.decode(sig) @@ -236,6 +246,8 @@ module OpenSSL "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}" end + alias ssh_signature_type ssh_type + # Converts the key to a blob, according to the SSH2 protocol. def to_blob @blob ||= Net::SSH::Buffer.from(:string, ssh_type, diff --git a/lib/net/ssh/transport/packet_stream.rb b/lib/net/ssh/transport/packet_stream.rb index 8b4032e..a4120d4 100644 --- a/lib/net/ssh/transport/packet_stream.rb +++ b/lib/net/ssh/transport/packet_stream.rb @@ -8,7 +8,6 @@ require 'net/ssh/transport/state' module Net module SSH module Transport - # A module that builds additional functionality onto the Net::SSH::BufferedIo # module. It adds SSH encryption, compression, and packet validation, as # per the SSH2 protocol. It also adds an abstraction for polling packets, @@ -81,7 +80,7 @@ module Net # available or not, and will return nil if there is no packet ready to be # returned. If the mode parameter is :block, then this method will block # until a packet is available or timeout seconds have passed. - def next_packet(mode=:nonblock, timeout=nil) + def next_packet(mode = :nonblock, timeout = nil) case mode when :nonblock then packet = poll_next_packet @@ -222,6 +221,7 @@ module Net if @packet.nil? minimum = server.block_size < 4 ? 4 : server.block_size return nil if available < minimum + aad_length + data = read_available(minimum + aad_length) # decipher it @@ -275,7 +275,6 @@ module Net end end # rubocop:enable Metrics/AbcSize - end end end diff --git a/lib/net/ssh/transport/server_version.rb b/lib/net/ssh/transport/server_version.rb index 1012685..50ffb15 100644 --- a/lib/net/ssh/transport/server_version.rb +++ b/lib/net/ssh/transport/server_version.rb @@ -2,10 +2,9 @@ require 'net/ssh/errors' require 'net/ssh/loggable' require 'net/ssh/version' -module Net - module SSH +module Net + module SSH module Transport - # Negotiates the SSH protocol version and trades information about server # and client. This is never used directly--it is always called by the # transport layer as part of the initialization process of the transport @@ -15,40 +14,41 @@ module Net # the authoritative reference for any queries regarding the version in effect. class ServerVersion include Loggable - + # The SSH version string as reported by Net::SSH PROTO_VERSION = "SSH-2.0-Ruby/Net::SSH_#{Net::SSH::Version::CURRENT} #{RUBY_PLATFORM}" - + # Any header text sent by the server prior to sending the version. attr_reader :header - + # The version string reported by the server. attr_reader :version - + # Instantiates a new ServerVersion and immediately (and synchronously) # negotiates the SSH protocol in effect, using the given socket. def initialize(socket, logger, timeout = nil) - @header = "" + @header = String.new @version = nil @logger = logger negotiate!(socket, timeout) end - + private - + # Negotiates the SSH protocol to use, via the given socket. If the server # reports an incompatible SSH version (e.g., SSH1), this will raise an # exception. def negotiate!(socket, timeout) info { "negotiating protocol version" } - + debug { "local is `#{PROTO_VERSION}'" } socket.write "#{PROTO_VERSION}\r\n" socket.flush - + raise Net::SSH::ConnectionTimeout, "timeout during server version negotiating" if timeout && !IO.select([socket], nil, nil, timeout) + loop do - @version = "" + @version = String.new loop do begin b = socket.readpartial(1) @@ -60,14 +60,15 @@ module Net break if b == "\n" end break if @version.match(/^SSH-/) + @header << @version end - + @version.chomp! debug { "remote is `#{@version}'" } - + raise Net::SSH::Exception, "incompatible SSH version `#{@version}'" unless @version.match(/^SSH-(1\.99|2\.0)-/) - + raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating" if timeout && !IO.select(nil, [socket], nil, timeout) end end diff --git a/lib/net/ssh/transport/session.rb b/lib/net/ssh/transport/session.rb index ce55a32..fb41c16 100644 --- a/lib/net/ssh/transport/session.rb +++ b/lib/net/ssh/transport/session.rb @@ -15,7 +15,6 @@ require 'net/ssh/verifiers/never' module Net module SSH module Transport - # The transport layer represents the lowest level of the SSH protocol, and # implements basic message exchanging and protocol initialization. It will # never be instantiated directly (unless you really know what you're about), @@ -56,7 +55,7 @@ module Net # Instantiates a new transport layer abstraction. This will block until # the initial key exchange completes, leaving you with a ready-to-use # transport session. - def initialize(host, options={}) + def initialize(host, options = {}) self.logger = options[:logger] @host = host @@ -160,6 +159,7 @@ module Net # one is performed, causing this method to block until it completes. def rekey_as_needed return if algorithms.pending? + socket.if_needs_rekey? { rekey! } end @@ -186,7 +186,7 @@ module Net # received, it will be enqueued and otherwise ignored. When a key-exchange # is not in process, and consume_queue is true, packets will be first # read from the queue before the socket is queried. - def poll_message(mode=:nonblock, consume_queue=true) + def poll_message(mode = :nonblock, consume_queue = true) loop do return @queue.shift if consume_queue && @queue.any? && algorithms.allow?(@queue.first) @@ -211,6 +211,7 @@ module Net else return packet if algorithms.allow?(packet) + push(packet) end end @@ -222,6 +223,7 @@ module Net def wait loop do break if block_given? && yield + message = poll_message(:nonblock, false) push(message) if message break if !block_given? @@ -250,27 +252,27 @@ module Net # Configure's the packet stream's client state with the given set of # options. This is typically used to define the cipher, compression, and # hmac algorithms to use when sending packets to the server. - def configure_client(options={}) + def configure_client(options = {}) socket.client.set(options) end # Configure's the packet stream's server state with the given set of # options. This is typically used to define the cipher, compression, and # hmac algorithms to use when reading packets from the server. - def configure_server(options={}) + def configure_server(options = {}) socket.server.set(options) end # Sets a new hint for the packet stream, which the packet stream may use # to change its behavior. (See PacketStream#hints). - def hint(which, value=true) + def hint(which, value = true) socket.hints[which] = value end public # this method is primarily for use in tests - attr_reader :queue #:nodoc: + attr_reader :queue # :nodoc: private diff --git a/lib/net/ssh/transport/state.rb b/lib/net/ssh/transport/state.rb index 07c509a..1af6b86 100644 --- a/lib/net/ssh/transport/state.rb +++ b/lib/net/ssh/transport/state.rb @@ -2,10 +2,9 @@ require 'zlib' require 'net/ssh/transport/cipher_factory' require 'net/ssh/transport/hmac' -module Net - module SSH +module Net + module SSH module Transport - # Encapsulates state information about one end of an SSH connection. Such # state includes the packet sequence number, the algorithms in use, how # many packets and blocks have been processed since the last reset, and so @@ -14,46 +13,46 @@ module Net class State # The socket object that owns this state object. attr_reader :socket - + # The next packet sequence number for this socket endpoint. attr_reader :sequence_number - + # The hmac algorithm in use for this endpoint. attr_reader :hmac - + # The compression algorithm in use for this endpoint. attr_reader :compression - + # The compression level to use when compressing data (or nil, for the default). attr_reader :compression_level - + # The number of packets processed since the last call to #reset! attr_reader :packets - + # The number of data blocks processed since the last call to #reset! attr_reader :blocks - + # The cipher algorithm in use for this socket endpoint. attr_reader :cipher - + # The block size for the cipher attr_reader :block_size - + # The role that this state plays (either :client or :server) attr_reader :role - + # The maximum number of packets that this endpoint wants to process before # needing a rekey. attr_accessor :max_packets - + # The maximum number of blocks that this endpoint wants to process before # needing a rekey. attr_accessor :max_blocks - + # The user-specified maximum number of bytes that this endpoint ought to # process before needing a rekey. attr_accessor :rekey_limit - + # Creates a new state object, belonging to the given socket. Initializes # the algorithms to "none". def initialize(socket, role) @@ -65,9 +64,9 @@ module Net @hmac = HMAC.get("none") @compression = nil @compressor = @decompressor = nil - @next_iv = "" + @next_iv = String.new end - + # A convenience method for quickly setting multiple values in a single # command. def set(values) @@ -76,19 +75,19 @@ module Net end reset! end - + def update_cipher(data) result = cipher.update(data) update_next_iv(role == :client ? result : data) return result end - + def final_cipher result = cipher.final update_next_iv(role == :client ? result : "", true) return result end - + # Increments the counters. The sequence number is incremented (and remapped # so it always fits in a 32-bit integer). The number of packets and blocks # are also incremented. @@ -97,18 +96,18 @@ module Net @packets += 1 @blocks += (packet_length + 4) / @block_size end - + # The compressor object to use when compressing data. This takes into account # the desired compression level. def compressor @compressor ||= Zlib::Deflate.new(compression_level || Zlib::DEFAULT_COMPRESSION) end - + # The decompressor object to use when decompressing data. def decompressor @decompressor ||= Zlib::Inflate.new(nil) end - + # Returns true if data compression/decompression is enabled. This will # return true if :standard compression is selected, or if :delayed # compression is selected and the :authenticated hint has been received @@ -116,33 +115,35 @@ module Net def compression? compression == :standard || (compression == :delayed && socket.hints[:authenticated]) end - + # Compresses the data. If no compression is in effect, this will just return # the data unmodified, otherwise it uses #compressor to compress the data. def compress(data) data = data.to_s return data unless compression? + compressor.deflate(data, Zlib::SYNC_FLUSH) end - + # Deompresses the data. If no compression is in effect, this will just return # the data unmodified, otherwise it uses #decompressor to decompress the data. def decompress(data) data = data.to_s return data unless compression? + decompressor.inflate(data) end - + # Resets the counters on the state object, but leaves the sequence_number # unchanged. It also sets defaults for and recomputes the max_packets and # max_blocks values. def reset! @packets = @blocks = 0 - + @max_packets ||= 1 << 31 - + @block_size = cipher.block_size - + if max_blocks.nil? # cargo-culted from openssh. the idea is that "the 2^(blocksize*2) # limit is too expensive for 3DES, blowfish, etc., so enforce a 1GB @@ -152,16 +153,16 @@ module Net else @max_blocks = (1 << 30) / @block_size end - + # if a limit on the # of bytes has been given, convert that into a # minimum number of blocks processed. - + @max_blocks = [@max_blocks, rekey_limit / @block_size].min if rekey_limit end - + cleanup end - + # Closes any the compressor and/or decompressor objects that have been # instantiated. def cleanup @@ -169,17 +170,17 @@ module Net @compressor.finish if !@compressor.finished? @compressor.close end - + if @decompressor # we call reset here so that we don't get warnings when we try to # close the decompressor @decompressor.reset @decompressor.close end - + @compressor = @decompressor = nil end - + # Returns true if the number of packets processed exceeds the maximum # number of packets, or if the number of blocks processed exceeds the # maximum number of blocks. @@ -187,22 +188,21 @@ module Net max_packets && packets > max_packets || max_blocks && blocks > max_blocks end - + private - - def update_next_iv(data, reset=false) + + def update_next_iv(data, reset = false) @next_iv << data @next_iv = @next_iv[@next_iv.size - cipher.iv_len..-1] - + if reset cipher.reset cipher.iv = @next_iv end - + return data end end - end end end diff --git a/lib/net/ssh/verifiers/accept_new.rb b/lib/net/ssh/verifiers/accept_new.rb index 677ee75..aa68ff2 100644 --- a/lib/net/ssh/verifiers/accept_new.rb +++ b/lib/net/ssh/verifiers/accept_new.rb @@ -5,7 +5,6 @@ require 'net/ssh/verifiers/always' module Net module SSH module Verifiers - # Does a strict host verification, looking the server up in the known # host files to see if a key has already been seen for this server. If this # server does not appear in any host file, this will silently add the @@ -29,7 +28,6 @@ module Net return true end end - end end end diff --git a/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb b/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb index d9f8589..198782d 100644 --- a/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +++ b/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb @@ -3,7 +3,6 @@ require 'net/ssh/verifiers/accept_new' module Net module SSH module Verifiers - # Basically the same as the AcceptNew verifier, but does not try to actually # verify a connection if the server is the localhost and the port is a # nonstandard port number. Those two conditions will typically mean the @@ -14,6 +13,7 @@ module Net # returns true. Otherwise, performs the standard strict verification. def verify(arguments) return true if tunnelled?(arguments) + super end @@ -28,7 +28,6 @@ module Net return ip == "127.0.0.1" || ip == "::1" end end - end end end diff --git a/lib/net/ssh/verifiers/always.rb b/lib/net/ssh/verifiers/always.rb index b3ce944..0c86589 100644 --- a/lib/net/ssh/verifiers/always.rb +++ b/lib/net/ssh/verifiers/always.rb @@ -4,7 +4,6 @@ require 'net/ssh/known_hosts' module Net module SSH module Verifiers - # Does a strict host verification, looking the server up in the known # host files to see if a key has already been seen for this server. If this # server does not appear in any host file, an exception will be raised @@ -22,9 +21,13 @@ module Net # If we found any matches, check to see that the key type and # blob also match. + found = host_keys.any? do |key| - key.ssh_type == arguments[:key].ssh_type && - key.to_blob == arguments[:key].to_blob + if key.respond_to?(:matches_key?) + key.matches_key?(arguments[:key]) + else + key.ssh_type == arguments[:key].ssh_type && key.to_blob == arguments[:key].to_blob + end end # If a match was found, return true. Otherwise, raise an exception @@ -50,7 +53,6 @@ module Net raise exception end end - end end end diff --git a/lib/net/ssh/verifiers/never.rb b/lib/net/ssh/verifiers/never.rb index 11fac1f..43ec072 100644 --- a/lib/net/ssh/verifiers/never.rb +++ b/lib/net/ssh/verifiers/never.rb @@ -1,7 +1,6 @@ module Net module SSH module Verifiers - # This host key verifier simply allows every key it sees, without # any verification. This is simple, but very insecure because it # exposes you to MiTM attacks. @@ -15,7 +14,6 @@ module Net true end end - end end end diff --git a/lib/net/ssh/version.rb b/lib/net/ssh/version.rb index 662e92e..c7de5f6 100644 --- a/lib/net/ssh/version.rb +++ b/lib/net/ssh/version.rb @@ -49,14 +49,14 @@ module Net MAJOR = 6 # The minor component of this version of the Net::SSH library - MINOR = 1 + MINOR = 3 # The tiny component of this version of the Net::SSH library TINY = 0 # The prerelease component of this version of the Net::SSH library # nil allowed - PRE = nil + PRE = "beta1" # The current version of the Net::SSH library as a Version instance CURRENT = new(*[MAJOR, MINOR, TINY, PRE].compact) diff --git a/net-ssh-public_cert.pem b/net-ssh-public_cert.pem index 9d06d3d..f51a7a0 100644 --- a/net-ssh-public_cert.pem +++ b/net-ssh-public_cert.pem @@ -1,7 +1,7 @@ -----BEGIN CERTIFICATE----- MIIDQDCCAiigAwIBAgIBATANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpuZXRz -c2gvREM9c29sdXRpb3VzL0RDPWNvbTAeFw0yMDA0MTEwNTQyMTZaFw0yMTA0MTEw -NTQyMTZaMCUxIzAhBgNVBAMMGm5ldHNzaC9EQz1zb2x1dGlvdXMvREM9Y29tMIIB +c2gvREM9c29sdXRpb3VzL0RDPWNvbTAeFw0yMTA4MTAwODMyMzBaFw0yMjA4MTAw +ODMyMzBaMCUxIzAhBgNVBAMMGm5ldHNzaC9EQz1zb2x1dGlvdXMvREM9Y29tMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxieE22fR/qmdPKUHyYTyUx2g wskLwrCkxay+Tvc97ZZUOwf85LDDDPqhQaTWLvRwnIOMgQE2nBPzwalVclK6a+pW x/18KDeZY15vm3Qn5p42b0wi9hUxOqPm3J2hdCLCcgtENgdX21nVzejn39WVqFJO @@ -11,10 +11,10 @@ fBbmDnsMLAtAtauMOxORrbx3EOY7sHku/kSrMg3FXFay7jc6BkbbUij+MjJ/k82l AQABo3sweTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUBfKiwO2e M4NEiRrVG793qEPLYyMwHwYDVR0RBBgwFoEUbmV0c3NoQHNvbHV0aW91cy5jb20w HwYDVR0SBBgwFoEUbmV0c3NoQHNvbHV0aW91cy5jb20wDQYJKoZIhvcNAQELBQAD -ggEBAJTylLYXo5AybI+tLq79+OXQ8/nbGZ7iydU1uTHQud1JZQ1MRV5dRDjeBmCT -lRxaEZT4NopEzuHO0sm3nVpSYtQwTaQyVKmnllNI3kc0f4H6i7dpPd7eUAQ3/O2I -eWjDJlzu0zwqTa+N6vzS8Y3ypDSGgb1gJKzluOv7viVUAthmuuJws7XQq/qMMaNw -3163oCKuJvMW1w8kdUMQqvlLJkVKaxz9K64r2+a04Ok1cKloTB3OSowfAYFoRlqP -voajiJNS75Pw/2j13WnPB4Q6w7dHSb57E/VluBpVKmcQZN0dGdAkEIVty3v7kw9g -y++VpCpWM/PstIFv4ApZMf501UY= +ggEBABRChgo0Jo+iXSnTpODNongzZoU0sWqwx3/FQVo8nyAyr1qFuiqpSPb4bDbU +DsVnUn3t0X/gGA8qJhutlmfTpEQCjUeyj2x9rWpD3lvttlGWV6btQ0qN4Dfc2gsw +rCp9Jpful0HGWhiwfjWfsarqAdtLzIG0UC47IN7LGeCMRJIijOsXQhiZ915eNBEw +g9+WSSGHkMFt/7vi2pFkvXSC0+RF8ovvRWf4Zw2aYXtJ1GElgi4ZS/s6ZU0gmv20 +i4SfC5m5UXIVZvOBYiMuZ/1B2m6R9xU41027zfOVwRFNtlVDiNfQRq6sDmz44At/ +dv8pkxXDgySe41vzlRXFsgIgz5A= -----END CERTIFICATE----- diff --git a/net-ssh.gemspec b/net-ssh.gemspec index df564ed..d9fdc57 100644 --- a/net-ssh.gemspec +++ b/net-ssh.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |spec| spec.description = %q{Net::SSH: a pure-Ruby implementation of the SSH2 client protocol. It allows you to write programs that invoke and interact with processes on remote servers, via SSH2.} spec.homepage = "https://github.com/net-ssh/net-ssh" spec.license = "MIT" - spec.required_ruby_version = Gem::Requirement.new(">= 2.3") + spec.required_ruby_version = Gem::Requirement.new(">= 2.5") spec.metadata = { "changelog_uri" => "https://github.com/net-ssh/net-ssh/blob/master/CHANGES.txt" } @@ -40,5 +40,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "minitest", "~> 5.10" spec.add_development_dependency "mocha", "~> 1.11.2" spec.add_development_dependency "rake", "~> 12.0" - spec.add_development_dependency "rubocop", "~> 0.74.0" + spec.add_development_dependency "rubocop", "~> 1.22.1" end diff --git a/support/ssh_tunnel_bug.rb b/support/ssh_tunnel_bug.rb index d5fa09d..3f8f0ba 100755 --- a/support/ssh_tunnel_bug.rb +++ b/support/ssh_tunnel_bug.rb @@ -15,12 +15,12 @@ # visible_hostname netsshtest # * Start squid squid -N -d 1 -D # * Run this script -# * Configure browser proxy to use localhost with LOCAL_PORT. +# * Configure browser proxy to use localhost with LOCAL_PORT. # * Load any page, wait for it to load fully. If the page loads # correctly, move on. If not, something needs to be corrected. # * Refresh the page several times. This should cause this # script to failed with the error: "closed stream". You may -# need to try a few times. +# need to try a few times. # require 'highline/import' @@ -37,7 +37,7 @@ pass = ask("Password: ") { |q| q.echo = "*" } puts "Configure your browser proxy to localhost:#{LOCAL_PORT}" begin - session = Net::SSH.start(host, user, password: pass) + session = Net::SSH.start(host, user, password: pass) session.forward.local(LOCAL_PORT, host, PROXY_PORT) session.loop {true} rescue StandardError => e diff --git a/test/authentication/methods/common.rb b/test/authentication/methods/common.rb index 77c6289..1162e8e 100644 --- a/test/authentication/methods/common.rb +++ b/test/authentication/methods/common.rb @@ -1,20 +1,19 @@ -module Authentication +module Authentication module Methods - module Common include Net::SSH::Authentication::Constants - + private - - def socket(options={}) + + def socket(options = {}) @socket ||= stub("socket", client_name: "me.ssh.test") end - - def transport(options={}) + + def transport(options = {}) @transport ||= MockTransport.new(options.merge(socket: socket)) end - - def session(options={}) + + def session(options = {}) @session ||= begin sess = stub("auth-session", logger: nil, transport: transport(options)) def sess.next_message @@ -23,14 +22,12 @@ module Authentication sess end end - + def reset_session(options = {}) @transport = nil @session = nil session(options) end - end - end -end
\ No newline at end of file +end diff --git a/test/authentication/methods/test_abstract.rb b/test/authentication/methods/test_abstract.rb index a839699..c7944f0 100644 --- a/test/authentication/methods/test_abstract.rb +++ b/test/authentication/methods/test_abstract.rb @@ -4,7 +4,6 @@ require 'net/ssh/authentication/methods/abstract' module Authentication module Methods - class TestAbstract < NetSSHTest include Common @@ -43,7 +42,7 @@ module Authentication private - def subject(options={}) + def subject(options = {}) @subject ||= Net::SSH::Authentication::Methods::Abstract.new(session(options), options) end end diff --git a/test/authentication/methods/test_hostbased.rb b/test/authentication/methods/test_hostbased.rb index 4fbd37a..99e42fe 100644 --- a/test/authentication/methods/test_hostbased.rb +++ b/test/authentication/methods/test_hostbased.rb @@ -4,7 +4,6 @@ require 'authentication/methods/common' module Authentication module Methods - class TestHostbased < NetSSHTest include Common @@ -57,21 +56,22 @@ module Authentication def signature_parameters(key) Proc.new do |given_key, data| next false unless given_key.to_blob == key.to_blob + buffer = Net::SSH::Buffer.new(data) buffer.read_string == "abcxyz123" && # session-id - buffer.read_byte == USERAUTH_REQUEST && # type - verify_userauth_request_packet(buffer, key) + buffer.read_byte == USERAUTH_REQUEST && # type + verify_userauth_request_packet(buffer, key) end end def verify_userauth_request_packet(packet, key) - packet.read_string == "jamis" && # user-name - packet.read_string == "ssh-connection" && # next service - packet.read_string == "hostbased" && # auth-method - packet.read_string == key.ssh_type && # key type - packet.read_buffer.read_key.to_blob == key.to_blob && # key - packet.read_string == "me.ssh.test." && # client hostname - packet.read_string == "jamis" # client username + packet.read_string == "jamis" && # user-name + packet.read_string == "ssh-connection" && # next service + packet.read_string == "hostbased" && # auth-method + packet.read_string == key.ssh_type && # key type + packet.read_buffer.read_key.to_blob == key.to_blob && # key + packet.read_string == "me.ssh.test." && # client hostname + packet.read_string == "jamis" # client username end @@keys = nil @@ -79,7 +79,7 @@ module Authentication @@keys ||= [OpenSSL::PKey::RSA.new(512), OpenSSL::PKey::DSA.new(512)] end - def key_manager(options={}) + def key_manager(options = {}) @key_manager ||= begin manager = stub("key_manager") manager.stubs(:each_identity).multiple_yields(*(options[:keys] || keys)) @@ -87,20 +87,20 @@ module Authentication end end - def subject(options={}) + def subject(options = {}) options[:key_manager] = key_manager(options) unless options.key?(:key_manager) @subject ||= Net::SSH::Authentication::Methods::Hostbased.new(session(options), options) end - def socket(options={}) + def socket(options = {}) @socket ||= stub("socket", client_name: "me.ssh.test") end - def transport(options={}) + def transport(options = {}) @transport ||= MockTransport.new(options.merge(socket: socket)) end - def session(options={}) + def session(options = {}) @session ||= begin sess = stub("auth-session", logger: nil, transport: transport(options)) def sess.next_message diff --git a/test/authentication/methods/test_keyboard_interactive.rb b/test/authentication/methods/test_keyboard_interactive.rb index 9fad914..ebe9c16 100644 --- a/test/authentication/methods/test_keyboard_interactive.rb +++ b/test/authentication/methods/test_keyboard_interactive.rb @@ -2,71 +2,70 @@ require_relative '../../common' require 'net/ssh/authentication/methods/keyboard_interactive' require_relative 'common' -module Authentication +module Authentication module Methods - class TestKeyboardInteractive < NetSSHTest include Common - + USERAUTH_INFO_REQUEST = 60 USERAUTH_INFO_RESPONSE = 61 - + def setup reset_subject({}) if defined? @subject && !@subject.options.empty? end - + def test_authenticate_should_raise_if_keyboard_interactive_disallowed - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert_equal "jamis", packet.read_string assert_equal "ssh-connection", packet.read_string assert_equal "keyboard-interactive", packet.read_string assert_equal "", packet.read_string # language tags assert_equal "", packet.read_string # submethods - + t.return(USERAUTH_FAILURE, :string, "password") end - + assert_raises Net::SSH::Authentication::DisallowedMethod do subject.authenticate("ssh-connection", "jamis") end end - + def test_authenticate_should_be_false_if_given_password_is_not_accepted reset_subject(non_interactive: true) - - transport.expect do |t,packet| + + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 1, :string, "Password:", :bool, false) - t.expect do |t2,packet2| + t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type assert_equal 1, packet2.read_long assert_equal "the-password", packet2.read_string t2.return(USERAUTH_FAILURE, :string, "keyboard-interactive") end end - + assert_equal false, subject.authenticate("ssh-connection", "jamis", "the-password") end - + def test_authenticate_should_be_true_if_given_password_is_accepted - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 1, :string, "Password:", :bool, false) - t.expect do |t2,packet2| + t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type t2.return(USERAUTH_SUCCESS) end end - + assert subject.authenticate("ssh-connection", "jamis", "the-password") end - + def test_authenticate_should_duplicate_password_as_needed_to_fill_request - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 2, :string, "Password:", :bool, false, :string, "Again:", :bool, false) - t.expect do |t2,packet2| + t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type assert_equal 2, packet2.read_long assert_equal "the-password", packet2.read_string @@ -74,16 +73,16 @@ module Authentication t2.return(USERAUTH_SUCCESS) end end - + assert subject.authenticate("ssh-connection", "jamis", "the-password") end - + def test_authenticate_should_not_prompt_for_input_when_in_non_interactive_mode reset_subject(non_interactive: true) - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 2, :string, "Name:", :bool, true, :string, "Password:", :bool, false) - t.expect do |t2,packet2| + t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type assert_equal 2, packet2.read_long assert_equal "", packet2.read_string @@ -91,20 +90,20 @@ module Authentication t2.return(USERAUTH_SUCCESS) end end - + assert subject.authenticate("ssh-connection", "jamis", nil) - end - + end + def test_authenticate_should_prompt_for_input_when_password_is_not_given prompt = MockPrompt.new prompt.expects(:_ask).with("Name:", anything, true).returns("name") prompt.expects(:_ask).with("Password:", anything, false).returns("password") reset_subject(password_prompt: prompt) - - transport.expect do |t,packet| + + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 2, :string, "Name:", :bool, true, :string, "Password:", :bool, false) - t.expect do |t2,packet2| + t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type assert_equal 2, packet2.read_long assert_equal "name", packet2.read_string @@ -112,22 +111,21 @@ module Authentication t2.return(USERAUTH_SUCCESS) end end - + assert subject.authenticate("ssh-connection", "jamis", nil) end - + private - - def subject(options={}) + + def subject(options = {}) @subject ||= Net::SSH::Authentication::Methods::KeyboardInteractive.new(session(options), options) end - + def reset_subject(options) @subject = nil reset_session(options) subject(options) end end - end end diff --git a/test/authentication/methods/test_none.rb b/test/authentication/methods/test_none.rb index 8e31c08..f199172 100644 --- a/test/authentication/methods/test_none.rb +++ b/test/authentication/methods/test_none.rb @@ -2,42 +2,40 @@ require 'common' require 'net/ssh/authentication/methods/none' require 'authentication/methods/common' -module Authentication +module Authentication module Methods - class TestNone < NetSSHTest include Common - + def test_authenticate_should_raise_if_none_disallowed - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert_equal "jamis", packet.read_string assert_equal "ssh-connection", packet.read_string assert_equal "none", packet.read_string - + t.return(USERAUTH_FAILURE, :string, "publickey") end - + assert_raises Net::SSH::Authentication::DisallowedMethod do subject.authenticate("ssh-connection", "jamis", "pass") end end - + def test_authenticate_should_return_true - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_SUCCESS) end - + assert subject.authenticate("ssh-connection", "", "") end - + private - - def subject(options={}) + + def subject(options = {}) @subject ||= Net::SSH::Authentication::Methods::None.new(session(options), options) end end - end end diff --git a/test/authentication/methods/test_password.rb b/test/authentication/methods/test_password.rb index d119fae..2205887 100644 --- a/test/authentication/methods/test_password.rb +++ b/test/authentication/methods/test_password.rb @@ -3,31 +3,30 @@ require 'net/ssh/authentication/methods/password' require 'net/ssh/authentication/session' require 'authentication/methods/common' -module Authentication +module Authentication module Methods - class TestPassword < NetSSHTest include Common - + def test_authenticate_should_raise_if_password_disallowed - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert_equal "jamis", packet.read_string assert_equal "ssh-connection", packet.read_string assert_equal "password", packet.read_string assert_equal false, packet.read_bool assert_equal "the-password", packet.read_string - + t.return(USERAUTH_FAILURE, :string, "publickey") end - + assert_raises Net::SSH::Authentication::DisallowedMethod do subject.authenticate("ssh-connection", "jamis", "the-password") end end - + def test_authenticate_ask_for_password_for_second_time_when_password_is_incorrect - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert_equal "jamis", packet.read_string assert_equal "ssh-connection", packet.read_string @@ -35,8 +34,8 @@ module Authentication assert_equal false, packet.read_bool assert_equal "the-password", packet.read_string t.return(USERAUTH_FAILURE, :string, "publickey,password") - - t.expect do |t2, packet2| + + t.expect do |_t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert_equal "jamis", packet2.read_string assert_equal "ssh-connection", packet2.read_string @@ -46,14 +45,14 @@ module Authentication t.return(USERAUTH_SUCCESS) end end - + prompt = MockPrompt.new prompt.expects(:_ask).with("jamis@'s password:", { type: 'password', user: 'jamis', host: nil }, false).returns("the-password-2") subject(password_prompt: prompt).authenticate("ssh-connection", "jamis", "the-password") end - + def test_authenticate_ask_for_password_if_not_given - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert_equal "bill", packet.read_string assert_equal "ssh-connection", packet.read_string @@ -62,37 +61,36 @@ module Authentication assert_equal "good-password", packet.read_string t.return(USERAUTH_SUCCESS) end - + transport.instance_eval { @host = 'testhost' } prompt = MockPrompt.new prompt.expects(:_ask).with("bill@testhost's password:", { type: 'password', user: 'bill', host: 'testhost' }, false).returns("good-password") subject(password_prompt: prompt).authenticate("ssh-connection", "bill", nil) end - + def test_authenticate_when_password_is_acceptible_should_return_true - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_SUCCESS) end - + assert subject.authenticate("ssh-connection", "jamis", "the-password") end - + def test_authenticate_should_return_false_if_password_change_request_is_received - transport.expect do |t,packet| + transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_PASSWD_CHANGEREQ, :string, "Change your password:", :string, "") end - + assert !subject.authenticate("ssh-connection", "jamis", "the-password") end - + private - - def subject(options={}) + + def subject(options = {}) @subject ||= Net::SSH::Authentication::Methods::Password.new(session(options), options) end end - end end diff --git a/test/authentication/methods/test_publickey.rb b/test/authentication/methods/test_publickey.rb index 8f2cc73..5e76ca0 100644 --- a/test/authentication/methods/test_publickey.rb +++ b/test/authentication/methods/test_publickey.rb @@ -4,7 +4,6 @@ require 'authentication/methods/common' module Authentication module Methods - class TestPublickey < NetSSHTest include Common @@ -40,7 +39,7 @@ module Authentication assert verify_userauth_request_packet(packet, keys.first, false) t.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first)) - t.expect do |t2,packet2| + t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.first, true) assert_equal "sig-one", packet2.read_string @@ -62,7 +61,7 @@ module Authentication assert verify_userauth_request_packet(packet, keys.first, false) t.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first)) - t.expect do |t2,packet2| + t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.first, true) assert_equal "sig-one", packet2.read_string @@ -73,7 +72,7 @@ module Authentication assert verify_userauth_request_packet(packet3, keys.last, false) t3.return(USERAUTH_PK_OK, :string, keys.last.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.last)) - t3.expect do |t4,packet4| + t3.expect do |t4, packet4| assert_equal USERAUTH_REQUEST, packet4.type assert verify_userauth_request_packet(packet4, keys.last, true) assert_equal "sig-two", packet4.read_string @@ -94,7 +93,7 @@ module Authentication assert verify_userauth_request_packet(packet, keys.first, false) t.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first)) - t.expect do |t2,packet2| + t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.first, true) assert_equal "sig-one", packet2.read_string @@ -105,25 +104,82 @@ module Authentication assert subject.authenticate("ssh-connection", "jamis") end + def test_authenticate_rsa_sha2 + key_manager.expects(:sign).with(&signature_parameters_with_alg(keys.first, "rsa-sha2-256")).returns("sig-one") + + transport.expect do |t, packet| + assert_equal USERAUTH_REQUEST, packet.type + assert verify_userauth_request_packet(packet, keys.first, false, "rsa-sha2-256") + t.return(USERAUTH_PK_OK, :string, "rsa-sha2-256", :string, Net::SSH::Buffer.from(:key, keys.first)) + + t.expect do |t2, packet2| + assert_equal USERAUTH_REQUEST, packet2.type + assert verify_userauth_request_packet(packet2, keys.first, true, "rsa-sha2-256") + assert_equal "sig-one", packet2.read_string + t2.return(USERAUTH_SUCCESS) + end + end + + assert subject(pubkey_algorithms: %w[rsa-sha2-256]).authenticate("ssh-connection", "jamis") + end + + def test_authenticate_rsa_sha2_fallback + key_manager.expects(:sign).with(&signature_parameters(keys.first)).returns("sig-one") + + transport.expect do |t, packet| + assert_equal USERAUTH_REQUEST, packet.type + assert verify_userauth_request_packet(packet, keys.first, false, "rsa-sha2-256") + t.return(USERAUTH_FAILURE, :string, "publickey") + + t.expect do |t2, packet2| + assert_equal USERAUTH_REQUEST, packet2.type + assert verify_userauth_request_packet(packet2, keys.first, false) + t2.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first)) + + t2.expect do |t3, packet3| + assert_equal USERAUTH_REQUEST, packet3.type + assert verify_userauth_request_packet(packet3, keys.first, true) + assert_equal "sig-one", packet3.read_string + t3.return(USERAUTH_SUCCESS) + end + end + end + + assert subject(pubkey_algorithms: %w[rsa-sha2-256 ssh-rsa]).authenticate("ssh-connection", "jamis") + end + private def signature_parameters(key) Proc.new do |given_key, data| next false unless given_key.to_blob == key.to_blob + + buffer = Net::SSH::Buffer.new(data) + buffer.read_string == "abcxyz123" && # session-id + buffer.read_byte == USERAUTH_REQUEST && # type + verify_userauth_request_packet(buffer, key, true) + end + end + + def signature_parameters_with_alg(key, alg) + Proc.new do |given_key, data, given_alg| + next false unless given_key.to_blob == key.to_blob + next false unless given_alg == alg + buffer = Net::SSH::Buffer.new(data) buffer.read_string == "abcxyz123" && # session-id - buffer.read_byte == USERAUTH_REQUEST && # type - verify_userauth_request_packet(buffer, key, true) + buffer.read_byte == USERAUTH_REQUEST && # type + verify_userauth_request_packet(buffer, key, true, alg) end end - def verify_userauth_request_packet(packet, key, has_sig) - packet.read_string == "jamis" && # user-name - packet.read_string == "ssh-connection" && # next service - packet.read_string == "publickey" && # auth-method - packet.read_bool == has_sig && # whether a signature is appended - packet.read_string == key.ssh_type && # ssh key type - packet.read_buffer.read_key.to_blob == key.to_blob # key + def verify_userauth_request_packet(packet, key, has_sig, alg = nil) + packet.read_string == "jamis" && # user-name + packet.read_string == "ssh-connection" && # next service + packet.read_string == "publickey" && # auth-method + packet.read_bool == has_sig && # whether a signature is appended + packet.read_string == (alg || key.ssh_type) && # ssh key type + packet.read_buffer.read_key.to_blob == key.to_blob # key end @@keys = nil @@ -131,7 +187,7 @@ module Authentication @@keys ||= [OpenSSL::PKey::RSA.new(512), OpenSSL::PKey::DSA.new(512)] end - def key_manager(options={}) + def key_manager(options = {}) @key_manager ||= begin manager = stub("key_manager") manager.stubs(:each_identity).multiple_yields(*(options[:keys] || keys)) @@ -139,8 +195,9 @@ module Authentication end end - def subject(options={}) + def subject(options = {}) options[:key_manager] = key_manager(options) unless options.key?(:key_manager) + options[:pubkey_algorithms] = %w[ssh-rsa] unless options.key?(:pubkey_algorithms) @subject ||= Net::SSH::Authentication::Methods::Publickey.new(session(options), options) end end diff --git a/test/authentication/test_agent.rb b/test/authentication/test_agent.rb index 717550e..87247dc 100644 --- a/test/authentication/test_agent.rb +++ b/test/authentication/test_agent.rb @@ -2,7 +2,6 @@ require_relative '../common' require 'net/ssh/authentication/agent' module Authentication - class TestAgent < NetSSHTest SSH2_AGENT_REQUEST_VERSION = 1 SSH2_AGENT_REQUEST_IDENTITIES = 11 @@ -12,6 +11,8 @@ module Authentication SSH2_AGENT_ADD_IDENTITY = 17 SSH2_AGENT_REMOVE_IDENTITY = 18 SSH2_AGENT_REMOVE_ALL_IDENTITIES = 19 + SSH2_AGENT_LOCK = 22 + SSH2_AGENT_UNLOCK = 23 SSH2_AGENT_ADD_ID_CONSTRAINED = 25 SSH2_AGENT_FAILURE = 30 SSH2_AGENT_VERSION_RESPONSE = 103 @@ -35,7 +36,7 @@ module Authentication Ics0b8bDqBzePaTbNxFUAAAAGmJhcnRsZUBCYXJ0bGVzLU1hY0Jvb2stUHJvAQID -----END OPENSSH PRIVATE KEY----- EOF - CERT = "\x00\x00\x00\x1Cssh-rsa-cert-v01@openssh.com\x00\x00\x00 Ir\xB9\xC9\x94l\x0ER\xA1h\xF5\xFDx\xB2J\xC6g\eHS\xDD\x162\x86\xF1\x90%\\$rf\xAF\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\xB3R\xBC\xF8\xEA\xA30\x90\x87\x85\xF6m\x80\xFB\x7F\x96%\xC0h\x85$\x05\x05J\x9BE\xD9\xDE\x81\xC0\xC9\xC2\xC0\x0F'\xD1TR\xCBb\xCD\xD0o\xA0\x15Q\x8B\xF26t\xC9!8\x85\xD2\f'\xC6\x14u\x1De\x90qyXl\a\x06\xA7\xD0\xB8 \xE1\xB3IP\xDE\xB5\xBE\x19\x0E\x97-M\xFDJT\x81\xE2\x8E>\xCD\x18\x9CJz\x1C\xB5}LsO\xF3\xAC\xAA\r\xAB\xF9\xD4\x83\x8DQ\x82\xE7F\xA4\x9F\x1C\x9A\xC5\xC3Y\x84k\x86\ef\xD7\x84\xE3\v\rlG\x15ya\xB0=\xDF\x11\x8D\x0FtZ/p\xBB\xB7g\xF5\xEBF8\xF5\x05}}\xDB\xFA\xA34dw\xE5\x80\xBC!=\x0E\x96\x18\bF\x10\a{\xFF\x9D2\xCA\xAAnu\x82\x82\xBA-F\x8C\x12\xBB\x04+nh\xE9N\xAF\fe\x16\x00Q\x9C\x1C\xCB\x94\x02\x8CQ\xFB,H[\x96\xF1Z4\nY]@\xE0\bs\x9Bh\x0E\xAA~\x105\x99\\\x8C\xA7q\x1A=\xA9\x9D\xBAbx\xF5`[\x8Aw\x80\b\xE0vy\x00\x00\x00\x00\x00\x00\x00c\x00\x00\x00\x01\x00\x00\x00\x06foobar\x00\x00\x00\b\x00\x00\x00\x04root\x00\x00\x00\x00Xk\\\x1C\x00\x00\x00\x00ZK>g\x00\x00\x00#\x00\x00\x00\rforce-command\x00\x00\x00\x0E\x00\x00\x00\n/bin/false\x00\x00\x00c\x00\x00\x00\x15permit-X11-forwarding\x00\x00\x00\x00\x00\x00\x00\x16permit-port-forwarding\x00\x00\x00\x00\x00\x00\x00\npermit-pty\x00\x00\x00\x00\x00\x00\x00\x0Epermit-user-rc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17\x00\x00\x00\assh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\x9DRU\x0E\x83\x8Eb}\x81vOn\xCA\xBA\x01%\xFE\x87\x80b\xB5\x98R%\xA9(\xC1\xAE\xEFq|\x82L\xADQ?\x1D\xC6o\xB8\xD8pI\e\xFC\xF8\xFE^\xAD*\xA4u;\x99S\fc\x11\xBE\xFD\x047B\x1C\xF2h\xBA\xB1\xB0\n\x12F\e\x16\xF7Z\x8D\xD3\xF2f\xC0\x1C\xD8\xBE\xCC\x82\x85Qka$\xB6\xBD\x1C)\x85B\xAAf\xC8\xF3V*\xC3\x1C\xAA\xDC\xC3I\xDDe\xEFu\x02M\x12\x1A\xE2};he\x9D\xB5\xA47\xE4\x12\x8F\xE0\xF1\xA5\x91/\xFB\xEA\t\x0F \x1E\xB4B@+6\x1F\xBD\xA7\xA9u\x80\x19\xAA\xAC\xFFK\\F\x8C\xD9u\f?\xB9#[M\xDF\xB0\xFC\xE8\xF6J\x98\xA4\x99\x8F\xF9]\x88\x1D|A%\xAB\e\x0EN\xAA\xD3 \xCF\xA7}c\xDE\xF5\xBA4\xC8\xD2\x81(\x13\xB3\x94@fC\xDC\xDF\xFD\xA1\e$?\x13\xA9m\xEB*\xCA'\xB3\x19\x19\xF0\xD2\xB3P\x00\x96ou\xE90\xC4-\x1F\xCF\x1Aw\x034\xC6\xDF\xA7\x8C\xCA^Ix\x15\xFA\x9A+\x00\x00\x01\x0F\x00\x00\x00\assh-rsa\x00\x00\x01\x00I\b%\x01\xB2\xCC\x87\xD7\e\xC5\x88\x93|\x9D\xEC}\xA4\x86\xD7\xBB\xB6\xD3\x93\xFD\\\xC73\xC2*\aV\xA2\x81\x05J\x91\x9AEKV\n\xB4\xEB\xF3\xBC\xBAr\x16\xE5\x9A\xB9\xDC(0\xB4\x1C\x9F\"\x9E\xF9\x91\xD0\x1F\x9Cp\r*\xE3\x8A\xD3\xB9W$[OI\xD2\x8F8\x9B\xA4\x9E\xFFuGg\x00\xA5\xCD\r\xDB\x95\xEE)_\xC3\xBCi\xA2\xCC\r\x86\xFD\xE9\xE6\x188\x92\xFD\xCC\n\x98t\x8C\x16\xF4O\xF6\xD5\xD4\xB7\\\xB95\x19\xA3\xBBW\xF3\xF7r<\xE6\x8C\xFC\xE5\x9F\xBF\xE0\xBF\x06\xE7v\xF2\x8Ek\xA4\x02\xB6fMd\xA5e\x87\xE1\x93\xF5\x81\xCF\xDF\x88\xDC\a\xA2\e\xD5\xCA\x14\xB2>\xF4\x8F|\xE5-w\xF5\x85\xD0\xF1F((\xD1\xEEE&\x1D\xA2+\xEC\x93\xE7\xC7\xAE\xE38\xE4\xAE\xF7 \xED\xC6\r\xD6\x1A\xE1#<\xA2)j\xB3TA\\\xFF;\xC5\xA6Tu\xAAap\xDE\xF4\xF7 p\xCA\xD2\xBA\xDC\xCDv\x17\xC2\xBCQ\xDF\xAB7^\xA1G\x18\xB9\xB2F\x81\x9Fq\x92\xD3".force_encoding('BINARY') + CERT = "\x00\x00\x00\x1Cssh-rsa-cert-v01@openssh.com\x00\x00\x00 Ir\xB9\xC9\x94l\x0ER\xA1h\xF5\xFDx\xB2J\xC6g\eHS\xDD\x162\x86\xF1\x90%\\$rf\xAF\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\xB3R\xBC\xF8\xEA\xA30\x90\x87\x85\xF6m\x80\xFB\x7F\x96%\xC0h\x85$\x05\x05J\x9BE\xD9\xDE\x81\xC0\xC9\xC2\xC0\x0F'\xD1TR\xCBb\xCD\xD0o\xA0\x15Q\x8B\xF26t\xC9!8\x85\xD2\f'\xC6\x14u\x1De\x90qyXl\a\x06\xA7\xD0\xB8 \xE1\xB3IP\xDE\xB5\xBE\x19\x0E\x97-M\xFDJT\x81\xE2\x8E>\xCD\x18\x9CJz\x1C\xB5}LsO\xF3\xAC\xAA\r\xAB\xF9\xD4\x83\x8DQ\x82\xE7F\xA4\x9F\x1C\x9A\xC5\xC3Y\x84k\x86\ef\xD7\x84\xE3\v\rlG\x15ya\xB0=\xDF\x11\x8D\x0FtZ/p\xBB\xB7g\xF5\xEBF8\xF5\x05}}\xDB\xFA\xA34dw\xE5\x80\xBC!=\x0E\x96\x18\bF\x10\a{\xFF\x9D2\xCA\xAAnu\x82\x82\xBA-F\x8C\x12\xBB\x04+nh\xE9N\xAF\fe\x16\x00Q\x9C\x1C\xCB\x94\x02\x8CQ\xFB,H[\x96\xF1Z4\nY]@\xE0\bs\x9Bh\x0E\xAA~\x105\x99\\\x8C\xA7q\x1A=\xA9\x9D\xBAbx\xF5`[\x8Aw\x80\b\xE0vy\x00\x00\x00\x00\x00\x00\x00c\x00\x00\x00\x01\x00\x00\x00\x06foobar\x00\x00\x00\b\x00\x00\x00\x04root\x00\x00\x00\x00Xk\\\x1C\x00\x00\x00\x00ZK>g\x00\x00\x00#\x00\x00\x00\rforce-command\x00\x00\x00\x0E\x00\x00\x00\n/bin/false\x00\x00\x00c\x00\x00\x00\x15permit-X11-forwarding\x00\x00\x00\x00\x00\x00\x00\x16permit-port-forwarding\x00\x00\x00\x00\x00\x00\x00\npermit-pty\x00\x00\x00\x00\x00\x00\x00\x0Epermit-user-rc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17\x00\x00\x00\assh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\x9DRU\x0E\x83\x8Eb}\x81vOn\xCA\xBA\x01%\xFE\x87\x80b\xB5\x98R%\xA9(\xC1\xAE\xEFq|\x82L\xADQ?\x1D\xC6o\xB8\xD8pI\e\xFC\xF8\xFE^\xAD*\xA4u;\x99S\fc\x11\xBE\xFD\x047B\x1C\xF2h\xBA\xB1\xB0\n\x12F\e\x16\xF7Z\x8D\xD3\xF2f\xC0\x1C\xD8\xBE\xCC\x82\x85Qka$\xB6\xBD\x1C)\x85B\xAAf\xC8\xF3V*\xC3\x1C\xAA\xDC\xC3I\xDDe\xEFu\x02M\x12\x1A\xE2};he\x9D\xB5\xA47\xE4\x12\x8F\xE0\xF1\xA5\x91/\xFB\xEA\t\x0F \x1E\xB4B@+6\x1F\xBD\xA7\xA9u\x80\x19\xAA\xAC\xFFK\\F\x8C\xD9u\f?\xB9#[M\xDF\xB0\xFC\xE8\xF6J\x98\xA4\x99\x8F\xF9]\x88\x1D|A%\xAB\e\x0EN\xAA\xD3 \xCF\xA7}c\xDE\xF5\xBA4\xC8\xD2\x81(\x13\xB3\x94@fC\xDC\xDF\xFD\xA1\e$?\x13\xA9m\xEB*\xCA'\xB3\x19\x19\xF0\xD2\xB3P\x00\x96ou\xE90\xC4-\x1F\xCF\x1Aw\x034\xC6\xDF\xA7\x8C\xCA^Ix\x15\xFA\x9A+\x00\x00\x01\x0F\x00\x00\x00\assh-rsa\x00\x00\x01\x00I\b%\x01\xB2\xCC\x87\xD7\e\xC5\x88\x93|\x9D\xEC}\xA4\x86\xD7\xBB\xB6\xD3\x93\xFD\\\xC73\xC2*\aV\xA2\x81\x05J\x91\x9AEKV\n\xB4\xEB\xF3\xBC\xBAr\x16\xE5\x9A\xB9\xDC(0\xB4\x1C\x9F\"\x9E\xF9\x91\xD0\x1F\x9Cp\r*\xE3\x8A\xD3\xB9W$[OI\xD2\x8F8\x9B\xA4\x9E\xFFuGg\x00\xA5\xCD\r\xDB\x95\xEE)_\xC3\xBCi\xA2\xCC\r\x86\xFD\xE9\xE6\x188\x92\xFD\xCC\n\x98t\x8C\x16\xF4O\xF6\xD5\xD4\xB7\\\xB95\x19\xA3\xBBW\xF3\xF7r<\xE6\x8C\xFC\xE5\x9F\xBF\xE0\xBF\x06\xE7v\xF2\x8Ek\xA4\x02\xB6fMd\xA5e\x87\xE1\x93\xF5\x81\xCF\xDF\x88\xDC\a\xA2\e\xD5\xCA\x14\xB2>\xF4\x8F|\xE5-w\xF5\x85\xD0\xF1F((\xD1\xEEE&\x1D\xA2+\xEC\x93\xE7\xC7\xAE\xE38\xE4\xAE\xF7 \xED\xC6\r\xD6\x1A\xE1#<\xA2)j\xB3TA\\\xFF;\xC5\xA6Tu\xAAap\xDE\xF4\xF7 p\xCA\xD2\xBA\xDC\xCDv\x17\xC2\xBCQ\xDF\xAB7^\xA1G\x18\xB9\xB2F\x81\x9Fq\x92\xD3".dup.force_encoding('BINARY') def setup @original, ENV['SSH_AUTH_SOCK'] = ENV['SSH_AUTH_SOCK'], "/path/to/ssh.agent.sock" @@ -75,7 +76,7 @@ module Authentication end def test_negotiate_should_raise_error_if_response_was_unexpected - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_VERSION, type s.return(255) end @@ -83,7 +84,7 @@ module Authentication end def test_negotiate_should_be_successful_with_expected_response - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_VERSION, type s.return(SSH_AGENT_RSA_IDENTITIES_ANSWER) end @@ -91,7 +92,7 @@ module Authentication end def test_identities_should_fail_if_SSH_AGENT_FAILURE_received - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH_AGENT_FAILURE) end @@ -99,7 +100,7 @@ module Authentication end def test_identities_should_fail_if_SSH2_AGENT_FAILURE_received - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH2_AGENT_FAILURE) end @@ -107,7 +108,7 @@ module Authentication end def test_identities_should_fail_if_SSH_COM_AGENT2_FAILURE_received - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH_COM_AGENT2_FAILURE) end @@ -115,7 +116,7 @@ module Authentication end def test_identities_should_fail_if_response_is_not_SSH2_AGENT_IDENTITIES_ANSWER - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(255) end @@ -126,7 +127,7 @@ module Authentication key1 = key key2 = OpenSSL::PKey::DSA.new(512) - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH2_AGENT_IDENTITIES_ANSWER, :long, 2, :string, Net::SSH::Buffer.from(:key, key1), :string, "My favorite key", :string, Net::SSH::Buffer.from(:key, key2), :string, "Okay, but not the best") end @@ -144,13 +145,13 @@ module Authentication key2.to_blob[0..5] = 'badkey' key3 = OpenSSL::PKey::DSA.new(512) - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH2_AGENT_IDENTITIES_ANSWER, :long, 3, :string, Net::SSH::Buffer.from(:key, key1), :string, "My favorite key", :string, Net::SSH::Buffer.from(:key, key2), :string, "bad", :string, Net::SSH::Buffer.from(:key, key3), :string, "Okay, but not the best") end result = agent.identities - assert_equal 2,result.size + assert_equal 2, result.size assert_equal key1.to_blob, result.first.to_blob assert_equal key3.to_blob, result.last.to_blob assert_equal "My favorite key", result.first.comment @@ -159,16 +160,16 @@ module Authentication def test_identities_should_ignore_invalid_ones key1 = key - key2_bad = Net::SSH::Buffer.new("") + key2_bad = Net::SSH::Buffer.new(String.new) key3 = OpenSSL::PKey::DSA.new(512) - socket.expect do |s, type, buffer| + socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH2_AGENT_IDENTITIES_ANSWER, :long, 3, :string, Net::SSH::Buffer.from(:key, key1), :string, "My favorite key", :string, key2_bad, :string, "bad", :string, Net::SSH::Buffer.from(:key, key3), :string, "Okay, but not the best") end result = agent.identities - assert_equal 2,result.size + assert_equal 2, result.size assert_equal key1.to_blob, result.first.to_blob assert_equal key3.to_blob, result.last.to_blob assert_equal "My favorite key", result.first.comment @@ -201,7 +202,7 @@ module Authentication end def test_sign_should_return_signed_data_from_agent - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_SIGN_REQUEST, type assert_equal key.to_blob, Net::SSH::Buffer.new(buffer.read_string).read_key.to_blob assert_equal "hello world", buffer.read_string @@ -215,7 +216,7 @@ module Authentication def test_add_rsa_identity_with_constraints rsa = OpenSSL::PKey::RSA.new(512) - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_ID_CONSTRAINED, type assert_equal buffer.read_string, "ssh-rsa" assert_equal buffer.read_bignum.to_s, rsa.n.to_s @@ -238,7 +239,7 @@ module Authentication def test_add_rsa_cert_identity cert = make_cert(OpenSSL::PKey::RSA.new(512)) - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ssh-rsa-cert-v01@openssh.com" assert_equal buffer.read_string, cert.to_blob @@ -257,7 +258,7 @@ module Authentication def test_add_dsa_identity dsa = OpenSSL::PKey::DSA.new(512) - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ssh-dss" assert_equal buffer.read_bignum.to_s, dsa.p.to_s @@ -276,7 +277,7 @@ module Authentication def test_add_dsa_cert_identity cert = make_cert(OpenSSL::PKey::DSA.new(512)) - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ssh-dss-cert-v01@openssh.com" assert_equal buffer.read_string, cert.to_blob @@ -292,7 +293,7 @@ module Authentication def test_add_ecdsa_identity ecdsa = OpenSSL::PKey::EC.new("prime256v1").generate_key - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ecdsa-sha2-nistp256" assert_equal buffer.read_string, "nistp256" @@ -309,7 +310,7 @@ module Authentication def test_add_ecdsa_cert_identity cert = make_cert(OpenSSL::PKey::EC.new("prime256v1").generate_key) - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ecdsa-sha2-nistp256-cert-v01@openssh.com" assert_equal buffer.read_string, cert.to_blob @@ -325,8 +326,9 @@ module Authentication def test_add_ed25519_identity return unless Net::SSH::Authentication::ED25519Loader::LOADED + ed25519 = Net::SSH::Authentication::ED25519::PrivKey.read(ED25519, nil) - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ssh-ed25519" assert_equal buffer.read_string, ed25519.public_key.verify_key.to_bytes @@ -342,8 +344,9 @@ module Authentication def test_add_ed25519_cert_identity return unless Net::SSH::Authentication::ED25519Loader::LOADED + cert = make_cert(Net::SSH::Authentication::ED25519::PrivKey.read(ED25519, nil)) - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ssh-ed25519-cert-v01@openssh.com" assert_equal buffer.read_string, cert.to_blob @@ -359,7 +362,7 @@ module Authentication end def test_add_identity_should_raise_error_on_failure - socket.expect do |s,type,buffer| + socket.expect do |s, _type, _buffer| s.return(SSH_AGENT_FAILURE) end @@ -369,7 +372,7 @@ module Authentication end def test_remove_identity - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_REMOVE_IDENTITY, type assert_equal buffer.read_string, key.to_blob assert buffer.eof? @@ -381,7 +384,7 @@ module Authentication end def test_remove_identity_should_raise_error_on_failure - socket.expect do |s,type,buffer| + socket.expect do |s, _type, _buffer| s.return(SSH_AGENT_FAILURE) end @@ -391,7 +394,7 @@ module Authentication end def test_remove_all_identities - socket.expect do |s,type,buffer| + socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_REMOVE_ALL_IDENTITIES, type assert buffer.eof? @@ -402,7 +405,7 @@ module Authentication end def test_remove_all_identities_should_raise_error_on_failure - socket.expect do |s,type,buffer| + socket.expect do |s, _type, _buffer| s.return(SSH_AGENT_FAILURE) end @@ -436,6 +439,7 @@ module Authentication def send(data, flags) raise "got #{data.inspect} but no packet was expected" unless @expectation + buffer = Net::SSH::Buffer.new(data) buffer.read_long # skip the length type = buffer.read_byte @@ -460,7 +464,7 @@ module Authentication @factory ||= stub("socket factory", open: socket) end - def agent(auto=:connect) + def agent(auto = :connect) @agent ||= begin agent = Net::SSH::Authentication::Agent.new agent.stubs(:unix_socket_class).returns(factory) @@ -470,8 +474,7 @@ module Authentication end def agent_socket_factory - @agent_socket_factory ||= -> {"/foo/bar.sock"} + @agent_socket_factory ||= -> { "/foo/bar.sock" } end end - end diff --git a/test/authentication/test_certificate.rb b/test/authentication/test_certificate.rb index 43802e0..de6691c 100644 --- a/test/authentication/test_certificate.rb +++ b/test/authentication/test_certificate.rb @@ -62,13 +62,13 @@ KEY = <<~EOF EOF # Generated via `ssh-keygen -s ca -I foobar -V +52w -O no-agent-forwarding -O force-command=/bin/false -z 99 key`. -SIGNED_CERT = "\x00\x00\x00\x1Cssh-rsa-cert-v01@openssh.com\x00\x00\x00 Ir\xB9\xC9\x94l\x0ER\xA1h\xF5\xFDx\xB2J\xC6g\eHS\xDD\x162\x86\xF1\x90%\\$rf\xAF\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\xB3R\xBC\xF8\xEA\xA30\x90\x87\x85\xF6m\x80\xFB\x7F\x96%\xC0h\x85$\x05\x05J\x9BE\xD9\xDE\x81\xC0\xC9\xC2\xC0\x0F'\xD1TR\xCBb\xCD\xD0o\xA0\x15Q\x8B\xF26t\xC9!8\x85\xD2\f'\xC6\x14u\x1De\x90qyXl\a\x06\xA7\xD0\xB8 \xE1\xB3IP\xDE\xB5\xBE\x19\x0E\x97-M\xFDJT\x81\xE2\x8E>\xCD\x18\x9CJz\x1C\xB5}LsO\xF3\xAC\xAA\r\xAB\xF9\xD4\x83\x8DQ\x82\xE7F\xA4\x9F\x1C\x9A\xC5\xC3Y\x84k\x86\ef\xD7\x84\xE3\v\rlG\x15ya\xB0=\xDF\x11\x8D\x0FtZ/p\xBB\xB7g\xF5\xEBF8\xF5\x05}}\xDB\xFA\xA34dw\xE5\x80\xBC!=\x0E\x96\x18\bF\x10\a{\xFF\x9D2\xCA\xAAnu\x82\x82\xBA-F\x8C\x12\xBB\x04+nh\xE9N\xAF\fe\x16\x00Q\x9C\x1C\xCB\x94\x02\x8CQ\xFB,H[\x96\xF1Z4\nY]@\xE0\bs\x9Bh\x0E\xAA~\x105\x99\\\x8C\xA7q\x1A=\xA9\x9D\xBAbx\xF5`[\x8Aw\x80\b\xE0vy\x00\x00\x00\x00\x00\x00\x00c\x00\x00\x00\x01\x00\x00\x00\x06foobar\x00\x00\x00\b\x00\x00\x00\x04root\x00\x00\x00\x00Xk\\\x1C\x00\x00\x00\x00ZK>g\x00\x00\x00#\x00\x00\x00\rforce-command\x00\x00\x00\x0E\x00\x00\x00\n/bin/false\x00\x00\x00c\x00\x00\x00\x15permit-X11-forwarding\x00\x00\x00\x00\x00\x00\x00\x16permit-port-forwarding\x00\x00\x00\x00\x00\x00\x00\npermit-pty\x00\x00\x00\x00\x00\x00\x00\x0Epermit-user-rc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17\x00\x00\x00\assh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\x9DRU\x0E\x83\x8Eb}\x81vOn\xCA\xBA\x01%\xFE\x87\x80b\xB5\x98R%\xA9(\xC1\xAE\xEFq|\x82L\xADQ?\x1D\xC6o\xB8\xD8pI\e\xFC\xF8\xFE^\xAD*\xA4u;\x99S\fc\x11\xBE\xFD\x047B\x1C\xF2h\xBA\xB1\xB0\n\x12F\e\x16\xF7Z\x8D\xD3\xF2f\xC0\x1C\xD8\xBE\xCC\x82\x85Qka$\xB6\xBD\x1C)\x85B\xAAf\xC8\xF3V*\xC3\x1C\xAA\xDC\xC3I\xDDe\xEFu\x02M\x12\x1A\xE2};he\x9D\xB5\xA47\xE4\x12\x8F\xE0\xF1\xA5\x91/\xFB\xEA\t\x0F \x1E\xB4B@+6\x1F\xBD\xA7\xA9u\x80\x19\xAA\xAC\xFFK\\F\x8C\xD9u\f?\xB9#[M\xDF\xB0\xFC\xE8\xF6J\x98\xA4\x99\x8F\xF9]\x88\x1D|A%\xAB\e\x0EN\xAA\xD3 \xCF\xA7}c\xDE\xF5\xBA4\xC8\xD2\x81(\x13\xB3\x94@fC\xDC\xDF\xFD\xA1\e$?\x13\xA9m\xEB*\xCA'\xB3\x19\x19\xF0\xD2\xB3P\x00\x96ou\xE90\xC4-\x1F\xCF\x1Aw\x034\xC6\xDF\xA7\x8C\xCA^Ix\x15\xFA\x9A+\x00\x00\x01\x0F\x00\x00\x00\assh-rsa\x00\x00\x01\x00I\b%\x01\xB2\xCC\x87\xD7\e\xC5\x88\x93|\x9D\xEC}\xA4\x86\xD7\xBB\xB6\xD3\x93\xFD\\\xC73\xC2*\aV\xA2\x81\x05J\x91\x9AEKV\n\xB4\xEB\xF3\xBC\xBAr\x16\xE5\x9A\xB9\xDC(0\xB4\x1C\x9F\"\x9E\xF9\x91\xD0\x1F\x9Cp\r*\xE3\x8A\xD3\xB9W$[OI\xD2\x8F8\x9B\xA4\x9E\xFFuGg\x00\xA5\xCD\r\xDB\x95\xEE)_\xC3\xBCi\xA2\xCC\r\x86\xFD\xE9\xE6\x188\x92\xFD\xCC\n\x98t\x8C\x16\xF4O\xF6\xD5\xD4\xB7\\\xB95\x19\xA3\xBBW\xF3\xF7r<\xE6\x8C\xFC\xE5\x9F\xBF\xE0\xBF\x06\xE7v\xF2\x8Ek\xA4\x02\xB6fMd\xA5e\x87\xE1\x93\xF5\x81\xCF\xDF\x88\xDC\a\xA2\e\xD5\xCA\x14\xB2>\xF4\x8F|\xE5-w\xF5\x85\xD0\xF1F((\xD1\xEEE&\x1D\xA2+\xEC\x93\xE7\xC7\xAE\xE38\xE4\xAE\xF7 \xED\xC6\r\xD6\x1A\xE1#<\xA2)j\xB3TA\\\xFF;\xC5\xA6Tu\xAAap\xDE\xF4\xF7 p\xCA\xD2\xBA\xDC\xCDv\x17\xC2\xBCQ\xDF\xAB7^\xA1G\x18\xB9\xB2F\x81\x9Fq\x92\xD3".force_encoding('BINARY') +SIGNED_CERT = "\x00\x00\x00\x1Cssh-rsa-cert-v01@openssh.com\x00\x00\x00 Ir\xB9\xC9\x94l\x0ER\xA1h\xF5\xFDx\xB2J\xC6g\eHS\xDD\x162\x86\xF1\x90%\\$rf\xAF\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\xB3R\xBC\xF8\xEA\xA30\x90\x87\x85\xF6m\x80\xFB\x7F\x96%\xC0h\x85$\x05\x05J\x9BE\xD9\xDE\x81\xC0\xC9\xC2\xC0\x0F'\xD1TR\xCBb\xCD\xD0o\xA0\x15Q\x8B\xF26t\xC9!8\x85\xD2\f'\xC6\x14u\x1De\x90qyXl\a\x06\xA7\xD0\xB8 \xE1\xB3IP\xDE\xB5\xBE\x19\x0E\x97-M\xFDJT\x81\xE2\x8E>\xCD\x18\x9CJz\x1C\xB5}LsO\xF3\xAC\xAA\r\xAB\xF9\xD4\x83\x8DQ\x82\xE7F\xA4\x9F\x1C\x9A\xC5\xC3Y\x84k\x86\ef\xD7\x84\xE3\v\rlG\x15ya\xB0=\xDF\x11\x8D\x0FtZ/p\xBB\xB7g\xF5\xEBF8\xF5\x05}}\xDB\xFA\xA34dw\xE5\x80\xBC!=\x0E\x96\x18\bF\x10\a{\xFF\x9D2\xCA\xAAnu\x82\x82\xBA-F\x8C\x12\xBB\x04+nh\xE9N\xAF\fe\x16\x00Q\x9C\x1C\xCB\x94\x02\x8CQ\xFB,H[\x96\xF1Z4\nY]@\xE0\bs\x9Bh\x0E\xAA~\x105\x99\\\x8C\xA7q\x1A=\xA9\x9D\xBAbx\xF5`[\x8Aw\x80\b\xE0vy\x00\x00\x00\x00\x00\x00\x00c\x00\x00\x00\x01\x00\x00\x00\x06foobar\x00\x00\x00\b\x00\x00\x00\x04root\x00\x00\x00\x00Xk\\\x1C\x00\x00\x00\x00ZK>g\x00\x00\x00#\x00\x00\x00\rforce-command\x00\x00\x00\x0E\x00\x00\x00\n/bin/false\x00\x00\x00c\x00\x00\x00\x15permit-X11-forwarding\x00\x00\x00\x00\x00\x00\x00\x16permit-port-forwarding\x00\x00\x00\x00\x00\x00\x00\npermit-pty\x00\x00\x00\x00\x00\x00\x00\x0Epermit-user-rc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17\x00\x00\x00\assh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\x9DRU\x0E\x83\x8Eb}\x81vOn\xCA\xBA\x01%\xFE\x87\x80b\xB5\x98R%\xA9(\xC1\xAE\xEFq|\x82L\xADQ?\x1D\xC6o\xB8\xD8pI\e\xFC\xF8\xFE^\xAD*\xA4u;\x99S\fc\x11\xBE\xFD\x047B\x1C\xF2h\xBA\xB1\xB0\n\x12F\e\x16\xF7Z\x8D\xD3\xF2f\xC0\x1C\xD8\xBE\xCC\x82\x85Qka$\xB6\xBD\x1C)\x85B\xAAf\xC8\xF3V*\xC3\x1C\xAA\xDC\xC3I\xDDe\xEFu\x02M\x12\x1A\xE2};he\x9D\xB5\xA47\xE4\x12\x8F\xE0\xF1\xA5\x91/\xFB\xEA\t\x0F \x1E\xB4B@+6\x1F\xBD\xA7\xA9u\x80\x19\xAA\xAC\xFFK\\F\x8C\xD9u\f?\xB9#[M\xDF\xB0\xFC\xE8\xF6J\x98\xA4\x99\x8F\xF9]\x88\x1D|A%\xAB\e\x0EN\xAA\xD3 \xCF\xA7}c\xDE\xF5\xBA4\xC8\xD2\x81(\x13\xB3\x94@fC\xDC\xDF\xFD\xA1\e$?\x13\xA9m\xEB*\xCA'\xB3\x19\x19\xF0\xD2\xB3P\x00\x96ou\xE90\xC4-\x1F\xCF\x1Aw\x034\xC6\xDF\xA7\x8C\xCA^Ix\x15\xFA\x9A+\x00\x00\x01\x0F\x00\x00\x00\assh-rsa\x00\x00\x01\x00I\b%\x01\xB2\xCC\x87\xD7\e\xC5\x88\x93|\x9D\xEC}\xA4\x86\xD7\xBB\xB6\xD3\x93\xFD\\\xC73\xC2*\aV\xA2\x81\x05J\x91\x9AEKV\n\xB4\xEB\xF3\xBC\xBAr\x16\xE5\x9A\xB9\xDC(0\xB4\x1C\x9F\"\x9E\xF9\x91\xD0\x1F\x9Cp\r*\xE3\x8A\xD3\xB9W$[OI\xD2\x8F8\x9B\xA4\x9E\xFFuGg\x00\xA5\xCD\r\xDB\x95\xEE)_\xC3\xBCi\xA2\xCC\r\x86\xFD\xE9\xE6\x188\x92\xFD\xCC\n\x98t\x8C\x16\xF4O\xF6\xD5\xD4\xB7\\\xB95\x19\xA3\xBBW\xF3\xF7r<\xE6\x8C\xFC\xE5\x9F\xBF\xE0\xBF\x06\xE7v\xF2\x8Ek\xA4\x02\xB6fMd\xA5e\x87\xE1\x93\xF5\x81\xCF\xDF\x88\xDC\a\xA2\e\xD5\xCA\x14\xB2>\xF4\x8F|\xE5-w\xF5\x85\xD0\xF1F((\xD1\xEEE&\x1D\xA2+\xEC\x93\xE7\xC7\xAE\xE38\xE4\xAE\xF7 \xED\xC6\r\xD6\x1A\xE1#<\xA2)j\xB3TA\\\xFF;\xC5\xA6Tu\xAAap\xDE\xF4\xF7 p\xCA\xD2\xBA\xDC\xCDv\x17\xC2\xBCQ\xDF\xAB7^\xA1G\x18\xB9\xB2F\x81\x9Fq\x92\xD3".dup.force_encoding('BINARY') module Authentication class TestCertificate < NetSSHTest def test_certificate cert = Net::SSH::Buffer.new(SIGNED_CERT).read_key - assert_equal "Ir\xB9\xC9\x94l\x0ER\xA1h\xF5\xFDx\xB2J\xC6g\eHS\xDD\x162\x86\xF1\x90%\\$rf\xAF".force_encoding('BINARY'), cert.nonce + assert_equal "Ir\xB9\xC9\x94l\x0ER\xA1h\xF5\xFDx\xB2J\xC6g\eHS\xDD\x162\x86\xF1\x90%\\$rf\xAF".dup.force_encoding('BINARY'), cert.nonce assert_equal 99, cert.serial assert_equal :user, cert.type assert_equal "foobar", cert.key_id @@ -79,8 +79,8 @@ module Authentication assert_equal({ "permit-X11-forwarding" => "", "permit-port-forwarding" => "", "permit-pty" => "", "permit-user-rc" => "" }, cert.extensions) assert_equal "", cert.reserved - assert_equal "\x00\x00\x00\assh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\x9DRU\x0E\x83\x8Eb}\x81vOn\xCA\xBA\x01%\xFE\x87\x80b\xB5\x98R%\xA9(\xC1\xAE\xEFq|\x82L\xADQ?\x1D\xC6o\xB8\xD8pI\e\xFC\xF8\xFE^\xAD*\xA4u;\x99S\fc\x11\xBE\xFD\x047B\x1C\xF2h\xBA\xB1\xB0\n\x12F\e\x16\xF7Z\x8D\xD3\xF2f\xC0\x1C\xD8\xBE\xCC\x82\x85Qka$\xB6\xBD\x1C)\x85B\xAAf\xC8\xF3V*\xC3\x1C\xAA\xDC\xC3I\xDDe\xEFu\x02M\x12\x1A\xE2};he\x9D\xB5\xA47\xE4\x12\x8F\xE0\xF1\xA5\x91/\xFB\xEA\t\x0F \x1E\xB4B@+6\x1F\xBD\xA7\xA9u\x80\x19\xAA\xAC\xFFK\\F\x8C\xD9u\f?\xB9#[M\xDF\xB0\xFC\xE8\xF6J\x98\xA4\x99\x8F\xF9]\x88\x1D|A%\xAB\e\x0EN\xAA\xD3 \xCF\xA7}c\xDE\xF5\xBA4\xC8\xD2\x81(\x13\xB3\x94@fC\xDC\xDF\xFD\xA1\e$?\x13\xA9m\xEB*\xCA'\xB3\x19\x19\xF0\xD2\xB3P\x00\x96ou\xE90\xC4-\x1F\xCF\x1Aw\x034\xC6\xDF\xA7\x8C\xCA^Ix\x15\xFA\x9A+".force_encoding('BINARY'), cert.signature_key.to_blob - expected_signature = "\x00\x00\x00\assh-rsa\x00\x00\x01\x00I\b%\x01\xB2\xCC\x87\xD7\e\xC5\x88\x93|\x9D\xEC}\xA4\x86\xD7\xBB\xB6\xD3\x93\xFD\\\xC73\xC2*\aV\xA2\x81\x05J\x91\x9AEKV\n\xB4\xEB\xF3\xBC\xBAr\x16\xE5\x9A\xB9\xDC(0\xB4\x1C\x9F\"\x9E\xF9\x91\xD0\x1F\x9Cp\r*\xE3\x8A\xD3\xB9W$[OI\xD2\x8F8\x9B\xA4\x9E\xFFuGg\x00\xA5\xCD\r\xDB\x95\xEE)_\xC3\xBCi\xA2\xCC\r\x86\xFD\xE9\xE6\x188\x92\xFD\xCC\n\x98t\x8C\x16\xF4O\xF6\xD5\xD4\xB7\\\xB95\x19\xA3\xBBW\xF3\xF7r<\xE6\x8C\xFC\xE5\x9F\xBF\xE0\xBF\x06\xE7v\xF2\x8Ek\xA4\x02\xB6fMd\xA5e\x87\xE1\x93\xF5\x81\xCF\xDF\x88\xDC\a\xA2\e\xD5\xCA\x14\xB2>\xF4\x8F|\xE5-w\xF5\x85\xD0\xF1F((\xD1\xEEE&\x1D\xA2+\xEC\x93\xE7\xC7\xAE\xE38\xE4\xAE\xF7 \xED\xC6\r\xD6\x1A\xE1#<\xA2)j\xB3TA\\\xFF;\xC5\xA6Tu\xAAap\xDE\xF4\xF7 p\xCA\xD2\xBA\xDC\xCDv\x17\xC2\xBCQ\xDF\xAB7^\xA1G\x18\xB9\xB2F\x81\x9Fq\x92\xD3".force_encoding('BINARY') + assert_equal "\x00\x00\x00\assh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\x9DRU\x0E\x83\x8Eb}\x81vOn\xCA\xBA\x01%\xFE\x87\x80b\xB5\x98R%\xA9(\xC1\xAE\xEFq|\x82L\xADQ?\x1D\xC6o\xB8\xD8pI\e\xFC\xF8\xFE^\xAD*\xA4u;\x99S\fc\x11\xBE\xFD\x047B\x1C\xF2h\xBA\xB1\xB0\n\x12F\e\x16\xF7Z\x8D\xD3\xF2f\xC0\x1C\xD8\xBE\xCC\x82\x85Qka$\xB6\xBD\x1C)\x85B\xAAf\xC8\xF3V*\xC3\x1C\xAA\xDC\xC3I\xDDe\xEFu\x02M\x12\x1A\xE2};he\x9D\xB5\xA47\xE4\x12\x8F\xE0\xF1\xA5\x91/\xFB\xEA\t\x0F \x1E\xB4B@+6\x1F\xBD\xA7\xA9u\x80\x19\xAA\xAC\xFFK\\F\x8C\xD9u\f?\xB9#[M\xDF\xB0\xFC\xE8\xF6J\x98\xA4\x99\x8F\xF9]\x88\x1D|A%\xAB\e\x0EN\xAA\xD3 \xCF\xA7}c\xDE\xF5\xBA4\xC8\xD2\x81(\x13\xB3\x94@fC\xDC\xDF\xFD\xA1\e$?\x13\xA9m\xEB*\xCA'\xB3\x19\x19\xF0\xD2\xB3P\x00\x96ou\xE90\xC4-\x1F\xCF\x1Aw\x034\xC6\xDF\xA7\x8C\xCA^Ix\x15\xFA\x9A+".dup.force_encoding('BINARY'), cert.signature_key.to_blob + expected_signature = "\x00\x00\x00\assh-rsa\x00\x00\x01\x00I\b%\x01\xB2\xCC\x87\xD7\e\xC5\x88\x93|\x9D\xEC}\xA4\x86\xD7\xBB\xB6\xD3\x93\xFD\\\xC73\xC2*\aV\xA2\x81\x05J\x91\x9AEKV\n\xB4\xEB\xF3\xBC\xBAr\x16\xE5\x9A\xB9\xDC(0\xB4\x1C\x9F\"\x9E\xF9\x91\xD0\x1F\x9Cp\r*\xE3\x8A\xD3\xB9W$[OI\xD2\x8F8\x9B\xA4\x9E\xFFuGg\x00\xA5\xCD\r\xDB\x95\xEE)_\xC3\xBCi\xA2\xCC\r\x86\xFD\xE9\xE6\x188\x92\xFD\xCC\n\x98t\x8C\x16\xF4O\xF6\xD5\xD4\xB7\\\xB95\x19\xA3\xBBW\xF3\xF7r<\xE6\x8C\xFC\xE5\x9F\xBF\xE0\xBF\x06\xE7v\xF2\x8Ek\xA4\x02\xB6fMd\xA5e\x87\xE1\x93\xF5\x81\xCF\xDF\x88\xDC\a\xA2\e\xD5\xCA\x14\xB2>\xF4\x8F|\xE5-w\xF5\x85\xD0\xF1F((\xD1\xEEE&\x1D\xA2+\xEC\x93\xE7\xC7\xAE\xE38\xE4\xAE\xF7 \xED\xC6\r\xD6\x1A\xE1#<\xA2)j\xB3TA\\\xFF;\xC5\xA6Tu\xAAap\xDE\xF4\xF7 p\xCA\xD2\xBA\xDC\xCDv\x17\xC2\xBCQ\xDF\xAB7^\xA1G\x18\xB9\xB2F\x81\x9Fq\x92\xD3".dup.force_encoding('BINARY') assert_equal expected_signature, cert.signature assert cert.signature_valid? assert_equal SIGNED_CERT, cert.to_blob diff --git a/test/authentication/test_ed25519.rb b/test/authentication/test_ed25519.rb index 7430541..d0d0e9e 100644 --- a/test/authentication/test_ed25519.rb +++ b/test/authentication/test_ed25519.rb @@ -6,7 +6,6 @@ unless ENV['NET_SSH_NO_ED25519'] require 'base64' module Authentication - class TestED25519 < NetSSHTest def setup raise "No ED25519 set NET_SSH_NO_ED25519 to ignore this test" unless Net::SSH::Authentication::ED25519Loader::LOADED @@ -23,7 +22,7 @@ unless ENV['NET_SSH_NO_ED25519'] shared_secret = "Hello" signed = priv_key.ssh_do_sign(shared_secret) - self.assert_equal(true,pub_key.ssh_do_verify(signed,shared_secret)) + self.assert_equal(true, pub_key.ssh_do_verify(signed, shared_secret)) self.assert_equal(priv_key.public_key.fingerprint, pub_key.fingerprint) self.assert_equal(pub_key.fingerprint, key_fingerprint_md5_no_pwd) self.assert_equal(pub_key.fingerprint('sha256'), key_fingerprint_sha256_no_pwd) @@ -40,7 +39,7 @@ unless ENV['NET_SSH_NO_ED25519'] shared_secret = "Hello" signed = priv_key.ssh_do_sign(shared_secret) - self.assert_equal(true,pub_key.ssh_do_verify(signed,shared_secret)) + self.assert_equal(true, pub_key.ssh_do_verify(signed, shared_secret)) self.assert_equal(priv_key.public_key.fingerprint, pub_key.fingerprint) self.assert_equal(pub_key.fingerprint, key_fingerprint_md5_no_pwd) self.assert_equal(pub_key.fingerprint('sha256'), key_fingerprint_sha256_no_pwd) @@ -57,11 +56,11 @@ unless ENV['NET_SSH_NO_ED25519'] priv = private_key_pwd pub_key = Net::SSH::Authentication::ED25519::PubKey.new(pub_data) - priv_key = Net::SSH::Authentication::ED25519::PrivKey.read(priv,'pwd') + priv_key = Net::SSH::Authentication::ED25519::PrivKey.read(priv, 'pwd') shared_secret = "Hello" signed = priv_key.ssh_do_sign(shared_secret) - self.assert_equal(true,pub_key.ssh_do_verify(signed,shared_secret)) + self.assert_equal(true, pub_key.ssh_do_verify(signed, shared_secret)) self.assert_equal(priv_key.public_key.fingerprint, pub_key.fingerprint) self.assert_equal(pub_key.fingerprint, key_fingerprint_md5_pwd) self.assert_equal(pub_key.fingerprint('sha256'), key_fingerprint_sha256_pwd) @@ -87,7 +86,7 @@ unless ENV['NET_SSH_NO_ED25519'] shared_secret = "Hello" signed = priv_key.ssh_do_sign(shared_secret) - self.assert_equal(true,pub_key.ssh_do_verify(signed,shared_secret)) + self.assert_equal(true, pub_key.ssh_do_verify(signed, shared_secret)) self.assert_equal(priv_key.public_key.fingerprint, pub_key.fingerprint) self.assert_equal(pub_key.fingerprint, key_fingerprint_md5_pwd) self.assert_equal(pub_key.fingerprint('sha256'), key_fingerprint_sha256_pwd) @@ -158,7 +157,6 @@ unless ENV['NET_SSH_NO_ED25519'] 'SHA256:u6mXnY8P1b0FODGp8mckqOB33u8+jvkSCtJbD5Q9klg' end end - end end diff --git a/test/authentication/test_key_manager.rb b/test/authentication/test_key_manager.rb index c40779f..05cb15d 100644 --- a/test/authentication/test_key_manager.rb +++ b/test/authentication/test_key_manager.rb @@ -2,7 +2,6 @@ require_relative '../common' require 'net/ssh/authentication/key_manager' module Authentication - class TestKeyManager < NetSSHTest def test_key_files_and_known_identities_are_empty_by_default assert manager.key_files.empty? @@ -23,7 +22,7 @@ module Authentication manager.add "/third" manager.add "/second" assert_equal 3, manager.key_files.length - final_files = manager.key_files.map {|item| item.split('/').last} + final_files = manager.key_files.map { |item| item.split('/').last } assert_equal %w[first second third], final_files end @@ -33,7 +32,7 @@ module Authentication manager.add_keycert "/third" manager.add_keycert "/second" assert_equal 3, manager.keycert_files.length - final_files = manager.keycert_files.map {|item| item.split('/').last} + final_files = manager.keycert_files.map { |item| item.split('/').last } assert_equal %w[first second third], final_files end @@ -322,7 +321,7 @@ module Authentication end end - def rsa(size=512) + def rsa(size = 512) @rsa ||= OpenSSL::PKey::RSA.new(size) end @@ -381,5 +380,4 @@ module Authentication @manager ||= Net::SSH::Authentication::KeyManager.new(nil, { password_prompt: prompt }.merge(options)) end end - end diff --git a/test/authentication/test_session.rb b/test/authentication/test_session.rb index db37467..4f1d482 100644 --- a/test/authentication/test_session.rb +++ b/test/authentication/test_session.rb @@ -2,7 +2,6 @@ require_relative '../common' require 'net/ssh/authentication/session' module Authentication - class TestSession < NetSSHTest include Net::SSH::Transport::Constants include Net::SSH::Authentication::Constants @@ -168,11 +167,13 @@ module Authentication private - def session(options={}) - @session ||= Net::SSH::Authentication::Session.new(transport(options), options) + def session(options = {}) + session_opts = options.clone + session_opts[:pubkey_algorithms] = %w[ssh-rsa] + @session ||= Net::SSH::Authentication::Session.new(transport(options), session_opts) end - def transport(options={}) + def transport(options = {}) @transport ||= MockTransport.new(options) end @@ -292,5 +293,4 @@ module Authentication EOF end end - end diff --git a/test/common.rb b/test/common.rb index 8ae521c..e6a69bf 100644 --- a/test/common.rb +++ b/test/common.rb @@ -5,13 +5,13 @@ if ENV["CI"] require 'simplecov' SimpleCov.start - require 'codecov' - SimpleCov.formatter = SimpleCov::Formatter::Codecov + # require 'codecov' + # SimpleCov.formatter = SimpleCov::Formatter::Codecov end end require 'minitest' -require 'mocha/setup' +require 'mocha/minitest' require 'net/ssh/buffer' require 'net/ssh/config' require 'net/ssh/loggable' @@ -94,7 +94,7 @@ class MockTransport < Net::SSH::Transport::Session attr_accessor :mock_enqueue - def initialize(options={}) + def initialize(options = {}) @options = options self.logger = options[:logger] self.host_as_string = "net.ssh.test,127.0.0.1" @@ -104,7 +104,7 @@ class MockTransport < Net::SSH::Transport::Session @hints = {} @socket = options[:socket] @algorithms = OpenStruct.new(session_id: "abcxyz123") - verifier { |data| true } + verifier { |_data| true } end def send_message(message) @@ -161,7 +161,7 @@ class MockTransport < Net::SSH::Transport::Session @server_options = options end - def hint(name, value=true) + def hint(name, value = true) @hints[name] = value end end diff --git a/test/connection/test_channel.rb b/test/connection/test_channel.rb index abda254..c1d7c20 100644 --- a/test/connection/test_channel.rb +++ b/test/connection/test_channel.rb @@ -2,7 +2,6 @@ require 'common' require 'net/ssh/connection/channel' module Connection - class TestChannel < NetSSHTest include Net::SSH::Connection::Constants @@ -43,7 +42,7 @@ module Connection def test_request_pty_without_options_should_use_defaults channel.expects(:send_channel_request).with("pty-req", :string, "xterm", - :long, 80, :long, 24, :long, 640, :long, 480, :string, "\0").yields + :long, 80, :long, 24, :long, 640, :long, 480, :string, "\0").yields found_block = false channel.request_pty { found_block = true } assert found_block, "expected block to be passed to send_channel_request" @@ -51,7 +50,7 @@ module Connection def test_request_pty_with_options_should_honor_options channel.expects(:send_channel_request).with("pty-req", :string, "vanilla", - :long, 60, :long, 15, :long, 400, :long, 200, :string, "\5\0\0\0\1\0") + :long, 60, :long, 15, :long, 400, :long, 200, :string, "\5\0\0\0\1\0") channel.request_pty term: "vanilla", chars_wide: 60, chars_high: 15, pixels_wide: 400, pixels_high: 200, modes: { 5 => 1 } end @@ -73,7 +72,7 @@ module Connection channel.do_open_confirmation(0, 100, 100) assert !channel.closing? - connection.expect { |t,packet| assert_equal CHANNEL_CLOSE, packet.type } + connection.expect { |_t, packet| assert_equal CHANNEL_CLOSE, packet.type } connection.expects(:cleanup_channel).with(channel) channel.close @@ -112,7 +111,7 @@ module Connection channel.do_open_confirmation(0, 100, 100) channel.send_data("hello world") - connection.expect do |t,packet| + connection.expect do |_t, packet| assert_equal CHANNEL_DATA, packet.type assert_equal 0, packet[:local_id] assert_equal 11, packet[:data].length @@ -125,12 +124,12 @@ module Connection channel.do_open_confirmation(0, 100, 8) channel.send_data("hello world") - connection.expect do |t,packet| + connection.expect do |t, packet| assert_equal CHANNEL_DATA, packet.type assert_equal 0, packet[:local_id] assert_equal "hello wo", packet[:data] - t.expect do |t2,packet2| + t.expect do |_t2, packet2| assert_equal CHANNEL_DATA, packet2.type assert_equal 0, packet2[:local_id] assert_equal "rld", packet2[:data] @@ -144,7 +143,7 @@ module Connection channel.do_open_confirmation(0, 8, 100) channel.send_data("hello world") - connection.expect do |t,packet| + connection.expect do |_t, packet| assert_equal CHANNEL_DATA, packet.type assert_equal 0, packet[:local_id] assert_equal "hello wo", packet[:data] @@ -210,7 +209,7 @@ module Connection def test_do_request_for_unhandled_request_should_send_CHANNEL_FAILURE_if_wants_reply channel.do_open_confirmation(0, 100, 100) - connection.expect { |t,packet| assert_equal CHANNEL_FAILURE, packet.type } + connection.expect { |_t, packet| assert_equal CHANNEL_FAILURE, packet.type } channel.do_request "keepalive@openssh.com", true, nil end @@ -234,7 +233,7 @@ module Connection channel.do_open_confirmation(0, 100, 100) flag = false channel.on_request("exit-status") { flag = true; true } - connection.expect { |t,p| assert_equal CHANNEL_SUCCESS, p.type } + connection.expect { |_t, p| assert_equal CHANNEL_SUCCESS, p.type } assert_nothing_raised { channel.do_request "exit-status", true, nil } assert flag, "callback should have been invoked" end @@ -243,14 +242,14 @@ module Connection channel.do_open_confirmation(0, 100, 100) flag = false channel.on_request("exit-status") { flag = true; raise Net::SSH::ChannelRequestFailed } - connection.expect { |t,p| assert_equal CHANNEL_FAILURE, p.type } + connection.expect { |_t, p| assert_equal CHANNEL_FAILURE, p.type } assert_nothing_raised { channel.do_request "exit-status", true, nil } assert flag, "callback should have been invoked" end def test_send_channel_request_without_callback_should_not_want_reply channel.do_open_confirmation(0, 100, 100) - connection.expect do |t,p| + connection.expect do |_t, p| assert_equal CHANNEL_REQUEST, p.type assert_equal 0, p[:local_id] assert_equal "exec", p[:request] @@ -275,7 +274,7 @@ module Connection def test_send_channel_request_with_callback_should_want_reply channel.do_open_confirmation(0, 100, 100) - connection.expect do |t,p| + connection.expect do |_t, p| assert_equal CHANNEL_REQUEST, p.type assert_equal 0, p[:local_id] assert_equal "exec", p[:request] @@ -299,7 +298,7 @@ module Connection flag = false channel { flag = true } assert !flag, "callback should not have been invoked yet" - channel.do_open_confirmation(1,2,3) + channel.do_open_confirmation(1, 2, 3) assert flag, "callback should have been invoked" end @@ -308,14 +307,14 @@ module Connection forward = mock("forward") forward.expects(:agent).with(channel) connection.expects(:forward).returns(forward) - channel.do_open_confirmation(1,2,3) + channel.do_open_confirmation(1, 2, 3) end def test_do_open_confirmation_with_non_session_channel_should_not_invoke_agent_forwarding_even_if_agent_forwarding_requested connection forward_agent: true channel type: "direct-tcpip" connection.expects(:forward).never - channel.do_open_confirmation(1,2,3) + channel.do_open_confirmation(1, 2, 3) end def test_do_window_adjust_should_adjust_remote_window_size_by_the_given_amount @@ -348,7 +347,7 @@ module Connection assert_equal 0x20000, channel.local_maximum_window_size assert_equal 0x20000, channel.local_window_size - connection.expect do |t,p| + connection.expect do |_t, p| assert_equal CHANNEL_WINDOW_ADJUST, p.type assert_equal 0, p[:local_id] assert_equal 0x20000, p[:extra_bytes] @@ -385,25 +384,25 @@ module Connection end def test_wait_should_block_while_channel_is_active? - channel.expects(:active?).times(3).returns(true,true,false) + channel.expects(:active?).times(3).returns(true, true, false) channel.wait end def test_wait_until_open_confirmed_should_block_while_remote_id_nil - channel.expects(:remote_id).times(3).returns(nil,nil,3) + channel.expects(:remote_id).times(3).returns(nil, nil, 3) channel.send(:wait_until_open_confirmed) end def test_eof_bang_should_send_eof_to_server channel.do_open_confirmation(0, 1000, 1000) - connection.expect { |t,p| assert_equal CHANNEL_EOF, p.type } + connection.expect { |_t, p| assert_equal CHANNEL_EOF, p.type } channel.eof! channel.process end def test_eof_bang_should_not_send_eof_if_eof_was_already_declared channel.do_open_confirmation(0, 1000, 1000) - connection.expect { |t,p| assert_equal CHANNEL_EOF, p.type } + connection.expect { |_t, p| assert_equal CHANNEL_EOF, p.type } channel.eof! assert_nothing_raised { channel.eof! } channel.process @@ -411,7 +410,7 @@ module Connection def test_eof_q_should_return_true_if_eof_declared channel.do_open_confirmation(0, 1000, 1000) - connection.expect { |t,p| assert_equal CHANNEL_EOF, p.type } + connection.expect { |_t, p| assert_equal CHANNEL_EOF, p.type } assert !channel.eof? channel.eof! @@ -421,7 +420,7 @@ module Connection def test_send_data_should_raise_exception_if_eof_declared channel.do_open_confirmation(0, 1000, 1000) - connection.expect { |t,p| assert_equal CHANNEL_EOF, p.type } + connection.expect { |_t, p| assert_equal CHANNEL_EOF, p.type } channel.eof! channel.process assert_raises(EOFError) { channel.send_data("die! die! die!") } @@ -429,9 +428,9 @@ module Connection def test_data_should_precede_eof channel.do_open_confirmation(0, 1000, 1000) - connection.expect do |_t,p| + connection.expect do |_t, p| assert_equal CHANNEL_DATA, p.type - connection.expect { |_t,p2| assert_equal CHANNEL_EOF, p2.type } + connection.expect { |_t, p2| assert_equal CHANNEL_EOF, p2.type } end channel.send_data "foo" channel.eof! @@ -445,7 +444,7 @@ module Connection attr_reader :options attr_reader :channels - def initialize(options={}) + def initialize(options = {}) @expectation = nil @options = options @channels = {} @@ -457,6 +456,7 @@ module Connection def send_message(msg) raise "#{msg.to_s.inspect} received but no message was expected" unless @expectation + packet = Net::SSH::Packet.new(msg.to_s) callback, @expectation = @expectation, nil callback.call(self, packet) @@ -472,16 +472,15 @@ module Connection end end - def connection(options={}) + def connection(options = {}) @connection ||= MockConnection.new(options) end - def channel(options={}, &block) + def channel(options = {}, &block) @channel ||= Net::SSH::Connection::Channel.new(connection(options), - options[:type] || "session", - options[:local_id] || 0, - &block) + options[:type] || "session", + options[:local_id] || 0, + &block) end end - end diff --git a/test/connection/test_session.rb b/test/connection/test_session.rb index 91e3ea9..5de5fd2 100644 --- a/test/connection/test_session.rb +++ b/test/connection/test_session.rb @@ -2,7 +2,6 @@ require_relative '../common' require 'net/ssh/connection/session' module Connection - class TestSession < NetSSHTest include Net::SSH::Connection::Constants @@ -62,14 +61,14 @@ module Connection flag = false channel = session.open_channel { flag = true } assert !flag, "callback should not be invoked immediately" - channel.do_open_confirmation(1,2,3) + channel.do_open_confirmation(1, 2, 3) assert flag, "callback should have been invoked" assert_equal "session", channel.type assert_equal 0, channel.local_id assert_equal channel, session.channels[channel.local_id] packet = P(:byte, CHANNEL_OPEN, :string, "session", :long, channel.local_id, - :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size) + :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size) assert_equal packet.to_s, socket.write_buffer end @@ -77,15 +76,15 @@ module Connection channel = session.open_channel("direct-tcpip") assert_equal "direct-tcpip", channel.type packet = P(:byte, CHANNEL_OPEN, :string, "direct-tcpip", :long, channel.local_id, - :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size) + :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size) assert_equal packet.to_s, socket.write_buffer end def test_open_channel_with_extras_should_append_extras_to_packet channel = session.open_channel("direct-tcpip", :string, "other.host", :long, 1234) packet = P(:byte, CHANNEL_OPEN, :string, "direct-tcpip", :long, channel.local_id, - :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size, - :string, "other.host", :long, 1234) + :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size, + :string, "other.host", :long, 1234) assert_equal packet.to_s, socket.write_buffer end @@ -225,7 +224,7 @@ module Connection def test_channel_open_packet_with_corresponding_handler_should_result_in_channel_open_failure_when_handler_returns_an_error transport.return(CHANNEL_OPEN, :string, "auth-agent", :long, 14, :long, 0x20000, :long, 0x10000) - session.on_open_channel "auth-agent" do |s, ch, p| + session.on_open_channel "auth-agent" do |_s, _ch, _p| raise Net::SSH::ChannelOpenFailed.new(1234, "we iz in ur channelz!") end process_times(2) @@ -336,19 +335,19 @@ module Connection end def test_writers_without_pending_writes_should_not_be_considered_for_select - IO.expects(:select).with([socket],[],nil,nil).returns([[],[],[]]) + IO.expects(:select).with([socket], [], nil, nil).returns([[], [], []]) session.process end def test_writers_with_pending_writes_should_be_considered_for_select socket.enqueue("laksdjflasdkf") - IO.expects(:select).with([socket],[socket],nil,nil).returns([[],[],[]]) + IO.expects(:select).with([socket], [socket], nil, nil).returns([[], [], []]) session.process end def test_ready_readers_should_be_filled socket.expects(:recv).returns("this is some data") - IO.expects(:select).with([socket],[],nil,nil).returns([[socket],[],[]]) + IO.expects(:select).with([socket], [], nil, nil).returns([[socket], [], []]) session.process assert_equal [socket], session.listeners.keys end @@ -356,7 +355,7 @@ module Connection def test_ready_readers_that_cant_be_filled_should_be_removed socket.expects(:recv).returns("") socket.expects(:close) - IO.expects(:select).with([socket],[],nil,nil).returns([[socket],[],[]]) + IO.expects(:select).with([socket], [], nil, nil).returns([[socket], [], []]) session.process assert session.listeners.empty? end @@ -366,7 +365,7 @@ module Connection flag = false session.stop_listening_to(socket) # so that we only have to test the presence of a single IO object session.listen_to(io) { flag = true } - IO.expects(:select).with([io],[],nil,nil).returns([[io],[],[]]) + IO.expects(:select).with([io], [], nil, nil).returns([[io], [], []]) session.process assert flag, "callback should have been invoked" end @@ -374,13 +373,13 @@ module Connection def test_ready_writers_should_call_send_pending socket.enqueue("laksdjflasdkf") socket.expects(:send).with("laksdjflasdkf", 0).returns(13) - IO.expects(:select).with([socket],[socket],nil,nil).returns([[],[socket],[]]) + IO.expects(:select).with([socket], [socket], nil, nil).returns([[], [socket], []]) session.process end def test_process_should_call_rekey_as_needed transport.expects(:rekey_as_needed) - IO.expects(:select).with([socket],[],nil,nil).returns([[],[],[]]) + IO.expects(:select).with([socket], [], nil, nil).returns([[], [], []]) session.process end @@ -388,7 +387,7 @@ module Connection timeout = Net::SSH::Connection::Session::DEFAULT_IO_SELECT_TIMEOUT options = { keepalive: true } expected_packet = P(:byte, Net::SSH::Packet::GLOBAL_REQUEST, :string, "keepalive@openssh.com", :bool, true) - IO.stubs(:select).with([socket],[],nil,timeout).returns(nil) + IO.stubs(:select).with([socket], [], nil, timeout).returns(nil) transport.expects(:enqueue_message).with { |msg| msg.content == expected_packet.content } session(options).process end @@ -397,15 +396,15 @@ module Connection timeout = Net::SSH::Connection::Session::DEFAULT_IO_SELECT_TIMEOUT options = { keepalive: true, keepalive_interval: 300, keepalive_maxcount: 3 } expected_packet = P(:byte, Net::SSH::Packet::GLOBAL_REQUEST, :string, "keepalive@openssh.com", :bool, true) - [1,2,3].each do |i| + [1, 2, 3].each do |i| Time.stubs(:now).returns(Time.at(i * 300)) - IO.stubs(:select).with([socket],[],nil,timeout).returns(nil) + IO.stubs(:select).with([socket], [], nil, timeout).returns(nil) transport.expects(:enqueue_message).with { |msg| msg.content == expected_packet.content } session(options).process end Time.stubs(:now).returns(Time.at(4 * 300)) - IO.stubs(:select).with([socket],[],nil,timeout).returns(nil) + IO.stubs(:select).with([socket], [], nil, timeout).returns(nil) transport.expects(:enqueue_message).with { |msg| msg.content == expected_packet.content } assert_raises(Net::SSH::Timeout) { session(options).process } end @@ -413,7 +412,7 @@ module Connection def test_process_should_not_call_enqueue_message_unless_io_select_timed_out timeout = Net::SSH::Connection::Session::DEFAULT_IO_SELECT_TIMEOUT options = { keepalive: true } - IO.stubs(:select).with([socket],[],nil,timeout).returns([[socket],[],[]]) + IO.stubs(:select).with([socket], [], nil, timeout).returns([[socket], [], []]) socket.stubs(:recv).returns("x") transport.expects(:enqueue_message).never session(options).process @@ -423,20 +422,20 @@ module Connection timeout = 10 options = { keepalive: true, keepalive_interval: timeout } Time.stubs(:now).returns(Time.at(0), Time.at(9), Time.at(timeout)) - IO.stubs(:select).with([socket],[],nil,timeout).returns(nil) + IO.stubs(:select).with([socket], [], nil, timeout).returns(nil) transport.expects(:enqueue_message).times(2) 3.times { session(options).process } end def test_process_should_call_io_select_with_nil_as_last_arg_if_keepalive_disabled - IO.expects(:select).with([socket],[],nil,nil).returns([[],[],[]]) + IO.expects(:select).with([socket], [], nil, nil).returns([[], [], []]) session.process end def test_process_should_call_io_select_with_interval_as_last_arg_if_keepalive_interval_passed timeout = 10 options = { keepalive: true, keepalive_interval: timeout } - IO.expects(:select).with([socket],[],nil,timeout).returns([[],[],[]]) + IO.expects(:select).with([socket], [], nil, timeout).returns([[], [], []]) session(options).process end @@ -444,13 +443,13 @@ module Connection timeout = 10 wait = 5 options = { keepalive: true, keepalive_interval: timeout } - IO.expects(:select).with([socket],[],nil,wait).returns([[],[],[]]) + IO.expects(:select).with([socket], [], nil, wait).returns([[], [], []]) session(options).process(wait) end def test_loop_should_call_process_until_process_returns_false session.expects(:process).with(0) - session.expects(:process).with(nil).times(4).returns(true,true,true,false).yields + session.expects(:process).with(nil).times(4).returns(true, true, true, false).yields n = 0 session.loop { n += 1 } assert_equal 4, n @@ -460,7 +459,7 @@ module Connection prep_exec("ls", :stdout, "data packet", :stderr, "extended data packet") call = :first - session.exec "ls" do |channel, type, data| + session.exec "ls" do |_channel, type, data| if call == :first assert_equal :stdout, type assert_equal "data packet", data @@ -490,7 +489,7 @@ module Connection def test_exec_bang_should_block_until_command_finishes prep_exec("ls", :stdout, "some data") called = false - session.exec! "ls" do |channel, type, data| + session.exec! "ls" do |_channel, type, data| called = true assert_equal :stdout, type assert_equal "some data", data @@ -521,7 +520,7 @@ module Connection private def prep_exec(command, *data) - IO.expects(:select).with([socket],[],nil,0).returns([[],[],[]]) + IO.expects(:select).with([socket], [], nil, 0).returns([[], [], []]) transport.mock_enqueue = true transport.expect do |t, p| assert_equal CHANNEL_OPEN, p.type @@ -536,6 +535,7 @@ module Connection data.each_slice(2) do |type, datum| next if datum.empty? + if type == :stdout t2.return(CHANNEL_DATA, :long, p[:remote_id], :string, datum) else @@ -544,7 +544,7 @@ module Connection end t2.return(CHANNEL_CLOSE, :long, p[:remote_id]) - t2.expect { |t3,p3| assert_equal CHANNEL_CLOSE, p3.type } + t2.expect { |_t3, p3| assert_equal CHANNEL_CLOSE, p3.type } end end end @@ -570,11 +570,11 @@ module Connection session.channels[local_id] = stub("channel", process: true, local_closed?: false) end - def transport(options={}) + def transport(options = {}) @transport ||= MockTransport.new(options.merge(socket: socket)) end - def session(options={}) + def session(options = {}) @session ||= Net::SSH::Connection::Session.new(transport, options) end @@ -583,5 +583,4 @@ module Connection session.process { (i += 1) < n } end end - end diff --git a/test/integration/Vagrantfile b/test/integration/Vagrantfile index 2c42d9a..2327e5d 100644 --- a/test/integration/Vagrantfile +++ b/test/integration/Vagrantfile @@ -1,10 +1,11 @@ VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = "ubuntu/trusty64" + config.vm.box = "ubuntu/bionic64" config.vm.provision "ansible" do |ansible| ansible.playbook = "./playbook.yml" - ansible.sudo = true + ansible.become = true + ansible.become_user = 'root' ansible.verbose = 'vvvv' ansible.compatibility_mode = "2.0" end diff --git a/test/integration/common.rb b/test/integration/common.rb index 6295ada..9f56fe9 100644 --- a/test/integration/common.rb +++ b/test/integration/common.rb @@ -20,14 +20,14 @@ module IntegrationTestHelpers end end - def set_authorized_key(user,pubkey) + def set_authorized_key(user, pubkey) authorized_key = "/home/#{user}/.ssh/authorized_keys" sh "sudo cp #{pubkey} #{authorized_key}" sh "sudo chown #{user} #{authorized_key}" sh "sudo chmod 0744 #{authorized_key}" end - def sign_user_key(user,pubkey) + def sign_user_key(user, pubkey) cert = "/etc/ssh/users_ca" sh "sudo ssh-keygen -s #{cert} -I user_#{user} -n #{user} -V +52w #{pubkey}" end @@ -48,7 +48,7 @@ module IntegrationTestHelpers end end - def ssh_add(key,password) + def ssh_add(key, password) command = "ssh-add #{key}" status = nil PTY.spawn(command) do |reader, writer, pid| @@ -64,11 +64,13 @@ module IntegrationTestHelpers pid, status = Process.wait2 pid end raise "Command: #{command} failed:#{status.exitstatus}" unless status + status.exitstatus end def with_sshd_config(sshd_config, &block) raise "Failed to copy config" unless system("sudo cp -f /etc/ssh/sshd_config /etc/ssh/sshd_config.original") + begin Tempfile.open('sshd_config') do |f| f.write(sshd_config) @@ -77,6 +79,7 @@ module IntegrationTestHelpers end system("sudo chmod 0644 /etc/ssh/sshd_config") raise "Failed to restart sshd" unless system("sudo service ssh restart") + yield ensure system("sudo cp -f /etc/ssh/sshd_config.original /etc/ssh/sshd_config") @@ -84,33 +87,74 @@ module IntegrationTestHelpers end end - def with_lines_as_tempfile(lines = [], &block) + def with_lines_as_tempfile(lines = [], add_pid: true, debug: false, &block) Tempfile.open('sshd_config') do |f| - f.write(lines) + f.write(lines.join("\n")) + pidpath = nil + if add_pid + pidpath = f.path + '.pid' + f.write("\nPidFile #{pidpath}\n") + end + f.write("\nLogLevel DEBUG3\n") if debug f.close - yield(f.path) + puts "CONFIG: #{f.path} PID: #{pidpath}" if debug + yield(f.path, pidpath) end end + def port_open?(path) + Socket.tcp("localhost", 10567, connect_timeout: 1) { true } rescue false # rubocop:disable Style/RescueModifier + end + # @yield [pid, port] - def start_sshd_7_or_later(port = '2200', config: nil) + def start_sshd_7_or_later(port = '2200', config: nil, debug: false) pid = nil + sshpidfile = nil if config - with_lines_as_tempfile(config) do |path| - pid = spawn('sudo', '/opt/net-ssh-openssh/sbin/sshd', '-D', '-f', path, '-p', port) + with_lines_as_tempfile(config, debug: debug) do |path, pidpath| + puts "DEBUG - SSH LOG: #{path}-log.txt config: #{path}" if debug + raise "A leftover sshd is already running" if port_open?(port) + + extra_params = [] + extra_params = ['-E', "#{path}-log.txt"] if debug + pid = spawn('sudo', '/opt/net-ssh-openssh/sbin/sshd', '-D', '-f', path, '-p', port, *extra_params) + sshpidfile = pidpath yield pid, port end else - pid = spawn('sudo', '/opt/net-ssh-openssh/sbin/sshd', '-D', '-p', port) - yield pid, port + with_lines_as_tempfile(['']) do |path, pidpath| + pid = spawn('sudo', '/opt/net-ssh-openssh/sbin/sshd', '-D', '-f', path, '-p', port) + sshpidfile = pidpath + yield pid, port + end end ensure - # Our pid is sudo, -9 (KILL) on sudo will not clean up its children + # Our pid is sudo and not sshd, -9 (KILL) on sudo will not clean up its children # properly, so we just have to hope that -15 (TERM) will manage to bring # down sshd. - if pid + if sshpidfile + sshpid = File.read(sshpidfile).strip + system('sudo', 'kill', '-15', sshpid.to_s) + begin + Timeout.timeout(20) do + Process.wait(pid) + end + rescue Timeout::Error + warn "Failed to kill openssh process: #{sshpid}" + system('sudo', 'kill', '-9', sshpid.to_s) + raise + end + elsif pid system('sudo', 'kill', '-15', pid.to_s) - Process.wait(pid) + begin + Timeout.timeout(20) do + Process.wait(pid) + end + rescue Timeout::Error + warn "Failed to kill openssh process: #{pid}" + system('sudo', 'kill', '-9', pid.to_s) + raise + end end end diff --git a/test/integration/mitm_server.rb b/test/integration/mitm_server.rb index e063687..750748c 100644 --- a/test/integration/mitm_server.rb +++ b/test/integration/mitm_server.rb @@ -49,10 +49,10 @@ class MitmServer < TCPServer server = start_server(server) do |local| remote = TCPSocket.new(remote_host, remote_port) loop do - r,_w,_e = IO.select([local, remote],nil,nil) + r, _w, _e = IO.select([local, remote], nil, nil) if r.include? local begin - data = local.recv local_read_size + data = local.recv local_read_size rescue StandardError => e data = nil dlog "Local closed: #{e}" diff --git a/test/integration/playbook.yml b/test/integration/playbook.yml index 47eba34..828dda2 100644 --- a/test/integration/playbook.yml +++ b/test/integration/playbook.yml @@ -8,11 +8,10 @@ homedir: /home/vagrant ruby_version: '2.0.0-p598' ruby_versions: - - '2.3.8' - - '2.4.10' - '2.5.8' - '2.6.6' - '2.7.1' + - '3.0.1' # - 'ruby-head' # - 'rbx-3.19' # - 'jruby-9.0.5.0' @@ -35,6 +34,10 @@ rvm1_gpg_key_server: pool.sks-keyservers.net, when: "'{{current_ruby_version.stdout|default()}}' != '{{ruby_version}}' and not no_rvm" } tasks: + - name: Install packages + apt: + pkg: + - libssl-dev - group: name="{{mygroup}}" state=present - user: name=net_ssh_1 password="{{foopwd}}" group="{{mygroup}}" state=present - user: name=net_ssh_2 password="{{foo2pwd}}" group="{{mygroup}}" state=present @@ -80,26 +83,46 @@ lineinfile: dest='/etc/ssh/sshd_config' line='TrustedUserCAKeys /etc/ssh/users_ca.pub' notify: restart sshd - name: sshd allow forward - lineinfile: dest='/etc/ssh/sshd_config' line='AllowTcpForwarding all' regexp=LogLevel + lineinfile: dest='/etc/ssh/sshd_config' line='AllowTcpForwarding all' regexp=AllowTcpForwarding notify: restart sshd - name: sshd allow forward - lineinfile: dest='/etc/ssh/sshd_config' line='GatewayPorts yes' regexp=LogLevel + lineinfile: dest='/etc/ssh/sshd_config' line='GatewayPorts yes' regexp=GatewayPorts + notify: restart sshd + - name: disable x11 forward + lineinfile: dest='/etc/ssh/sshd_config' line='X11Forwarding no' regexp=X11Forwarding + notify: restart sshd + - name: sshd allow forward + lineinfile: dest='/etc/ssh/sshd_config' line='#PasswordAuthentication no' regexp='#?PasswordAuthentication.+no' + notify: restart sshd + - name: sshd allow forward + lineinfile: dest='/etc/ssh/sshd_config' line='PasswordAuthentication yes' regexp=PasswordAuthentication notify: restart sshd - name: put NET_SSH_RUN_INTEGRATION_TESTS=YES environment lineinfile: dest='/etc/environment' line='NET_SSH_RUN_INTEGRATION_TESTS=YES' - name: change dir in bashrc lineinfile: dest="{{homedir}}/.bashrc" owner="{{myuser}}" mode=0644 regexp='^cd ' line='cd /net-ssh' - - name: add host aliases + - name: add host aliases1 lineinfile: dest='/etc/hosts' owner='root' group='root' mode=0644 regexp='^127\.0\.0\.1\s+gateway.netssh' line='127.0.0.1 gateway.netssh' - - apt: + - name: add host aliases2 + lineinfile: dest='/etc/hosts' owner='root' group='root' mode=0644 + regexp='^127\.0\.0\.1\s+one.hosts.netssh' line='127.0.0.1 one.hosts.netssh' + - name: Update APT Cache + apt: + update_cache: yes + force_apt_get: yes + - name: Wait for locfile removal + become: yes + shell: while sudo fuser /var/lib/dpkg/lock >/dev/null 2>&1; do sleep 5; done; + - name: Install packages + apt: pkg: - pv - libgmp3-dev - git + - libssl-dev state: present - update_cache: yes - copy: content='echo "cd /net-ssh ; rake integration-test"' dest=/etc/update-motd.d/99-net-ssh-tests mode=0755 - name: add user to rvm group so they can change gem wrappers user: diff --git a/test/integration/test_agent.rb b/test/integration/test_agent.rb index 4045c9a..294a1b8 100644 --- a/test/integration/test_agent.rb +++ b/test/integration/test_agent.rb @@ -1,7 +1,7 @@ require_relative 'common' require 'net/ssh' -CERT = "\x00\x00\x00\x1Cssh-rsa-cert-v01@openssh.com\x00\x00\x00 Ir\xB9\xC9\x94l\x0ER\xA1h\xF5\xFDx\xB2J\xC6g\eHS\xDD\x162\x86\xF1\x90%\\$rf\xAF\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\xB3R\xBC\xF8\xEA\xA30\x90\x87\x85\xF6m\x80\xFB\x7F\x96%\xC0h\x85$\x05\x05J\x9BE\xD9\xDE\x81\xC0\xC9\xC2\xC0\x0F'\xD1TR\xCBb\xCD\xD0o\xA0\x15Q\x8B\xF26t\xC9!8\x85\xD2\f'\xC6\x14u\x1De\x90qyXl\a\x06\xA7\xD0\xB8 \xE1\xB3IP\xDE\xB5\xBE\x19\x0E\x97-M\xFDJT\x81\xE2\x8E>\xCD\x18\x9CJz\x1C\xB5}LsO\xF3\xAC\xAA\r\xAB\xF9\xD4\x83\x8DQ\x82\xE7F\xA4\x9F\x1C\x9A\xC5\xC3Y\x84k\x86\ef\xD7\x84\xE3\v\rlG\x15ya\xB0=\xDF\x11\x8D\x0FtZ/p\xBB\xB7g\xF5\xEBF8\xF5\x05}}\xDB\xFA\xA34dw\xE5\x80\xBC!=\x0E\x96\x18\bF\x10\a{\xFF\x9D2\xCA\xAAnu\x82\x82\xBA-F\x8C\x12\xBB\x04+nh\xE9N\xAF\fe\x16\x00Q\x9C\x1C\xCB\x94\x02\x8CQ\xFB,H[\x96\xF1Z4\nY]@\xE0\bs\x9Bh\x0E\xAA~\x105\x99\\\x8C\xA7q\x1A=\xA9\x9D\xBAbx\xF5`[\x8Aw\x80\b\xE0vy\x00\x00\x00\x00\x00\x00\x00c\x00\x00\x00\x01\x00\x00\x00\x06foobar\x00\x00\x00\b\x00\x00\x00\x04root\x00\x00\x00\x00Xk\\\x1C\x00\x00\x00\x00ZK>g\x00\x00\x00#\x00\x00\x00\rforce-command\x00\x00\x00\x0E\x00\x00\x00\n/bin/false\x00\x00\x00c\x00\x00\x00\x15permit-X11-forwarding\x00\x00\x00\x00\x00\x00\x00\x16permit-port-forwarding\x00\x00\x00\x00\x00\x00\x00\npermit-pty\x00\x00\x00\x00\x00\x00\x00\x0Epermit-user-rc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17\x00\x00\x00\assh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\x9DRU\x0E\x83\x8Eb}\x81vOn\xCA\xBA\x01%\xFE\x87\x80b\xB5\x98R%\xA9(\xC1\xAE\xEFq|\x82L\xADQ?\x1D\xC6o\xB8\xD8pI\e\xFC\xF8\xFE^\xAD*\xA4u;\x99S\fc\x11\xBE\xFD\x047B\x1C\xF2h\xBA\xB1\xB0\n\x12F\e\x16\xF7Z\x8D\xD3\xF2f\xC0\x1C\xD8\xBE\xCC\x82\x85Qka$\xB6\xBD\x1C)\x85B\xAAf\xC8\xF3V*\xC3\x1C\xAA\xDC\xC3I\xDDe\xEFu\x02M\x12\x1A\xE2};he\x9D\xB5\xA47\xE4\x12\x8F\xE0\xF1\xA5\x91/\xFB\xEA\t\x0F \x1E\xB4B@+6\x1F\xBD\xA7\xA9u\x80\x19\xAA\xAC\xFFK\\F\x8C\xD9u\f?\xB9#[M\xDF\xB0\xFC\xE8\xF6J\x98\xA4\x99\x8F\xF9]\x88\x1D|A%\xAB\e\x0EN\xAA\xD3 \xCF\xA7}c\xDE\xF5\xBA4\xC8\xD2\x81(\x13\xB3\x94@fC\xDC\xDF\xFD\xA1\e$?\x13\xA9m\xEB*\xCA'\xB3\x19\x19\xF0\xD2\xB3P\x00\x96ou\xE90\xC4-\x1F\xCF\x1Aw\x034\xC6\xDF\xA7\x8C\xCA^Ix\x15\xFA\x9A+\x00\x00\x01\x0F\x00\x00\x00\assh-rsa\x00\x00\x01\x00I\b%\x01\xB2\xCC\x87\xD7\e\xC5\x88\x93|\x9D\xEC}\xA4\x86\xD7\xBB\xB6\xD3\x93\xFD\\\xC73\xC2*\aV\xA2\x81\x05J\x91\x9AEKV\n\xB4\xEB\xF3\xBC\xBAr\x16\xE5\x9A\xB9\xDC(0\xB4\x1C\x9F\"\x9E\xF9\x91\xD0\x1F\x9Cp\r*\xE3\x8A\xD3\xB9W$[OI\xD2\x8F8\x9B\xA4\x9E\xFFuGg\x00\xA5\xCD\r\xDB\x95\xEE)_\xC3\xBCi\xA2\xCC\r\x86\xFD\xE9\xE6\x188\x92\xFD\xCC\n\x98t\x8C\x16\xF4O\xF6\xD5\xD4\xB7\\\xB95\x19\xA3\xBBW\xF3\xF7r<\xE6\x8C\xFC\xE5\x9F\xBF\xE0\xBF\x06\xE7v\xF2\x8Ek\xA4\x02\xB6fMd\xA5e\x87\xE1\x93\xF5\x81\xCF\xDF\x88\xDC\a\xA2\e\xD5\xCA\x14\xB2>\xF4\x8F|\xE5-w\xF5\x85\xD0\xF1F((\xD1\xEEE&\x1D\xA2+\xEC\x93\xE7\xC7\xAE\xE38\xE4\xAE\xF7 \xED\xC6\r\xD6\x1A\xE1#<\xA2)j\xB3TA\\\xFF;\xC5\xA6Tu\xAAap\xDE\xF4\xF7 p\xCA\xD2\xBA\xDC\xCDv\x17\xC2\xBCQ\xDF\xAB7^\xA1G\x18\xB9\xB2F\x81\x9Fq\x92\xD3".force_encoding('BINARY') +CERT = "\x00\x00\x00\x1Cssh-rsa-cert-v01@openssh.com\x00\x00\x00 Ir\xB9\xC9\x94l\x0ER\xA1h\xF5\xFDx\xB2J\xC6g\eHS\xDD\x162\x86\xF1\x90%\\$rf\xAF\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\xB3R\xBC\xF8\xEA\xA30\x90\x87\x85\xF6m\x80\xFB\x7F\x96%\xC0h\x85$\x05\x05J\x9BE\xD9\xDE\x81\xC0\xC9\xC2\xC0\x0F'\xD1TR\xCBb\xCD\xD0o\xA0\x15Q\x8B\xF26t\xC9!8\x85\xD2\f'\xC6\x14u\x1De\x90qyXl\a\x06\xA7\xD0\xB8 \xE1\xB3IP\xDE\xB5\xBE\x19\x0E\x97-M\xFDJT\x81\xE2\x8E>\xCD\x18\x9CJz\x1C\xB5}LsO\xF3\xAC\xAA\r\xAB\xF9\xD4\x83\x8DQ\x82\xE7F\xA4\x9F\x1C\x9A\xC5\xC3Y\x84k\x86\ef\xD7\x84\xE3\v\rlG\x15ya\xB0=\xDF\x11\x8D\x0FtZ/p\xBB\xB7g\xF5\xEBF8\xF5\x05}}\xDB\xFA\xA34dw\xE5\x80\xBC!=\x0E\x96\x18\bF\x10\a{\xFF\x9D2\xCA\xAAnu\x82\x82\xBA-F\x8C\x12\xBB\x04+nh\xE9N\xAF\fe\x16\x00Q\x9C\x1C\xCB\x94\x02\x8CQ\xFB,H[\x96\xF1Z4\nY]@\xE0\bs\x9Bh\x0E\xAA~\x105\x99\\\x8C\xA7q\x1A=\xA9\x9D\xBAbx\xF5`[\x8Aw\x80\b\xE0vy\x00\x00\x00\x00\x00\x00\x00c\x00\x00\x00\x01\x00\x00\x00\x06foobar\x00\x00\x00\b\x00\x00\x00\x04root\x00\x00\x00\x00Xk\\\x1C\x00\x00\x00\x00ZK>g\x00\x00\x00#\x00\x00\x00\rforce-command\x00\x00\x00\x0E\x00\x00\x00\n/bin/false\x00\x00\x00c\x00\x00\x00\x15permit-X11-forwarding\x00\x00\x00\x00\x00\x00\x00\x16permit-port-forwarding\x00\x00\x00\x00\x00\x00\x00\npermit-pty\x00\x00\x00\x00\x00\x00\x00\x0Epermit-user-rc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17\x00\x00\x00\assh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\x9DRU\x0E\x83\x8Eb}\x81vOn\xCA\xBA\x01%\xFE\x87\x80b\xB5\x98R%\xA9(\xC1\xAE\xEFq|\x82L\xADQ?\x1D\xC6o\xB8\xD8pI\e\xFC\xF8\xFE^\xAD*\xA4u;\x99S\fc\x11\xBE\xFD\x047B\x1C\xF2h\xBA\xB1\xB0\n\x12F\e\x16\xF7Z\x8D\xD3\xF2f\xC0\x1C\xD8\xBE\xCC\x82\x85Qka$\xB6\xBD\x1C)\x85B\xAAf\xC8\xF3V*\xC3\x1C\xAA\xDC\xC3I\xDDe\xEFu\x02M\x12\x1A\xE2};he\x9D\xB5\xA47\xE4\x12\x8F\xE0\xF1\xA5\x91/\xFB\xEA\t\x0F \x1E\xB4B@+6\x1F\xBD\xA7\xA9u\x80\x19\xAA\xAC\xFFK\\F\x8C\xD9u\f?\xB9#[M\xDF\xB0\xFC\xE8\xF6J\x98\xA4\x99\x8F\xF9]\x88\x1D|A%\xAB\e\x0EN\xAA\xD3 \xCF\xA7}c\xDE\xF5\xBA4\xC8\xD2\x81(\x13\xB3\x94@fC\xDC\xDF\xFD\xA1\e$?\x13\xA9m\xEB*\xCA'\xB3\x19\x19\xF0\xD2\xB3P\x00\x96ou\xE90\xC4-\x1F\xCF\x1Aw\x034\xC6\xDF\xA7\x8C\xCA^Ix\x15\xFA\x9A+\x00\x00\x01\x0F\x00\x00\x00\assh-rsa\x00\x00\x01\x00I\b%\x01\xB2\xCC\x87\xD7\e\xC5\x88\x93|\x9D\xEC}\xA4\x86\xD7\xBB\xB6\xD3\x93\xFD\\\xC73\xC2*\aV\xA2\x81\x05J\x91\x9AEKV\n\xB4\xEB\xF3\xBC\xBAr\x16\xE5\x9A\xB9\xDC(0\xB4\x1C\x9F\"\x9E\xF9\x91\xD0\x1F\x9Cp\r*\xE3\x8A\xD3\xB9W$[OI\xD2\x8F8\x9B\xA4\x9E\xFFuGg\x00\xA5\xCD\r\xDB\x95\xEE)_\xC3\xBCi\xA2\xCC\r\x86\xFD\xE9\xE6\x188\x92\xFD\xCC\n\x98t\x8C\x16\xF4O\xF6\xD5\xD4\xB7\\\xB95\x19\xA3\xBBW\xF3\xF7r<\xE6\x8C\xFC\xE5\x9F\xBF\xE0\xBF\x06\xE7v\xF2\x8Ek\xA4\x02\xB6fMd\xA5e\x87\xE1\x93\xF5\x81\xCF\xDF\x88\xDC\a\xA2\e\xD5\xCA\x14\xB2>\xF4\x8F|\xE5-w\xF5\x85\xD0\xF1F((\xD1\xEEE&\x1D\xA2+\xEC\x93\xE7\xC7\xAE\xE38\xE4\xAE\xF7 \xED\xC6\r\xD6\x1A\xE1#<\xA2)j\xB3TA\\\xFF;\xC5\xA6Tu\xAAap\xDE\xF4\xF7 p\xCA\xD2\xBA\xDC\xCDv\x17\xC2\xBCQ\xDF\xAB7^\xA1G\x18\xB9\xB2F\x81\x9Fq\x92\xD3".dup.force_encoding('BINARY') ED25519 = <<~EOF -----BEGIN OPENSSH PRIVATE KEY----- diff --git a/test/integration/test_cert_host_auth.rb b/test/integration/test_cert_host_auth.rb new file mode 100644 index 0000000..fee5b2b --- /dev/null +++ b/test/integration/test_cert_host_auth.rb @@ -0,0 +1,94 @@ +require_relative 'common' +require 'fileutils' +require 'tmpdir' +require 'net/ssh' + +require 'timeout' + +# see Vagrantfile,playbook for env. +# we're running as net_ssh_1 user password foo +# and usually connecting to net_ssh_2 user password foo2pwd +class TestCertHostAuth < NetSSHTest + include IntegrationTestHelpers + + def setup_ssh_env(&block) + tmpdir do |dir| + cert_type = "rsa" + # cert_type = "ssh-ed25519" + host_key_type = "ecdsa" + # host_key_type = "ed25519" + + # create a cert, and sign the host key + @cert = "#{dir}/ca" + sh "rm -rf #{@cert} #{@cert}.pub" + sh "ssh-keygen -t #{cert_type} -N '' -C 'ca@hosts.netssh' -f #{@cert} #{debug ? '' : '-q'}" + FileUtils.cp "/etc/ssh/ssh_host_#{host_key_type}_key.pub", "#{dir}/one.hosts.netssh.pub" + Dir.chdir(dir) do + sh "ssh-keygen -s #{@cert} -h -I one.hosts.netssh -n one.hosts.netssh #{debug ? '' : '-q'} #{dir}/one.hosts.netssh.pub" + sh "ssh-keygen -L -f one.hosts.netssh-cert.pub" if debug + end + signed_host_key = "/etc/ssh/ssh_host_#{host_key_type}_key-cert.pub" + sh "sudo cp -f #{dir}/one.hosts.netssh-cert.pub #{signed_host_key}" + + # we don't use this for signing the cert + @badcert = "#{dir}/badca" + sh "rm -rf #{@badcert} #{@badcert}.pub" + sh "ssh-keygen -t #{cert_type} -N '' -C 'ca@hosts.netssh' -f #{@badcert} #{debug ? '' : '-q'}" + yield(cert_pub: "#{@cert}.pub", badcert_pub: "#{@badcert}.pub", signed_host_key: signed_host_key) + end + end + + def debug + false + end + + def test_host_should_match_when_host_key_was_signed_by_key + Tempfile.open('cert_kh') do |f| + setup_ssh_env do |params| + data = File.read(params[:cert_pub]) + f.write("@cert-authority [*.hosts.netssh]:2200 #{data}") + f.close + + config_lines = ["HostCertificate #{params[:signed_host_key]}"] + start_sshd_7_or_later(config: config_lines) do |_pid, port| + Timeout.timeout(500) do + # sleep 0.2 + # sh "ssh -v -i ~/.ssh/id_ed25519 one.hosts.netssh -o UserKnownHostsFile=#{f.path} -p 2200" + ret = Net::SSH.start("one.hosts.netssh", "net_ssh_1", password: 'foopwd', port: port, verify_host_key: :always, user_known_hosts_file: [f.path]) do |ssh| + ssh.exec! "echo 'foo'" + end + assert_equal "foo\n", ret + rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH + sleep 0.25 + retry + end + end + end + end + end + + def test_with_other_pub_key_host_key_should_not_match + Tempfile.open('cert_kh') do |f| + setup_ssh_env do |params| + data = File.read(params[:badcert_pub]) + f.write("@cert-authority [*.hosts.netssh]:2200 #{data}") + f.close + + config_lines = ["HostCertificate #{params[:signed_host_key]}"] + start_sshd_7_or_later(config: config_lines) do |_pid, port| + Timeout.timeout(100) do + sleep 0.2 + assert_raises(Net::SSH::HostKeyMismatch) do + Net::SSH.start("one.hosts.netssh", "net_ssh_1", password: 'foopwd', port: port, verify_host_key: :always, user_known_hosts_file: [f.path]) do |ssh| + ssh.exec! "echo 'foo'" + end + end + rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH + sleep 0.25 + retry + end + end + end + end + end +end diff --git a/test/integration/test_cert_user_auth.rb b/test/integration/test_cert_user_auth.rb index b975d0a..596d150 100644 --- a/test/integration/test_cert_user_auth.rb +++ b/test/integration/test_cert_user_auth.rb @@ -14,7 +14,7 @@ unless ENV['NET_SSH_NO_ED25519'] Dir.mktmpdir do |dir| sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub" sh "ssh-keygen -q -f #{dir}/id_rsa_ed25519 -t ed25519 -N ''" - sign_user_key('net_ssh_1',"#{dir}/id_rsa_ed25519.pub") + sign_user_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") ret = Net::SSH.start("localhost", "net_ssh_1", keys: "#{dir}/id_rsa_ed25519") do |ssh| ssh.exec! 'echo "hello from:$USER"' @@ -27,7 +27,7 @@ unless ENV['NET_SSH_NO_ED25519'] Dir.mktmpdir do |dir| sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub" sh "ssh-keygen -q -f #{dir}/id_rsa_ed25519 -t ed25519 -N ''" - sign_user_key('net_ssh_1',"#{dir}/id_rsa_ed25519.pub") + sign_user_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") sh "mv #{dir}/id_rsa_ed25519-cert.pub #{dir}/cert" ret = Net::SSH.start("localhost", "net_ssh_1", keys: "#{dir}/id_rsa_ed25519", keycerts: "#{dir}/cert") do |ssh| @@ -42,7 +42,7 @@ unless ENV['NET_SSH_NO_ED25519'] with_agent do sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub" sh "ssh-keygen -q -f #{dir}/id_rsa_ed25519 -t ed25519 -N 'pwd'" - sign_user_key('net_ssh_1',"#{dir}/id_rsa_ed25519.pub") + sign_user_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") ssh_add("#{dir}/id_rsa_ed25519", "pwd") sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub #{dir}/id_rsa_ed25519-cert.pub" @@ -61,7 +61,7 @@ unless ENV['NET_SSH_NO_ED25519'] sh "ssh-keygen -q -f #{dir}/id_rsa_ed25519 -t ed25519 -N ''" # add key before signing cert ssh_add("#{dir}/id_rsa_ed25519", "pwd") - sign_user_key('net_ssh_1',"#{dir}/id_rsa_ed25519.pub") + sign_user_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub" ret = Net::SSH.start("localhost", "net_ssh_1", keycerts: "#{dir}/id_rsa_ed25519-cert.pub") do |ssh| diff --git a/test/integration/test_channel.rb b/test/integration/test_channel.rb index d07e0a1..cd23c9e 100644 --- a/test/integration/test_channel.rb +++ b/test/integration/test_channel.rb @@ -25,7 +25,7 @@ class TestChannel < NetSSHTest @key_id_rsa = "#{dir}/id_rsa" sh "rm -rf #{@key_id_rsa} #{@key_id_rsa}.pub" sh "ssh-keygen -q -f #{@key_id_rsa} -t rsa -N ''" - set_authorized_key(user,"#{@key_id_rsa}.pub") + set_authorized_key(user, "#{@key_id_rsa}.pub") yield end end @@ -34,6 +34,7 @@ class TestChannel < NetSSHTest ssh.open_channel do |channel| channel.exec(command) do |_ch, success| raise "could not execute command: #{command.inspect}" unless success + channel_success_handler.call channel.on_data do |ch2, data| yield(ch2, :stdout, data) @@ -56,7 +57,7 @@ class TestChannel < NetSSHTest system("killall /bin/nc") end channel = ssh_exec(ssh, "echo Begin ; sleep 100 ; echo End", channel_success_handler) do |ch, _type, data| - ch[:result] ||= "" + ch[:result] ||= String.new ch[:result] << data end assert_raises(IOError) { channel.wait } @@ -77,7 +78,7 @@ class TestChannel < NetSSHTest system("killall /bin/nc") end channel = ssh_exec(ssh, "echo Hello!", channel_success_handler) do |ch, _type, data| - ch[:result] ||= "" + ch[:result] ||= "".dup ch[:result] << data end channel.wait @@ -107,31 +108,29 @@ class TestChannel < NetSSHTest def test_channel_should_set_environment_variables_on_remote setup_ssh_env do - start_sshd_7_or_later(config: 'AcceptEnv *') do |_pid, port| - Timeout.timeout(4) do - begin - # We have our own sshd, give it a chance to come up before - # listening. - proxy = Net::SSH::Proxy::Command.new("/bin/nc localhost #{port}") - res = nil - Net::SSH.start(*ssh_start_params(port: port, proxy: proxy, set_env: { foo: 'bar', baz: 'whale will' })) do |ssh| - channel_success_handler = lambda do - sleep(0.1) - system("killall /bin/nc") - end - channel = ssh_exec(ssh, "echo $foo; echo $baz", channel_success_handler) do |ch, _type, data| - ch[:result] ||= "" - ch[:result] << data - end - channel.wait - res = channel[:result] - assert_equal(res, "bar\nwhale will\n") + start_sshd_7_or_later(config: ['AcceptEnv foo baz']) do |_pid, port| + Timeout.timeout(20) do + # We have our own sshd, give it a chance to come up before + # listening. + proxy = Net::SSH::Proxy::Command.new("/bin/nc localhost #{port}") + res = nil + Net::SSH.start(*ssh_start_params(port: port, proxy: proxy, set_env: { foo: 'bar', baz: 'whale will' })) do |ssh| + channel_success_handler = lambda do + sleep(0.1) + system("killall /bin/nc") + end + channel = ssh_exec(ssh, "echo A:$foo; echo B:$baz", channel_success_handler) do |ch, _type, data| + ch[:result] ||= String.new + ch[:result] << data end - assert_equal(res, "bar\nwhale will\n") - rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::SSH::Proxy::ConnectError - sleep 0.25 - retry + channel.wait + res = channel[:result] + assert_equal(res, "A:bar\nB:whale will\n") end + assert_equal(res, "A:bar\nB:whale will\n") + rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::SSH::Proxy::ConnectError + sleep 0.25 + retry end end end diff --git a/test/integration/test_curve25519sha256.rb b/test/integration/test_curve25519sha256.rb index e2e38c2..e6a152b 100644 --- a/test/integration/test_curve25519sha256.rb +++ b/test/integration/test_curve25519sha256.rb @@ -12,7 +12,6 @@ unless ENV['NET_SSH_NO_ED25519'] # and usually connecting to net_ssh_2 user password foo2pwd class TestCurve25519Sha256Keys < NetSSHTest include IntegrationTestHelpers - def test_with_only_curve_kex config_lines = File.read('/etc/ssh/sshd_config').split("\n") @@ -29,21 +28,19 @@ unless ENV['NET_SSH_NO_ED25519'] f.close start_sshd_7_or_later(config: config_lines) do |_pid, port| Timeout.timeout(4) do - begin - # We have our own sshd, give it a chance to come up before - # listening. - ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd', port: port, user_known_hosts_file: [f.path]) do |ssh| - assert_equal ssh.transport.algorithms.kex, "curve25519-sha256" - ssh.exec! "echo 'foo'" - end - assert_equal "foo\n", ret - rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH - sleep 0.25 - retry + # We have our own sshd, give it a chance to come up before + # listening. + ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd', port: port, user_known_hosts_file: [f.path]) do |ssh| + assert_equal ssh.transport.algorithms.kex, "curve25519-sha256" + ssh.exec! "echo 'foo'" end + assert_equal "foo\n", ret + rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH + sleep 0.25 + retry end end end end end -end
\ No newline at end of file +end diff --git a/test/integration/test_ed25519_pkeys.rb b/test/integration/test_ed25519_pkeys.rb index d42cbfd..0017801 100644 --- a/test/integration/test_ed25519_pkeys.rb +++ b/test/integration/test_ed25519_pkeys.rb @@ -10,31 +10,31 @@ unless ENV['NET_SSH_NO_ED25519'] # and usually connecting to net_ssh_2 user password foo2pwd class TestED25519PKeys < NetSSHTest include IntegrationTestHelpers - + def test_in_file_no_password Dir.mktmpdir do |dir| sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub" sh "ssh-keygen -q -f #{dir}/id_rsa_ed25519 -t ed25519 -N ''" - set_authorized_key('net_ssh_1',"#{dir}/id_rsa_ed25519.pub") + set_authorized_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") ret = Net::SSH.start("localhost", "net_ssh_1", { keys: "#{dir}/id_rsa_ed25519" }) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end - end - + end + def test_ssh_agent Dir.mktmpdir do |dir| with_agent do sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub" sh "ssh-keygen -q -f #{dir}/id_rsa_ed25519 -t ed25519 -N 'pwd'" - set_authorized_key('net_ssh_1',"#{dir}/id_rsa_ed25519.pub") - ssh_add("#{dir}/id_rsa_ed25519","pwd") - + set_authorized_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") + ssh_add("#{dir}/id_rsa_ed25519", "pwd") + # TODO: fix bug in net ssh which reads public key even if private key is there sh "mv #{dir}/id_rsa_ed25519.pub #{dir}/id_rsa_ed25519.pub.hidden" - + ret = Net::SSH.start("localhost", "net_ssh_1") do |ssh| ssh.exec! 'echo "hello from:$USER"' end @@ -42,23 +42,23 @@ unless ENV['NET_SSH_NO_ED25519'] end end end - + def test_in_file_with_password Dir.mktmpdir do |dir| sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub" sh "ssh-keygen -q -f #{dir}/id_rsa_ed25519 -t ed25519 -N 'pwd'" - set_authorized_key('net_ssh_1',"#{dir}/id_rsa_ed25519.pub") - + set_authorized_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") + # TODO: fix bug in net ssh which reads public key even if private key is there sh "mv #{dir}/id_rsa_ed25519.pub #{dir}/id_rsa_ed25519.pub.hidden" - ret = Net::SSH.start("localhost", "net_ssh_1", { keys: "#{dir}/id_rsa_ed25519", passphrase:'pwd' }) do |ssh| + ret = Net::SSH.start("localhost", "net_ssh_1", { keys: "#{dir}/id_rsa_ed25519", passphrase: 'pwd' }) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end - + def test_with_only_ed25519_host_key config_lines = File.read('/etc/ssh/sshd_config').split("\n") config_lines = config_lines.map do |line| @@ -68,7 +68,7 @@ unless ENV['NET_SSH_NO_ED25519'] line end end - + Tempfile.open('empty_kh') do |f| f.close with_sshd_config(config_lines.join("\n")) do diff --git a/test/integration/test_encoding.rb b/test/integration/test_encoding.rb index febdfba..9396e22 100644 --- a/test/integration/test_encoding.rb +++ b/test/integration/test_encoding.rb @@ -20,4 +20,4 @@ class TestEncoding < NetSSHTest end assert_equal ret, "#{string}\n" end -end
\ No newline at end of file +end diff --git a/test/integration/test_forward.rb b/test/integration/test_forward.rb index 3ff76cd..f006e2d 100644 --- a/test/integration/test_forward.rb +++ b/test/integration/test_forward.rb @@ -33,7 +33,7 @@ class ForwardTestBase < NetSSHTest end def ssh_start_params(options = {}) - [localhost,user, { keys: @key_id_rsa }.merge(options)] + [localhost, user, { keys: @key_id_rsa }.merge(options)] end def setup_ssh_env(&block) @@ -41,7 +41,7 @@ class ForwardTestBase < NetSSHTest @key_id_rsa = "#{dir}/id_rsa" sh "rm -rf #{@key_id_rsa} #{@key_id_rsa}.pub" sh "ssh-keygen -q -f #{@key_id_rsa} -t rsa -N ''" - set_authorized_key(user,"#{@key_id_rsa}.pub") + set_authorized_key(user, "#{@key_id_rsa}.pub") yield end end @@ -51,15 +51,13 @@ class ForwardTestBase < NetSSHTest Thread.start do loop do Thread.start(server.accept) do |client| - begin - 10000.times do |i| - client.puts "item#{i}" - end - client.close - rescue StandardError - exceptions << $! - raise + 10000.times do |i| + client.puts "item#{i}" end + client.close + rescue StandardError + exceptions << $! + raise end end end @@ -68,19 +66,17 @@ class ForwardTestBase < NetSSHTest end class TestForward < ForwardTestBase - def start_server_closing_soon(exceptions=nil) + def start_server_closing_soon(exceptions = nil) server = TCPServer.open(0) Thread.start do loop do Thread.start(server.accept) do |client| - begin - client.recv(1024) - client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii")) - client.close - rescue StandardError - exceptions << $! - raise - end + client.recv(1024) + client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii")) + client.close + rescue StandardError + exceptions << $! + raise end end end @@ -187,14 +183,12 @@ class TestForward < ForwardTestBase session.forward.local(local_port, localhost, remote_port) client_done = Queue.new Thread.start do - begin - client = TCPSocket.new(localhost, local_port) - client.recv(1024) - client.close - sleep(0.2) - ensure - client_done << true - end + client = TCPSocket.new(localhost, local_port) + client.recv(1024) + client.close + sleep(0.2) + ensure + client_done << true end session.loop(0.1) { client_done.empty? } assert_equal "Broken pipe", server_exc.pop.to_s unless server_exc.empty? @@ -211,15 +205,13 @@ class TestForward < ForwardTestBase session.forward.local(local_port, localhost, remote_port) client_done = Queue.new Thread.start do - begin - client = TCPSocket.new(localhost, local_port) - client.recv(1024) - client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii")) - client.close - sleep(0.1) - ensure - client_done << true - end + client = TCPSocket.new(localhost, local_port) + client.recv(1024) + client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii")) + client.close + sleep(0.1) + ensure + client_done << true end session.loop(0.1) { client_done.empty? } assert_equal "Broken pipe", server_exc.pop.to_s unless server_exc.empty? @@ -235,16 +227,14 @@ class TestForward < ForwardTestBase session.forward.local(local_port, localhost, remote_port) client_done = Queue.new Thread.start do - begin - client = TCPSocket.new(localhost, local_port) - 1.times do |i| - client.puts "item#{i}" - end - client.close - sleep(0.1) - ensure - client_done << true + client = TCPSocket.new(localhost, local_port) + 1.times do |i| + client.puts "item#{i}" end + client.close + sleep(0.1) + ensure + client_done << true end session.loop(0.1) { client_done.empty? } end @@ -268,31 +258,27 @@ class TestForward < ForwardTestBase session = Net::SSH.start(*ssh_start_params) server_done = Queue.new server = start_server do |client| - begin - data = client.read message.size - server_done << data - client.close - rescue StandardError - server_done << $! - end + data = client.read message.size + server_done << data + client.close + rescue StandardError + server_done << $! end client_done = Queue.new got_remote_port = Queue.new local_port = server.addr[1] - session.forward.remote(0, localhost, local_port, localhost) do |actual_remote_port| + session.forward.remote(local_port, localhost, 0, localhost) do |actual_remote_port| got_remote_port << actual_remote_port end session.loop(0.1) { got_remote_port.empty? } remote_port = got_remote_port.pop Thread.start do - begin - client = TCPSocket.new(localhost, remote_port) - client.write(message) - client.close - client_done << true - rescue StandardError - client_done << $! - end + client = TCPSocket.new(localhost, remote_port) + client.write(message) + client.close + client_done << true + rescue StandardError + client_done << $! end Timeout.timeout(5) do session.loop(0.1) { server_done.empty? } @@ -302,13 +288,13 @@ class TestForward < ForwardTestBase end class TCPProxy - def initialize() + def initialize @sockets = [] end attr_reader :sockets def open(host, port, connection_options = nil) - socket = TCPSocket.new(host,port) + socket = TCPSocket.new(host, port) @sockets << socket socket end @@ -333,25 +319,21 @@ class TestForward < ForwardTestBase # read on forwarded port client_done = Queue.new Thread.start do - begin - client = TCPSocket.new(localhost, local_port) - client.read(6) - proxy.close_all - client.read(7) - client.close - client_done << true - rescue StandardError - client_done << $! - end + client = TCPSocket.new(localhost, local_port) + client.read(6) + proxy.close_all + client.read(7) + client.close + client_done << true + rescue StandardError + client_done << $! end server_error = nil Timeout.timeout(5) do - begin - session.loop(0.1) { true } - rescue IOError, Errno::EBADF - server_error = $! - # puts "Error: #{$!} #{$!.backtrace.join("\n")}" - end + session.loop(0.1) { true } + rescue IOError, Errno::EBADF + server_error = $! + # puts "Error: #{$!} #{$!.backtrace.join("\n")}" end begin Timeout.timeout(5) do @@ -379,16 +361,14 @@ class TestForward < ForwardTestBase # read on forwarded port client_done = Queue.new Thread.start do - begin - client = TCPSocket.new(localhost, local_port) - client.read(6) - system("killall /bin/nc") - client.read(7) - client.close - client_done << true - rescue StandardError - client_done << $! - end + client = TCPSocket.new(localhost, local_port) + client.read(6) + system("killall /bin/nc") + client.read(7) + client.close + client_done << true + rescue StandardError + client_done << $! end Timeout.timeout(5) do begin @@ -411,26 +391,22 @@ class TestForward < ForwardTestBase session = Net::SSH.start(*ssh_start_params) server_done = Queue.new server = start_server do |client| - begin - data = client.read message.size - server_done << data - client.close - rescue StandardError - server_done << $! - end + data = client.read message.size + server_done << data + client.close + rescue StandardError + server_done << $! end client_done = Queue.new remote_port = server.addr[1] local_port = session.forward.local(0, localhost, remote_port) Thread.start do - begin - client = TCPSocket.new(localhost, local_port) - client.write(message) - client.close - client_done << true - rescue StandardError - client_done << $! - end + client = TCPSocket.new(localhost, local_port) + client.write(message) + client.close + client_done << true + rescue StandardError + client_done << $! end Timeout.timeout(5) do session.loop(0.1) { server_done.empty? } @@ -442,7 +418,7 @@ class TestForward < ForwardTestBase def test_server_eof_should_be_handled_remote setup_ssh_env do message = "This is a small message!" - session = Net::SSH.start(*ssh_start_params) + session = Net::SSH.start(*ssh_start_params(verbose: :debug)) server = start_server do |client| client.write message client.close @@ -450,20 +426,21 @@ class TestForward < ForwardTestBase client_done = Queue.new got_remote_port = Queue.new local_port = server.addr[1] - session.forward.remote(0, localhost, local_port, localhost) do |actual_remote_port| + puts "LOCAL PORT: #{local_port}" + session.forward.remote(local_port, localhost, 0, localhost) do |actual_remote_port| got_remote_port << actual_remote_port end session.loop(0.1) { got_remote_port.empty? } remote_port = got_remote_port.pop + puts "Remote port:#{remote_port}" Thread.start do - begin - client = TCPSocket.new(localhost, remote_port) - data = client.read(4096) - client.close - client_done << data - rescue StandardError - client_done << $! - end + client = TCPSocket.new(localhost, remote_port) + data = client.read(4096) + client.close + client_done << data + puts "Received: #{data}" + rescue StandardError + client_done << $! end Timeout.timeout(5) do session.loop(0.1) { client_done.empty? } @@ -484,14 +461,12 @@ class TestForward < ForwardTestBase remote_port = server.addr[1] local_port = session.forward.local(0, localhost, remote_port) Thread.start do - begin - client = TCPSocket.new(localhost, local_port) - data = client.read(4096) - client.close - client_done << data - rescue StandardError - client_done << $! - end + client = TCPSocket.new(localhost, local_port) + data = client.read(4096) + client.close + client_done << data + rescue StandardError + client_done << $! end Timeout.timeout(5) do session.loop(0.1) { client_done.empty? } @@ -502,14 +477,12 @@ class TestForward < ForwardTestBase def _run_reading_client(client_done, local_port) Thread.start do - begin - client = TCPSocket.new(localhost, local_port) - data = client.read(4096) - client.close - client_done << data - rescue StandardError - client_done << $! - end + client = TCPSocket.new(localhost, local_port) + data = client.read(4096) + client.close + client_done << data + rescue StandardError + client_done << $! end end @@ -621,14 +594,12 @@ class TestForwardOnUnixSockets < ForwardTestBase client_done = Queue.new Thread.start do - begin - client = UNIXSocket.new(local_socket.path) - client_data = client.recv(1024) - client.close - sleep(0.2) - ensure - client_done << true - end + client = UNIXSocket.new(local_socket.path) + client_data = client.recv(1024) + client.close + sleep(0.2) + ensure + client_done << true end begin @@ -646,16 +617,15 @@ class TestForwardOnUnixSockets < ForwardTestBase def test_forward_local_unix_socket_to_remote_socket setup_ssh_env do start_sshd_7_or_later do |_pid, port| - session = Timeout.timeout(4) do - begin - # We have our own sshd, give it a chance to come up before - # listening. + session = + # We have our own sshd, give it a chance to come up before + # listening. + Timeout.timeout(4) do Net::SSH.start(*ssh_start_params(port: port)) rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH sleep 0.25 retry end - end create_local_socket do |remote_socket| # Make sure sshd can 'rw'. diff --git a/test/integration/test_hmac_etm.rb b/test/integration/test_hmac_etm.rb index 3254289..1111a26 100644 --- a/test/integration/test_hmac_etm.rb +++ b/test/integration/test_hmac_etm.rb @@ -30,29 +30,27 @@ class TestHMacEtm < NetSSHTest config_lines.push("MACs #{macs}") end - variants.each do |key,variant| + variants.each do |key, variant| define_method "test_with_only_hmac_etm#{key}" do start_sshd_7_or_later(config: config_with_macs(variant)) do |_pid, port| Timeout.timeout(4) do - begin - # We have our own sshd, give it a chance to come up before - # listening. - ret = Net::SSH.start( - "localhost", - "net_ssh_1", - password: 'foopwd', - port: port, - hmac: [variant] - ) do |ssh| - assert_equal ssh.transport.algorithms.hmac_client, variant - assert_equal ssh.transport.algorithms.hmac_server, variant - ssh.exec! "echo 'foo123'" - end - assert_equal "foo123\n", ret - rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH - sleep 0.25 - retry + # We have our own sshd, give it a chance to come up before + # listening. + ret = Net::SSH.start( + "localhost", + "net_ssh_1", + password: 'foopwd', + port: port, + hmac: [variant] + ) do |ssh| + assert_equal ssh.transport.algorithms.hmac_client, variant + assert_equal ssh.transport.algorithms.hmac_server, variant + ssh.exec! "echo 'foo123'" end + assert_equal "foo123\n", ret + rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH + sleep 0.25 + retry end end end @@ -80,4 +78,4 @@ class TestHMacEtm < NetSSHTest ssh.close end end -end
\ No newline at end of file +end diff --git a/test/integration/test_http_proxy.rb b/test/integration/test_http_proxy.rb index bfde488..a59eb60 100644 --- a/test/integration/test_http_proxy.rb +++ b/test/integration/test_http_proxy.rb @@ -31,7 +31,7 @@ class TestHTTPProxy < NetSSHTest @key_id_rsa = "#{dir}/id_rsa" sh "rm -rf #{@key_id_rsa} #{@key_id_rsa}.pub" sh "ssh-keygen -q -f #{@key_id_rsa} -t rsa -N ''" - set_authorized_key(user,"#{@key_id_rsa}.pub") + set_authorized_key(user, "#{@key_id_rsa}.pub") yield end end @@ -80,4 +80,4 @@ class TestHTTPProxy < NetSSHTest end end end -end
\ No newline at end of file +end diff --git a/test/integration/test_id_rsa_keys.rb b/test/integration/test_id_rsa_keys.rb index 84d6728..44894cc 100644 --- a/test/integration/test_id_rsa_keys.rb +++ b/test/integration/test_id_rsa_keys.rb @@ -14,7 +14,7 @@ class TestIDRSAPKeys < NetSSHTest tmpdir do |dir| sh "rm -rf #{dir}/id_rsa #{dir}/id_rsa.pub" sh "ssh-keygen -q -f #{dir}/id_rsa -t rsa -N ''" - set_authorized_key('net_ssh_1',"#{dir}/id_rsa.pub") + set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") ret = Net::SSH.start("localhost", "net_ssh_1", { keys: "#{dir}/id_rsa" }) do |ssh| ssh.exec! 'echo "hello from:$USER"' @@ -29,8 +29,8 @@ class TestIDRSAPKeys < NetSSHTest with_agent do sh "rm -rf #{dir}/id_rsa #{dir}/id_rsa.pub" sh "ssh-keygen -q -f #{dir}/id_rsa -t rsa -N 'pwd123'" - set_authorized_key('net_ssh_1',"#{dir}/id_rsa.pub") - ssh_add("#{dir}/id_rsa","pwd123") + set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") + ssh_add("#{dir}/id_rsa", "pwd123") ret = Net::SSH.start("localhost", "net_ssh_1") do |ssh| ssh.exec! 'echo "hello from:$USER"' @@ -45,8 +45,8 @@ class TestIDRSAPKeys < NetSSHTest with_agent do sh "rm -rf #{dir}/id_rsa #{dir}/id_rsa.pub" sh "ssh-keygen -q -f #{dir}/id_rsa -t rsa -N 'pwd123'" - set_authorized_key('net_ssh_1',"#{dir}/id_rsa.pub") - ssh_add("#{dir}/id_rsa","pwd123") + set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") + ssh_add("#{dir}/id_rsa", "pwd123") ret = Net::SSH.start("localhost", "net_ssh_1", keys: ["#{dir}/id_rsa"]) do |ssh| ssh.exec! 'echo "hello from:$USER"' @@ -60,7 +60,7 @@ class TestIDRSAPKeys < NetSSHTest tmpdir do |dir| sh "rm -rf #{dir}/id_rsa #{dir}/id_rsa.pub" sh "ssh-keygen -q -f #{dir}/id_rsa -t rsa -N 'pwd12'" - set_authorized_key('net_ssh_1',"#{dir}/id_rsa.pub") + set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") ret = Net::SSH.start("localhost", "net_ssh_1", { keys: "#{dir}/id_rsa", passphrase: 'pwd12' }) do |ssh| ssh.exec! 'echo "hello from:$USER"' @@ -74,7 +74,7 @@ class TestIDRSAPKeys < NetSSHTest tmpdir do |dir| sh "rm -rf #{dir}/id_rsa #{dir}/id_rsa.pub" sh "ssh-keygen -q -f #{dir}/id_rsa -t rsa -N 'pwd12'" - set_authorized_key('net_ssh_1',"#{dir}/id_rsa.pub") + set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") private_key = File.read("#{dir}/id_rsa") options = { keys: [], key_data: [private_key] } diff --git a/test/integration/test_proxy.rb b/test/integration/test_proxy.rb index d73b7ba..6572c42 100644 --- a/test/integration/test_proxy.rb +++ b/test/integration/test_proxy.rb @@ -3,6 +3,7 @@ require 'net/ssh/buffer' require 'net/ssh' require 'timeout' require 'tempfile' +require 'fileutils' require 'net/ssh/proxy/command' require 'net/ssh/proxy/jump' @@ -18,7 +19,7 @@ class TestProxy < NetSSHTest end def ssh_start_params(options) - [localhost,user, { keys: @key_id_rsa }.merge(options)] + [localhost, user, { keys: @key_id_rsa }.merge(options)] end def setup_ssh_env(&block) @@ -26,7 +27,7 @@ class TestProxy < NetSSHTest @key_id_rsa = "#{dir}/id_rsa" sh "rm -rf #{@key_id_rsa} #{@key_id_rsa}.pub" sh "ssh-keygen -q -f #{@key_id_rsa} -t rsa -N ''" - set_authorized_key(user,"#{@key_id_rsa}.pub") + set_authorized_key(user, "#{@key_id_rsa}.pub") yield end end @@ -38,11 +39,12 @@ class TestProxy < NetSSHTest @gwkey_id_rsa = "#{dir}/id_rsa" sh "rm -rf #{@gwkey_id_rsa} #{@gwkey_id_rsa}.pub" sh "ssh-keygen -q -f #{@gwkey_id_rsa} -t rsa -N ''" - set_authorized_key(gwuser,"#{@gwkey_id_rsa}.pub") + set_authorized_key(gwuser, "#{@gwkey_id_rsa}.pub") config = "Host #{gwhost} IdentityFile #{@gwkey_id_rsa} StrictHostKeyChecking no " + FileUtils.mkdir_p File.expand_path("~/.ssh") my_config = File.expand_path("~/.ssh/config") File.open(my_config, 'w') { |file| file.write(config) } begin @@ -65,7 +67,7 @@ class TestProxy < NetSSHTest end end - def with_spurious_write_wakeup_emulate(rate=99,&block) + def with_spurious_write_wakeup_emulate(rate = 99, &block) orig_io_select = IO.method(:select) count = 0 @@ -73,7 +75,7 @@ class TestProxy < NetSSHTest count += 1 if (count % rate != 0) if params && params[1] && !params[1].empty? - return [[],params[1],[]] + return [[], params[1], []] end end orig_io_select.call(*params) @@ -97,7 +99,7 @@ class TestProxy < NetSSHTest ok = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh| with_spurious_write_wakeup_emulate do ret = ssh.exec! "echo \"$USER:#{large_msg}\"" - assert_equal "/bin/sh: Argument list too long\n", ret + assert_match(%r{/bin/.*sh: Argument list too long\n}, ret) hello_count = 1000 ret = ssh.exec! "ruby -e 'puts \"Hello\"*#{hello_count}'" assert_equal "Hello" * hello_count + "\n", ret @@ -137,6 +139,7 @@ class TestProxy < NetSSHTest @io end end + def test_does_close_proxy_on_proxy_failure setup_ssh_env do proxy = DbgProxy.new(Net::SSH::Proxy::Command.new('sleep 2 && ssh -W %h:%p -o "PreferredAuthentications none" user@localhost')) diff --git a/test/manual/test_pageant.rb b/test/manual/test_pageant.rb index 0f8b591..b07e82c 100644 --- a/test/manual/test_pageant.rb +++ b/test/manual/test_pageant.rb @@ -15,7 +15,6 @@ require_relative '../common' require 'net/ssh/authentication/agent' module Authentication - class TestPageapnt < NetSSHTest def test_agent_should_be_able_to_negotiate begin @@ -29,7 +28,7 @@ module Authentication private - def agent(auto=:connect) + def agent(auto = :connect) @agent ||= begin agent = Net::SSH::Authentication::Agent.new agent.connect! if auto == :connect @@ -37,5 +36,4 @@ module Authentication end end end - end diff --git a/test/start/test_transport.rb b/test/start/test_transport.rb index fcf1c4e..e94f32b 100644 --- a/test/start/test_transport.rb +++ b/test/start/test_transport.rb @@ -5,19 +5,19 @@ module NetSSH class TestStart < NetSSHTest attr_reader :transport_session attr_reader :authentication_session - + def setup @transport_session = mock('transport_session') @authentication_session = mock('authentication_session') Net::SSH::Transport::Session.expects(new: transport_session) Net::SSH::Authentication::Session.expects(new: authentication_session) end - + def test_close_transport_when_authentication_fails authentication_session.expects(authenticate: false) - + transport_session.expects(:close).at_least_once - + begin Net::SSH.start('localhost', 'testuser') {} rescue Net::SSH::AuthenticationFailed @@ -25,4 +25,4 @@ module NetSSH end end end -end
\ No newline at end of file +end diff --git a/test/start/test_user_nil.rb b/test/start/test_user_nil.rb index ff2cd59..aea4efa 100644 --- a/test/start/test_user_nil.rb +++ b/test/start/test_user_nil.rb @@ -18,7 +18,7 @@ module NetSSH end def test_start_should_use_default_user_when_nil - @authentication_session.stubs(:authenticate).with {|_next_service, user, _password| user == Etc.getpwuid.name }.returns(true) + @authentication_session.stubs(:authenticate).with { |_next_service, user, _password| user == Etc.getpwuid.name }.returns(true) assert_nothing_raised do Net::SSH.start('localhost', nil, config: false) end diff --git a/test/test/test_test.rb b/test/test/test_test.rb index a1dc4f4..d2b8894 100644 --- a/test/test/test_test.rb +++ b/test/test/test_test.rb @@ -73,4 +73,4 @@ class TestNetSSHTest < NetSSHTest connection.loop end end -end
\ No newline at end of file +end diff --git a/test/test_all.rb b/test/test_all.rb index c4df9b4..0d060d3 100644 --- a/test/test_all.rb +++ b/test/test_all.rb @@ -9,4 +9,4 @@ Dir.chdir(File.dirname(__FILE__)) do test_files = test_files.select { |f| f =~ Regexp.new(ENV['ONLY']) } if ENV['ONLY'] test_files = test_files.reject { |f| f =~ Regexp.new(ENV['EXCEPT']) } if ENV['EXCEPT'] test_files.each { |file| require(file) } -end
\ No newline at end of file +end diff --git a/test/test_buffer.rb b/test/test_buffer.rb index e1fcbd2..c4fc9f2 100644 --- a/test/test_buffer.rb +++ b/test/test_buffer.rb @@ -1,4 +1,5 @@ # encoding: ASCII-8BIT +# frozen_string_literal: false require_relative 'common' require 'net/ssh/buffer' @@ -55,7 +56,7 @@ class TestBuffer < NetSSHTest end def test_from_with_array_argument_should_write_multiple_of_the_given_type - buffer = Net::SSH::Buffer.from(:byte, [1,2,3,4,5]) + buffer = Net::SSH::Buffer.from(:byte, [1, 2, 3, 4, 5]) assert_equal "\1\2\3\4\5", buffer.to_s end diff --git a/test/test_buffered_io.rb b/test/test_buffered_io.rb index eefe7f9..1002686 100644 --- a/test/test_buffered_io.rb +++ b/test/test_buffered_io.rb @@ -60,4 +60,4 @@ class TestBufferedIo < NetSSHTest io end end -end
\ No newline at end of file +end diff --git a/test/test_config.rb b/test/test_config.rb index f71a952..82c2377 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -122,29 +122,29 @@ class TestConfig < NetSSHTest def test_translate_should_correctly_translate_from_openssh_to_net_ssh_names open_ssh = { - 'bindaddress' => "127.0.0.1", - 'ciphers' => "a,b,c", - 'compression' => true, - 'compressionlevel' => 6, - 'connecttimeout' => 100, - 'forwardagent' => true, + 'bindaddress' => "127.0.0.1", + 'ciphers' => "a,b,c", + 'compression' => true, + 'compressionlevel' => 6, + 'connecttimeout' => 100, + 'forwardagent' => true, 'hostbasedauthentication' => true, - 'hostkeyalgorithms' => "d,e,f", - 'identityfile' => %w(g h i), - 'macs' => "j,k,l", - 'certificatefile' => %w(m n o), - 'passwordauthentication' => true, - 'port' => 1234, - 'pubkeyauthentication' => true, - 'rekeylimit' => 1024, - 'sendenv' => "LC_*", - 'setenv' => 'foo="bar" baz=whale cat="black hole"', + 'hostkeyalgorithms' => "d,e,f", + 'identityfile' => %w(g h i), + 'macs' => "j,k,l", + 'certificatefile' => %w(m n o), + 'passwordauthentication' => true, + 'port' => 1234, + 'pubkeyauthentication' => true, + 'rekeylimit' => 1024, + 'sendenv' => "LC_*", + 'setenv' => 'foo="bar" baz=whale cat="black hole"', 'numberofpasswordprompts' => '123', - 'serveraliveinterval' => '2', - 'serveralivecountmax' => '4', - 'fingerprinthash' => 'MD5', - 'userknownhostsfile' => '/dev/null', - 'stricthostkeychecking' => false + 'serveraliveinterval' => '2', + 'serveralivecountmax' => '4', + 'fingerprinthash' => 'MD5', + 'userknownhostsfile' => '/dev/null', + 'stricthostkeychecking' => false } net_ssh = Net::SSH::Config.translate(open_ssh) @@ -170,16 +170,32 @@ class TestConfig < NetSSHTest assert_equal 'MD5', net_ssh[:fingerprint_hash] assert_equal true, net_ssh[:keepalive] assert_equal '/dev/null', net_ssh[:user_known_hosts_file] - assert_equal false, net_ssh[:strict_host_key_checking] + assert_equal :never, net_ssh[:verify_host_key] + end + + def test_translate_should_turn_on_host_key_verification + open_ssh = { 'stricthostkeychecking' => true } + + net_ssh = Net::SSH::Config.translate(open_ssh) + + assert_equal :always, net_ssh[:verify_host_key] + end + + def test_translate_should_accept_new_host_key + open_ssh = { 'stricthostkeychecking' => 'accept-new' } + + net_ssh = Net::SSH::Config.translate(open_ssh) + + assert_equal :accept_new, net_ssh[:verify_host_key] end def test_translate_should_turn_off_authentication_methods open_ssh = { - 'hostbasedauthentication' => false, - 'passwordauthentication' => false, - 'pubkeyauthentication' => false, + 'hostbasedauthentication' => false, + 'passwordauthentication' => false, + 'pubkeyauthentication' => false, 'challengeresponseauthentication' => false, - 'kbdinteractiveauthentication' => false + 'kbdinteractiveauthentication' => false } net_ssh = Net::SSH::Config.translate(open_ssh) @@ -189,11 +205,11 @@ class TestConfig < NetSSHTest def test_translate_should_turn_on_authentication_methods open_ssh = { - 'hostbasedauthentication' => true, - 'passwordauthentication' => true, - 'pubkeyauthentication' => true, + 'hostbasedauthentication' => true, + 'passwordauthentication' => true, + 'pubkeyauthentication' => true, 'challengeresponseauthentication' => true, - 'kbdinteractiveauthentication' => true + 'kbdinteractiveauthentication' => true } net_ssh = Net::SSH::Config.translate(open_ssh) @@ -202,9 +218,7 @@ class TestConfig < NetSSHTest end def test_translate_should_not_disable_keyboard_interactive_when_challange_or_keyboardinterective_is_on - open_ssh = { - 'kbdinteractiveauthentication' => false - } + open_ssh = { 'kbdinteractiveauthentication' => false } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal %w(keyboard-interactive none password publickey), net_ssh[:auth_methods].sort @@ -529,7 +543,7 @@ class TestConfig < NetSSHTest private - def with_home_env(value,&block) + def with_home_env(value, &block) env_home_before = ENV['HOME'] begin ENV['HOME'] = value diff --git a/test/test_key_factory.rb b/test/test_key_factory.rb index 2a92a08..c6f7705 100644 --- a/test/test_key_factory.rb +++ b/test/test_key_factory.rb @@ -65,7 +65,7 @@ class TestKeyFactory < NetSSHTest def test_load_encrypted_private_key_should_give_three_tries_for_the_password_and_then_raise_exception prompt = MockPrompt.new File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password")) - prompt.expects(:_ask).times(3).with("Enter passphrase for #{@key_file}:", has_entries(type: 'private_key', filename: @key_file), false).returns("passwod","passphrase","passwd") + prompt.expects(:_ask).times(3).with("Enter passphrase for #{@key_file}:", has_entries(type: 'private_key', filename: @key_file), false).returns("passwod", "passphrase", "passwd") if OpenSSL::PKey.respond_to?(:read) error_class = [ArgumentError, OpenSSL::PKey::PKeyError] else @@ -277,7 +277,7 @@ class TestKeyFactory < NetSSHTest end def public(key, args = nil) - result = "" + result = String.new if !args.nil? result << "#{args} " end diff --git a/test/test_known_hosts.rb b/test/test_known_hosts.rb index 6b1fda6..f960351 100644 --- a/test/test_known_hosts.rb +++ b/test/test_known_hosts.rb @@ -1,4 +1,4 @@ -require 'common' +require_relative './common' class TestKnownHosts < NetSSHTest def perform_test(source) @@ -28,101 +28,123 @@ class TestKnownHosts < NetSSHTest assert_equal(0, keys.count) assert_equal([], keys.to_a) - kh.add('github2.com',rsa_key) + kh.add('github2.com', rsa_key) keys2 = kh.keys_for("github2.com") assert_equal([rsa_key.to_blob], keys2.to_a.map(&:to_blob)) end end def test_search_for - options = { user_known_hosts_file: path("known_hosts/github") } - keys = Net::SSH::KnownHosts.search_for('github.com',options) + options = { user_known_hosts_file: path("known_hosts/github"), global_known_hosts_file: [] } + keys = Net::SSH::KnownHosts.search_for('github.com', options) assert_equal(["ssh-rsa"], keys.map(&:ssh_type)) end def test_search_for_with_hostname_and_right_ip_with_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: true } - keys = Net::SSH::KnownHosts.search_for('gitlab.com,35.231.145.151',options) + keys = Net::SSH::KnownHosts.search_for('gitlab.com,35.231.145.151', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_and_right_ip_without_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: false } - keys = Net::SSH::KnownHosts.search_for('gitlab.com,35.231.145.151',options) + keys = Net::SSH::KnownHosts.search_for('gitlab.com,35.231.145.151', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_and_wrong_ip_with_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: true } - keys = Net::SSH::KnownHosts.search_for('gitlab.com,192.0.2.1',options) + keys = Net::SSH::KnownHosts.search_for('gitlab.com,192.0.2.1', options) assert_equal(0, keys.count) end def test_search_for_with_hostname_and_wrong_ip_without_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: false } - keys = Net::SSH::KnownHosts.search_for('gitlab.com,192.0.2.2',options) + keys = Net::SSH::KnownHosts.search_for('gitlab.com,192.0.2.2', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_only_with_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: true } - keys = Net::SSH::KnownHosts.search_for('gitlab.com',options) + keys = Net::SSH::KnownHosts.search_for('gitlab.com', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_only_without_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: false } - keys = Net::SSH::KnownHosts.search_for('gitlab.com',options) + keys = Net::SSH::KnownHosts.search_for('gitlab.com', options) assert_equal(1, keys.count) end def test_search_for_with_ip_only_with_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: true } - keys = Net::SSH::KnownHosts.search_for('35.231.145.151',options) + keys = Net::SSH::KnownHosts.search_for('35.231.145.151', options) assert_equal(1, keys.count) end def test_search_for_with_ip_only_without_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: false } - keys = Net::SSH::KnownHosts.search_for('35.231.145.151',options) + keys = Net::SSH::KnownHosts.search_for('35.231.145.151', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_matching_pattern options = { user_known_hosts_file: path("known_hosts/misc") } - keys = Net::SSH::KnownHosts.search_for('subdomain.gitfoo.com',options) + keys = Net::SSH::KnownHosts.search_for('subdomain.gitfoo.com', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_not_matching_pattern_1 options = { user_known_hosts_file: path("known_hosts/misc") } - keys = Net::SSH::KnownHosts.search_for('gitfoo.com',options) + keys = Net::SSH::KnownHosts.search_for('gitfoo.com', options) assert_equal(0, keys.count) end def test_search_for_with_hostname_not_matching_pattern_2 options = { user_known_hosts_file: path("known_hosts/misc") } - keys = Net::SSH::KnownHosts.search_for('subdomain.gitmisc.com',options) + keys = Net::SSH::KnownHosts.search_for('subdomain.gitmisc.com', options) assert_equal(0, keys.count) end def test_search_for_with_hostname_not_matching_pattern_3 options = { user_known_hosts_file: path("known_hosts/misc") } - keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo.com',options) - assert_equal(0, keys.count) + keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo.com', options) + assert_equal(1, keys.count) + end + + def test_asterisk_matches_multiple_dots + with_config_file(lines: ["*.git???.com #{sample_key}"]) do |path| + options = { user_known_hosts_file: path } + keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo.com', options) + assert_equal(1, keys.count) + + keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo2.com', options) + assert_equal(0, keys.count) + end + end + + def test_asterisk_matches_everything + with_config_file(lines: ["* #{sample_key}"]) do |path| + options = { user_known_hosts_file: path } + keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo.com', options) + assert_equal(1, keys.count) + + keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo2.com', options) + assert_equal(1, keys.count) + end end def test_search_for_then_add Tempfile.open('github') do |f| f.write(File.read(path("known_hosts/github"))) options = { user_known_hosts_file: f.path } - keys = Net::SSH::KnownHosts.search_for('github2.com',options) + keys = Net::SSH::KnownHosts.search_for('github2.com', options) assert_equal(0, keys.count) keys.add_host_key(rsa_key) assert_equal([rsa_key.to_blob], keys.map(&:to_blob)) - keys = Net::SSH::KnownHosts.search_for('github2.com',options) + keys = Net::SSH::KnownHosts.search_for('github2.com', options) assert_equal([rsa_key.to_blob], keys.map(&:to_blob)) end end @@ -131,6 +153,18 @@ class TestKnownHosts < NetSSHTest File.join(File.dirname(__FILE__), relative_path) end + def sample_key + "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" + end + + def with_config_file(lines: [], &block) + Tempfile.open('known_hosts') do |f| + f.write(lines.join("\n")) + f.close + yield(f.path) + end + end + def rsa_key key = OpenSSL::PKey::RSA.new if key.respond_to?(:set_key) diff --git a/test/transport/hmac/test_md5.rb b/test/transport/hmac/test_md5.rb index e0abccc..3188b04 100644 --- a/test/transport/hmac/test_md5.rb +++ b/test/transport/hmac/test_md5.rb @@ -3,41 +3,39 @@ require 'common' require 'net/ssh/transport/hmac/md5' -module Transport +module Transport module HMAC - class TestMD5 < NetSSHTest def test_expected_digest_class assert_equal OpenSSL::Digest::MD5, subject.digest_class assert_equal OpenSSL::Digest::MD5, subject.new.digest_class end - + def test_expected_key_length assert_equal 16, subject.key_length assert_equal 16, subject.new.key_length end - + def test_expected_mac_length assert_equal 16, subject.mac_length assert_equal 16, subject.new.mac_length end - + def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "\275\345\006\307y~Oi\035<.\341\031\250<\257", hmac.digest("hello world") end - + def test_key_should_be_truncated_to_required_length hmac = subject.new("12345678901234567890") assert_equal "1234567890123456", hmac.key end - + private - + def subject Net::SSH::Transport::HMAC::MD5 end end - end -end
\ No newline at end of file +end diff --git a/test/transport/hmac/test_md5_96.rb b/test/transport/hmac/test_md5_96.rb index 4ee1680..2b238e5 100644 --- a/test/transport/hmac/test_md5_96.rb +++ b/test/transport/hmac/test_md5_96.rb @@ -4,26 +4,24 @@ require 'common' require 'transport/hmac/test_md5' require 'net/ssh/transport/hmac/md5_96' -module Transport +module Transport module HMAC - class TestMD5_96 < TestMD5 def test_expected_mac_length assert_equal 12, subject.mac_length assert_equal 12, subject.new.mac_length end - + def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "\275\345\006\307y~Oi\035<.\341", hmac.digest("hello world") end - + private - + def subject Net::SSH::Transport::HMAC::MD5_96 end end - end -end
\ No newline at end of file +end diff --git a/test/transport/hmac/test_none.rb b/test/transport/hmac/test_none.rb index ae8fe7e..812075e 100644 --- a/test/transport/hmac/test_none.rb +++ b/test/transport/hmac/test_none.rb @@ -1,36 +1,34 @@ require 'common' require 'net/ssh/transport/hmac/none' -module Transport +module Transport module HMAC - class TestNone < NetSSHTest def test_expected_digest_class assert_nil subject.digest_class assert_nil subject.new.digest_class end - + def test_expected_key_length assert_equal 0, subject.key_length assert_equal 0, subject.new.key_length end - + def test_expected_mac_length assert_equal 0, subject.mac_length assert_equal 0, subject.new.mac_length end - + def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "", hmac.digest("hello world") end - + private - + def subject Net::SSH::Transport::HMAC::None end end - end end diff --git a/test/transport/hmac/test_ripemd160.rb b/test/transport/hmac/test_ripemd160.rb index bcc14f8..e082485 100644 --- a/test/transport/hmac/test_ripemd160.rb +++ b/test/transport/hmac/test_ripemd160.rb @@ -3,36 +3,34 @@ require 'common' require 'net/ssh/transport/hmac/ripemd160' -module Transport +module Transport module HMAC - class TestRipemd160 < NetSSHTest def test_expected_digest_class assert_equal OpenSSL::Digest::RIPEMD160, subject.digest_class assert_equal OpenSSL::Digest::RIPEMD160, subject.new.digest_class end - + def test_expected_key_length assert_equal 20, subject.key_length assert_equal 20, subject.new.key_length end - + def test_expected_mac_length assert_equal 20, subject.mac_length assert_equal 20, subject.new.mac_length end - + def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "\xE4\x10\t\xB3\xD8,\x14\xA0k\x10\xB5\x0F?\x0E\x96q\x02\x16;E", hmac.digest("hello world") end - + private - + def subject Net::SSH::Transport::HMAC::RIPEMD160 end end - end end diff --git a/test/transport/hmac/test_sha1.rb b/test/transport/hmac/test_sha1.rb index be64bf6..4bf3b28 100644 --- a/test/transport/hmac/test_sha1.rb +++ b/test/transport/hmac/test_sha1.rb @@ -3,36 +3,34 @@ require 'common' require 'net/ssh/transport/hmac/sha1' -module Transport +module Transport module HMAC - class TestSHA1 < NetSSHTest def test_expected_digest_class assert_equal OpenSSL::Digest::SHA1, subject.digest_class assert_equal OpenSSL::Digest::SHA1, subject.new.digest_class end - + def test_expected_key_length assert_equal 20, subject.key_length assert_equal 20, subject.new.key_length end - + def test_expected_mac_length assert_equal 20, subject.mac_length assert_equal 20, subject.new.mac_length end - + def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "\000\004W\202\204+&\335\311\251P\266\250\214\276\206;\022U\365", hmac.digest("hello world") end - + private - + def subject Net::SSH::Transport::HMAC::SHA1 end end - end -end
\ No newline at end of file +end diff --git a/test/transport/hmac/test_sha1_96.rb b/test/transport/hmac/test_sha1_96.rb index d5dec22..cefc16f 100644 --- a/test/transport/hmac/test_sha1_96.rb +++ b/test/transport/hmac/test_sha1_96.rb @@ -4,26 +4,24 @@ require 'common' require 'transport/hmac/test_sha1' require 'net/ssh/transport/hmac/sha1_96' -module Transport +module Transport module HMAC - class TestSHA1_96 < TestSHA1 def test_expected_mac_length assert_equal 12, subject.mac_length assert_equal 12, subject.new.mac_length end - + def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "\000\004W\202\204+&\335\311\251P\266", hmac.digest("hello world") end - + private - + def subject Net::SSH::Transport::HMAC::SHA1_96 end end - end -end
\ No newline at end of file +end diff --git a/test/transport/hmac/test_sha2_256.rb b/test/transport/hmac/test_sha2_256.rb index 8a2e07d..834967b 100644 --- a/test/transport/hmac/test_sha2_256.rb +++ b/test/transport/hmac/test_sha2_256.rb @@ -5,7 +5,6 @@ require 'net/ssh/transport/hmac/sha2_256' module Transport module HMAC - class TestSHA2_256 < NetSSHTest def test_expected_digest_class assert_equal OpenSSL::Digest::SHA256, subject.digest_class diff --git a/test/transport/hmac/test_sha2_256_96.rb b/test/transport/hmac/test_sha2_256_96.rb index 07799fe..4083b1a 100644 --- a/test/transport/hmac/test_sha2_256_96.rb +++ b/test/transport/hmac/test_sha2_256_96.rb @@ -4,26 +4,24 @@ require 'common' require 'transport/hmac/test_sha2_256' require 'net/ssh/transport/hmac/sha2_256_96' -module Transport +module Transport module HMAC - class TestSHA2_256_96 < TestSHA2_256 def test_expected_mac_length assert_equal 12, subject.mac_length assert_equal 12, subject.new.mac_length end - + def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "\x16^>\x9FhO}\xB1>(\xBAF", hmac.digest("hello world") end - + private - + def subject Net::SSH::Transport::HMAC::SHA2_256_96 end end - end end diff --git a/test/transport/hmac/test_sha2_256_etm.rb b/test/transport/hmac/test_sha2_256_etm.rb index f66afda..5a50cef 100644 --- a/test/transport/hmac/test_sha2_256_etm.rb +++ b/test/transport/hmac/test_sha2_256_etm.rb @@ -5,7 +5,6 @@ require 'net/ssh/transport/hmac/sha2_256_etm' module Transport module HMAC - class TestSHA2_256_Etm < NetSSHTest def test_expected_digest_class assert_equal OpenSSL::Digest::SHA256, subject.digest_class diff --git a/test/transport/hmac/test_sha2_512.rb b/test/transport/hmac/test_sha2_512.rb index 886c683..a1b7048 100644 --- a/test/transport/hmac/test_sha2_512.rb +++ b/test/transport/hmac/test_sha2_512.rb @@ -5,7 +5,6 @@ require 'net/ssh/transport/hmac/sha2_512' module Transport module HMAC - class TestSHA2_512 < NetSSHTest def test_expected_digest_class assert_equal OpenSSL::Digest::SHA512, subject.digest_class diff --git a/test/transport/hmac/test_sha2_512_96.rb b/test/transport/hmac/test_sha2_512_96.rb index 955da06..a80bc2c 100644 --- a/test/transport/hmac/test_sha2_512_96.rb +++ b/test/transport/hmac/test_sha2_512_96.rb @@ -4,26 +4,24 @@ require 'common' require 'transport/hmac/test_sha2_512' require 'net/ssh/transport/hmac/sha2_512_96' -module Transport +module Transport module HMAC - class TestSHA2_512_96 < TestSHA2_512 def test_expected_mac_length assert_equal 12, subject.mac_length assert_equal 12, subject.new.mac_length end - + def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "^\xB6\"\xED\x8B\xC4\xDE\xD4\xCF\xD0\r\x18", hmac.digest("hello world") end - + private - + def subject Net::SSH::Transport::HMAC::SHA2_512_96 end end - end end diff --git a/test/transport/hmac/test_sha2_512_etm.rb b/test/transport/hmac/test_sha2_512_etm.rb index 364905e..0805b97 100644 --- a/test/transport/hmac/test_sha2_512_etm.rb +++ b/test/transport/hmac/test_sha2_512_etm.rb @@ -5,7 +5,6 @@ require 'net/ssh/transport/hmac/sha2_512_etm' module Transport module HMAC - class TestSHA2_512_Etm < NetSSHTest def test_expected_digest_class assert_equal OpenSSL::Digest::SHA512, subject.digest_class diff --git a/test/transport/kex/test_curve25519_sha256.rb b/test/transport/kex/test_curve25519_sha256.rb index 8177a38..42bbe0a 100644 --- a/test/transport/kex/test_curve25519_sha256.rb +++ b/test/transport/kex/test_curve25519_sha256.rb @@ -13,7 +13,7 @@ unless ENV['NET_SSH_NO_ED25519'] raise 'No X25519 set NET_SSH_NO_ED25519 to ignore this test' unless Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED @ecdh = @algorithms = @connection = @server_key = - @packet_data = @shared_secret = nil + @packet_data = @shared_secret = nil end def test_exchange_keys_should_return_expected_results_when_successful @@ -153,4 +153,4 @@ unless ENV['NET_SSH_NO_ED25519'] end end end -end
\ No newline at end of file +end diff --git a/test/transport/kex/test_diffie_hellman_group14_sha1.rb b/test/transport/kex/test_diffie_hellman_group14_sha1.rb index 5d006e1..9c06c0c 100644 --- a/test/transport/kex/test_diffie_hellman_group14_sha1.rb +++ b/test/transport/kex/test_diffie_hellman_group14_sha1.rb @@ -1,11 +1,10 @@ -require 'common' +require_relative '../../common' require 'net/ssh/transport/kex/diffie_hellman_group14_sha1' -require 'transport/kex/test_diffie_hellman_group1_sha1' +require_relative './test_diffie_hellman_group1_sha1' require 'ostruct' -module Transport +module Transport module Kex - class TestDiffieHellmanGroup14SHA1 < TestDiffieHellmanGroup1SHA1 def subject Net::SSH::Transport::Kex::DiffieHellmanGroup14SHA1 diff --git a/test/transport/kex/test_diffie_hellman_group14_sha256.rb b/test/transport/kex/test_diffie_hellman_group14_sha256.rb new file mode 100644 index 0000000..db15d5b --- /dev/null +++ b/test/transport/kex/test_diffie_hellman_group14_sha256.rb @@ -0,0 +1,16 @@ +require_relative '../../common' +require_relative './test_diffie_hellman_group14_sha1' + +module Transport + module Kex + class TestDiffieHellmanGroup14SHA256 < TestDiffieHellmanGroup14SHA1 + def subject + Net::SSH::Transport::Kex::DiffieHellmanGroup14SHA256 + end + + def digest_type + OpenSSL::Digest::SHA256 + end + end + end +end diff --git a/test/transport/kex/test_diffie_hellman_group1_sha1.rb b/test/transport/kex/test_diffie_hellman_group1_sha1.rb index be51720..a3b846c 100644 --- a/test/transport/kex/test_diffie_hellman_group1_sha1.rb +++ b/test/transport/kex/test_diffie_hellman_group1_sha1.rb @@ -4,13 +4,12 @@ require 'ostruct' module Transport module Kex - class TestDiffieHellmanGroup1SHA1 < NetSSHTest include Net::SSH::Transport::Constants def setup @dh_options = @dh = @algorithms = @connection = @server_key = - @packet_data = @shared_secret = nil + @packet_data = @shared_secret = nil end def digest_type @@ -86,7 +85,7 @@ module Transport private - def exchange!(options={}) + def exchange!(options = {}) connection.expect do |t, buffer| assert_equal KEXDH_INIT, buffer.type assert_equal dh.dh.pub_key, buffer.read_bignum @@ -100,7 +99,7 @@ module Transport dh.exchange_keys end - def dh_options(options={}) + def dh_options(options = {}) @dh_options = options end @@ -108,7 +107,7 @@ module Transport @dh ||= subject.new(algorithms, connection, packet_data.merge(need_bytes: 20).merge(@dh_options || {})) end - def algorithms(options={}) + def algorithms(options = {}) @algorithms ||= OpenStruct.new(host_key: options[:host_key] || "ssh-rsa", host_key_format: options[:host_key] || "ssh-rsa") end @@ -122,7 +121,7 @@ module Transport # 512 bits is the smallest possible key that will work with this, so # we use it for speed reasons - def server_key(bits=512) + def server_key(bits = 512) @server_key ||= OpenSSL::PKey::RSA.new(bits) end @@ -144,14 +143,14 @@ module Transport def session_id @session_id ||= begin buffer = Net::SSH::Buffer.from(:string, packet_data[:client_version_string], - :string, packet_data[:server_version_string], - :string, packet_data[:client_algorithm_packet], - :string, packet_data[:server_algorithm_packet], - :string, Net::SSH::Buffer.from(:key, server_key), - :bignum, dh.dh.pub_key, - :bignum, server_dh_pubkey, - :bignum, shared_secret) - OpenSSL::Digest::SHA1.digest(buffer.to_s) + :string, packet_data[:server_version_string], + :string, packet_data[:client_algorithm_packet], + :string, packet_data[:server_algorithm_packet], + :string, Net::SSH::Buffer.from(:key, server_key), + :bignum, dh.dh.pub_key, + :bignum, server_dh_pubkey, + :bignum, shared_secret) + digest_type.digest(buffer.to_s) end end @@ -159,7 +158,7 @@ module Transport @signature ||= server_key.ssh_do_sign(session_id) end - def bn(number, base=10) + def bn(number, base = 10) OpenSSL::BN.new(number.to_s, base) end @@ -167,6 +166,5 @@ module Transport Net::SSH::Buffer.from(*args) end end - end end diff --git a/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb b/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb index 800a8c6..d0df04b 100644 --- a/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +++ b/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb @@ -2,61 +2,61 @@ require 'common' require 'transport/kex/test_diffie_hellman_group1_sha1' require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1' -module Transport +module Transport module Kex - class TestDiffieHellmanGroupExchangeSHA1 < TestDiffieHellmanGroup1SHA1 KEXDH_GEX_GROUP = 31 KEXDH_GEX_INIT = 32 KEXDH_GEX_REPLY = 33 KEXDH_GEX_REQUEST = 34 - + def test_exchange_with_fewer_than_minimum_bits_uses_minimum_bits dh_options need_bytes: 20 assert_equal 1024, need_bits assert_nothing_raised { exchange! } end - + def test_exchange_with_optional_minimum_bits_declared dh_options minimum_dh_bits: 4096 assert_equal 4096, need_bits assert_nothing_raised { exchange! } end - + def test_exchange_with_fewer_than_maximum_bits_uses_need_bits dh_options need_bytes: 500 need_bits(8001) assert_nothing_raised { exchange! } end - + def test_exchange_with_more_than_maximum_bits_uses_maximum_bits dh_options need_bytes: 2000 need_bits(8192) assert_nothing_raised { exchange! } end - + def test_that_p_and_g_are_provided_by_the_server assert_nothing_raised { exchange! p: default_p + 2, g: 3 } assert_equal default_p + 2, dh.dh.p assert_equal 3, dh.dh.g end - + private - - def need_bits(bits=1024) + + def need_bits(bits = 1024) @need_bits ||= need_minimum(bits) end - - def need_minimum(bits=1024) + + def need_minimum(bits = 1024) return @dh_options[:minimum_dh_bits] if @dh_options && @dh_options[:minimum_dh_bits] + bits end - + def default_p 142326151570335518660743995281621698377057354949884468943021767573608899048361360422513557553514790045512299468953431585300812548859419857171094366358158903433167915517332113861059747425408670144201099811846875730766487278261498262568348338476437200556998366087779709990807518291581860338635288400119315130179 end - - def exchange!(options={}) + + def exchange!(options = {}) connection.expect do |t, buffer| assert_equal KEXDH_GEX_REQUEST, buffer.type assert_equal need_minimum, buffer.read_long @@ -73,37 +73,36 @@ module Transport end end end - + dh.exchange_keys end - + def subject Net::SSH::Transport::Kex::DiffieHellmanGroupExchangeSHA1 end - + def digest_type OpenSSL::Digest::SHA1 end - + def session_id @session_id ||= begin buffer = Net::SSH::Buffer.from(:string, packet_data[:client_version_string], - :string, packet_data[:server_version_string], - :string, packet_data[:client_algorithm_packet], - :string, packet_data[:server_algorithm_packet], - :string, Net::SSH::Buffer.from(:key, server_key), - :long, 1024, - :long, need_bits, # need bits, figure this part out, - :long, 8192, - :bignum, dh.dh.p, - :bignum, dh.dh.g, - :bignum, dh.dh.pub_key, - :bignum, server_dh_pubkey, - :bignum, shared_secret) + :string, packet_data[:server_version_string], + :string, packet_data[:client_algorithm_packet], + :string, packet_data[:server_algorithm_packet], + :string, Net::SSH::Buffer.from(:key, server_key), + :long, 1024, + :long, need_bits, # need bits, figure this part out, + :long, 8192, + :bignum, dh.dh.p, + :bignum, dh.dh.g, + :bignum, dh.dh.pub_key, + :bignum, server_dh_pubkey, + :bignum, shared_secret) digest_type.digest(buffer.to_s) end end end - end end diff --git a/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb b/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb index 6355c18..d3d6d76 100644 --- a/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +++ b/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb @@ -2,20 +2,18 @@ require 'common' require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1' require 'transport/kex/test_diffie_hellman_group_exchange_sha1' -module Transport +module Transport module Kex - class TestDiffieHellmanGroupExchangeSHA256 < TestDiffieHellmanGroupExchangeSHA1 private - + def subject Net::SSH::Transport::Kex::DiffieHellmanGroupExchangeSHA256 end - + def digest_type OpenSSL::Digest::SHA256 end end - end end diff --git a/test/transport/kex/test_ecdh_sha2_nistp256.rb b/test/transport/kex/test_ecdh_sha2_nistp256.rb index 932d8d7..9f26428 100644 --- a/test/transport/kex/test_ecdh_sha2_nistp256.rb +++ b/test/transport/kex/test_ecdh_sha2_nistp256.rb @@ -1,7 +1,7 @@ require 'openssl' require 'ostruct' require_relative '../../common' -require 'transport/kex/test_diffie_hellman_group1_sha1' +require_relative './test_diffie_hellman_group1_sha1' require 'net/ssh/transport/kex/ecdh_sha2_nistp256' module Transport @@ -11,7 +11,7 @@ module Transport def setup @ecdh = @algorithms = @connection = @server_key = - @packet_data = @shared_secret = nil + @packet_data = @shared_secret = nil end def test_exchange_keys_should_return_expected_results_when_successful diff --git a/test/transport/kex/test_ecdh_sha2_nistp384.rb b/test/transport/kex/test_ecdh_sha2_nistp384.rb index 395f42f..fc722df 100644 --- a/test/transport/kex/test_ecdh_sha2_nistp384.rb +++ b/test/transport/kex/test_ecdh_sha2_nistp384.rb @@ -6,7 +6,7 @@ module Transport class TestEcdhSHA2NistP384 < TestEcdhSHA2NistP256 def setup @ecdh = @algorithms = @connection = @server_key = - @packet_data = @shared_secret = nil + @packet_data = @shared_secret = nil end def test_exchange_keys_should_return_expected_results_when_successful diff --git a/test/transport/kex/test_ecdh_sha2_nistp521.rb b/test/transport/kex/test_ecdh_sha2_nistp521.rb index 42ef32f..fc7543e 100644 --- a/test/transport/kex/test_ecdh_sha2_nistp521.rb +++ b/test/transport/kex/test_ecdh_sha2_nistp521.rb @@ -6,7 +6,7 @@ module Transport class TestEcdhSHA2NistP521 < TestEcdhSHA2NistP256 def setup @ecdh = @algorithms = @connection = @server_key = - @packet_data = @shared_secret = nil + @packet_data = @shared_secret = nil end def test_exchange_keys_should_return_expected_results_when_successful diff --git a/test/transport/test_algorithms.rb b/test/transport/test_algorithms.rb index 105f3af..e52782c 100644 --- a/test/transport/test_algorithms.rb +++ b/test/transport/test_algorithms.rb @@ -3,7 +3,6 @@ require 'logger' require 'net/ssh/transport/algorithms' module Transport - class TestAlgorithms < NetSSHTest include Net::SSH::Transport::Constants @@ -18,8 +17,8 @@ module Transport end def test_constructor_should_build_default_list_of_preferred_algorithms - assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa], algorithms[:host_key] - assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha1], algorithms[:kex] + assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512], algorithms[:host_key] + assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1], algorithms[:kex] assert_equal %w[aes256-ctr aes192-ctr aes128-ctr], algorithms[:encryption] assert_equal %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512 hmac-sha2-256 hmac-sha1], algorithms[:hmac] assert_equal %w[none zlib@openssh.com zlib], algorithms[:compression] @@ -27,8 +26,8 @@ module Transport end def test_constructor_should_build_complete_list_of_algorithms_with_append_all_supported_algorithms - assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa ssh-dss], algorithms(append_all_supported_algorithms: true)[:host_key] - assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1], algorithms(append_all_supported_algorithms: true)[:kex] + assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss], algorithms(append_all_supported_algorithms: true)[:host_key] + assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1], algorithms(append_all_supported_algorithms: true)[:kex] assert_equal %w[aes256-ctr aes192-ctr aes128-ctr aes256-cbc aes192-cbc aes128-cbc rijndael-cbc@lysator.liu.se blowfish-ctr blowfish-cbc cast128-ctr cast128-cbc 3des-ctr 3des-cbc idea-cbc none], algorithms(append_all_supported_algorithms: true)[:encryption] assert_equal %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512 hmac-sha2-256 hmac-sha1 hmac-sha2-512-96 hmac-sha2-256-96 hmac-sha1-96 hmac-ripemd160 hmac-ripemd160@openssh.com hmac-md5 hmac-md5-96 none], algorithms(append_all_supported_algorithms: true)[:hmac] assert_equal %w[none zlib@openssh.com zlib], algorithms(append_all_supported_algorithms: true)[:compression] @@ -43,12 +42,15 @@ module Transport end def test_constructor_with_preferred_host_key_type_should_put_preferred_host_key_type_first - assert_equal %w[ssh-dss] + ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa], algorithms(host_key: "ssh-dss", append_all_supported_algorithms: true)[:host_key] + assert_equal %w[ssh-dss] + ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512], algorithms(host_key: "ssh-dss", append_all_supported_algorithms: true)[:host_key] end def test_constructor_with_known_hosts_reporting_known_host_key_should_use_that_host_key_type - Net::SSH::KnownHosts.expects(:search_for).with("net.ssh.test,127.0.0.1", {}).returns([stub("key", ssh_type: "ssh-dss")]) - assert_equal %w[ssh-dss] + ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa], algorithms[:host_key] + Net::SSH::KnownHosts.expects(:search_for).with( + "net.ssh.test,127.0.0.1", + { user_known_hosts_file: "/dev/null", global_known_hosts_file: "/dev/null" } + ).returns([stub("key", ssh_type: "ssh-dss")]) + assert_equal %w[ssh-dss] + ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512], algorithms[:host_key] end def ed_host_keys @@ -73,7 +75,7 @@ module Transport end def test_constructor_with_unrecognized_host_key_type_should_return_whats_supported - assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa ssh-dss], + assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss], algorithms(host_key: "bogus ssh-rsa", append_all_supported_algorithms: true)[:host_key] end @@ -90,22 +92,22 @@ module Transport end def test_constructor_with_preferred_kex_should_put_preferred_kex_first - assert_equal %w[diffie-hellman-group1-sha1] + x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1], + assert_equal %w[diffie-hellman-group1-sha1] + x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1], algorithms(kex: "diffie-hellman-group1-sha1", append_all_supported_algorithms: true)[:kex] end def test_constructor_with_unrecognized_kex_should_not_raise_exception - assert_equal %w[diffie-hellman-group1-sha1] + x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1], + assert_equal %w[diffie-hellman-group1-sha1] + x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1], algorithms(kex: %w[bogus diffie-hellman-group1-sha1], append_all_supported_algorithms: true)[:kex] end def test_constructor_with_preferred_kex_supports_additions - assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1], + assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1], algorithms(kex: %w[+diffie-hellman-group1-sha1])[:kex] end def test_constructor_with_preferred_kex_supports_removals_with_wildcard - assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256], + assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256], algorithms(kex: %w[-diffie-hellman-group*-sha1 -diffie-hellman-group-exchange-sha1])[:kex] end @@ -144,7 +146,7 @@ module Transport def test_constructor_with_unrecognized_hmac_should_ignore_those assert_equal %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512 hmac-sha2-256 hmac-sha1 hmac-sha2-512-96 hmac-sha2-256-96 hmac-sha1-96 hmac-ripemd160 hmac-ripemd160@openssh.com hmac-md5 hmac-md5-96 none], - algorithms(hmac: "unknown hmac-md5-96", append_all_supported_algorithms: true)[:hmac] + algorithms(hmac: "unknown hmac-md5-96", append_all_supported_algorithms: true)[:hmac] end def test_constructor_with_preferred_hmac_supports_additions @@ -182,7 +184,7 @@ module Transport end def test_constructor_with_host_key_removals_with_wildcard - assert_equal ed_host_keys + %w[ecdsa-sha2-nistp521-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp521 ecdsa-sha2-nistp384 ecdsa-sha2-nistp256], algorithms(host_key: %w[-ssh-rsa* -ssh-dss])[:host_key] + assert_equal ed_host_keys + %w[ecdsa-sha2-nistp521-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp521 ecdsa-sha2-nistp384 ecdsa-sha2-nistp256], algorithms(host_key: %w[-ssh-rsa* -ssh-dss -rsa-sha*])[:host_key] end def test_initial_state_should_be_neither_pending_nor_initialized @@ -191,7 +193,7 @@ module Transport end def test_key_exchange_when_initiated_by_server - transport.expect do |t, buffer| + transport.expect do |_t, buffer| assert_kexinit(buffer) install_mock_key_exchange(buffer) end @@ -204,7 +206,7 @@ module Transport def test_key_exchange_when_initiated_by_client state = nil - transport.expect do |t, buffer| + transport.expect do |_t, buffer| assert_kexinit(buffer) state = :sent_kexinit install_mock_key_exchange(buffer) @@ -222,7 +224,7 @@ module Transport def test_key_exchange_when_server_does_not_support_preferred_kex_should_fallback_to_secondary kexinit kex: "diffie-hellman-group14-sha1" - transport.expect do |t,buffer| + transport.expect do |_t, buffer| assert_kexinit(buffer) install_mock_key_exchange(buffer, kex: Net::SSH::Transport::Kex::DiffieHellmanGroup1SHA1) end @@ -231,7 +233,7 @@ module Transport def test_key_exchange_when_server_does_not_support_any_preferred_kex_should_raise_error kexinit kex: "something-obscure" - transport.expect { |t,buffer| assert_kexinit(buffer) } + transport.expect { |_t, buffer| assert_kexinit(buffer) } assert_raises(Net::SSH::Exception) { algorithms.accept_kexinit(kexinit) } end @@ -259,7 +261,7 @@ module Transport def test_exchange_with_zlib_compression_enabled_sets_compression_to_standard algorithms compression: 'zlib' - transport.expect do |t, buffer| + transport.expect do |_t, buffer| assert_kexinit(buffer, compression_client: 'zlib', compression_server: 'zlib') install_mock_key_exchange(buffer) end @@ -274,7 +276,7 @@ module Transport def test_exchange_with_zlib_at_openssh_dot_com_compression_enabled_sets_compression_to_delayed algorithms compression: 'zlib@openssh.com' - transport.expect do |t, buffer| + transport.expect do |_t, buffer| assert_kexinit(buffer, compression_client: 'zlib@openssh.com', compression_server: 'zlib@openssh.com') install_mock_key_exchange(buffer) end @@ -289,7 +291,7 @@ module Transport # Verification for https://github.com/net-ssh/net-ssh/issues/483 def test_that_algorithm_undefined_doesnt_throw_exception # Create a logger explicitly with DEBUG logging - string_io = StringIO.new("") + string_io = StringIO.new(String.new) debug_logger = Logger.new(string_io) debug_logger.level = Logger::DEBUG @@ -333,27 +335,28 @@ module Transport private - def install_mock_key_exchange(buffer, options={}) + def install_mock_key_exchange(buffer, options = {}) kex = options[:kex] || Net::SSH::Transport::Kex::DiffieHellmanGroupExchangeSHA256 - Net::SSH::Transport::Kex::MAP.each do |name, klass| + Net::SSH::Transport::Kex::MAP.each do |_name, klass| next if klass == kex + klass.expects(:new).never end kex.expects(:new) .with(algorithms, transport, - client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION, - server_version_string: transport.server_version.version, - server_algorithm_packet: kexinit.to_s, - client_algorithm_packet: buffer.to_s, - need_bytes: 32, - minimum_dh_bits: nil, - logger: nil) + client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION, + server_version_string: transport.server_version.version, + server_algorithm_packet: kexinit.to_s, + client_algorithm_packet: buffer.to_s, + need_bytes: 32, + minimum_dh_bits: nil, + logger: nil) .returns(stub("kex", exchange_keys: { shared_secret: shared_secret, session_id: session_id, hashing_algorithm: hashing_algorithm })) end - def install_mock_algorithm_lookups(options={}) + def install_mock_algorithm_lookups(options = {}) params = { shared: shared_secret.to_ssh, hash: session_id, digester: hashing_algorithm } Net::SSH::Transport::CipherFactory.expects(:get) .with(options[:client_cipher] || "aes256-ctr", params.merge(iv: key("A"), key: key("C"), encrypt: true)) @@ -383,31 +386,31 @@ module Transport hashing_algorithm.digest(shared_secret.to_ssh + session_id + salt + session_id) end - def cipher(type, options={}) + def cipher(type, options = {}) Net::SSH::Transport::CipherFactory.get(type, options) end - def kexinit(options={}) + def kexinit(options = {}) @kexinit ||= P(:byte, KEXINIT, - :long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF), - :string, options[:kex] || "diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1", - :string, options[:host_key] || "ssh-rsa,ssh-dss", - :string, options[:encryption_client] || "aes256-ctr,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc", - :string, options[:encryption_server] || "aes256-ctr,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc", - :string, options[:hmac_client] || "hmac-sha2-256,hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96", - :string, options[:hmac_server] || "hmac-sha2-256,hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96", - :string, options[:compression_client] || "none,zlib@openssh.com,zlib", - :string, options[:compression_server] || "none,zlib@openssh.com,zlib", - :string, options[:language_client] || "", - :string, options[:language_server] || "", - :bool, options[:first_kex_follows]) - end - - def assert_kexinit(buffer, options={}) + :long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF), :long, rand(0xFFFFFFFF), + :string, options[:kex] || "diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1", + :string, options[:host_key] || "ssh-rsa,ssh-dss", + :string, options[:encryption_client] || "aes256-ctr,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc", + :string, options[:encryption_server] || "aes256-ctr,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc", + :string, options[:hmac_client] || "hmac-sha2-256,hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96", + :string, options[:hmac_server] || "hmac-sha2-256,hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96", + :string, options[:compression_client] || "none,zlib@openssh.com,zlib", + :string, options[:compression_server] || "none,zlib@openssh.com,zlib", + :string, options[:language_client] || "", + :string, options[:language_server] || "", + :bool, options[:first_kex_follows]) + end + + def assert_kexinit(buffer, options = {}) assert_equal KEXINIT, buffer.type assert_equal 16, buffer.read(16).length - assert_equal options[:kex] || (x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha1]).join(','), buffer.read_string - assert_equal options[:host_key] || (ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa]).join(','), buffer.read_string + assert_equal options[:kex] || (x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1]).join(','), buffer.read_string + assert_equal options[:host_key] || (ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512]).join(','), buffer.read_string assert_equal options[:encryption_client] || 'aes256-ctr,aes192-ctr,aes128-ctr', buffer.read_string assert_equal options[:encryption_server] || 'aes256-ctr,aes192-ctr,aes128-ctr', buffer.read_string assert_equal options[:hmac_client] || 'hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-sha1', buffer.read_string @@ -430,16 +433,20 @@ module Transport assert_equal :server_hmac, transport.server_options[:hmac] end - def algorithms(algorithms_options={}, transport_options={}) + def algorithms(algorithms_options = {}, transport_options = {}) @algorithms ||= Net::SSH::Transport::Algorithms.new( transport(transport_options), algorithms_options ) end - def transport(transport_options={}) - @transport ||= MockTransport.new(transport_options) + def transport(transport_options = {}) + @transport ||= MockTransport.new( + { + user_known_hosts_file: '/dev/null', + global_known_hosts_file: '/dev/null' + }.merge(transport_options) + ) end end - end diff --git a/test/transport/test_cipher_factory.rb b/test/transport/test_cipher_factory.rb index 49ff2cd..0d651aa 100644 --- a/test/transport/test_cipher_factory.rb +++ b/test/transport/test_cipher_factory.rb @@ -4,73 +4,72 @@ require 'common' require 'net/ssh/transport/cipher_factory' module Transport - class TestCipherFactory < NetSSHTest def self.if_supported?(name) yield if Net::SSH::Transport::CipherFactory.supported?(name) end def test_lengths_for_none - assert_equal [0,0], factory.get_lengths("none") - assert_equal [0,0], factory.get_lengths("bogus") + assert_equal [0, 0], factory.get_lengths("none") + assert_equal [0, 0], factory.get_lengths("bogus") end def test_lengths_for_blowfish_cbc - assert_equal [16,8], factory.get_lengths("blowfish-cbc") + assert_equal [16, 8], factory.get_lengths("blowfish-cbc") end if_supported?("idea-cbc") do def test_lengths_for_idea_cbc - assert_equal [16,8], factory.get_lengths("idea-cbc") + assert_equal [16, 8], factory.get_lengths("idea-cbc") end end def test_lengths_for_rijndael_cbc - assert_equal [32,16], factory.get_lengths("rijndael-cbc@lysator.liu.se") + assert_equal [32, 16], factory.get_lengths("rijndael-cbc@lysator.liu.se") end def test_lengths_for_cast128_cbc - assert_equal [16,8], factory.get_lengths("cast128-cbc") + assert_equal [16, 8], factory.get_lengths("cast128-cbc") end def test_lengths_for_3des_cbc - assert_equal [24,8], factory.get_lengths("3des-cbc") + assert_equal [24, 8], factory.get_lengths("3des-cbc") end def test_lengths_for_aes128_cbc - assert_equal [16,16], factory.get_lengths("aes128-cbc") + assert_equal [16, 16], factory.get_lengths("aes128-cbc") end def test_lengths_for_aes192_cbc - assert_equal [24,16], factory.get_lengths("aes192-cbc") + assert_equal [24, 16], factory.get_lengths("aes192-cbc") end def test_lengths_for_aes256_cbc - assert_equal [32,16], factory.get_lengths("aes256-cbc") + assert_equal [32, 16], factory.get_lengths("aes256-cbc") end def test_lengths_for_3des_ctr - assert_equal [24,8], factory.get_lengths("3des-ctr") + assert_equal [24, 8], factory.get_lengths("3des-ctr") end def test_lengths_for_aes128_ctr - assert_equal [16,16], factory.get_lengths("aes128-ctr") + assert_equal [16, 16], factory.get_lengths("aes128-ctr") end def test_lengths_for_aes192_ctr - assert_equal [24,16], factory.get_lengths("aes192-ctr") + assert_equal [24, 16], factory.get_lengths("aes192-ctr") end def test_lengths_for_aes256_ctr - assert_equal [32,16], factory.get_lengths("aes256-ctr") + assert_equal [32, 16], factory.get_lengths("aes256-ctr") end def test_lengths_for_blowfish_ctr - assert_equal [16,8], factory.get_lengths("blowfish-ctr") + assert_equal [16, 8], factory.get_lengths("blowfish-ctr") end def test_lengths_for_cast128_ctr - assert_equal [16,8], factory.get_lengths("cast128-ctr") + assert_equal [16, 8], factory.get_lengths("cast128-ctr") end BLOWFISH_CBC = "\210\021\200\315\240_\026$\352\204g\233\244\242x\332e\370\001\327\224Nv@9_\323\037\252kb\037\036\237\375]\343/y\037\237\312Q\f7]\347Y\005\275%\377\0010$G\272\250B\265Nd\375\342\372\025r6}+Y\213y\n\237\267\\\374^\346BdJ$\353\220Ik\023<\236&H\277=\225" @@ -232,7 +231,7 @@ module Transport end def test_aes256_ctr_for_encryption2 - assert_equal [AES256_CTR,AES256_CTR2], encrypt2("aes256-ctr") + assert_equal [AES256_CTR, AES256_CTR2], encrypt2("aes256-ctr") end def test_aes256_ctr_for_decryption @@ -308,5 +307,4 @@ module Transport [first, second] end end - end diff --git a/test/transport/test_hmac.rb b/test/transport/test_hmac.rb index 4e2238b..4317b1a 100644 --- a/test/transport/test_hmac.rb +++ b/test/transport/test_hmac.rb @@ -2,12 +2,11 @@ require 'common' require 'net/ssh/transport/hmac' module Transport - class TestHMAC < NetSSHTest - Net::SSH::Transport::HMAC::MAP.each do |name, value| + Net::SSH::Transport::HMAC::MAP.each do |name, _value| method = name.tr("-", "_") define_method("test_get_with_#{method}_returns_new_hmac_instance") do - key = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!&$%"[0,Net::SSH::Transport::HMAC::MAP[name].key_length] + key = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!&$%"[0, Net::SSH::Transport::HMAC::MAP[name].key_length] hmac = Net::SSH::Transport::HMAC.get(name, key, { shared: "123", hash: "^&*", digester: OpenSSL::Digest::SHA1 }) assert_instance_of Net::SSH::Transport::HMAC::MAP[name], hmac assert_equal key, hmac.key @@ -30,5 +29,4 @@ module Transport end end end - end diff --git a/test/transport/test_identity_cipher.rb b/test/transport/test_identity_cipher.rb index a8f87d6..3f332db 100644 --- a/test/transport/test_identity_cipher.rb +++ b/test/transport/test_identity_cipher.rb @@ -2,7 +2,6 @@ require 'common' require 'net/ssh/transport/identity_cipher' module Transport - class TestIdentityCipher < NetSSHTest def test_block_size_should_be_8 assert_equal 8, cipher.block_size @@ -34,5 +33,4 @@ module Transport Net::SSH::Transport::IdentityCipher end end - end diff --git a/test/transport/test_packet_stream.rb b/test/transport/test_packet_stream.rb index 25c6b32..2921765 100644 --- a/test/transport/test_packet_stream.rb +++ b/test/transport/test_packet_stream.rb @@ -1,11 +1,10 @@ # encoding: ASCII-8BIT -require 'common' +require_relative '../common' require 'timeout' require 'net/ssh/transport/packet_stream' module Transport - class TestPacketStream < NetSSHTest # rubocop:disable Metrics/ClassLength include Net::SSH::Transport::Constants @@ -58,12 +57,12 @@ module Transport end def test_available_for_read_should_return_nontrue_when_self_is_not_ready - IO.expects(:select).with([stream], nil, nil, 0).returns([[],[],[]]) + IO.expects(:select).with([stream], nil, nil, 0).returns([[], [], []]) assert !stream.available_for_read? end def test_available_for_read_should_return_true_when_self_is_ready - IO.expects(:select).with([stream], nil, nil, 0).returns([[self],[],[]]) + IO.expects(:select).with([stream], nil, nil, 0).returns([[self], [], []]) assert stream.available_for_read? end @@ -135,7 +134,7 @@ module Transport def test_next_packet_should_eventually_return_packet_when_non_blocking_and_partial_read IO.stubs(:select).returns([[stream]]) - stream.stubs(:recv).returns(packet[0,10], packet[10..-1]) + stream.stubs(:recv).returns(packet[0, 10], packet[10..-1]) assert_nil stream.next_packet(:nonblock) packet = stream.next_packet(:nonblock) assert_not_nil packet @@ -160,7 +159,7 @@ module Transport def test_next_packet_should_block_when_requested_until_entire_packet_is_available IO.stubs(:select).returns([[stream]]) - stream.stubs(:recv).returns(packet[0,10], packet[10,20], packet[20..-1]) + stream.stubs(:recv).returns(packet[0, 10], packet[10, 20], packet[20..-1]) packet = stream.next_packet(:block) assert_not_nil packet assert_equal DEBUG, packet.type @@ -182,7 +181,7 @@ module Transport end def test_send_packet_should_enqueue_and_send_data_immediately - stream.expects(:send).times(3).with { |a,b| a == stream.write_buffer && b == 0 }.returns(15) + stream.expects(:send).times(3).with { |a, b| a == stream.write_buffer && b == 0 }.returns(15) IO.expects(:select).times(2).returns([[], [stream]]) stream.send_packet(ssh_packet) assert !stream.pending_write? @@ -1129,5 +1128,4 @@ module Transport end end end - end diff --git a/test/transport/test_server_version.rb b/test/transport/test_server_version.rb index ae443cc..c4b92a3 100644 --- a/test/transport/test_server_version.rb +++ b/test/transport/test_server_version.rb @@ -42,7 +42,7 @@ module Transport private - def socket(good, version_header, raise_eot=false) + def socket(good, version_header, raise_eot = false) socket = mock("socket") socket.expects(:write).with("#{Net::SSH::Transport::ServerVersion::PROTO_VERSION}\r\n") diff --git a/test/transport/test_session.rb b/test/transport/test_session.rb index 1b3747a..7a26dfc 100644 --- a/test/transport/test_session.rb +++ b/test/transport/test_session.rb @@ -10,7 +10,6 @@ require 'logger' Object.send(:undef_method, :verify) if Object.instance_methods.any? { |v| v.to_sym == :verify } module Transport - class TestSession < NetSSHTest include Net::SSH::Transport::Constants @@ -403,7 +402,7 @@ module Transport @algorithms ||= stub("algorithms", initialized?: true, allow?: true, start: true) end - def session(options={}) + def session(options = {}) @session ||= begin host = options.delete(:host) || TEST_HOST if (proxy = options[:proxy]) @@ -421,5 +420,4 @@ module Transport # version makes it look more like the session is being instantiated alias session! session end - end diff --git a/test/transport/test_state.rb b/test/transport/test_state.rb index aefd347..bc9b00d 100644 --- a/test/transport/test_state.rb +++ b/test/transport/test_state.rb @@ -4,7 +4,6 @@ require 'common' require 'net/ssh/transport/state' module Transport - class TestState < NetSSHTest def setup @socket = @state = @deflater = @inflater = nil @@ -115,18 +114,20 @@ module Transport end def test_compress_when_compression_is_enabled_should_return_compressed_text - state.set compression: :standard + state.set compression: :standard # JRuby Zlib implementation (1.4 & 1.5) does not have byte-to-byte compatibility with MRI's. # skip this test under JRuby. return if defined?(JRUBY_VERSION) + assert_equal "x\234\312H\315\311\311WH-K-\252L\312O\251\004\000\000\000\377\377", state.compress("hello everybody") end def test_decompress_when_compression_is_enabled_should_return_decompressed_text - state.set compression: :standard + state.set compression: :standard # JRuby Zlib implementation (1.4 & 1.5) does not have byte-to-byte compatibility with MRI's. # skip this test under JRuby. return if defined?(JRUBY_VERSION) + assert_equal "hello everybody", state.decompress("x\234\312H\315\311\311WH-K-\252L\312O\251\004\000\000\000\377\377") end @@ -160,7 +161,7 @@ module Transport private - def deflater(level=Zlib::DEFAULT_COMPRESSION) + def deflater(level = Zlib::DEFAULT_COMPRESSION) @deflater ||= Zlib::Deflate.new(level) end @@ -176,5 +177,4 @@ module Transport @state ||= Net::SSH::Transport::State.new(socket, :test) end end - -end
\ No newline at end of file +end diff --git a/test/verifiers/test_always.rb b/test/verifiers/test_always.rb index 64547ff..c387d7f 100644 --- a/test/verifiers/test_always.rb +++ b/test/verifiers/test_always.rb @@ -10,31 +10,31 @@ class TestAlways < NetSSHTest 'foo' end assert_raises(Net::SSH::HostKeyUnknown) { - secure_verifier.verify(session:OpenStruct.new(host_keys:host_keys)) + secure_verifier.verify(session: OpenStruct.new(host_keys: host_keys)) } end def test_passess_if_sam secure_verifier = Net::SSH::Verifiers::Always.new - key = OpenStruct.new(ssh_type:'key_type',to_blob:'keyblob') + key = OpenStruct.new(ssh_type: 'key_type', to_blob: 'keyblob') host_keys = [key] def host_keys.host 'foo' end - secure_verifier.verify(session:OpenStruct.new(host_keys:host_keys), key:key) + secure_verifier.verify(session: OpenStruct.new(host_keys: host_keys), key: key) end def test_raises_mismatch_error_if_not_the_same secure_verifier = Net::SSH::Verifiers::Always.new - key_in_known_hosts = OpenStruct.new(ssh_type:'key_type',to_blob:'keyblob') - key_actual = OpenStruct.new(ssh_type:'key_type',to_blob:'not keyblob') + key_in_known_hosts = OpenStruct.new(ssh_type: 'key_type', to_blob: 'keyblob') + key_actual = OpenStruct.new(ssh_type: 'key_type', to_blob: 'not keyblob') host_keys = [key_in_known_hosts] def host_keys.host 'foo' end assert_raises(Net::SSH::HostKeyMismatch) { - secure_verifier.verify(session:OpenStruct.new(host_keys:host_keys), key:key_actual) + secure_verifier.verify(session: OpenStruct.new(host_keys: host_keys), key: key_actual) } end diff --git a/test/win_integration/test_pageant.rb b/test/win_integration/test_pageant.rb index 374433f..0a909a7 100644 --- a/test/win_integration/test_pageant.rb +++ b/test/win_integration/test_pageant.rb @@ -2,35 +2,35 @@ require_relative '../common' require 'net/ssh/authentication/agent' module Authentication - unless RUBY_PLATFORM == "java" class TestPageapnt < NetSSHTest def with_pagent pageant_path = 'C:\ProgramData\chocolatey\lib\putty.portable\tools\pageant.exe' raise "No pageant found at:#{pageant_path}" unless File.executable?(pageant_path) + pageant_pid = Process.spawn(pageant_path) sleep 4 yield ensure Process.kill(9, pageant_pid) end - + def test_agent_should_be_able_to_negotiate_with_pagent with_pagent do agent.negotiate! end end - + def test_agent_should_raise_without_pagent assert_raises Net::SSH::Authentication::AgentNotAvailable do agent.negotiate! end end - + private - - def agent(auto=:connect) + + def agent(auto = :connect) @agent ||= begin agent = Net::SSH::Authentication::Agent.new agent.connect! if auto == :connect @@ -40,5 +40,4 @@ module Authentication end end - end |