diff options
149 files changed, 1978 insertions, 1519 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 diff --git a/.rubocop.yml b/.rubocop.yml index 41e4915..291461a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,11 +1,21 @@ +AllCops: + Exclude: + - 'tryout/**/*' + - "vendor/**/.*" + - "vendor/**/*" + NewCops: enable + 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
\ No newline at end of file diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d554db7..9497937 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,45 +1,39 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-05-29 10:37:36 +0200 using RuboCop version 0.74.0. +# on 2021-07-01 20:27:30 UTC using RuboCop version 1.17.0. # 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: 1 +# Configuration parameters: Include. +# Include: **/*.gemspec +Gemspec/RequiredRubyVersion: + Exclude: + - 'net-ssh.gemspec' + # Offense count: 75 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: with_first_argument, with_fixed_indentation -Layout/AlignArguments: +Layout/ArgumentAlignment: Enabled: false -# Offense count: 63 +# Offense count: 35 # 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: +Layout/HashAlignment: 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: 172 -# 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: 23 +# Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented @@ -50,18 +44,8 @@ Layout/MultilineOperationIndentation: - '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: 291 +# Offense count: 290 # Cop supports --auto-correct. Layout/SpaceAfterComma: Enabled: false @@ -73,7 +57,7 @@ Layout/SpaceAfterComma: Layout/SpaceAroundEqualsInParameterDefault: Enabled: false -# Offense count: 12 +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space @@ -83,9 +67,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. @@ -96,12 +77,6 @@ Layout/SpaceInsideReferenceBrackets: Exclude: - 'lib/net/ssh/transport/algorithms.rb' -# Offense count: 730 -# Cop supports --auto-correct. -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Enabled: false - # Offense count: 4 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: @@ -111,21 +86,29 @@ 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' - - 'test/start/test_transport.rb' + +# Offense count: 1 +# Configuration parameters: AllowComments. +Lint/EmptyWhen: + Exclude: + - 'lib/net/ssh/config.rb' # Offense count: 72 Lint/ImplicitStringConcatenation: @@ -133,40 +116,68 @@ Lint/ImplicitStringConcatenation: - '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: - Exclude: - - 'lib/net/ssh/authentication/pageant.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 +Lint/MissingSuper: + Exclude: + - '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: 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: 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: 60 +# Offense count: 15 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: @@ -174,21 +185,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: 65 +# Offense count: 69 # 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: @@ -204,13 +209,20 @@ Lint/UselessAssignment: - 'test/integration/common.rb' - 'test/integration/test_forward.rb' -# Offense count: 239 +# Offense count: 1 +# Cop supports --auto-correct. +Lint/UselessTimes: + Exclude: + - 'test/integration/test_forward.rb' + +# Offense count: 201 +# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: - Max: 71 + Max: 77 -# Offense count: 17 -# Configuration parameters: CountComments, ExcludedMethods. -# ExcludedMethods: refine +# Offense count: 16 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. +# IgnoredMethods: refine Metrics/BlockLength: Max: 59 @@ -220,32 +232,35 @@ Metrics/BlockNesting: Max: 4 # Offense count: 33 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 488 -# Offense count: 40 +# Offense count: 36 +# Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: - Max: 28 + Max: 32 # Offense count: 224 -# Configuration parameters: CountComments, ExcludedMethods. +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Max: 72 # Offense count: 3 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: Max: 160 -# Offense count: 1 +# Offense count: 3 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: Max: 6 + MaxOptionalParameters: 4 -# Offense count: 32 +# Offense count: 31 +# Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: - Max: 20 + Max: 32 # Offense count: 10 Naming/AccessorMethodName: @@ -259,12 +274,15 @@ 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: 16 +# Configuration parameters: AllowedNames. +# AllowedNames: module_parent Naming/ClassAndModuleCamelCase: Enabled: false @@ -276,8 +294,8 @@ Naming/ConstantName: - 'lib/net/ssh/transport/openssl.rb' # Offense count: 12 -# Configuration parameters: Blacklist. -# Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) +# Configuration parameters: ForbiddenDelimiters. +# ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: Exclude: - 'test/authentication/test_agent.rb' @@ -296,7 +314,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: @@ -309,19 +327,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' @@ -335,14 +344,44 @@ Naming/UncommunicativeMethodParamName: - '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: 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. @@ -353,28 +392,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. +# AllowedChars: © Style/AsciiComments: Exclude: - 'lib/net/ssh/authentication/pageant.rb' @@ -382,7 +412,7 @@ Style/AsciiComments: # 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 @@ -397,37 +427,42 @@ 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: 17 +# 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/service/forward.rb' + # Offense count: 7 Style/ClassVars: Exclude: @@ -442,10 +477,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 +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE Style/CommentAnnotation: Exclude: - 'lib/net/ssh/authentication/ed25519.rb' @@ -454,6 +495,7 @@ Style/CommentAnnotation: - 'lib/net/ssh/config.rb' # Offense count: 3 +# Cop supports --auto-correct. Style/CommentedKeyword: Exclude: - 'test/connection/test_session.rb' @@ -471,7 +513,8 @@ Style/ConditionalAssignment: - 'lib/net/ssh/transport/state.rb' - 'test/test_key_factory.rb' -# Offense count: 13 +# Offense count: 12 +# Configuration parameters: AllowedConstants. Style/Documentation: Exclude: - 'spec/**/*' @@ -486,15 +529,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. @@ -505,26 +552,26 @@ 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: 171 +# Offense count: 173 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. -# SupportedStyles: always, never +# SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false +# 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: 1 +# Cop supports --auto-correct. # Configuration parameters: AllowIfModifier. Style/IfInsideElse: Exclude: @@ -559,11 +606,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' @@ -575,15 +617,15 @@ 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. Style/MultipleComparison: Exclude: - 'lib/net/ssh/authentication/agent.rb' @@ -591,7 +633,7 @@ 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 @@ -642,7 +684,7 @@ Style/Not: Exclude: - 'lib/net/ssh/connection/channel.rb' -# Offense count: 10 +# Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: Strict. Style/NumericLiterals: @@ -650,11 +692,28 @@ 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: 17 +# 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/integration/common.rb' + - 'test/transport/test_server_version.rb' + # Offense count: 15 # Cop supports --auto-correct. Style/ParallelAssignment: @@ -687,7 +746,7 @@ Style/PercentLiteralDelimiters: - 'net-ssh.gemspec' - 'test/test_config.rb' -# Offense count: 15 +# Offense count: 16 # Cop supports --auto-correct. Style/PerlBackrefs: Exclude: @@ -713,21 +772,56 @@ 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: 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: 61 +# Offense count: 1 +# Cop supports --auto-correct. +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: + - 'net-ssh.gemspec' + +# 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: @@ -750,8 +844,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' @@ -798,6 +892,15 @@ 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. @@ -812,7 +915,23 @@ Style/SpecialGlobalVars: - 'test/manual/test_pageant.rb' - 'test/test_all.rb' -# Offense count: 1801 +# Offense count: 27 +# Cop supports --auto-correct. +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: 1820 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes @@ -821,7 +940,7 @@ Style/StringLiterals: # Offense count: 7 # Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. +# Configuration parameters: AllowMethodsWithArguments, IgnoredMethods. # IgnoredMethods: respond_to, define_method Style/SymbolProc: Exclude: @@ -831,24 +950,16 @@ Style/SymbolProc: - 'lib/net/ssh/test/extensions.rb' - 'lib/net/ssh/transport/algorithms.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/UnneededCondition: - Exclude: - - 'lib/net/ssh/proxy/command.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -Style/UnneededInterpolation: - Exclude: - - 'lib/net/ssh/proxy/socks5.rb' - - 'lib/net/ssh/transport/session.rb' - -# Offense count: 2 +# Offense count: 14 # Cop supports --auto-correct. -Style/UnneededPercentQ: +Style/UnpackFirst: Exclude: - - 'net-ssh.gemspec' + - '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. @@ -857,7 +968,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 @@ -871,3 +982,93 @@ Style/ZeroLengthPredicate: Exclude: - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/connection/channel.rb' + +Gemspec/DateAssignment: # (new in 1.10) + Enabled: true + +Layout/SpaceBeforeBrackets: # (new in 1.7) + Enabled: true + +Lint/AmbiguousAssignment: # (new in 1.7) + Enabled: true + +Lint/DeprecatedConstants: # (new in 1.8) + Enabled: true + +Lint/DuplicateBranch: # (new in 1.3) + Enabled: true + +Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1) + Enabled: true + +Lint/EmptyBlock: # (new in 1.1) + Enabled: false + +Lint/EmptyClass: # (new in 1.3) + Enabled: true + +Lint/LambdaWithoutLiteralBlock: # (new in 1.8) + Enabled: true + +Lint/NoReturnInBeginEndBlocks: # (new in 1.2) + Enabled: true + +Lint/NumberedParameterAssignment: # (new in 1.9) + Enabled: true + +Lint/OrAssignmentToConstant: # (new in 1.9) + Enabled: false + +Lint/RedundantDirGlobSort: # (new in 1.8) + Enabled: true + +Lint/SymbolConversion: # (new in 1.9) + Enabled: true + +Lint/ToEnumArguments: # (new in 1.1) + Enabled: true + +Lint/TripleQuotes: # (new in 1.9) + Enabled: true + +Lint/UnexpectedBlockArity: # (new in 1.5) + Enabled: true + +Lint/UnmodifiedReduceAccumulator: # (new in 1.1) + Enabled: true + +Style/ArgumentsForwarding: # (new in 1.1) + Enabled: true + +Style/CollectionCompact: # (new in 1.2) + Enabled: true + +Style/DocumentDynamicEvalDefinition: # (new in 1.1) + Enabled: true + +Style/EndlessMethod: # (new in 1.8) + Enabled: true + +Style/HashConversion: # (new in 1.10) + Enabled: false + +Style/HashExcept: # (new in 1.7) + Enabled: true + +Style/IfWithBooleanLiteralBranches: # (new in 1.9) + Enabled: true + +Style/NegatedIfElseCondition: # (new in 1.2) + Enabled: false + +Style/NilLambda: # (new in 1.3) + Enabled: true + +Style/RedundantArgument: # (new in 1.4) + Enabled: false + +Style/StringChars: # (new in 1.12) + Enabled: false + +Style/SwapValues: # (new in 1.1) + Enabled: true
\ No newline at end of file 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 6dbc9fb..8fb56bf 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,8 @@ +=== 6.3.0 beta1 + + * Support cert based host key auth, fix asterisk in known_hosts [#833] + * Support kex dh-group14-sha256 [#795] + === 6.2.0 rc1 === 6.2.0 beta1 @@ -43,7 +48,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 @@ -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..bbae909 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). diff --git a/lib/net/ssh/authentication/agent.rb b/lib/net/ssh/authentication/agent.rb index 59912a8..f521e55 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 @@ -107,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 @@ -196,13 +198,13 @@ module Net 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 diff --git a/lib/net/ssh/authentication/certificate.rb b/lib/net/ssh/authentication/certificate.rb index 310ad7d..deeb71d 100644 --- a/lib/net/ssh/authentication/certificate.rb +++ b/lib/net/ssh/authentication/certificate.rb @@ -31,7 +31,7 @@ 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. @@ -125,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 @@ -134,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 9416bbc..0c5bdf2 100644 --- a/lib/net/ssh/authentication/ed25519.rb +++ b/lib/net/ssh/authentication/ed25519.rb @@ -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 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..d41eed8 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 @@ -177,6 +176,7 @@ module Net if info[:from] == :agent raise KeyManagerError, "the agent is no longer available" unless agent + return agent.sign(info[:identity], data.to_s) end @@ -201,6 +201,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 +249,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..ec35043 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 diff --git a/lib/net/ssh/authentication/methods/hostbased.rb b/lib/net/ssh/authentication/methods/hostbased.rb index a4afbb4..c4cd4cb 100644 --- a/lib/net/ssh/authentication/methods/hostbased.rb +++ b/lib/net/ssh/authentication/methods/hostbased.rb @@ -4,7 +4,6 @@ module Net module SSH module Authentication module Methods - # Implements the host-based SSH authentication method. class Hostbased < Abstract include Constants @@ -67,7 +66,6 @@ module Net 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..c241eda 100644 --- a/lib/net/ssh/authentication/methods/keyboard_interactive.rb +++ b/lib/net/ssh/authentication/methods/keyboard_interactive.rb @@ -5,7 +5,6 @@ module Net module SSH module Authentication module Methods - # Implements the "keyboard-interactive" SSH authentication method. class KeyboardInteractive < Abstract USERAUTH_INFO_REQUEST = 60 @@ -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..09fdac8 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")) + 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..9cb1828 100644 --- a/lib/net/ssh/authentication/methods/password.rb +++ b/lib/net/ssh/authentication/methods/password.rb @@ -6,7 +6,6 @@ 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 @@ -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..65e5dca 100644 --- a/lib/net/ssh/authentication/methods/publickey.rb +++ b/lib/net/ssh/authentication/methods/publickey.rb @@ -6,7 +6,6 @@ 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 @@ -90,7 +89,6 @@ module Net end end end - end end end diff --git a/lib/net/ssh/authentication/pageant.rb b/lib/net/ssh/authentication/pageant.rb index a79802b..9c44c19 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,28 +32,27 @@ 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' include DL::Win32Types - + SIZEOF_DWORD = DL::SIZEOF_LONG else extend Fiddle::Importer @@ -62,7 +60,7 @@ module Net 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 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] 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] end - + def self.get_sid_ptr(user) user.to_ptr.ptr end @@ -307,28 +306,28 @@ 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) @@ -336,10 +335,10 @@ module Net 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" 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}" 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/session.rb b/lib/net/ssh/authentication/session.rb index dfc5c06..4451cde 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 @@ -70,22 +69,21 @@ 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]) + 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 +127,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..3934ae7 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 @@ -134,7 +133,7 @@ module Net # 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 @@ -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..464ba4c 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,7 +47,7 @@ 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. @@ -56,7 +55,7 @@ module Net # 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. @@ -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) 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: output.to_s end - + 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 @@ -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..98714e4 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 @@ -186,12 +185,10 @@ 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 @@ -302,7 +299,7 @@ module Net # host names. def pattern2regex(pattern) tail = pattern - prefix = "" + prefix = String.new while !tail.empty? do head,sep,tail = tail.partition(/[\*\?]/) prefix = prefix + Regexp.quote(head) diff --git a/lib/net/ssh/connection/channel.rb b/lib/net/ssh/connection/channel.rb index 7bfee72..b169679 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: - + # 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: - + # 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). @@ -221,21 +220,21 @@ module Net 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) 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,15 +488,16 @@ 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) 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 @@ -502,12 +505,12 @@ module Net # generally not need to invoke it directly. 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,7 +520,7 @@ 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 @@ -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,7 +548,7 @@ 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 @@ -554,7 +557,7 @@ module Net @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 @@ -565,20 +568,20 @@ module Net # request-specific data as the second. 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 @@ -588,7 +591,7 @@ module Net 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..869e38f 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,46 +11,49 @@ module Net # and we don't pass session. class EventLoop include Loggable - + 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 = [] @@ -64,11 +67,11 @@ module Net 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] @@ -81,11 +84,11 @@ module Net (fired_sessions[session] ||= { r: [],w: [] })[:w] << writer end end - + 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,17 +100,20 @@ 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) readers, writers, = IO.select(sr, sw, nil, actwait) - + session.ev_do_handle_events(readers,writers) session.ev_do_postprocess(!((readers.nil? || readers.empty?) && (writers.nil? || writers.empty?))) 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..4f09fc8 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: - + # The map of listeners that the event loop knows about. See #listen_to. attr_reader :listeners #:nodoc: - + # The map of specialized handlers for opening specific channel types. See # #on_open_channel. attr_reader :channel_open_handlers #:nodoc: - + # The list of callbacks for pending requests. See #send_global_request. 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={}) 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 @@ -157,7 +156,7 @@ module Net 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?). @@ -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, @@ -229,7 +228,7 @@ module Net 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,18 +236,20 @@ 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) @@ -256,12 +257,12 @@ module Net w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? } [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 @@ -336,25 +337,25 @@ module Net # channel.wait 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) 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| 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..c445459 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})" @@ -46,42 +46,42 @@ module Net class HostKeyError < Net::SSH::Exception # the callback to use when #remember_host! is called attr_writer :callback #:nodoc: - + # situation-specific data describing the host (see #host, #port, etc.) 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..c8df306 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 diff --git a/lib/net/ssh/known_hosts.rb b/lib/net/ssh/known_hosts.rb index 23a5606..eb7d7a7 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 @@ -87,12 +144,10 @@ module Net # to. 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..e47d71e 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,7 +35,7 @@ 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) @@ -46,12 +45,12 @@ module Net $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..e7dc3d0 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 @@ -46,47 +45,47 @@ module Net @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..4f4fdb2 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. @@ -22,9 +21,9 @@ module Net 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 @@ -32,12 +31,12 @@ module Net 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..a964d1e 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: # @@ -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..8a0ae91 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: # @@ -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..0edfaa7 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 @@ -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..d97eebf 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 @@ -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 7cebb9a..3ee56e2 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,8 +57,8 @@ 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 @@ -69,7 +68,7 @@ module Net 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. @@ -78,42 +77,42 @@ module Net 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) 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 diff --git a/lib/net/ssh/test/extensions.rb b/lib/net/ssh/test/extensions.rb index 5090f6c..527c88b 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: 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: 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: base.extend(ClassMethods) end - + @extension_enabled = false - + def self.with_test_extension(&block) orig_value = @extension_enabled @extension_enabled = true @@ -126,11 +126,11 @@ module Net @extension_enabled = orig_value end end - + def self.extension_enabled? @extension_enabled end - + module ClassMethods def self.extended(obj) #:nodoc: class <<obj @@ -138,23 +138,24 @@ module Net 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) 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..e604b98 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] @@ -90,6 +89,7 @@ module Net 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 e2c6903..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 diff --git a/lib/net/ssh/test/script.rb b/lib/net/ssh/test/script.rb index 6200bf1..7879ad7 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,12 +21,12 @@ 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. @@ -37,26 +36,26 @@ module Net 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 @@ -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. @@ -167,7 +166,7 @@ module Net 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 diff --git a/lib/net/ssh/test/socket.rb b/lib/net/ssh/test/socket.rb index 57fe8f6..42ece27 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={}) @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 9ab87b6..f0d8d15 100644 --- a/lib/net/ssh/transport/algorithms.rb +++ b/lib/net/ssh/transport/algorithms.rb @@ -41,6 +41,7 @@ module Net 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], @@ -277,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 diff --git a/lib/net/ssh/transport/cipher_factory.rb b/lib/net/ssh/transport/cipher_factory.rb index 8adc851..bedf1ea 100644 --- a/lib/net/ssh/transport/cipher_factory.rb +++ b/lib/net/ssh/transport/cipher_factory.rb @@ -3,10 +3,9 @@ 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 @@ -35,9 +34,10 @@ 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 @@ -46,12 +46,13 @@ module Net 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..9c67aff 100644 --- a/lib/net/ssh/transport/ctr.rb +++ b/lib/net/ssh/transport/ctr.rb @@ -32,7 +32,7 @@ module Net::SSH::Transport 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,7 +89,7 @@ module Net::SSH::Transport def final s = @remaining.empty? ? '' : xor!(@remaining, _update(@counter)) - @remaining = "" + @remaining = String.new s end diff --git a/lib/net/ssh/transport/hmac/abstract.rb b/lib/net/ssh/transport/hmac/abstract.rb index f8efa3e..575b03b 100644 --- a/lib/net/ssh/transport/hmac/abstract.rb +++ b/lib/net/ssh/transport/hmac/abstract.rb @@ -5,7 +5,6 @@ module Net module SSH module Transport module HMAC - # The base class of all OpenSSL-based HMAC algorithm wrappers. class Abstract class <<self 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..b8a90bc 100644 --- a/lib/net/ssh/transport/identity_cipher.rb +++ b/lib/net/ssh/transport/identity_cipher.rb @@ -1,7 +1,6 @@ -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). @@ -11,49 +10,48 @@ module Net 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..a43d713 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' @@ -14,6 +15,7 @@ module Net::SSH::Transport MAP = { '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, diff --git a/lib/net/ssh/transport/kex/curve25519_sha256.rb b/lib/net/ssh/transport/kex/curve25519_sha256.rb index 9aeba85..a9ce16c 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' 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_group_exchange_sha1.rb b/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb index bd5e4a9..e081669 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 @@ -69,5 +69,4 @@ module Net::SSH::Transport::Kex response end end - end diff --git a/lib/net/ssh/transport/key_expander.rb b/lib/net/ssh/transport/key_expander.rb index 108e9b9..93fbaf1 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={}) 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 d12a74b..af0a0cc 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) diff --git a/lib/net/ssh/transport/packet_stream.rb b/lib/net/ssh/transport/packet_stream.rb index 8b4032e..fc07ea1 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, @@ -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..261e5c2 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), @@ -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 @@ -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? diff --git a/lib/net/ssh/transport/state.rb b/lib/net/ssh/transport/state.rb index 07c509a..b472191 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) @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 cee010a..54cadee 100644 --- a/lib/net/ssh/version.rb +++ b/lib/net/ssh/version.rb @@ -56,7 +56,7 @@ module Net # The prerelease component of this version of the Net::SSH library # nil allowed - PRE = "rc1" + PRE = "rc2" # The current version of the Net::SSH library as a Version instance CURRENT = new(*[MAJOR, MINOR, TINY, PRE].compact) diff --git a/net-ssh.gemspec b/net-ssh.gemspec index df564ed..9fcd437 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.12.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 8cf2ca2..be9c11a 100644 --- a/test/authentication/methods/common.rb +++ b/test/authentication/methods/common.rb @@ -1,19 +1,18 @@ -module Authentication +module Authentication module Methods - module Common include Net::SSH::Authentication::Constants - + private - + def socket(options={}) @socket ||= stub("socket", client_name: "me.ssh.test") end - + def transport(options={}) @transport ||= MockTransport.new(options.merge(socket: socket)) end - + def session(options={}) @session ||= begin sess = stub("auth-session", logger: nil, transport: transport(options)) @@ -23,14 +22,12 @@ module Authentication sess end end - + def reset_session(options = {}) @transport = nil @session = nil session(options) end - end - end end diff --git a/test/authentication/methods/test_abstract.rb b/test/authentication/methods/test_abstract.rb index a839699..4f930a9 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 diff --git a/test/authentication/methods/test_hostbased.rb b/test/authentication/methods/test_hostbased.rb index 4fbd37a..f93a5a5 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 diff --git a/test/authentication/methods/test_keyboard_interactive.rb b/test/authentication/methods/test_keyboard_interactive.rb index 9fad914..76d5272 100644 --- a/test/authentication/methods/test_keyboard_interactive.rb +++ b/test/authentication/methods/test_keyboard_interactive.rb @@ -2,19 +2,18 @@ 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| assert_equal USERAUTH_REQUEST, packet.type @@ -23,18 +22,18 @@ module Authentication 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| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 1, :string, "Password:", :bool, false) @@ -45,10 +44,10 @@ module Authentication 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| assert_equal USERAUTH_REQUEST, packet.type @@ -58,10 +57,10 @@ module Authentication 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| assert_equal USERAUTH_REQUEST, packet.type @@ -74,10 +73,10 @@ 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| @@ -91,16 +90,16 @@ 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| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 2, :string, "Name:", :bool, true, :string, "Password:", :bool, false) @@ -112,22 +111,21 @@ module Authentication t2.return(USERAUTH_SUCCESS) end end - + assert subject.authenticate("ssh-connection", "jamis", nil) end - + private - + 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..bc71505 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| 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| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_SUCCESS) end - + assert subject.authenticate("ssh-connection", "", "") end - + private - + 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..db520bd 100644 --- a/test/authentication/methods/test_password.rb +++ b/test/authentication/methods/test_password.rb @@ -3,12 +3,11 @@ 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| assert_equal USERAUTH_REQUEST, packet.type @@ -17,15 +16,15 @@ module Authentication 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| assert_equal USERAUTH_REQUEST, packet.type @@ -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,12 +45,12 @@ 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| assert_equal USERAUTH_REQUEST, packet.type @@ -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| 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| 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={}) @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..029338d 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 @@ -110,20 +109,21 @@ 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, true) + buffer.read_byte == USERAUTH_REQUEST && # type + verify_userauth_request_packet(buffer, key, true) 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 + 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 end @@keys = nil diff --git a/test/authentication/test_agent.rb b/test/authentication/test_agent.rb index 9dea16e..a6cb623 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 @@ -37,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" @@ -72,7 +71,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 @@ -80,7 +79,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 @@ -88,7 +87,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 @@ -96,7 +95,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 @@ -104,7 +103,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 @@ -112,7 +111,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 @@ -123,7 +122,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 @@ -141,7 +140,7 @@ 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 @@ -156,10 +155,10 @@ 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 @@ -322,6 +321,7 @@ 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| assert_equal SSH2_AGENT_ADD_IDENTITY, type @@ -339,6 +339,7 @@ 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| assert_equal SSH2_AGENT_ADD_IDENTITY, type @@ -356,7 +357,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 @@ -378,7 +379,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 @@ -399,7 +400,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 @@ -433,6 +434,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 @@ -467,8 +469,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..279aa85 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 @@ -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..0ae8914 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 @@ -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..3e46def 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 @@ -292,5 +291,4 @@ module Authentication EOF end end - end diff --git a/test/common.rb b/test/common.rb index 8ae521c..43df743 100644 --- a/test/common.rb +++ b/test/common.rb @@ -11,7 +11,7 @@ if ENV["CI"] end require 'minitest' -require 'mocha/setup' +require 'mocha/minitest' require 'net/ssh/buffer' require 'net/ssh/config' require 'net/ssh/loggable' @@ -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) diff --git a/test/connection/test_channel.rb b/test/connection/test_channel.rb index abda254..ef9f952 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 @@ -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 @@ -130,7 +129,7 @@ module Connection 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] @@ -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] @@ -396,14 +395,14 @@ module Connection 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!") } @@ -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) @@ -483,5 +483,4 @@ module Connection &block) end end - end diff --git a/test/connection/test_session.rb b/test/connection/test_session.rb index 91e3ea9..6eb1432 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 @@ -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) @@ -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 @@ -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 @@ -583,5 +583,4 @@ module Connection session.process { (i += 1) < n } end end - end diff --git a/test/integration/common.rb b/test/integration/common.rb index 8fe0881..b897ece 100644 --- a/test/integration/common.rb +++ b/test/integration/common.rb @@ -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,39 +87,72 @@ 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) begin - Timeout.timeout(5) do + Timeout.timeout(20) do Process.wait(pid) end rescue Timeout::Error - warn "Failed to kill net-ssh process: #{pid}" - system('sudo', 'kill', '-9', pid) + warn "Failed to kill openssh process: #{pid}" + system('sudo', 'kill', '-9', pid.to_s) raise end end diff --git a/test/integration/mitm_server.rb b/test/integration/mitm_server.rb index e063687..365c318 100644 --- a/test/integration/mitm_server.rb +++ b/test/integration/mitm_server.rb @@ -52,7 +52,7 @@ class MitmServer < TCPServer 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 ae9b96c..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 @@ -45,7 +48,7 @@ - lineinfile: dest=/etc/sudoers.d/net_ssh_1 mode=0440 state=present create=yes line='net_ssh_2 ALL=(ALL) NOPASSWD:ALL' regexp=net_ssh_2 - unarchive: - src: https://cloudflare.cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-7.9p1.tar.gz + src: https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-7.9p1.tar.gz dest: /tmp remote_src: True validate_certs: False @@ -80,19 +83,31 @@ 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' + - 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 @@ -106,6 +121,7 @@ - pv - libgmp3-dev - git + - libssl-dev state: present - 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 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_channel.rb b/test/integration/test_channel.rb index d07e0a1..2a24eda 100644 --- a/test/integration/test_channel.rb +++ b/test/integration/test_channel.rb @@ -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 2be5d98..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,18 +28,16 @@ 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 diff --git a/test/integration/test_ed25519_pkeys.rb b/test/integration/test_ed25519_pkeys.rb index d42cbfd..3358af7 100644 --- a/test/integration/test_ed25519_pkeys.rb +++ b/test/integration/test_ed25519_pkeys.rb @@ -10,7 +10,7 @@ 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" @@ -22,8 +22,8 @@ unless ENV['NET_SSH_NO_ED25519'] end assert_equal "hello from:net_ssh_1\n", ret end - end - + end + def test_ssh_agent Dir.mktmpdir do |dir| with_agent do @@ -31,10 +31,10 @@ unless ENV['NET_SSH_NO_ED25519'] 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") - + # 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") - + # 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_forward.rb b/test/integration/test_forward.rb index 60630fc..e7c87da 100644 --- a/test/integration/test_forward.rb +++ b/test/integration/test_forward.rb @@ -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 @@ -73,14 +71,12 @@ class TestForward < ForwardTestBase 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? } @@ -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 c077012..0c7182c 100644 --- a/test/integration/test_hmac_etm.rb +++ b/test/integration/test_hmac_etm.rb @@ -34,25 +34,23 @@ class TestHMacEtm < NetSSHTest 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 diff --git a/test/integration/test_proxy.rb b/test/integration/test_proxy.rb index d73b7ba..f1419f8 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' @@ -43,6 +44,7 @@ class TestProxy < NetSSHTest 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 @@ -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..e9a72da 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 @@ -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 78e3280..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 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_buffer.rb b/test/test_buffer.rb index e1fcbd2..1275896 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' diff --git a/test/test_config.rb b/test/test_config.rb index f71a952..b75486b 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) @@ -175,11 +175,11 @@ class TestConfig < NetSSHTest 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 +189,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) diff --git a/test/test_key_factory.rb b/test/test_key_factory.rb index 2a92a08..f0a5116 100644 --- a/test/test_key_factory.rb +++ b/test/test_key_factory.rb @@ -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..5bd7668 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) @@ -35,7 +35,7 @@ class TestKnownHosts < NetSSHTest end def test_search_for - options = { user_known_hosts_file: path("known_hosts/github") } + 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 @@ -109,7 +109,29 @@ class TestKnownHosts < NetSSHTest 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) + 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 @@ -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 16ee10f..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 diff --git a/test/transport/hmac/test_md5_96.rb b/test/transport/hmac/test_md5_96.rb index b45d505..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 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 468da6d..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 diff --git a/test/transport/hmac/test_sha1_96.rb b/test/transport/hmac/test_sha1_96.rb index 72cb6ca..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 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 44ba928..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 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..5d6df93 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 @@ -151,7 +150,7 @@ module Transport :bignum, dh.dh.pub_key, :bignum, server_dh_pubkey, :bignum, shared_secret) - OpenSSL::Digest::SHA1.digest(buffer.to_s) + digest_type.digest(buffer.to_s) end 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..29a7d36 100644 --- a/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +++ b/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb @@ -2,60 +2,60 @@ 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) @need_bits ||= need_minimum(bits) end - + 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={}) connection.expect do |t, buffer| assert_equal KEXDH_GEX_REQUEST, buffer.type @@ -73,18 +73,18 @@ 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], @@ -104,6 +104,5 @@ module Transport 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 3489230..9f26428 100644 --- a/test/transport/kex/test_ecdh_sha2_nistp256.rb +++ b/test/transport/kex/test_ecdh_sha2_nistp256.rb @@ -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 aac8b9a..d4f9737 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 @@ -19,7 +18,7 @@ module Transport 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 rsa-sha2-256 rsa-sha2-512], algorithms[:host_key] - assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha1], algorithms[:kex] + 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] @@ -28,7 +27,7 @@ module Transport 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 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-sha1 diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1], algorithms(append_all_supported_algorithms: true)[:kex] + 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] @@ -47,7 +46,10 @@ module Transport 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")]) + 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 @@ -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 @@ -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 @@ -336,8 +338,9 @@ module Transport 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 @@ -390,7 +393,7 @@ module Transport 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[: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", @@ -406,7 +409,7 @@ module Transport 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[: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 @@ -438,8 +441,12 @@ module Transport end def transport(transport_options={}) - @transport ||= MockTransport.new(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..768fbf8 100644 --- a/test/transport/test_cipher_factory.rb +++ b/test/transport/test_cipher_factory.rb @@ -4,7 +4,6 @@ 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) @@ -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..009c900 100644 --- a/test/transport/test_hmac.rb +++ b/test/transport/test_hmac.rb @@ -2,9 +2,8 @@ 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] @@ -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..b79f640 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 @@ -1129,5 +1128,4 @@ module Transport end end end - end diff --git a/test/transport/test_session.rb b/test/transport/test_session.rb index 1b3747a..117fb89 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 @@ -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 1f9b9ce..389f012 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 @@ -176,5 +177,4 @@ module Transport @state ||= Net::SSH::Transport::State.new(socket, :test) end end - end diff --git a/test/verifiers/test_always.rb b/test/verifiers/test_always.rb index 64547ff..9d93d9a 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..b7e89fa 100644 --- a/test/win_integration/test_pageant.rb +++ b/test/win_integration/test_pageant.rb @@ -2,34 +2,34 @@ 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) @agent ||= begin agent = Net::SSH::Authentication::Agent.new @@ -40,5 +40,4 @@ module Authentication end end - end |