pax_global_header00006660000000000000000000000064145403613300014511gustar00rootroot0000000000000052 comment=2e65064a52d73396bfc3806c9196fc8108f33cd8 net-ssh-7.2.1/000077500000000000000000000000001454036133000131015ustar00rootroot00000000000000net-ssh-7.2.1/.dockerignore000066400000000000000000000000741454036133000155560ustar00rootroot00000000000000# Remove Git .git/ .gitignore docker-compose.yml README.md net-ssh-7.2.1/.github/000077500000000000000000000000001454036133000144415ustar00rootroot00000000000000net-ssh-7.2.1/.github/FUNDING.yml000066400000000000000000000000231454036133000162510ustar00rootroot00000000000000github: [mfazekas] net-ssh-7.2.1/.github/config/000077500000000000000000000000001454036133000157065ustar00rootroot00000000000000net-ssh-7.2.1/.github/config/rubocop_linter_action.yml000066400000000000000000000001001454036133000230030ustar00rootroot00000000000000rubocop_fail_level: "convention" versions: rubocop: "1.27.0" net-ssh-7.2.1/.github/workflows/000077500000000000000000000000001454036133000164765ustar00rootroot00000000000000net-ssh-7.2.1/.github/workflows/ci-with-docker.yml000066400000000000000000000016461454036133000220410ustar00rootroot00000000000000name: Test on: pull_request: push: { branches: master } jobs: test: name: Run test suite with docker runs-on: ubuntu-latest env: COMPOSE_FILE: docker-compose.yml steps: - name: Checkout code uses: actions/checkout@v3 - name: Build docker images run: docker-compose build - name: Run ruby 2.6 run: docker-compose run ruby-2.6 - name: Run ruby 2.7 run: docker-compose run ruby-2.7 - name: Run ruby 3.0 run: docker-compose run ruby-3.0 - name: Run ruby 3.1 run: docker-compose run ruby-3.1 test_openssl3: name: Run test suite with docker and openssl 3.0 runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Build docker images run: docker build -t netssh_openssl3 -f Dockerfile.openssl3 . - name: Run ruby 3.0 with openssl3 run: docker run --rm netssh_openssl3 net-ssh-7.2.1/.github/workflows/ci.yml000066400000000000000000000063231454036133000176200ustar00rootroot00000000000000name: CI on: pull_request: push: { branches: master } jobs: test: runs-on: ubuntu-22.04 strategy: matrix: ruby-version: [2.6.10, 2.7.7, 3.0.6, 3.1.3, 3.2.1, '3.3.0-rc1'] steps: - uses: actions/checkout@v3 - 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 ${{ (startsWith(matrix.ruby-version, '2.6.') || startsWith(matrix.ruby-version, '2.7.')) && '-v 2.4.22' || '' }} 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 rbnacl) run: bundle exec rake test env: BUNDLE_GEMFILE: ./Gemfile.norbnacl 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 net-ssh-7.2.1/.github/workflows/rubocop.yml000066400000000000000000000004641454036133000206760ustar00rootroot00000000000000name: Rubocop on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Ruby 3.1 uses: ruby/setup-ruby@v1 with: ruby-version: 3.1 bundler-cache: true - name: Run RuboCop run: bundle exec rubocop net-ssh-7.2.1/.gitignore000066400000000000000000000002531454036133000150710ustar00rootroot00000000000000/Gemfile.lock /Gemfile.noed25519.lock doc rdoc ri pkg *.swp test/integration/.vagrant test/integration/playbook.retry lib/net/ssh/version.rb.old .byebug_history tryout net-ssh-7.2.1/.rubocop.yml000066400000000000000000000005371454036133000153600ustar00rootroot00000000000000AllCops: Exclude: - 'tryout/**/*' - "vendor/**/.*" - "vendor/**/*" NewCops: enable TargetRubyVersion: 2.6 inherit_from: .rubocop_todo.yml Style/DoubleNegation: Exclude: - 'lib/net/ssh/key_factory.rb' Layout/LineLength: Max: 150 Exclude: - 'test/**/*.rb' - 'net-ssh.gemspec' Style/EmptyLiteral: Enabled: false net-ssh-7.2.1/.rubocop_todo.yml000066400000000000000000001012121454036133000163750ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2022-04-29 12:31:14 UTC using RuboCop version 1.28.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: Include. # Include: **/*.gemspec Gemspec/RequireMFA: Exclude: - 'net-ssh.gemspec' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented Layout/LineEndStringConcatenationIndentation: Exclude: - 'lib/net/ssh/transport/algorithms.rb' # Offense count: 7 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented Layout/MultilineOperationIndentation: Exclude: - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/proxy/https.rb' - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/state.rb' # Offense count: 5 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: Exclude: - 'lib/net/ssh/authentication/session.rb' - 'lib/net/ssh/transport/ctr.rb' - 'support/ssh_tunnel_bug.rb' # Offense count: 6 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. # SupportedStyles: space, no_space # SupportedStylesForEmptyBrackets: space, no_space Layout/SpaceInsideReferenceBrackets: Exclude: - 'lib/net/ssh/transport/algorithms.rb' # Offense count: 11 # This cop supports safe auto-correction (--auto-correct). Lint/AmbiguousOperatorPrecedence: Exclude: - 'lib/net/ssh/authentication/certificate.rb' - 'lib/net/ssh/config.rb' - 'lib/net/ssh/loggable.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb' - 'lib/net/ssh/transport/openssl.rb' - 'lib/net/ssh/transport/state.rb' - 'lib/net/ssh/version.rb' - 'test/integration/test_proxy.rb' # Offense count: 4 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Exclude: - 'lib/net/ssh/connection/channel.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/proxy/command.rb' # Offense count: 1 # Configuration parameters: AllowedMethods. # AllowedMethods: enums Lint/ConstantDefinitionInBlock: Exclude: - 'test/transport/test_cipher_factory.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Lint/DeprecatedClassMethods: Exclude: - 'lib/net/ssh/transport/packet_stream.rb' # Offense count: 12 # This cop supports safe auto-correction (--auto-correct). Lint/DeprecatedOpenSSLConstant: Exclude: - 'lib/net/ssh/transport/openssl.rb' # Offense count: 2 # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: - 'test/common.rb' - 'test/start/test_transport.rb' # Offense count: 1 # Configuration parameters: AllowComments. Lint/EmptyWhen: Exclude: - 'lib/net/ssh/config.rb' # Offense count: 72 Lint/ImplicitStringConcatenation: Exclude: - 'lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' # Offense count: 8 # This cop supports safe auto-correction (--auto-correct). Lint/IncompatibleIoSelectWithFiberScheduler: Exclude: - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/proxy/command.rb' - 'lib/net/ssh/transport/packet_stream.rb' - 'lib/net/ssh/transport/server_version.rb' # Offense count: 2 # This cop supports unsafe auto-correction (--auto-correct-all). 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: 2 # This cop supports unsafe auto-correction (--auto-correct-all). Lint/OrAssignmentToConstant: Exclude: - 'lib/net/ssh/authentication/pageant.rb' # Offense count: 6 # This cop supports unsafe auto-correction (--auto-correct-all). # Configuration parameters: AllowedImplicitNamespaces. # AllowedImplicitNamespaces: Gem Lint/RaiseException: Exclude: - 'Rakefile' - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/key_factory.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Lint/RedundantCopDisableDirective: Exclude: - 'lib/net/ssh/key_factory.rb' # Offense count: 3 Lint/RescueException: Exclude: - 'lib/net/ssh/authentication/key_manager.rb' - 'lib/net/ssh/service/forward.rb' # Offense count: 4 # This cop supports safe auto-correction (--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: 15 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Exclude: - 'lib/net/ssh/connection/keepalive.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/service/forward.rb' # Offense count: 74 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. Lint/UnusedMethodArgument: Enabled: false # Offense count: 3 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/connection/channel.rb' - 'lib/net/ssh/transport/session.rb' # Offense count: 10 Lint/UselessAssignment: Exclude: - 'lib/net/ssh/proxy/socks4.rb' - 'lib/net/ssh/proxy/socks5.rb' - 'test/integration/common.rb' - 'test/integration/test_forward.rb' # Offense count: 1 # This cop supports unsafe auto-correction (--auto-correct-all). Lint/UselessTimes: Exclude: - 'test/integration/test_forward.rb' # Offense count: 205 # Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: Max: 74 # Offense count: 16 # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. # IgnoredMethods: refine Metrics/BlockLength: Max: 59 # Offense count: 1 # Configuration parameters: CountBlocks. Metrics/BlockNesting: Max: 4 # Offense count: 33 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 350 # Offense count: 38 # Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: Max: 32 # Offense count: 232 # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Max: 72 # Offense count: 3 # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: Max: 160 # Offense count: 2 # Configuration parameters: Max, CountKeywordArgs. Metrics/ParameterLists: MaxOptionalParameters: 4 # Offense count: 34 # Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: Max: 32 # Offense count: 10 Naming/AccessorMethodName: Exclude: - 'lib/net/ssh/authentication/methods/password.rb' - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/connection/channel.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/transport/kex/abstract5656.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb' # Offense count: 2 # This cop supports safe auto-correction (--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 # Offense count: 4 Naming/ConstantName: Exclude: - 'lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' - 'lib/net/ssh/transport/openssl.rb' # Offense count: 15 # Configuration parameters: ForbiddenDelimiters. # ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: Exclude: - 'test/authentication/test_agent.rb' - 'test/authentication/test_certificate.rb' - 'test/authentication/test_ed25519.rb' - 'test/authentication/test_session.rb' - 'test/integration/test_agent.rb' - 'test/test_key_factory.rb' # Offense count: 5 # Configuration parameters: EnforcedStyleForLeadingUnderscores. # SupportedStylesForLeadingUnderscores: disallowed, required, optional Naming/MemoizedInstanceVariableName: Exclude: - 'lib/net/ssh/transport/openssl.rb' - 'test/authentication/test_key_manager.rb' # Offense count: 32 # Configuration parameters: EnforcedStyle, AllowedPatterns, IgnoredPatterns. # SupportedStyles: snake_case, camelCase Naming/MethodName: Exclude: - 'lib/net/ssh/authentication/ed25519_loader.rb' - 'lib/net/ssh/transport/kex/curve25519_sha256_loader.rb' - 'test/authentication/test_agent.rb' - 'test/authentication/test_session.rb' - 'test/common.rb' - 'test/connection/test_channel.rb' - 'test/test_config.rb' - 'test/test_key_factory.rb' # Offense count: 23 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # 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' - 'lib/net/ssh/authentication/key_manager.rb' - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/test/socket.rb' - 'lib/net/ssh/transport/ctr.rb' - 'lib/net/ssh/transport/hmac/abstract.rb' - 'lib/net/ssh/transport/identity_cipher.rb' - 'test/connection/test_session.rb' # Offense count: 4 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: PreferredName. Naming/RescuedExceptionsVariableName: Exclude: - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/service/forward.rb' - 'lib/net/ssh/verifiers/accept_new.rb' # Offense count: 5 # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. # SupportedStyles: snake_case, normalcase, non_integer # AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 Naming/VariableNumber: Exclude: - 'test/test_buffer.rb' - 'test/test_known_hosts.rb' - 'test/transport/test_identity_cipher.rb' # Offense count: 1 # This cop supports unsafe auto-correction (--auto-correct-all). Security/IoMethods: Exclude: - 'lib/net/ssh/config.rb' # Offense count: 2 # Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols. # SupportedStyles: inline, group Style/AccessModifierDeclarations: Exclude: - 'lib/net/ssh/authentication/pageant.rb' # Offense count: 31 # This cop supports safe auto-correction (--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 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: prefer_alias, prefer_alias_method Style/Alias: Exclude: - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/service/forward.rb' # Offense count: 9 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, conditionals Style/AndOr: Exclude: - 'lib/net/ssh/connection/channel.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/service/forward.rb' # Offense count: 9 # This cop supports safe auto-correction (--auto-correct). # 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 # IgnoredMethods: lambda, proc, it Style/BlockDelimiters: Exclude: - 'Rakefile' - 'lib/net/ssh/authentication/key_manager.rb' - 'lib/net/ssh/config.rb' - 'lib/net/ssh/connection/keepalive.rb' - 'lib/net/ssh/proxy/command.rb' - 'lib/net/ssh/transport/ctr.rb' - 'test/verifiers/test_always.rb' # Offense count: 2 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowOnConstant. Style/CaseEquality: Exclude: - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/connection/session.rb' # Offense count: 3 # This cop supports unsafe auto-correction (--auto-correct-all). Style/CaseLikeIf: Exclude: - 'lib/net/ssh/transport/openssl.rb' - 'test/connection/test_session.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Style/CharacterLiteral: Exclude: - 'test/test_buffer.rb' # Offense count: 18 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: Enabled: false # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: IgnoredMethods. # IgnoredMethods: ==, equal?, eql? Style/ClassEqualityComparison: Exclude: - 'lib/net/ssh/service/forward.rb' # Offense count: 7 Style/ClassVars: Exclude: - 'lib/net/ssh/config.rb' - 'lib/net/ssh/packet.rb' - 'test/authentication/methods/test_hostbased.rb' - 'test/authentication/methods/test_publickey.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). 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 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: Keywords, RequireColon. # Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE Style/CommentAnnotation: Exclude: - 'lib/net/ssh/authentication/ed25519.rb' - 'lib/net/ssh/authentication/session.rb' - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/config.rb' # Offense count: 3 # This cop supports safe auto-correction (--auto-correct). Style/CommentedKeyword: Exclude: - 'test/connection/test_session.rb' - 'test/integration/test_forward.rb' # Offense count: 7 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Exclude: - 'lib/net/ssh/config.rb' - 'lib/net/ssh/proxy/socks5.rb' - 'lib/net/ssh/test/script.rb' - 'lib/net/ssh/transport/state.rb' - 'test/test_key_factory.rb' # Offense count: 12 # Configuration parameters: AllowedConstants. Style/Documentation: Exclude: - 'spec/**/*' - 'test/**/*' - 'lib/net/ssh/authentication/ed25519.rb' - 'lib/net/ssh/connection/keepalive.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/test/extensions.rb' - 'lib/net/ssh/transport/kex.rb' - 'lib/net/ssh/transport/key_expander.rb' - 'lib/net/ssh/transport/openssl.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Style/EvenOdd: Exclude: - 'lib/net/ssh/buffer.rb' # Offense count: 9 # This cop supports safe auto-correction (--auto-correct). Style/ExplicitBlockArgument: Exclude: - '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: 12 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowedVars. Style/FetchEnvVar: Exclude: - 'lib/net/ssh.rb' - 'lib/net/ssh/authentication/agent.rb' - 'lib/net/ssh/authentication/methods/hostbased.rb' - 'test/authentication/test_agent.rb' - 'test/common.rb' - 'test/start/test_options.rb' - 'test/test_all.rb' - 'test/test_config.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Style/FileWrite: Exclude: - 'test/integration/test_proxy.rb' # Offense count: 2 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: format, sprintf, percent Style/FormatString: Exclude: - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/loggable.rb' # Offense count: 174 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Style/GlobalStdStream: Exclude: - 'lib/net/ssh.rb' # Offense count: 35 # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Enabled: false # Offense count: 3 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowSplatArgument. Style/HashConversion: Exclude: - 'lib/net/ssh/authentication/certificate.rb' - 'test/test_config.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowIfModifier. Style/IfInsideElse: Exclude: - 'lib/net/ssh/connection/session.rb' # Offense count: 13 # This cop supports safe auto-correction (--auto-correct). Style/IfUnlessModifier: Exclude: - 'lib/net/ssh.rb' - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/proxy/command.rb' - 'lib/net/ssh/service/forward.rb' - 'lib/net/ssh/transport/ctr.rb' - 'lib/net/ssh/transport/key_expander.rb' - 'test/integration/test_proxy.rb' - 'test/test_key_factory.rb' # Offense count: 1 # This cop supports unsafe auto-correction (--auto-correct-all). Style/InfiniteLoop: Exclude: - 'lib/net/ssh/authentication/pageant.rb' # Offense count: 27 # This cop supports safe auto-correction (--auto-correct). Style/LineEndConcatenation: Exclude: - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' - 'lib/net/ssh/verifiers/always.rb' # Offense count: 1 # This cop supports unsafe auto-correction (--auto-correct-all). Style/MapToHash: Exclude: - 'lib/net/ssh/config.rb' # Offense count: 1 Style/MissingRespondToMissing: Exclude: - 'lib/net/ssh/connection/session.rb' # Offense count: 3 # This cop supports safe auto-correction (--auto-correct). Style/MultilineIfThen: Exclude: - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/service/forward.rb' # Offense count: 7 # This cop supports safe auto-correction (--auto-correct). Style/MultilineWhenThen: Exclude: - 'lib/net/ssh/transport/packet_stream.rb' - 'lib/net/ssh/transport/session.rb' # Offense count: 5 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowMethodComparison. Style/MultipleComparison: Exclude: - 'lib/net/ssh/authentication/agent.rb' - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/known_hosts.rb' - 'lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb' # Offense count: 42 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: Enabled: false # Offense count: 14 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: both, prefix, postfix Style/NegatedIf: Exclude: - 'lib/net/ssh.rb' - 'lib/net/ssh/authentication/key_manager.rb' - 'lib/net/ssh/service/forward.rb' - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/hmac/abstract.rb' - 'lib/net/ssh/transport/session.rb' - 'lib/net/ssh/transport/state.rb' - 'test/test_key_factory.rb' - 'test/transport/test_state.rb' # Offense count: 2 # This cop supports safe auto-correction (--auto-correct). Style/NegatedIfElseCondition: Exclude: - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/cipher_factory.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Style/NegatedWhile: Exclude: - 'lib/net/ssh/config.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle, MinBodyLength. # SupportedStyles: skip_modifier_ifs, always Style/Next: Exclude: - 'lib/net/ssh/authentication/key_manager.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: predicate, comparison Style/NilComparison: Exclude: - 'lib/net/ssh/proxy/command.rb' # Offense count: 3 # This cop supports safe auto-correction (--auto-correct). Style/Not: Exclude: - 'lib/net/ssh/connection/channel.rb' # Offense count: 11 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: Strict, AllowedNumbers. Style/NumericLiterals: MinDigits: 310 # Offense count: 29 # This cop supports unsafe auto-correction (--auto-correct-all). # Configuration parameters: EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Enabled: false # Offense count: 13 Style/OpenStructUse: Exclude: - 'test/authentication/test_ed25519.rb' - 'test/common.rb' - 'test/transport/kex/test_curve25519_sha256.rb' - 'test/transport/kex/test_diffie_hellman_group1_sha1.rb' - 'test/transport/kex/test_ecdh_sha2_nistp256.rb' - 'test/verifiers/test_always.rb' # Offense count: 16 # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/key_factory.rb' - 'lib/net/ssh/prompt.rb' - 'lib/net/ssh/test/channel.rb' - 'lib/net/ssh/test/script.rb' - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/session.rb' - 'lib/net/ssh/transport/state.rb' - 'test/common.rb' - 'test/transport/test_server_version.rb' # Offense count: 15 # This cop supports safe auto-correction (--auto-correct). Style/ParallelAssignment: Exclude: - 'lib/net/ssh/config.rb' - 'lib/net/ssh/connection/channel.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/errors.rb' - 'lib/net/ssh/test/socket.rb' - 'lib/net/ssh/version.rb' - 'test/authentication/test_agent.rb' - 'test/common.rb' - 'test/connection/test_channel.rb' # Offense count: 5 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowSafeAssignment, AllowInMultilineConditions. Style/ParenthesesAroundCondition: Exclude: - 'lib/net/ssh/authentication/ed25519.rb' - 'lib/net/ssh/service/forward.rb' - 'lib/net/ssh/transport/ctr.rb' - 'test/integration/test_proxy.rb' # Offense count: 23 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: Exclude: - 'net-ssh.gemspec' - 'test/test_config.rb' # Offense count: 17 # This cop supports safe auto-correction (--auto-correct). Style/PerlBackrefs: Exclude: - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/config.rb' - 'lib/net/ssh/key_factory.rb' - 'lib/net/ssh/proxy/command.rb' - 'lib/net/ssh/proxy/socks5.rb' - 'test/integration/common.rb' # Offense count: 15 # This cop supports safe auto-correction (--auto-correct). Style/Proc: Exclude: - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/test/channel.rb' - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/verifiers/always.rb' - 'test/authentication/methods/test_hostbased.rb' - 'test/authentication/methods/test_publickey.rb' - 'test/connection/test_channel.rb' - 'test/connection/test_session.rb' # Offense count: 7 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle, AllowedCompactTypes. # SupportedStyles: compact, exploded Style/RaiseArgs: Exclude: - 'lib/net/ssh/authentication/ed25519.rb' # Offense count: 6 # This cop supports unsafe auto-correction (--auto-correct-all). # Configuration parameters: Methods. Style/RedundantArgument: Exclude: - 'lib/net/ssh/known_hosts.rb' - 'test/authentication/test_ed25519.rb' # Offense count: 5 # This cop supports safe auto-correction (--auto-correct). Style/RedundantBegin: Exclude: - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/service/forward.rb' - 'lib/net/ssh/verifiers/accept_new.rb' - 'test/manual/test_pageant.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Style/RedundantCondition: Exclude: - 'lib/net/ssh/proxy/command.rb' # Offense count: 1 # This cop supports safe auto-correction (--auto-correct). Style/RedundantFileExtensionInRequire: Exclude: - 'lib/net/ssh/transport/cipher_factory.rb' # Offense count: 2 # This cop supports safe auto-correction (--auto-correct). Style/RedundantInterpolation: Exclude: - 'lib/net/ssh/proxy/socks5.rb' - 'lib/net/ssh/transport/session.rb' # Offense count: 2 # This cop supports safe auto-correction (--auto-correct). Style/RedundantPercentQ: Exclude: - 'net-ssh.gemspec' # Offense count: 11 # This cop supports safe auto-correction (--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: 87 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Enabled: false # Offense count: 18 # This cop supports safe auto-correction (--auto-correct). Style/RedundantSelf: Exclude: - 'lib/net/ssh/connection/channel.rb' - 'lib/net/ssh/test/extensions.rb' - 'test/authentication/test_ed25519.rb' # Offense count: 6 # This cop supports safe auto-correction (--auto-correct). Style/RescueModifier: Exclude: - 'lib/net/ssh/service/forward.rb' - 'lib/net/ssh/transport/algorithms.rb' # Offense count: 25 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: Exclude: - 'lib/net/ssh/authentication/key_manager.rb' - 'lib/net/ssh/authentication/methods/keyboard_interactive.rb' - 'lib/net/ssh/authentication/methods/password.rb' - 'lib/net/ssh/authentication/session.rb' - 'lib/net/ssh/connection/channel.rb' - 'lib/net/ssh/connection/event_loop.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/key_factory.rb' - 'lib/net/ssh/loggable.rb' - 'lib/net/ssh/test/local_packet.rb' - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/packet_stream.rb' # Offense count: 3 # This cop supports safe auto-correction (--auto-correct). Style/SelectByRegexp: Exclude: - 'test/test_all.rb' # Offense count: 2 # This cop supports safe auto-correction (--auto-correct). Style/SelfAssignment: Exclude: - 'lib/net/ssh/config.rb' # Offense count: 7 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowAsExpressionSeparator. Style/Semicolon: Exclude: - 'lib/net/ssh/buffer.rb' - 'test/connection/test_channel.rb' - 'test/connection/test_session.rb' # Offense count: 2 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle. # SupportedStyles: only_raise, only_fail, semantic Style/SignalException: Exclude: - 'lib/net/ssh/config.rb' - 'lib/net/ssh/connection/channel.rb' # Offense count: 2 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: AllowIfMethodIsEmpty. Style/SingleLineMethods: Exclude: - 'lib/net/ssh/buffered_io.rb' # Offense count: 11 # This cop supports unsafe auto-correction (--auto-correct-all). Style/SlicingWithRange: Exclude: - 'lib/net/ssh/authentication/ed25519.rb' - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/config.rb' - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/packet_stream.rb' - 'lib/net/ssh/transport/state.rb' - 'test/transport/test_packet_stream.rb' # Offense count: 3 # This cop supports safe auto-correction (--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 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: RequireEnglish, EnforcedStyle. # SupportedStyles: use_perl_names, use_english_names, use_builtin_english_names Style/SpecialGlobalVars: Exclude: - 'lib/net/ssh/authentication/agent.rb' - 'lib/net/ssh/connection/session.rb' - 'support/ssh_tunnel_bug.rb' - 'test/integration/common.rb' - 'test/integration/test_forward.rb' - 'test/manual/test_pageant.rb' - 'test/test_all.rb' # Offense count: 1 # This cop supports unsafe auto-correction (--auto-correct-all). Style/StringChars: Exclude: - 'test/transport/test_server_version.rb' # Offense count: 27 # This cop supports unsafe auto-correction (--auto-correct-all). # Configuration parameters: Mode. Style/StringConcatenation: Exclude: - 'lib/net/ssh/authentication/certificate.rb' - 'lib/net/ssh/authentication/key_manager.rb' - 'lib/net/ssh/authentication/pageant.rb' - 'lib/net/ssh/config.rb' - 'lib/net/ssh/transport/algorithms.rb' - 'lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb' - 'test/authentication/test_key_manager.rb' - 'test/integration/common.rb' - 'test/integration/test_proxy.rb' - 'test/test_buffer.rb' - 'test/test_key_factory.rb' # Offense count: 1849 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Enabled: false # Offense count: 6 # This cop supports unsafe auto-correction (--auto-correct-all). # Configuration parameters: AllowMethodsWithArguments, IgnoredMethods, AllowComments. # IgnoredMethods: respond_to, define_method Style/SymbolProc: Exclude: - 'lib/net/ssh/authentication/session.rb' - 'lib/net/ssh/buffer.rb' - 'lib/net/ssh/connection/session.rb' - 'lib/net/ssh/test/extensions.rb' # Offense count: 14 # This cop supports safe auto-correction (--auto-correct). Style/UnpackFirst: Exclude: - '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 # This cop supports safe auto-correction (--auto-correct). Style/WhileUntilDo: Exclude: - 'lib/net/ssh/config.rb' - 'test/integration/common.rb' # Offense count: 4 # This cop supports safe auto-correction (--auto-correct). # Configuration parameters: WordRegex. # SupportedStyles: percent, brackets Style/WordArray: EnforcedStyle: percent MinSize: 3 # Offense count: 4 # This cop supports unsafe auto-correction (--auto-correct-all). Style/ZeroLengthPredicate: Exclude: - 'lib/net/ssh/buffered_io.rb' - 'lib/net/ssh/connection/channel.rb' net-ssh-7.2.1/CHANGES.txt000066400000000000000000000605011454036133000147140ustar00rootroot00000000000000=== 7.2.1 rc1 * feat: allow load of certkey from string [#926] * fix: fix for Socket#recv returning nil on ruby 3.3.0 [#928] === 7.2.0 * Add debugging information for algorithm of pubkey in use [#918] === 7.2.0 rc1 * Allow IdentityAgent as option to Net::SSH.start [#912] === 7.2.0 beta1 * Support `chacha20-poly1305@opnessh.com` cypher if `RbNaCl` gem is installed [#908] === 7.1.0 * Accept pubkey_algorithms option when starting a new connection [#891] === 7.1.0 beta1 * Don't use the deprecated set_XXX methods on RSA keys. [#875] * Raise error when BCryptPbkdf fails [#876] === 7.0.1 * Drop leftover debug statement [#866] === 7.0.0 * BREAKING: Drop support for Ruby 2.5 * Fix decoding of ecdsa-sha2-nistp256 private keys [#657, #854] * Fix missing require [#855] * Support `~` in the path to the SSH agent's unix socket [#850] * Add support for RSA client authentication with SHA-2 [a45f54] * openssl: DSA: don't hardcode expected signature size, see ruby/openssl#483 [23a15c] * Internal housekeeping (rubocop, codecov, remove travis, adding/improving tests) === 6.3.0 beta1 * Support cert based host key auth, fix asterisk in known_hosts [#833] * Support kex dh-group14-sha256 [#795] * Fix StrictHostKeyChecking ssh config parameter translation [#765] === 6.2.0 rc1 === 6.2.0 beta1 * rsa-sha2-512, rsa-sha2-256 host_key algs [#771] * JRuby aes*-ctr suppport [#767] === 6.1.0 * Adapt to ssh's default behaviors when no username is provided. When Net::SSH.start user is nil and config has no entry we default to Etc.getpwuid.name() instead of Etc.getlogin(). [#749] === 6.1.0.rc1 * Make sha2-{256,512}-etm@openssh.com MAC default again [#761] * Support algorithm subtraction syntax from ssh_config [#751] === 6.0.2 * Fix corrupted hmac issue in etm hmac [#759] === 6.0.1 * Make sha2-{256,512}-etm@openssh.com MAC opt-in as they seems to have issues [#757] === 6.0.0 * Support empty lines and comments in known_hosts [donoghuc, #742] * Add sha2-{256,512}-etm@openssh.com MAC algorithms [graaff, #714] === 6.0.0 beta2 * Support :certkeys and CertificateFile configuration option [Anders Carling, #722] === 6.0.0 beta1 * curve25519sha256 support [Florian Wininger ,#690] * disabled insecure algs [Florian Wininger , #709] === 5.2.0 === 5.2.0.rc3 * Fix check_host_ip read from config * Support ssh-ed25519 in known hosts === 5.2.0.rc2 * Read check_host_ip from ssh config files === 5.2.0.rc1 * Interpret * and ? in know_hosts file [Romain Tartière, #660] * New :check_host_ip so ip checking can be disabled in known hosts [Romain Tartière, #656] === 5.1.0 === 5.1.0.rc1 * Support new OpenSSH private key format for rsa - bcrypt for rsa (ed25519 already supported) [#646] * Support IdentityAgent is ssh config [Frank Groeneveld, #645] * Improve Match processing in ssh config [Aleksandrs Ļedovskis, #642] * Ignore signature verification when verify_host_key is never [Piotr Kliczewski, #641] * Alg preference was changed to prefer stronger encryptions [Tray, #637] === 5.0.2 * Fix ctr for jruby [#612] === 5.0.1 * default_keys were not loaded even if no keys or key_data options specified [#607] === 5.0.0 * Breaking change: ed25519 now requires ed25519 gem instead of RbNaCl gem [#563] * Verify_host_key options rename (true, false, :very, :secure depreacted new equivalents are :never, :accept_new_or_local_tunnel :accept_new :always) [Jared Beck, #595] === 5.0.0.rc2 * Add .dll extensions to dlopen on cygwin [#603] * Fix host certificate validation [#601] === 5.0.0.rc1 * Fix larger than 4GB file transfers [#599] * Update HTTP proxy to version 1.1 [Connor Dunn, #597] === 5.0.0.beta2 * Support for sha256 pubkey fingerprint [Tom Maher, #585] * Don't try to load default_keys if key_data option is used [Josh Larson, #589] * Added fingerprint_hash defaulting to SHA256 as fingerprint format, and MD5 can be used as an option [Miklós Fazekas, #591] === 5.0.0.beta1 * Don't leave proxy command as zombie on timeout [DimitriosLisenko, #560] * Use OpenSSL for aes*-ctr for up to 5x throughput improvement [Miklós Fazekas, Harald Sitter, #570] * Optimize slice! usage in CTR for up to 2x throughput improvement [Harald Sitter, #569] * Replace RbNaCl dependency with ed25519 gem [Tony Arcieri ,#563] * Add initial Match support [Kasumi Hanazuki, #553] === 4.2.0.rc2 * Fix double close bug on auth failure (or ruby 2.2 or earlier) [#538] === 4.2.0.rc1 * Improved logging with proxy command [Dmitriy Ivliev, #530] * Close transport on proxy error [adamruzicka, #526] * Support multiple identity files [Kimura Masayuki, #528] * Move `none` cipher to end of cipher list [Brian Cain, #525] * Deprecate `:paranoid` in favor of `:verify_host_key` [Jared Beck, #524] * Support Multile Include ssh config files [Kasumi Hanazuki, #516] * Support Relative path in ssh confif files [Akinori MUSHA, #510] * add direct-streamlocal@openssh.com support in Forward class [Harald Sitter, #502] === 4.1.0 === 4.1.0.rc1 * ProxyJump support [Ryan McGeary, #500] * Fix agent detection on Windows [Christian Koehler, #495] === 4.1.0.beta1 * Fix nil error when libsodium is not there [chapmajs ,#488] * SSH certificate support for client auth [David Bartley, #485] === 4.0.1 === 4.0.1.rc2 * ENV["HOME"] might be empty so filter non expandable paths [Matt Casper, #351] === 4.0.1.rc1 * support of rbnacl 4.0 and better error message [#479] * support include in config files [Kimura Masayuki, #475] * fixed issue with ruby 2.2 or older on windows [#472] === 4.0.0 === 4.0.0.rc3 * parse `+` character in config files [Christoph Lupprich, #470, #314] === 4.0.0.rc2 * Fixed OpenSSL 2.0/Ruby 2.4.0 warnings [Miklós Fazekas, #468] * Added ssh-ed25519 to KnownHosts:SUPPORTED_TYPE [detatka-kuzlatka-otevrete, Miklós Fazekas, #459] * Allow nil for :passhrase and passing in nil option is now a depreaction warning [Miklós Fazekas, #465] === 4.0.0.rc1 * Allow :password to be nil for capistrano v2 compatibility [Will Bryant, #357] * In next_packet if prefer consuming buffer before filling it again if we have enough data [Miklós Fazekas, #454] === 4.0.0.beta4 * Added exitstatus method to exec's return [Miklós Fazekas, #452] * Don't raise from exec if server closes transport just after channel close [Miklós Fazekas, #450] * Removed java_pageant, as jruby should be using regular pagent impl [Miklós Fazekas, ] * Use SSH_AUTH_SOCK if possible on windows (cygwin) [Miklós Fazekas, Martin Dürst, #365, #361] * HTTPS proxy support [Marcus Ilgner, #432] * Supports ruby 2.4.0.dev new exception type from OpenSSL::PKey.read === 4.0.0.beta3 * Fix Net::SSH::Disconnect exceptions when channels are closed cleanly [Miklos Fazekas, #421, #422] === 4.0.0.beta2 * Fix raiseUnlessLoaded undefined ERROR issue [Miklos Fazekas, #418] === 4.0.0.beta1 * Fix pageant [elconas, #235] * Relaxed rbnacl,rbnacl-selenium contstraints ang give better errors about them [Miklos Fazekas, #398] * Fix UTF-8 encoding issues [Ethan J. Brown, #407] === 4.0.0.alpha4 * Experimental event loop abstraction [Miklos Fazekas] * RbNacl dependency is optional [Miklos Fazekas] * agent_socket_factory option [Alon Goldboim] * client sends KEXINIT, it doesn't have to wait for server [Miklos Fazekas] * better error message when option is nil [Kane Morgan] * prompting can be customized [Miklos Fazekas] === 4.0.0.alpha3 * added max_select_wait_time [Eugene Kenny] === 4.0.0.alpha2 * when transport closes we're cleaning up channels [Miklos Fazekas] === 4.0.0.alpha1 * ed25519 key support [Miklos Fazekas] * removed camellia [Miklos Fazekas] === 3.1.0 === 3.1.0.rc1 * fix Secure#verify [Jean Boussier] * use the smallest of don't spend longer time than keepalive if it's configured [Eugene Kenny] === 3.1.0.beta3 * forward/on_open_failed should stop listning closed socket otherwise it locks #269 [Miklos Fazekas,Scott McGillivray] * fix incorrect pattern handling in config files #310 [Miklos Fazekas] === 3.1.0.beta2 * trying to execute something on a not yet opend channel throws nicer messag [Miklos Fazekas] * calling close on a not opened channel marks the channel for close [Miklos Fazekas] * read keepalive configuration from ssh config files [Miklos Fazekas] * send client version on hadshake before waiting for server to reduce handshake time [Miklos Fazekas] * allow custom Net::SSH::KnownHosts implementations [Jean Boussier] * memoize known host so we only search it once per session [Jean Boussier, Miklos Fazekas] === 3.0.2 === 3.0.2.rc1 * fixed rare WaitWritable error with proxy commands [Miklos Fazkas, Andre Meij]] * if Net::SSH.start user is nil and config has no entry we default to Etc.getlogin * Bugfix: CHANNEL_CLOSE was sent before draining ouput buffer #280 [Christopher F. Auston] === 3.0.1 === 3.0.1.rc1 * Breaking change from 2.* series: exec! without block now returns empty string instread of nil if command has no output [https://github.com/net-ssh/net-ssh/pull/273] * Support remote_user as %r in proxy commands [Dominic Scheirlinck] * Raise Net::SSH::ConnectionTimeout from connection timeout [Carl Hoerberg] === 3.0.0.rc1 * SemVer: Major version change because of dropping of ruby 1.9 === 2.10.1.rc2 * Win: Use fiddle on ruby 2.1 too [Charlie Savage] === 2.10.1.rc1 * Added ruby 2.0 requirement to gemspec [Alex Schultz] === 2.10.0 === 2.10.0-beta2 * Fix :passphrase option with :non_interactive [Jeremy Stanley] * Use Socket.tcp with connect_timeout instead of Timeout::timeout [Carl Hörberg] * Support for hostname hashes [Jef Mathiot] * Ruby 1.9.3 is no longer supported but should moslty work expect for stuff like connect_timeout === 2.10.0-beta1 * Fix could not parse PKey error. [Andrey Voronkov] * Workaround for threading issue in MRI + singleton method declaration [Matt Brictson] * Configuration change: we no longer append all supported algorithms, this is so you can exclude insecure algorithms. If you want to use the old behaviour specify append_all_supported_algorithms => true [voidus, mfazekas] * New configuration option: :non_interactive => true in case you prefer an authmethod to fail rather than prompt. [mfazekas] * Configuration change: password will now ask for password up to the :number_of_password_prompts times. If you want the 2.9.1 behaviour of never asking password please set number_of_password_prompts to 0. === 2.9.4-beta1 * Use sysread and syswrite on Windows instead of read_nonblock and write [marc-etienne] * Windows/peagant: use fiddle on ruby 2.2+/windows [Charlie Savage] * Check if ssh key is a file [kiela] === 2.9.3 === 2.9.2-rc3 * Remove advertised algorithms that were not working (curve25519-sha256@libssh.org) [mfazekas] === 2.9.2-rc2 * number_of_password_prompts is now accepted as ssh option, by setting it 0 net-ssh will not ask for password for password auth as with previous versions [mfazekas] === 2.9.2-rc1 * Documentation fixes and refactoring to keepalive [detiber, mfazekas] === 2.9.2-beta * Remove advertised algorithms that were not working (ssh-rsa-cert-* *ed25519 acm*-gcm@openssh.com) [mfazekas] * Unknown algorithms now ignored instead of failed [mfazekas] * Configuration change: Asks for password with password auth (up to number_of_password_prompts) [mfazekas] * Removed warnings [amatsuda] === 2.9.1 / 13 May 2014 * Fix for unknown response from agent on Windows with 64-bit PuTTY [chrahunt] * Support negative patterns in host lookup from the SSH config file [nirvdrum] === 2.9.0 / 30 Apr 2014 * New ciphers [chr4] * Added host keys: ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-ed25519-cert-v01@openssh.com ssh-ed25519 * Added HMACs: hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com umac-128-etm@openssh.com * Added Kex: aes256-gcm@openssh.com aes128-gcm@openssh.com curve25519-sha256@libssh.org * Added private key support for id_ed25519 * IdentiesOnly will not disable ssh_agent - fixes #148 and new fix for #137 [mfazekas] * Ignore errors during ssh agent negotiation [simonswine, jasiek] * Added an optional "options" argument to test socket open method [jefmathiot] * Added gem signing (again) with new cert [delano] === 2.8.1 / 19 Feb 2014 * Correct location of global known_hosts files [mfischer-zd] * Fix for password authentication [blackpond, zachlipton, delano] === 2.8.0 / 01 Feb 2014 * Handle ssh-rsa and ssh-dss certificate files [bobveznat] * Correctly interpret /etc/ssh_config Authentication settings based on openssh /etc/ssh_config system defaults [therealjessesanford, liggitt] * Fixed pageant support for Windows [jarredholman] * Support %r in ProxyCommand configuration in ssh_config files as defined in OpenSSH [yugui] * Don't use ssh-agent if :keys_only is true [SFEley] * Fix the bug in keys with comments [bobtfish] * Add a failing tests for options in pub keys [bobtfish] * Assert that the return value from ssh block is returned [carlhoerberg] * Don't close the connection it's already closed [carlhoerberg] * Ensure the connection closes even on exception [carlhoerberg] * Make the authentication error message more useful [deric] * Fix "ConnectionError" typo in lib/net/ssh/proxy/socks5.rb [mirakui] * Allow KeyManager to recover from incompatible agents [ecki, delano] * Fix for "Authentication Method determination can pick up a class from the root namespace" [dave.sieh] === 2.7.0 / 11 Sep 2013 * Fix for 'Could not parse PKey: no start line' error on private keys with passphrases (issue #101) [metametaclass] * Automatically forward environment variables defined in OpenSSH config files [fnordfish] * Guard against socket.gets being nil in Net::SSH::Proxy::HTTP [krishicks] * Implemented experimental keepalive feature [noric] === 2.6.8 / 6 Jul 2013 * Added support for host wildcard substitution [GabKlein] * Added a wait to the loop in close to help fix possible blocks [Josh Kalderimis] * Fixed test file encoding issues with Ruby 2.0 (#87) [voxik] === 2.6.7 / 11 Apr 2013 * Decreased default packet size to 32768 as described in RFC 4253 [Olipro] * Added max_pkt_size and max_win_size options to Net::SSH.start [Olipro] === 2.6.6 / 03 Mar 2013 * Fix for ruby 2.0 in windows [jansegre] === 2.6.5 / 06 Feb 2013 * Fixed path in gemspec [thanks priteau] === 2.6.4 / 06 Feb 2013 * Added license info to gemspec [jordimassaguerpla] * Added public cert. All gem releases are now signed. === 2.6.3 / 10 Jan 2013 * Small doc fix and correct error class for PKey::EC key type [Andreas Wolff] * Improve test dependencies [Kenichi Kamiya] === 2.6.2 / 22 Nov 2012 * Net::SSH.start now returns result of block [mhuffnagle] * Add stderr handling to Net::SSH::Test [ohrite] * Fix Invalid key size in JRuby [ohrite] === 2.6.1 / 18 Oct 2012 * Remove platform specific jruby dependency from gemspec * Changed encoding of file to prevent warnings when generating docs [iltempo] === 2.6.0 / 19 Sep 2012 * Use OpenSSL::PKey.read to read arbitrary private key. [nagachika] * Check availability of UNIXSocket and UNIXServer for Windows [Nobuhiro IMAI] * Bump version to 2.5.3 and depend on newer jruby-pageant version for Java 1.5 compat. [arturaz] * Implementation of the "none"-authentication method [dubspeed] * Add class for stricter host key verification [Andy Brody] === 2.5.2 / 25 May 2012 * Fix for Net::SSH::KnownHosts::SUPPORTED_TYPE [Marco Sandrini] === 2.5.1 / 24 May 2012 * Added missing file to manifest [Marco Sandrini] === 2.5.0 / 24 May 2012 * Implement many algorithms [Ryosuke Yamazaki] * Key Exchange * diffie-hellman-group14-sha1 * ecdh-sha2-nistp{256,384,521} * Host Key * ecdsa-sha2-nistp{256,384,521} * Authentication * ecdsa-sha2-nistp{256,384,521} * HMAC * hmac-ripemd160 * Cipher: * aes{128,192,256}-ctr * camellia{128,192,256}-ctr * blowfish-ctr * cast128-ctr * 3des-ctr * arcfour (has problems with weak keys, and should be used with caution) * camellia{128,192,256}-cbc === 2.4.0 / 17 May 2012 * Support for JRuby + Pageant + Windows [arturaz] === 2.3.0 / 11 Jan 2012 * Support for hmac-sha2 and diffie-hellman-group-exchange-sha256 [Ryosuke Yamazaki] === 2.2.2 / 04 Jan 2012 * Fixed: Connection hangs on ServerVersion.new(socket, logger) [muffl0n] * Avoid dying when unsupported auth mechanisms are defined [pcn] === 2.2.1 / 24 Aug 2011 * Do not prompt any passphrases before trying all identities from agent. [musybite] (see: http://net-ssh.lighthouseapp.com/projects/36253-net-ssh/tickets/30) === 2.2.0 / 16 Aug 2011 * Add support for forward a local UNIX domain socket to a remote TCP socket. [Mark Imbriaco] === 2.1.4 / 3 Apr 2011 * Add ConnectionTimeout exception class. [Joel Watson] See: https://github.com/net-ssh/net-ssh-multi/pull/1 === 2.1.3 / 2 Mar 2011 * Call to transport.closed should be transport.close [Woon Jung] === 2.1.2 / 1 Mar 2011 * Fix for Net::SSH Continues to attempt authentication when notified it is not allowed [Eric Hodel] (see: http://net-ssh.lighthouseapp.com/projects/36253-net-ssh/tickets/26) * Fix for transport won't be closed if authentication fails [Patrick Marchi] === 2.1 / 19 Jan 2011 * Support "IdentitiesOnly" directive (LH-24) [Musy Bite, Edmund Haselwanter] * Speeding up the Loggable module (LH-23) [robbebob] === 2.0.24 / 14 Jan 2011 * Fix for process code to correctly wait until remote_id is set before sending any output, including eof. [Daniel Pittman, Markus Roberts] * Fix circular require warning in Ruby 1.9.2 [Gavin Brock] === 2.0.23 / 03 Jun 2010 * delay CHANNEL_EOF packet until output buffer is empty [Rich Lane] Previously, calling #eof! after #send_data would result in the CHANNEL_EOF packet being sent immediately, ahead of the data in the output buffer. Now buffer becomes empty. === 2.0.22 / 20 Apr 2010 * Fix for: "Parsing the config errors out because it coerces the "1" into an integer and then tries to split it on spaces for multiple host checking." (http://net-ssh.lighthouseapp.com/projects/36253/tickets/10) [Lee Marlow] === 2.0.21 / 20 Mar 2010 * Fix for "IdentifyFile" in ~/.ssh/config does not work if no "Host" statement is given (http://net-ssh.lighthouseapp.com/projects/36253/tickets/9-identifyfile-in-sshconfig-does-not-work-if-no-host-statement-is-given#ticket-9-5) [xbaldauf, Delano Mandelbaum] * Fix for client closes a forwarded connection, but the server is reading, net-ssh terminates with IOError socket closed (http://net-ssh.lighthouseapp.com/projects/36253/tickets/7) [Miklós Fazekas] * Fix for client force closes (RST) a forwarded connection, but server is reading, net-ssh terminates with exception [Miklós Fazekas] * Fix for server closes the sending side, the on_eof is not handled. [Miklós Fazekas] * Removed Hanna dependency in Rakefile [Delano Mandelbaum] === 2.0.20 / 10 Feb 2010 * Support "ProxyCommand none" directive [Andy Lo-A-Foe] === 2.0.19 / 16 Jan 2010 * Support plus sign in sshconfig hostname [Jason Weathered] === 2.0.18 / 15 Jan 2010 * Fix related to #recv(1) to #readpartial change in 2.0.16 [Hans de Graaff, Delano Mandelbaum] === 2.0.17 / 14 Dec 2009 * Don't load net/ssh/authentication/pageant on Windows with Ruby 1.9 [Travis Reeder, Delano Mandelbaum] === 2.0.16 / 28 Nov 2009 * Fix for "multiple hosts are separated by whitespace" [Akinori MUSHA] * Add support for the ProxyCommand directive [Akinori MUSHA] * Switched from #recv(1) to #readpartial in lib/net/ssh/transport/server_version.rb, so that closed sockets are recognized [Alex Peuchert] === 2.0.15 / 03 Sep 2009 * Scale back IO#select patch so it mutexes only zero-timeout calls [Daniel Azuma, Will Bryant] === 2.0.14 / 28 Aug 2009 * Fix for IO#select threading bug in Ruby 1.8 (LH-1) [Daniel Azuma] * Fix for "uninitialized constant OpenSSL::Digest::MD5" exception in Net::SFTP [DL Redden] === 2.0.13 / 17 Aug 2009 * Added fix for hanging in ServerVersion#negotiate! when using SOCKS5 proxy (GH-9) [Gerald Talton] * Added support for specifying a list of hosts in .ssh/config, with tests (GH-6) [ckoehler, Delano Mandelbaum] * Added tests for arcfour128/256/512 lengths, encryption, and decryption [Delano Mandelbaum] * Skip packet stream tests for arcfour128/256/512 [Delano Mandelbaum] * Fix for OpenSSL cipher key length because it always returns 16, even when 32 byte keys are required, e.g. for arcfour256 and arcfour512 ciphers [Karl Varga] === 2.0.12 / 08 Jun 2009 * Applied patch for arcfour128 and arcfour256 support [Denis Bernard] * Use unbuffered reads when negotiating the protocol version [Steven Hazel] === 2.0.11 / 24 Feb 2009 * Add :key_data option for specifying raw private keys in PEM format [Alex Holems, Andrew Babkin] === 2.0.10 / 4 Feb 2009 * Added Net::SSH.configuration_for to make it easier to query the SSH configuration file(s) [Jamis Buck] === 2.0.9 / 1 Feb 2009 * Specifying non-nil user argument overrides user in .ssh/config [Jamis Buck] * Ignore requests for non-existent channels (workaround ssh server bug) [Jamis Buck] * Add terminate! method for hard shutdown scenarios [Jamis Buck] * Revert to pre-2.0.7 key-loading behavior by default, but load private-key if public-key doesn't exist [Jamis Buck] * Make sure :passphrase option gets passed to key manager [Bob Cotton] === 2.0.8 / 29 December 2008 * Fix private key change from 2.0.7 so that keys are loaded just-in-time, avoiding unecessary prompts from encrypted keys. [Jamis Buck] === 2.0.7 / 29 December 2008 * Make key manager use private keys instead of requiring public key to exist [arilerner@mac.com] * Fix failing tests [arilerner@mac.com] * Don't include pageant when running under JRuby [Angel N. Sciortino] === 2.0.6 / 6 December 2008 * Update the Manifest file so that the gem includes all necessary files [Jamis Buck] === 2.0.5 / 6 December 2008 * Make the Pageant interface comply with more of the Socket interface to avoid related errors [Jamis Buck] * Don't busy-wait on session close for remaining channels to close [Will Bryant] * Ruby 1.9 compatibility [Jamis Buck] * Fix Cipher#final to correctly flag a need for a cipher reset [Jamis Buck] === 2.0.4 / 27 Aug 2008 * Added Connection::Session#closed? and Transport::Session#closed? [Jamis Buck] * Numeric host names in .ssh/config are now parsed correct [Yanko Ivanov] * Make sure the error raised when a public key file is malformed is more informative than a MethodMissing error [Jamis Buck] * Cipher#reset is now called after Cipher#final, with the last n bytes used as the next initialization vector [Jamis Buck] === 2.0.3 / 27 Jun 2008 * Make Net::SSH::Version comparable [Brian Candler] * Fix errors in port forwarding when a channel could not be opened due to a typo in the exception name [Matthew Todd] * Use #chomp instead of #strip when cleaning the version string reported by the remote host, so that trailing whitespace is preserved (this is to play nice with servers like Mocana SSH) [Timo Gatsonides] * Correctly parse ssh_config entries with eq-sign delimiters [Jamis Buck] * Ignore malformed ssh_config entries [Jamis Buck] === 2.0.2 / 29 May 2008 * Make sure the agent client understands both RSA "identities answers" [Jamis Buck] * Fixed key truncation bug that caused hmacs other than SHA1 to fail with "corrupt hmac" errors [Jamis Buck] * Fix detection and loading of public keys when the keys don't actually exist [David Dollar] === 2.0.1 / 5 May 2008 * Teach Net::SSH about a handful of default key names [Jamis Buck] === 2.0.0 / 1 May 2008 * Allow the :verbose argument to accept symbols (:debug, etc.) as well as Logger level constants (Logger::DEBUG, etc.) [Jamis Buck] === 2.0 Preview Release 4 (1.99.3) / 19 Apr 2008 * Make sure HOME is set to something sane, even on OS's that don't set it by default [Jamis Buck] * Add a :passphrase option to specify the passphrase to use with private keys [Francis Sullivan] * Open a new auth agent connection for every auth-agent channel request [Jamis Buck] === 2.0 Preview Release 3 (1.99.2) / 10 Apr 2008 * Session properties [Jamis Buck] * Make channel open failure work with a callback so that failures can be handled similarly to successes [Jamis Buck] === 2.0 Preview Release 2 (1.99.1) / 22 Mar 2008 * Partial support for ~/.ssh/config (and related) SSH configuration files [Daniel J. Berger, Jamis Buck] * Added Net::SSH::Test to facilitate testing complex SSH state machines [Jamis Buck] * Reworked Net::SSH::Prompt to use conditionally-selected modules [Jamis Buck, suggested by James Rosen] * Added Channel#eof? and Channel#eof! [Jamis Buck] * Fixed bug in strict host key verifier on cache miss [Mike Timm] === 2.0 Preview Release 1 (1.99.0) / 21 Aug 2007 * First preview release of Net::SSH v2 net-ssh-7.2.1/DEVELOPMENT.md000066400000000000000000000013071454036133000152060ustar00rootroot00000000000000### Development notes ## Building/running ssh server in debug mode clone the openssh server from `https://github.com/openssh/openssh-portable` ```sh brew install openssl /usr/local/Cellar/openssl@3/3.1.0/bin/openssl autoreconf ./configure --with-ssl-dir=/usr/local/Cellar/openssl@3/3.1.0/ --with-audit=debug --enable-debug CPPFLAGS="-DDEBUG -DPACKET_DEBUG" CFLAGS="-g -O0" make ``` To run server in debug mode: ```sh echo '#' > /tmp/sshd_config ssh-keygen -t rsa -f /tmp/ssh_host_rsa_key # /Users/boga/Work/OSS/NetSSH/openssh-portable/sshd -p 2222 -D -d -d -d -e -f /tmp/sshd_config /Users/boga/Work/OSS/NetSSH/openssh-portable/sshd -p 2222 -D -d -d -d -e -f /tmp/sshd_config -h /tmp/ssh_host_rsa_key ``` net-ssh-7.2.1/Dockerfile000066400000000000000000000016401454036133000150740ustar00rootroot00000000000000ARG RUBY_VERSION=3.1 FROM ruby:${RUBY_VERSION} ARG BUNDLERV= RUN apt update && apt install -y openssh-server sudo netcat-openbsd \ && useradd --create-home --shell '/bin/bash' --comment 'NetSSH' 'net_ssh_1' \ && useradd --create-home --shell '/bin/bash' --comment 'NetSSH' 'net_ssh_2' \ && echo net_ssh_1:foopwd | chpasswd \ && echo net_ssh_2:foo2pwd | chpasswd \ && mkdir -p /home/net_ssh_1/.ssh \ && mkdir -p /home/net_ssh_2/.ssh \ && echo "net_ssh_1 ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \ && echo "net_ssh_2 ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \ && ssh-keygen -f /etc/ssh/users_ca -N '' ENV INSTALL_PATH="/netssh" WORKDIR $INSTALL_PATH COPY Gemfile net-ssh.gemspec $INSTALL_PATH/ COPY lib/net/ssh/version.rb $INSTALL_PATH/lib/net/ssh/version.rb RUN gem install bundler ${BUNDLERV} && bundle install COPY . $INSTALL_PATH/ CMD service ssh start && rake test && NET_SSH_NO_ED25519=1 rake test net-ssh-7.2.1/Dockerfile.openssl3000066400000000000000000000006421454036133000166420ustar00rootroot00000000000000FROM ubuntu:22.04 ENV INSTALL_PATH="/netssh" RUN apt update && apt install -y openssl ruby ruby-dev git build-essential WORKDIR $INSTALL_PATH COPY Gemfile net-ssh.gemspec $INSTALL_PATH/ COPY lib/net/ssh/version.rb $INSTALL_PATH/lib/net/ssh/version.rb RUN ls -l && gem install bundler && bundle install COPY . $INSTALL_PATH/ CMD openssl version && ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION' && rake test net-ssh-7.2.1/Gemfile000066400000000000000000000005701454036133000143760ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in mygem.gemspec gemspec gem 'byebug', group: %i[development test] if !Gem.win_platform? && RUBY_ENGINE == "ruby" 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 net-ssh-7.2.1/Gemfile.noed25519000066400000000000000000000005271454036133000157320ustar00rootroot00000000000000source 'https://rubygems.org' ENV['NET_SSH_NO_ED25519'] = 'true' # Specify your gem's dependencies in mygem.gemspec gemspec 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 net-ssh-7.2.1/Gemfile.norbnacl000066400000000000000000000005261454036133000161740ustar00rootroot00000000000000source 'https://rubygems.org' ENV['NET_SSH_NO_RBNACL'] = 'true' # Specify your gem's dependencies in mygem.gemspec gemspec 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 net-ssh-7.2.1/ISSUE_TEMPLATE.md000066400000000000000000000011711454036133000156060ustar00rootroot00000000000000### Expected behavior Tell us what should happen ### Actual behavior Tell us what happens instead. ### System configuration - net-ssh version - Ruby version ### Example App Please provide an example script that reproduces the problem. This will save maintainers time so they can spend it fixing your issues instead of trying to build a reproduction case from sparse instructions. You can use this as stating point: ```ruby gem 'net-ssh', '= 4.0.0.beta3' require 'net/ssh' puts Net::SSH::Version::CURRENT @host = 'localhost' @user = ENV['USER'] Net::SSH.start(@host, @user) do |ssh| puts ssh.exec!('echo "hello"') end ``` net-ssh-7.2.1/LICENSE.txt000066400000000000000000000020451454036133000147250ustar00rootroot00000000000000Copyright © 2008 Jamis Buck Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. net-ssh-7.2.1/Manifest000066400000000000000000000106261454036133000145770ustar00rootroot00000000000000CHANGELOG.rdoc Manifest README.rdoc Rakefile Rudyfile THANKS.rdoc lib/net/ssh.rb lib/net/ssh/authentication/agent.rb lib/net/ssh/authentication/constants.rb lib/net/ssh/authentication/key_manager.rb lib/net/ssh/authentication/methods/abstract.rb lib/net/ssh/authentication/methods/hostbased.rb lib/net/ssh/authentication/methods/keyboard_interactive.rb lib/net/ssh/authentication/methods/password.rb lib/net/ssh/authentication/methods/publickey.rb lib/net/ssh/authentication/pageant.rb lib/net/ssh/authentication/session.rb lib/net/ssh/buffer.rb lib/net/ssh/buffered_io.rb lib/net/ssh/config.rb lib/net/ssh/connection/channel.rb lib/net/ssh/connection/constants.rb lib/net/ssh/connection/session.rb lib/net/ssh/connection/term.rb lib/net/ssh/errors.rb lib/net/ssh/key_factory.rb lib/net/ssh/known_hosts.rb lib/net/ssh/loggable.rb lib/net/ssh/packet.rb lib/net/ssh/prompt.rb lib/net/ssh/proxy/command.rb lib/net/ssh/proxy/errors.rb lib/net/ssh/proxy/http.rb lib/net/ssh/proxy/socks4.rb lib/net/ssh/proxy/socks5.rb lib/net/ssh/service/forward.rb lib/net/ssh/test.rb lib/net/ssh/test/channel.rb lib/net/ssh/test/extensions.rb lib/net/ssh/test/kex.rb lib/net/ssh/test/local_packet.rb lib/net/ssh/test/packet.rb lib/net/ssh/test/remote_packet.rb lib/net/ssh/test/script.rb lib/net/ssh/test/socket.rb lib/net/ssh/transport/algorithms.rb lib/net/ssh/transport/cipher_factory.rb lib/net/ssh/transport/constants.rb lib/net/ssh/transport/ctr.rb lib/net/ssh/transport/hmac.rb lib/net/ssh/transport/hmac/abstract.rb lib/net/ssh/transport/hmac/md5.rb lib/net/ssh/transport/hmac/md5_96.rb lib/net/ssh/transport/hmac/none.rb lib/net/ssh/transport/hmac/ripemd160.rb lib/net/ssh/transport/hmac/sha1.rb lib/net/ssh/transport/hmac/sha1_96.rb lib/net/ssh/transport/hmac/sha2_256.rb lib/net/ssh/transport/hmac/sha2_256_96.rb lib/net/ssh/transport/hmac/sha2_512.rb lib/net/ssh/transport/hmac/sha2_512_96.rb lib/net/ssh/transport/identity_cipher.rb lib/net/ssh/transport/key_expander.rb lib/net/ssh/transport/kex.rb lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb lib/net/ssh/transport/openssl.rb lib/net/ssh/transport/packet_stream.rb lib/net/ssh/transport/server_version.rb lib/net/ssh/transport/session.rb lib/net/ssh/transport/state.rb lib/net/ssh/verifiers/accept_new.rb lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb lib/net/ssh/verifiers/always.rb lib/net/ssh/verifiers/never.rb lib/net/ssh/version.rb net-ssh.gemspec setup.rb support/arcfour_check.rb support/ssh_tunnel_bug.rb test/authentication/methods/common.rb test/authentication/methods/test_abstract.rb test/authentication/methods/test_hostbased.rb test/authentication/methods/test_keyboard_interactive.rb test/authentication/methods/test_password.rb test/authentication/methods/test_publickey.rb test/authentication/test_agent.rb test/authentication/test_key_manager.rb test/authentication/test_session.rb test/common.rb test/configs/eqsign test/configs/exact_match test/configs/host_plus test/configs/multihost test/configs/wild_cards test/connection/test_channel.rb test/connection/test_session.rb test/test_all.rb test/test_buffer.rb test/test_buffered_io.rb test/test_config.rb test/test_key_factory.rb test/transport/hmac/test_md5.rb test/transport/hmac/test_md5_96.rb test/transport/hmac/test_none.rb test/transport/hmac/test_ripemd160.rb test/transport/hmac/test_sha1.rb test/transport/hmac/test_sha1_96.rb test/transport/hmac/test_sha2_256.rb test/transport/hmac/test_sha2_256_96.rb test/transport/hmac/test_sha2_512.rb test/transport/hmac/test_sha2_512_96.rb test/transport/kex/test_diffie_hellman_group1_sha1.rb test/transport/kex/test_diffie_hellman_group14_sha1.rb test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb test/transport/kex/test_ecdh_sha2_nistp256.rb test/transport/kex/test_ecdh_sha2_nistp384.rb test/transport/kex/test_ecdh_sha2_nistp521.rb test/transport/test_algorithms.rb test/transport/test_cipher_factory.rb test/transport/test_hmac.rb test/transport/test_identity_cipher.rb test/transport/test_packet_stream.rb test/transport/test_server_version.rb test/transport/test_session.rb test/transport/test_state.rb net-ssh-7.2.1/README.md000066400000000000000000000276011454036133000143660ustar00rootroot00000000000000[![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://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 7.x * 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 *As of v2.6.4, all gem releases are signed. See [INSTALL](#install).* ## DESCRIPTION: Net::SSH is 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. ## FEATURES: * Execute processes on remote servers and capture their output * Run multiple processes in parallel over a single SSH connection * Support for SSH subsystems * Forward local and remote ports via an SSH connection ## Supported Algorithms Net::SSH 6.0 disables by default the usage of weak algorithms. We strongly recommend that you install a servers's version that supports the latest algorithms. It is possible to return to the previous behavior by adding the option : `append_all_supported_algorithms: true` Unsecure algoritms will definitely be removed in Net::SSH 8.*. ### Host Keys | Name | Support | Details | |----------------------|-----------------------|----------| | ssh-rsa | OK | | | ssh-ed25519 | OK | Require the gem `ed25519` | | ecdsa-sha2-nistp521 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) | | ecdsa-sha2-nistp384 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) | | ecdsa-sha2-nistp256 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) | | ssh-dss | Deprecated in 6.0 | unsecure, will be removed in 8.0 | ### Key Exchange | Name | Support | Details | |--------------------------------------|-----------------------|----------| | curve25519-sha256 | OK | Require the gem `x25519` | | ecdh-sha2-nistp521 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) | | ecdh-sha2-nistp384 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) | | ecdh-sha2-nistp256 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) | | diffie-hellman-group1-sha1 | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | diffie-hellman-group14-sha1 | OK | | | diffie-hellman-group-exchange-sha1 | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | diffie-hellman-group-exchange-sha256 | OK | | ### Encryption algorithms (ciphers) | Name | Support | Details | |--------------------------------------|-----------------------|----------| | aes256-ctr / aes192-ctr / aes128-ctr | OK | | | chacha20-poly1305@openssh.com | OK. | Requires the gem `rbnacl` | | aes256-cbc / aes192-cbc / aes128-cbc | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | rijndael-cbc@lysator.liu.se | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | blowfish-ctr blowfish-cbc | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | cast128-ctr cast128-cbc | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | 3des-ctr 3des-cbc | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | idea-cbc | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | none | Deprecated in 6.0 | unsecure, will be removed in 8.0 | ### Message Authentication Code algorithms | Name | Support | Details | |----------------------|-----------------------|----------| | hmac-sha2-512-etm | OK | | | hmac-sha2-256-etm | OK | | | hmac-sha2-512 | OK | | | hmac-sha2-256 | OK | | | hmac-sha2-512-96 | Deprecated in 6.0 | removed from the specification, will be removed in 8.0 | | hmac-sha2-256-96 | Deprecated in 6.0 | removed from the specification, will be removed in 8.0 | | hmac-sha1 | OK | for backward compatibility | | hmac-sha1-96 | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | hmac-ripemd160 | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | hmac-md5 | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | hmac-md5-96 | Deprecated in 6.0 | unsecure, will be removed in 8.0 | | none | Deprecated in 6.0 | unsecure, will be removed in 8.0 | ## SYNOPSIS: In a nutshell: ```ruby require 'net/ssh' Net::SSH.start('host', 'user', password: "password") do |ssh| # capture all stderr and stdout output from a remote process output = ssh.exec!("hostname") puts output # capture only stdout matching a particular pattern stdout = "" ssh.exec!("ls -l /home/jamis") do |channel, stream, data| stdout << data if stream == :stdout && /foo/.match(data) end puts stdout # run multiple processes in parallel to completion ssh.exec "sed ..." ssh.exec "awk ..." ssh.exec "rm -rf ..." ssh.loop # open a new channel and configure a minimal set of callbacks, then run # the event loop until the channel finishes (closes) channel = ssh.open_channel do |ch| ch.exec "/usr/local/bin/ruby /path/to/file.rb" do |ch, success| raise "could not execute command" unless success # "on_data" is called when the process writes something to stdout ch.on_data do |c, data| $stdout.print data end # "on_extended_data" is called when the process writes something to stderr ch.on_extended_data do |c, type, data| $stderr.print data end ch.on_close { puts "done!" } end end channel.wait # forward connections on local port 1234 to port 80 of www.capify.org ssh.forward.local(1234, "www.capify.org", 80) ssh.loop { true } end ``` See Net::SSH for more documentation, and links to further information. ## REQUIREMENTS: The only requirement you might be missing is the OpenSSL bindings for Ruby with a version greather than `1.0.1`. These are built by default on most platforms, but you can verify that they're built and installed on your system by running the following command line: ```sh ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION' ``` If that spits out something like `OpenSSL 1.0.1 14 Mar 2012`, then you're set. If you get an error, then you'll need to see about rebuilding ruby with OpenSSL support, or (if your platform supports it) installing the OpenSSL bindings separately. ## INSTALL: ```sh gem install net-ssh # might need sudo privileges ``` NOTE: If you are running on jruby on windows you need to install `jruby-pageant` manually (gemspec doesn't allow for platform specific dependencies at gem installation time). However, in order to be sure the code you're installing hasn't been tampered with, it's recommended that you verify the [signature](http://docs.rubygems.org/read/chapter/21). To do this, you need to add my public key as a trusted certificate (you only need to do this once): ```sh # Add the public key as a trusted certificate # (You only need to do this once) curl -O https://raw.githubusercontent.com/net-ssh/net-ssh/master/net-ssh-public_cert.pem gem cert --add net-ssh-public_cert.pem ``` Then, when install the gem, do so with high security: ```sh gem install net-ssh -P HighSecurity ``` If you don't add the public key, you'll see an error like "Couldn't verify data signature". If you're still having trouble let me know and I'll give you a hand. For ed25519 public key auth support your bundle file should contain `ed25519`, `bcrypt_pbkdf` dependencies. ```sh gem install ed25519 gem install bcrypt_pbkdf ``` For curve25519-sha256 kex exchange support your bundle file should contain `x25519` dependency. ## RUBY SUPPORT * See [net-ssh.gemspec](https://github.com/net-ssh/net-ssh/blob/master/net-ssh.gemspec) for current versions ruby requirements ## RUNNING TESTS If you want to run the tests or use any of the Rake tasks, you'll need Mocha and other dependencies listed in Gemfile Run the test suite from the net-ssh directory with the following command: ```sh bundle exec rake test ``` NOTE : you can run test on all ruby versions with docker : ``` docker-compose up --build ``` Run a single test file like this: ```sh ruby -Ilib -Itest test/transport/test_server_version.rb ``` To run integration tests see [here](test/integration/README.md) ### BUILDING GEM ```sh rake build ``` ### GEM SIGNING (for maintainers) If you have the net-ssh private signing key, you will be able to create signed release builds. Make sure the private key path matches the `signing_key` path set in `net-ssh.gemspec` and tell rake to sign the gem by setting the `NET_SSH_BUILDGEM_SIGNED` flag: ```sh NET_SSH_BUILDGEM_SIGNED=true rake build ``` For time to time, the public certificate associated to the private key needs to be renewed. You can do this with the following command: ```sh gem cert --build netssh@solutious.com --private-key path/2/net-ssh-private_key.pem mv gem-public_cert.pem net-ssh-public_cert.pem gem cert --add net-ssh-public_cert.pem ``` ## Security contact information See [SECURITY.md](SECURITY.md) ## CREDITS ### Contributors This project exists thanks to all the people who contribute. [![contributors](https://opencollective.com/net-ssh/contributors.svg?width=890&button=false)](graphs/contributors) ### Backers Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/net-ssh#backer) [![backers](https://opencollective.com/net-ssh/backers.svg?width=890)](https://opencollective.com/net-ssh#backers) ### Sponsors Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/net-ssh#sponsor) [![Sponsor](https://opencollective.com/net-ssh/sponsor/0/avatar.svg)](https://opencollective.com/net-ssh/sponsor/0/website) ## LICENSE: (The MIT License) Copyright (c) 2008 Jamis Buck Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. net-ssh-7.2.1/Rakefile000066400000000000000000000137151454036133000145550ustar00rootroot00000000000000# # Also in your terminal environment run: # $ export LANG=en_US.UTF-8 # $ export LANGUAGE=en_US.UTF-8 # $ export LC_ALL=en_US.UTF-8 require "rubygems" require "rake" require "rake/clean" require "bundler/gem_tasks" require "rdoc/task" desc "When releasing make sure NET_SSH_BUILDGEM_SIGNED is set" task :check_NET_SSH_BUILDGEM_SIGNED do raise "NET_SSH_BUILDGEM_SIGNED should be set to release" unless ENV['NET_SSH_BUILDGEM_SIGNED'] end Rake::Task[:release].enhance [:check_NET_SSH_BUILDGEM_SIGNED] Rake::Task[:release].prerequisites.unshift(:check_NET_SSH_BUILDGEM_SIGNED) task default: ["build"] CLEAN.include ['pkg', 'rdoc'] name = "net-ssh" require_relative "lib/net/ssh/version" version = Net::SSH::Version::CURRENT extra_files = %w[LICENSE.txt THANKS.txt CHANGES.txt] RDoc::Task.new do |rdoc| rdoc.rdoc_dir = "rdoc" rdoc.title = "#{name} #{version}" rdoc.generator = 'hanna' # gem install hanna-nouveau rdoc.main = 'README.md' rdoc.rdoc_files.include("README*") rdoc.rdoc_files.include("bin/*.rb") rdoc.rdoc_files.include("lib/**/*.rb") extra_files.each { |file| rdoc.rdoc_files.include(file) if File.exist?(file) } end namespace :cert do desc "Update public cert from private - only run if public is expired" task :update_public_when_expired do require 'openssl' require 'time' 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" end end def change_version(&block) version_file = 'lib/net/ssh/version.rb' require_relative version_file pre = Net::SSH::Version::PRE tiny = Net::SSH::Version::TINY result = block[pre: pre, tiny: Net::SSH::Version::TINY] raise "Version change logic should always return a pre", ArgumentError unless result.key?(:pre) new_pre = result[:pre] new_tiny = result[:tiny] || tiny found = { pre: false, tiny: false } File.open("#{version_file}.new", "w") do |f| File.readlines(version_file).each do |line| match = if pre.nil? /^(\s+PRE\s+=\s+)nil(\s*)$/.match(line) else /^(\s+PRE\s+=\s+")#{pre}("\s*)$/.match(line) end if match prefix = match[1] postfix = match[2] prefix.delete_suffix!('"') postfix.delete_prefix!('"') new_line = "#{prefix}#{new_pre.inspect}#{postfix}" puts "Changing:\n - #{line} + #{new_line}" line = new_line found[:pre] = true end if new_tiny != tiny match = /^(\s+TINY\s+=\s+)#{tiny}(\s*)$/.match(line) if match prefix = match[1] postfix = match[2] new_line = "#{prefix}#{new_tiny}#{postfix}" puts "Changing:\n - #{line} + #{new_line}" line = new_line found[:tiny] = true end end f.write(line) end raise ArgumentError, "Cound not find line: PRE = \"#{pre}\" in #{version_file}" unless found[:pre] raise ArgumentError, "Cound not find line: TINY = \"#{tiny}\" in #{version_file}" unless found[:tiny] || new_tiny == tiny end FileUtils.mv version_file, "#{version_file}.old" FileUtils.mv "#{version_file}.new", version_file end namespace :vbump do desc "Final release" task :final do change_version do |pre:, tiny:| _ = tiny raise ArgumentError, "Unexpected pre: #{pre}" if pre.nil? { pre: nil } end end desc "Increment prerelease" task :pre, [:type] do |_t, args| change_version do |pre:, tiny:| puts " PRE => #{pre.inspect}" match = /^([a-z]+)(\d+)/.match(pre) raise ArgumentError, "Unexpected pre: #{pre}" if match.nil? && args[:type].nil? if match.nil? || (!args[:type].nil? && args[:type] != match[1]) if pre.nil? { pre: "#{args[:type]}1", tiny: tiny + 1 } else { pre: "#{args[:type]}1" } end else { pre: "#{match[1]}#{match[2].to_i + 1}" } end end end end namespace :rdoc do desc "Update gh-pages branch" task :publish do # copy/checkout rm_rf "/tmp/net-ssh-rdoc" rm_rf "/tmp/net-ssh-gh-pages" cp_r "./rdoc", "/tmp/net-ssh-rdoc" mkdir "/tmp/net-ssh-gh-pages" Dir.chdir "/tmp/net-ssh-gh-pages" do sh "git clone --branch gh-pages --single-branch https://github.com/net-ssh/net-ssh" rm_rf "/tmp/net-ssh-gh-pages/net-ssh/*" end # update sh "cp -rf ./rdoc/* /tmp/net-ssh-gh-pages/net-ssh/" Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do sh "git add -A ." sh "git commit -m \"Update docs\"" end # publish Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do sh "git push origin gh-pages" end end end require 'rake/testtask' Rake::TestTask.new do |t| t.libs = ["lib", "test"] t.libs << "test/integration" if ENV['NET_SSH_RUN_INTEGRATION_TESTS'] t.libs << "test/win_integration" if ENV['NET_SSH_RUN_WIN_INTEGRATION_TESTS'] test_files = FileList['test/**/test_*.rb'] test_files -= FileList['test/integration/**/test_*.rb'] unless ENV['NET_SSH_RUN_INTEGRATION_TESTS'] test_files -= FileList['test/win_integration/**/test_*.rb'] unless ENV['NET_SSH_RUN_WIN_INTEGRATION_TESTS'] test_files -= FileList['test/manual/test_*.rb'] test_files -= FileList['test/test_pageant.rb'] test_files -= FileList['test/test/**/test_*.rb'] t.test_files = test_files end # We need to enable the OpenSSL 3.0 legacy providers for our test suite require 'openssl' ENV['OPENSSL_CONF'] = 'test/openssl3.conf' if OpenSSL::OPENSSL_LIBRARY_VERSION.start_with? "OpenSSL 3" desc "Run tests of Net::SSH:Test" Rake::TestTask.new do |t| t.name = "test_test" # we need to run test/test separatedly as it hacks io + other modules t.libs = ["lib", "test"] test_files = FileList['test/test/**/test_*.rb'] t.test_files = test_files end net-ssh-7.2.1/SECURITY.md000066400000000000000000000004231454036133000146710ustar00rootroot00000000000000## Security contact information To report a security vulnerability, please use the [GitHub private vulnerability reporting feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability). net-ssh-7.2.1/THANKS.txt000066400000000000000000000034361454036133000146400ustar00rootroot00000000000000Net::SSH was originally written by Jamis Buck . It is currently maintained by Delano Mandelbaum . In addition, the following individuals are gratefully acknowledged for their contributions: GOTOU Yuuzou * help and code related to OpenSSL Guillaume Marçais * support for communicating with the the PuTTY "pageant" process Daniel Berger * help getting unit tests in earlier Net::SSH versions to pass in Windows * initial version of Net::SSH::Config provided inspiration and encouragement Chris Andrews and Lee Jensen * support for ssh agent forwarding Hiroshi Nakamura * fixed errors with JRuby tests bobveznat therealjessesanford liggitt jarredholman yugui SFEley bobtfish carlhoerberg deric mirakui ecki Dave Sieh metametaclass fnordfish krishicks noric GabKlein Josh Kalderimis voxik Olipro jansegre priteau jordimassaguerpla Kenichi Kamiya Andreas Wolff mhuffnagle ohrite iltempo nagachika Nobuhiro IMAI arturaz dubspeed Andy Brody Marco Sandrini Ryosuke Yamazaki muffl0n pcn musybite Mark Imbriaco Joel Watson Woon Jung Edmund Haselwanter robbebob Daniel Pittman Markus Roberts Gavin Brock Rich Lane Lee Marlow xbaldauf Delano Mandelbaum Miklós Fazekas Andy Lo-A-Foe Jason Weathered Hans de Graaff Travis Reeder Akinori MUSHA Alex Peuchert Daniel Azuma Will Bryant Gerald Talton ckoehler Karl Varga Denis Bernard Steven Hazel Alex Holems Andrew Babkin Bob Cotton Yanko Ivanov Angel N. Sciortino arilerner@mac.com David Dollar Timo Gatsonides Matthew Todd Brian Candler Francis Sullivan James Rosen Mike Timm guns devrandom kachick Pablo Merino thedarkone czarneckid jbarnette watsonian Grant Hutchins Michael Schubert mtrudel Aurélien Derouineau net-ssh-7.2.1/appveyor.yml000066400000000000000000000036031454036133000154730ustar00rootroot00000000000000version: '{build}' skip_tags: true environment: matrix: - ruby_version: "jruby-9.1.2.0" - ruby_version: "26-x64" - ruby_version: "25-x64" - ruby_version: "24-x64" - ruby_version: "23" - ruby_version: "23-x64" matrix: allow_failures: - ruby_version: "jruby-9.1.2.0" #init: # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) #on_finish: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) platform: - x86 install: - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - if "%ruby_version%" == "jruby-9.1.2.0" ( cinst javaruntime -i ) - if "%ruby_version%" == "jruby-9.1.2.0" ( cinst jruby --version 9.1.2.0 -i --allow-empty-checksums ) - if "%ruby_version%" == "jruby-9.1.2.0" ( SET "PATH=C:\jruby-9.1.2.0\bin\;%PATH%" ) - ruby --version - gem install bundler --no-document --user-install -v 1.17 - SET BUNDLE_GEMFILE=Gemfile.noed25519 - bundle install --retry=3 - cinst freesshd - cinst putty --allow-empty-checksums - ps: | if ($env:Processor_Architecture -eq "x86") { dir 'C:\Program Files\' dir 'C:\Program Files\freeSSHd' cp 'test\win_integration\FreeSSHDService.ini' 'C:\Program Files\freeSSHd\FreeSSHDService.ini' & 'C:\Program Files\freeSSHd\FreeSSHDService.exe' } else { dir 'C:\Program Files (x86)\' dir 'C:\Program Files (x86)\freeSSHd' cp 'test\win_integration\FreeSSHDService32.ini' 'C:\Program Files (x86)\freeSSHd\FreeSSHDService.ini' & 'C:\Program Files (x86)\freeSSHd\FreeSSHDService.exe' } test_script: - SET BUNDLE_GEMFILE=Gemfile.noed25519 - SET NET_SSH_RUN_WIN_INTEGRATION_TESTS=YES - bundle exec rake test build: off net-ssh-7.2.1/docker-compose.yml000066400000000000000000000006201454036133000165340ustar00rootroot00000000000000version: '3' services: ruby-3.1: build: context: . args: RUBY_VERSION: 3.1 ruby-3.0: build: context: . args: RUBY_VERSION: 3.0 ruby-2.7: build: context: . args: RUBY_VERSION: 2.7 BUNDLERV: "-v 2.2.28" ruby-2.6: build: context: . args: RUBY_VERSION: 2.6 BUNDLERV: "-v 2.4.22" net-ssh-7.2.1/lib/000077500000000000000000000000001454036133000136475ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/000077500000000000000000000000001454036133000144355ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh.rb000066400000000000000000000405061454036133000155640ustar00rootroot00000000000000# Make sure HOME is set, regardless of OS, so that File.expand_path works # as expected with tilde characters. ENV['HOME'] ||= ENV['HOMEPATH'] ? "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" : Dir.pwd require 'logger' require 'etc' require 'shellwords' require 'net/ssh/config' require 'net/ssh/errors' require 'net/ssh/loggable' require 'net/ssh/transport/session' require 'net/ssh/authentication/session' 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 # via the convenience methods on Net::SSH::Connection::Session, by opening # and interacting with new channels (Net::SSH::Connection:Session#open_channel # and Net::SSH::Connection::Channel), or by forwarding local and/or # remote ports through the connection (Net::SSH::Service::Forward). # # The SSH protocol is very event-oriented. Requests are sent from the client # to the server, and are answered asynchronously. This gives great flexibility # (since clients can have multiple requests pending at a time), but it also # adds complexity. Net::SSH tries to manage this complexity by providing # some simpler methods of synchronous communication (see Net::SSH::Connection::Session#exec!). # # In general, though, and if you want to do anything more complicated than # simply executing commands and capturing their output, you'll need to use # channels (Net::SSH::Connection::Channel) to build state machines that are # executed while the event loop runs (Net::SSH::Connection::Session#loop). # # Net::SSH::Connection::Session and Net::SSH::Connection::Channel have more # information about this technique. # # = "Um, all I want to do is X, just show me how!" # # == X == "execute a command and capture the output" # # Net::SSH.start("host", "user", password: "password") do |ssh| # result = ssh.exec!("ls -l") # puts result # end # # == X == "forward connections on a local port to a remote host" # # Net::SSH.start("host", "user", password: "password") do |ssh| # ssh.forward.local(1234, "www.google.com", 80) # ssh.loop { true } # end # # == X == "forward connections on a remote port to the local host" # # Net::SSH.start("host", "user", password: "password") do |ssh| # ssh.forward.remote(80, "www.google.com", 1234) # ssh.loop { true } # end module SSH # This is the set of options that Net::SSH.start recognizes. See # Net::SSH.start for a description of each option. VALID_OPTIONS = %i[ auth_methods bind_address compression compression_level config encryption forward_agent hmac host_key identity_agent remote_user keepalive keepalive_interval keepalive_maxcount kex keys key_data keycerts keycert_data languages logger paranoid password port proxy rekey_blocks_limit rekey_limit rekey_packet_limit timeout verbose known_hosts global_known_hosts_file user_known_hosts_file host_key_alias host_name user properties passphrase keys_only max_pkt_size max_win_size send_env set_env use_agent number_of_password_prompts append_all_supported_algorithms non_interactive password_prompt agent_socket_factory minimum_dh_bits verify_host_key fingerprint_hash check_host_ip pubkey_algorithms ] # The standard means of starting a new SSH connection. When used with a # block, the connection will be closed when the block terminates, otherwise # the connection will just be returned. The yielded (or returned) value # will be an instance of Net::SSH::Connection::Session (q.v.). (See also # Net::SSH::Connection::Channel and Net::SSH::Service::Forward.) # # Net::SSH.start("host", "user") do |ssh| # ssh.exec! "cp /some/file /another/location" # hostname = ssh.exec!("hostname") # # ssh.open_channel do |ch| # ch.exec "sudo -p 'sudo password: ' ls" do |ch, success| # abort "could not execute sudo ls" unless success # # ch.on_data do |ch, data| # print data # if data =~ /sudo password: / # ch.send_data("password\n") # end # end # end # end # # ssh.loop # end # # This method accepts the following options (all are optional): # # * :auth_methods => an array of authentication methods to try # * :bind_address => the IP address on the connecting machine to use in # establishing connection. (:bind_address is discarded if :proxy # is set.) # * :check_host_ip => Also ckeck IP address when connecting to remote host. # Defaults to +true+. # * :compression => the compression algorithm to use, or +true+ to use # whatever is supported. # * :compression_level => the compression level to use when sending data # * :config => set to +true+ to load the default OpenSSH config files # (~/.ssh/config, /etc/ssh_config), or to +false+ to not load them, or to # a file-name (or array of file-names) to load those specific configuration # files. Defaults to +true+. # * :encryption => the encryption cipher (or ciphers) to use # * :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. # * :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). # * :hmac => the hmac algorithm (or algorithms) to use # * :host_key => the host key algorithm (or algorithms) to use # * :host_key_alias => the host name to use when looking up or adding a # host to a known_hosts dictionary file # * :host_name => the real host name or IP to log into. This is used # instead of the +host+ parameter, and is primarily only useful when # specified in an SSH configuration file. It lets you specify an # "alias", similarly to adding an entry in /etc/hosts but without needing # to modify /etc/hosts. # * :keepalive => set to +true+ to send a keepalive packet to the SSH server # when there's no traffic between the SSH server and Net::SSH client for # the keepalive_interval seconds. Defaults to +false+. # * :keepalive_interval => the interval seconds for keepalive. # Defaults to +300+ seconds. # * :keepalive_maxcount => the maximun number of keepalive packet miss allowed. # Defaults to 3 # * :kex => the key exchange algorithm (or algorithms) to use # * :keys => an array of file names of private keys to use for publickey # and hostbased authentication # * :keycerts => an array of file names of key certificates to use # with publickey authentication # * :keycert_data => an array of strings, which each element of the array # being a key certificate to use with publickey authentication # * :key_data => an array of strings, with each element of the array being # a raw private key in PEM format. # * :keys_only => set to +true+ to use only private keys from +keys+ and # +key_data+ parameters, even if ssh-agent offers more identities. This # option is intended for situations where ssh-agent offers many different # identites. # * :logger => the logger instance to use when logging # * :max_pkt_size => maximum size we tell the other side that is supported per # packet. Default is 0x8000 (32768 bytes). Increase to 0x10000 (65536 bytes) # for better performance if your SSH server supports it (most do). # * :max_win_size => maximum size we tell the other side that is supported for # the window. # * :non_interactive => set to true if your app is non interactive and prefers # authentication failure vs password prompt. Non-interactive applications # should set it to true to prefer failing a password/etc auth methods vs. # asking for password. # * :paranoid => deprecated alias for :verify_host_key # * :passphrase => the passphrase to use when loading a private key (default # is +nil+, for no passphrase) # * :password => the password to use to login # * :port => the port to use when connecting to the remote host # * :properties => a hash of key/value pairs to add to the new connection's # properties (see Net::SSH::Connection::Session#properties) # * :proxy => a proxy instance (see Proxy) to use when connecting # * :pubkey_algorithms => the public key authentication algorithms to use for # this connection. Valid values are 'rsa-sha2-256-cert-v01@openssh.com', # 'ssh-rsa-cert-v01@openssh.com', 'rsa-sha2-256', 'ssh-rsa'. Currently, this # option is only used for RSA public key authentication and ignored for other # types. # * :rekey_blocks_limit => the max number of blocks to process before rekeying # * :rekey_limit => the max number of bytes to process before rekeying # * :rekey_packet_limit => the max number of packets to process before rekeying # * :send_env => an array of local environment variable names to export to the # remote environment. Names may be given as String or Regexp. # * :set_env => a hash of environment variable names and values to set to the # remote environment. Override the ones if specified in +send_env+. # * :timeout => how long to wait for the initial connection to be made # * :user => the user name to log in as; this overrides the +user+ # parameter, and is primarily only useful when provided via an SSH # configuration file. # * :remote_user => used for substitution into the '%r' part of a ProxyCommand # * :user_known_hosts_file => the location of the user known hosts file. # Set to an array to specify multiple user known hosts files. # Defaults to %w(~/.ssh/known_hosts ~/.ssh/known_hosts2). # * :use_agent => Set false to disable the use of ssh-agent. Defaults to # true # * :identity_agent => the path to the ssh-agent's UNIX socket # * :verbose => how verbose to be (Logger verbosity constants, Logger::DEBUG # is very verbose, Logger::FATAL is all but silent). Logger::FATAL is the # default. The symbols :debug, :info, :warn, :error, and :fatal are also # supported and are translated to the corresponding Logger constant. # * :append_all_supported_algorithms => set to +true+ to append all supported # algorithms by net-ssh. Was the default behaviour until 2.10 # * :number_of_password_prompts => Number of prompts for the password # authentication method defaults to 3 set to 0 to disable prompt for # password auth method # * :password_prompt => a custom prompt object with ask method. See Net::SSH::Prompt # # * :agent_socket_factory => enables the user to pass a lambda/block that will serve as the socket factory # Net::SSH.start(host,user,agent_socket_factory: ->{ UNIXSocket.open('/foo/bar') }) # example: ->{ UNIXSocket.open('/foo/bar')} # * :verify_host_key => specify how strict host-key verification should be. # In order of increasing strictness: # * :never (very insecure) ::Net::SSH::Verifiers::Never # * :accept_new_or_local_tunnel (insecure) ::Net::SSH::Verifiers::AcceptNewOrLocalTunnel # * :accept_new (insecure) ::Net::SSH::Verifiers::AcceptNew # * :always (secure) ::Net::SSH::Verifiers::Always # You can also provide an own Object which responds to +verify+. The argument # given to +verify+ is a hash consisting of the +:key+, the +:key_blob+, # the +:fingerprint+ and the +:session+. Returning true accepts the host key, # returning false declines it and closes the connection. # * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256' # If +user+ parameter is nil it defaults to USER from ssh_config, or # local username def self.start(host, user = nil, options = {}, &block) invalid_options = options.keys - VALID_OPTIONS if invalid_options.any? raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}" end assign_defaults(options) _sanitize_options(options) options[:user] = user if user options = configuration_for(host, options.fetch(:config, true)).merge(options) host = options.fetch(:host_name, host) options[:check_host_ip] = true unless options.key?(:check_host_ip) if options[:non_interactive] options[:number_of_password_prompts] = 0 end _support_deprecated_option_paranoid(options) if options[:verbose] options[:logger].level = case options[:verbose] when Integer then options[:verbose] when :debug then Logger::DEBUG when :info then Logger::INFO when :warn then Logger::WARN when :error then Logger::ERROR when :fatal then Logger::FATAL else raise ArgumentError, "can't convert #{options[:verbose].inspect} to any of the Logger level constants" end end transport = Transport::Session.new(host, options) auth = Authentication::Session.new(transport, options) user = options.fetch(:user, user) || Etc.getpwuid.name if auth.authenticate("ssh-connection", user, options[:password]) connection = Connection::Session.new(transport, options) if block_given? begin yield connection ensure connection.close unless connection.closed? end else return connection end else transport.close raise AuthenticationFailed, "Authentication failed for user #{user}@#{host}" end end # Returns a hash of the configuration options for the given host, as read # from the SSH configuration file(s). If +use_ssh_config+ is true (the # default), this will load configuration from both ~/.ssh/config and # /etc/ssh_config. If +use_ssh_config+ is nil or false, nothing will be # loaded (and an empty hash returned). Otherwise, +use_ssh_config+ may # be a file name (or array of file names) of SSH configuration file(s) # to read. # # See Net::SSH::Config for the full description of all supported options. def self.configuration_for(host, use_ssh_config) files = case use_ssh_config when true then Net::SSH::Config.expandable_default_files when false, nil then return {} else Array(use_ssh_config) end Net::SSH::Config.for(host, files) end def self.assign_defaults(options) if !options[:logger] options[:logger] = Logger.new(STDERR) options[:logger].level = Logger::FATAL end options[:password_prompt] ||= Prompt.default(options) %i[password passphrase].each do |key| options.delete(key) if options.key?(key) && options[key].nil? end end def self._sanitize_options(options) invalid_option_values = [nil, [nil]] unless (options.values & invalid_option_values).empty? nil_options = options.select { |_k, v| invalid_option_values.include?(v) }.map(&:first) Kernel.warn "#{caller_locations(2, 1)[0]}: Passing nil, or [nil] to Net::SSH.start is deprecated for keys: #{nil_options.join(', ')}" end end private_class_method :_sanitize_options def self._support_deprecated_option_paranoid(options) if options.key?(:paranoid) Kernel.warn( ":paranoid is deprecated, please use :verify_host_key. Supported " \ "values are exactly the same, only the name of the option has changed." ) if options.key?(:verify_host_key) Kernel.warn( "Both :paranoid and :verify_host_key were specified. " \ ":verify_host_key takes precedence, :paranoid will be ignored." ) else options[:verify_host_key] = options.delete(:paranoid) end end end private_class_method :_support_deprecated_option_paranoid end end net-ssh-7.2.1/lib/net/ssh/000077500000000000000000000000001454036133000152325ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/authentication/000077500000000000000000000000001454036133000202515ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/authentication/agent.rb000066400000000000000000000300141454036133000216720ustar00rootroot00000000000000require 'net/ssh/buffer' require 'net/ssh/errors' require 'net/ssh/loggable' require 'net/ssh/transport/server_version' require 'socket' require 'rubygems' require 'net/ssh/authentication/pageant' if Gem.win_platform? && RUBY_PLATFORM != "java" module Net module SSH 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 # This class implements a simple client for the ssh-agent protocol. It # does not implement any specific protocol, but instead copies the # behavior of the ssh-agent functions in the OpenSSH library (3.8). # # This means that although it behaves like a SSH1 client, it also has # some SSH2 functionality (like signing data). class Agent include Loggable # A simple module for extending keys, to allow comments to be specified # for them. module Comment attr_accessor :comment end SSH2_AGENT_REQUEST_VERSION = 1 SSH2_AGENT_REQUEST_IDENTITIES = 11 SSH2_AGENT_IDENTITIES_ANSWER = 12 SSH2_AGENT_SIGN_REQUEST = 13 SSH2_AGENT_SIGN_RESPONSE = 14 SSH2_AGENT_ADD_IDENTITY = 17 SSH2_AGENT_REMOVE_IDENTITY = 18 SSH2_AGENT_REMOVE_ALL_IDENTITIES = 19 SSH2_AGENT_LOCK = 22 SSH2_AGENT_UNLOCK = 23 SSH2_AGENT_ADD_ID_CONSTRAINED = 25 SSH2_AGENT_FAILURE = 30 SSH2_AGENT_VERSION_RESPONSE = 103 SSH_COM_AGENT2_FAILURE = 102 SSH_AGENT_REQUEST_RSA_IDENTITIES = 1 SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2 SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5 SSH_AGENT_FAILURE = 5 SSH_AGENT_SUCCESS = 6 SSH_AGENT_CONSTRAIN_LIFETIME = 1 SSH_AGENT_CONSTRAIN_CONFIRM = 2 SSH_AGENT_RSA_SHA2_256 = 0x02 SSH_AGENT_RSA_SHA2_512 = 0x04 # The underlying socket being used to communicate with the SSH agent. attr_reader :socket # Instantiates a new agent object, connects to a running SSH agent, # negotiates the agent protocol version, and returns the agent object. def self.connect(logger = nil, agent_socket_factory = nil, identity_agent = nil) agent = new(logger) agent.connect!(agent_socket_factory, identity_agent) agent.negotiate! agent end # Creates a new Agent object, using the optional logger instance to # report status. def initialize(logger = nil) self.logger = logger end # Connect to the agent process using the socket factory and socket name # given by the attribute writers. If the agent on the other end of the # socket reports that it is an SSH2-compatible agent, this will fail # (it only supports the ssh-agent distributed by OpenSSH). def connect!(agent_socket_factory = nil, identity_agent = nil) debug { "connecting to ssh-agent" } @socket = if agent_socket_factory agent_socket_factory.call elsif identity_agent unix_socket_class.open(File.expand_path(identity_agent)) elsif ENV['SSH_AUTH_SOCK'] && unix_socket_class unix_socket_class.open(File.expand_path(ENV['SSH_AUTH_SOCK'])) elsif Gem.win_platform? && RUBY_ENGINE != "jruby" Pageant::Socket.open else raise AgentNotAvailable, "Agent not configured" end rescue StandardError => e error { "could not connect to ssh-agent: #{e.message}" } raise AgentNotAvailable, $!.message end # Attempts to negotiate the SSH agent protocol version. Raises an error # if the version could not be negotiated successfully. def negotiate! # determine what type of agent we're communicating with 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 raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}" end end # Return an array of all identities (public keys) known to the agent. # Each key returned is augmented with a +comment+ property which is set # to the comment returned by the agent for that key. def identities type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES) raise AgentError, "could not get identity count" if agent_failed(type) raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER identities = [] body.read_long.times do key_str = body.read_string comment_str = body.read_string begin key = Buffer.new(key_str).read_key if key.nil? error { "ignoring invalid key: #{comment_str}" } next end key.extend(Comment) key.comment = comment_str identities.push key rescue NotImplementedError => e error { "ignoring unimplemented key:#{e.message} #{comment_str}" } end end return identities end # Closes this socket. This agent reference is no longer able to # query the agent. def close @socket.close end # Using the agent and the given public key, sign the given data. The # signature is returned in SSH2 format. def sign(key, data, flags = 0) type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, flags) raise AgentError, "agent could not sign data with requested identity" if agent_failed(type) raise AgentError, "bad authentication response #{type}" if type != SSH2_AGENT_SIGN_RESPONSE return reply.read_string end # Adds the private key with comment to the agent. # If lifetime is given, the key will automatically be removed after lifetime # seconds. # If confirm is true, confirmation will be required for each agent signing # operation. def add_identity(priv_key, comment, lifetime: nil, confirm: false) constraints = Buffer.new if lifetime constraints.write_byte(SSH_AGENT_CONSTRAIN_LIFETIME) constraints.write_long(lifetime) end constraints.write_byte(SSH_AGENT_CONSTRAIN_CONFIRM) if confirm req_type = constraints.empty? ? SSH2_AGENT_ADD_IDENTITY : SSH2_AGENT_ADD_ID_CONSTRAINED type, = send_and_wait(req_type, :string, priv_key.ssh_type, :raw, blob_for_add(priv_key), :string, comment, :raw, constraints) raise AgentError, "could not add identity to agent" if type != SSH_AGENT_SUCCESS end # Removes key from the agent. def remove_identity(key) type, = send_and_wait(SSH2_AGENT_REMOVE_IDENTITY, :string, key.to_blob) raise AgentError, "could not remove identity from agent" if type != SSH_AGENT_SUCCESS end # Removes all identities from the agent. def remove_all_identities type, = send_and_wait(SSH2_AGENT_REMOVE_ALL_IDENTITIES) raise AgentError, "could not remove all identity from agent" if type != SSH_AGENT_SUCCESS end # lock the ssh agent with password def lock(password) type, = send_and_wait(SSH2_AGENT_LOCK, :string, password) raise AgentError, "could not lock agent" if type != SSH_AGENT_SUCCESS end # unlock the ssh agent with password def unlock(password) type, = send_and_wait(SSH2_AGENT_UNLOCK, :string, password) raise AgentError, "could not unlock agent" if type != SSH_AGENT_SUCCESS end private def unix_socket_class defined?(UNIXSocket) && UNIXSocket end # Send a new packet of the given type, with the associated data. def send_packet(type, *args) buffer = Buffer.from(*args) data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*") debug { "sending agent request #{type} len #{buffer.length}" } @socket.send data, 0 end # Read the next packet from the agent. This will return a two-part # tuple consisting of the packet type, and the packet's body (which # is returned as a Net::SSH::Buffer). def read_packet buffer = Net::SSH::Buffer.new(@socket.read(4)) buffer.append(@socket.read(buffer.read_long)) type = buffer.read_byte debug { "received agent packet #{type} len #{buffer.length - 4}" } return type, buffer end # Send the given packet and return the subsequent reply from the agent. # (See #send_packet and #read_packet). def send_and_wait(type, *args) send_packet(type, *args) read_packet end # Returns +true+ if the parameter indicates a "failure" response from # the agent, and +false+ otherwise. def agent_failed(type) type == SSH_AGENT_FAILURE || type == SSH2_AGENT_FAILURE || type == SSH_COM_AGENT2_FAILURE end def blob_for_add(priv_key) # Ideally we'd have something like `to_private_blob` on the various key types, but the # nuances with encoding (e.g. `n` and `e` are reversed for RSA keys) make this impractical. case priv_key.ssh_type when /^ssh-dss$/ Net::SSH::Buffer.from(:bignum, priv_key.p, :bignum, priv_key.q, :bignum, priv_key.g, :bignum, priv_key.pub_key, :bignum, priv_key.priv_key).to_s when /^ssh-dss-cert-v01@openssh\.com$/ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.priv_key).to_s when /^ecdsa\-sha2\-(\w*)$/ curve_name = OpenSSL::PKey::EC::CurveNameAliasInv[priv_key.group.curve_name] Net::SSH::Buffer.from(:string, curve_name, :mstring, priv_key.public_key.to_bn.to_s(2), :bignum, priv_key.private_key).to_s when /^ecdsa\-sha2\-(\w*)-cert-v01@openssh\.com$/ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.private_key).to_s when /^ssh-ed25519$/ Net::SSH::Buffer.from(:string, priv_key.public_key.verify_key.to_bytes, :string, priv_key.sign_key.keypair).to_s when /^ssh-ed25519-cert-v01@openssh\.com$/ # Unlike the other certificate types, the public key is included after the certifiate. Net::SSH::Buffer.from(:string, priv_key.to_blob, :string, priv_key.key.public_key.verify_key.to_bytes, :string, priv_key.key.sign_key.keypair).to_s when /^ssh-rsa$/ # `n` and `e` are reversed compared to the ordering in `OpenSSL::PKey::RSA#to_blob`. Net::SSH::Buffer.from(:bignum, priv_key.n, :bignum, priv_key.e, :bignum, priv_key.d, :bignum, priv_key.iqmp, :bignum, priv_key.p, :bignum, priv_key.q).to_s when /^ssh-rsa-cert-v01@openssh\.com$/ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.d, :bignum, priv_key.key.iqmp, :bignum, priv_key.key.p, :bignum, priv_key.key.q).to_s end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/certificate.rb000066400000000000000000000137331454036133000230670ustar00rootroot00000000000000require 'securerandom' module Net module SSH module Authentication # Class for representing an SSH certificate. # # http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.10&content-type=text/plain class Certificate attr_accessor :nonce attr_accessor :key attr_accessor :serial attr_accessor :type attr_accessor :key_id attr_accessor :valid_principals attr_accessor :valid_after attr_accessor :valid_before attr_accessor :critical_options attr_accessor :extensions attr_accessor :reserved attr_accessor :signature_key attr_accessor :signature # Read a certificate blob associated with a key of the given type. def self.read_certblob(buffer, type) cert = Certificate.new cert.nonce = buffer.read_string cert.key = buffer.read_keyblob(type) cert.serial = buffer.read_int64 cert.type = type_symbol(buffer.read_long) 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. # 0x20c49ba2d52500 = 292278993-01-01 00:00:00 +0000 # JRuby 9.1 does not accept the year 292278994 because of edge cases (https://github.com/JodaOrg/joda-time/issues/190) Time.at([0x20c49ba2d52500, buffer.read_int64].min) else Time.at(buffer.read_int64) end cert.critical_options = read_options(buffer) cert.extensions = read_options(buffer) cert.reserved = buffer.read_string cert.signature_key = buffer.read_buffer.read_key cert.signature = buffer.read_string cert end def ssh_type key.ssh_type + "-cert-v01@openssh.com" end def ssh_signature_type key.ssh_type end # Serializes the certificate (and key). def to_blob Buffer.from( :raw, to_blob_without_signature, :string, signature ).to_s end def ssh_do_sign(data, sig_alg = nil) key.ssh_do_sign(data, sig_alg) end def ssh_do_verify(sig, data, options = {}) key.ssh_do_verify(sig, data, options) end def to_pem key.to_pem end def fingerprint key.fingerprint end # Signs the certificate with key. def sign!(key, sign_nonce = nil) # ssh-keygen uses 32 bytes of nonce. self.nonce = sign_nonce || SecureRandom.random_bytes(32) self.signature_key = key self.signature = Net::SSH::Buffer.from( :string, key.ssh_signature_type, :mstring, key.ssh_do_sign(to_blob_without_signature) ).to_s self end def sign(key, sign_nonce = nil) cert = clone cert.sign!(key, sign_nonce) end # Checks whether the certificate's signature was signed by signature key. def signature_valid? buffer = Buffer.new(signature) sig_format = buffer.read_string signature_key.ssh_do_verify(buffer.read_string, to_blob_without_signature, host_key: sig_format) end def self.read_options(buffer) names = [] options = buffer.read_buffer.read_all do |b| name = b.read_string names << name data = b.read_string data = Buffer.new(data).read_string unless data.empty? [name, data] end raise ArgumentError, "option/extension names must be in sorted order" if names.sort != names Hash[options] end private_class_method :read_options 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 private def type_value(type) types = { user: 1, host: 2 } raise ArgumentError("unsupported type: #{type}") unless types.include?(type) types.fetch(type) end def ssh_time(t) # Times in certificates are represented as a uint64. [[t.to_i, 0].max, 2 << 64 - 1].min end def to_blob_without_signature Buffer.from( :string, ssh_type, :string, nonce, :raw, key_without_type, :int64, serial, :long, type_value(type), :string, key_id, :string, valid_principals.inject(Buffer.new) { |acc, elem| acc.write_string(elem) }.to_s, :int64, ssh_time(valid_after), :int64, ssh_time(valid_before), :string, options_to_blob(critical_options), :string, options_to_blob(extensions), :string, reserved, :string, signature_key.to_blob ).to_s end def key_without_type # key.to_blob gives us e.g. "ssh-rsa," but we just want "". tmp = Buffer.new(key.to_blob) tmp.read_string # skip the underlying key type tmp.read end def options_to_blob(options) options.keys.sort.inject(Buffer.new) do |b, name| b.write_string(name) data = options.fetch(name) data = Buffer.from(:string, data).to_s unless data.empty? b.write_string(data) end.to_s end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/constants.rb000066400000000000000000000011561454036133000226150ustar00rootroot00000000000000module 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. module Constants USERAUTH_REQUEST = 50 USERAUTH_FAILURE = 51 USERAUTH_SUCCESS = 52 USERAUTH_BANNER = 53 USERAUTH_PASSWD_CHANGEREQ = 60 USERAUTH_PK_OK = 60 USERAUTH_METHOD_RANGE = 60..79 end end end end net-ssh-7.2.1/lib/net/ssh/authentication/ed25519.rb000066400000000000000000000132061454036133000215760ustar00rootroot00000000000000gem 'ed25519', '~> 1.2' gem 'bcrypt_pbkdf', '~> 1.0' unless RUBY_PLATFORM == "java" require 'ed25519' require 'base64' require 'net/ssh/transport/cipher_factory' require 'net/ssh/authentication/pub_key_fingerprint' require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java" module Net module SSH module Authentication module ED25519 class SigningKeyFromFile < SimpleDelegator def initialize(pk, sk) key = ::Ed25519::SigningKey.from_keypair(sk) raise ArgumentError, "pk does not match sk" unless pk == key.verify_key.to_bytes super(key) end end class OpenSSHPrivateKeyLoader CipherFactory = Net::SSH::Transport::CipherFactory MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n" MEND = "-----END OPENSSH PRIVATE KEY-----" MAGIC = "openssh-key-v1" class DecryptError < ArgumentError def initialize(message, encrypted_key: false) super(message) @encrypted_key = encrypted_key end def encrypted_key? return @encrypted_key end end def self.read(datafull, password) 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 raise ArgumentError.new("#{ciphername} in private key is not supported") unless CipherFactory.supported?(ciphername) kdfname = buffer.read_string raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w[none bcrypt].include?(kdfname) 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 keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true) raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if ((len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0)) if kdfname == 'bcrypt' salt = kdfopts.read_string rounds = kdfopts.read_long raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java" key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds) raise DecryptError.new("BCyryptPbkdf failed", encrypted_key: true) unless key else key = '\x00' * (keylen + ivlen) end 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 decoded = Net::SSH::Buffer.new(decoded) check1 = decoded.read_long check2 = decoded.read_long raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2) type_name = decoded.read_string case type_name when "ssh-ed25519" PrivKey.new(decoded) else decoded.read_private_keyblob(type_name) end end end class PubKey include Net::SSH::Authentication::PubKeyFingerprint attr_reader :verify_key def initialize(data) @verify_key = ::Ed25519::VerifyKey.new(data) end def self.read_keyblob(buffer) PubKey.new(buffer.read_string) end def to_blob Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s end def ssh_type "ssh-ed25519" end def ssh_signature_type ssh_type end def ssh_do_verify(sig, data, options = {}) @verify_key.verify(sig, data) end def to_pem # TODO this is not pem ssh_type + Base64.encode64(@verify_key.to_bytes) end end class PrivKey CipherFactory = Net::SSH::Transport::CipherFactory MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n" MEND = "-----END OPENSSH PRIVATE KEY-----\n" MAGIC = "openssh-key-v1" attr_reader :sign_key def initialize(buffer) pk = buffer.read_string sk = buffer.read_string _comment = buffer.read_string @pk = pk @sign_key = SigningKeyFromFile.new(pk, sk) end def to_blob public_key.to_blob end def ssh_type "ssh-ed25519" end def ssh_signature_type ssh_type end def public_key PubKey.new(@pk) end def ssh_do_sign(data, sig_alg = nil) @sign_key.sign(data) end def self.read(data, password) OpenSSHPrivateKeyLoader.read(data, password) end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/ed25519_loader.rb000066400000000000000000000020631454036133000231230ustar00rootroot00000000000000module 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 ERROR = nil rescue LoadError => e 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 end net-ssh-7.2.1/lib/net/ssh/authentication/key_manager.rb000066400000000000000000000306721454036133000230700ustar00rootroot00000000000000require 'net/ssh/errors' require 'net/ssh/key_factory' require 'net/ssh/loggable' 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 # This class encapsulates all operations done by clients on a user's # private keys. In practice, the client should never need a reference # to a private key; instead, they grab a list of "identities" (public # keys) that are available from the KeyManager, and then use # the KeyManager to do various private key operations using those # identities. # # The KeyManager also uses the Agent class to encapsulate the # ssh-agent. Thus, from a client's perspective it is completely # hidden whether an identity comes from the ssh-agent or from a file # on disk. class KeyManager include Loggable # The list of user key files that will be examined attr_reader :key_files # The list of user key data that will be examined attr_reader :key_data # The list of user key certificate files that will be examined attr_reader :keycert_files # The list of user key certificate data that will be examined attr_reader :keycert_data # The map of loaded identities attr_reader :known_identities # The map of options that were passed to the key-manager attr_reader :options # Create a new KeyManager. By default, the manager will # use the ssh-agent if it is running and the `:use_agent` option # is not false. def initialize(logger, options = {}) self.logger = logger @key_files = [] @key_data = [] @keycert_files = [] @keycert_data = [] @use_agent = options[:use_agent] != false @known_identities = {} @agent = nil @options = options end # Clear all knowledge of any loaded user keys. This also clears the list # of default identity files that are to be loaded, thus making it # appropriate to use if a client wishes to NOT use the default identity # files. def clear! key_files.clear key_data.clear keycert_data.clear known_identities.clear self end # Add the given key_file to the list of key files that will be used. def add(key_file) key_files.push(File.expand_path(key_file)).uniq! self end # Add the given keycert_file to the list of keycert files that will be used. def add_keycert(keycert_file) keycert_files.push(File.expand_path(keycert_file)).uniq! self end # Add the given keycert_data to the list of keycerts that will be used. def add_keycert_data(keycert_data_) keycert_data.push(keycert_data_).uniq! self end # Add the given key_file to the list of keys that will be used. def add_key_data(key_data_) key_data.push(key_data_).uniq! self end # This is used as a hint to the KeyManager indicating that the agent # connection is no longer needed. Any other open resources may be closed # at this time. # # Calling this does NOT indicate that the KeyManager will no longer # be used. Identities may still be requested and operations done on # loaded identities, in which case, the agent will be automatically # reconnected. This method simply allows the client connection to be # closed when it will not be used in the immediate future. def finish @agent.close if @agent @agent = nil end # Iterates over all available identities (public keys) known to this # manager. As it finds one, it will then yield it to the caller. # The origin of the identities may be from files on disk or from an # ssh-agent. Note that identities from an ssh-agent are always listed # first in the array, with other identities coming after. # # If key manager was created with :keys_only option, any identity # from ssh-agent will be ignored unless it present in key_files or # key_data. def each_identity prepared_identities = prepare_identities_from_files + prepare_identities_from_data user_identities = load_identities(prepared_identities, false, true) if agent agent.identities.each do |key| corresponding_user_identity = user_identities.detect { |identity| identity[:public_key] && identity[:public_key].to_pem == key.to_pem } user_identities.delete(corresponding_user_identity) if corresponding_user_identity if !options[:keys_only] || corresponding_user_identity known_identities[key] = { from: :agent, identity: key } yield key end end end user_identities = load_identities(user_identities, !options[:non_interactive], false) user_identities.each do |identity| key = identity.delete(:public_key) known_identities[key] = identity yield key end known_identity_blobs = known_identities.keys.map(&:to_blob) keycerts.each do |keycert| next if known_identity_blobs.include?(keycert.to_blob) (_, corresponding_identity) = known_identities.detect { |public_key, _| public_key.to_pem == keycert.to_pem } if corresponding_identity known_identities[keycert] = corresponding_identity yield keycert end end self end # Sign the given data, using the corresponding private key of the given # identity. If the identity was originally obtained from an ssh-agent, # then the ssh-agent will be used to sign the data, otherwise the # private key for the identity will be loaded from disk (if it hasn't # been loaded already) and will then be used to sign the data. # # Regardless of the identity's origin or who does the signing, this # will always return the signature in an SSH2-specified "signature # blob" format. def sign(identity, data, sig_alg = nil) info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager" if info[:key].nil? && info[:from] == :file begin info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive], options[:password_prompt]) rescue OpenSSL::OpenSSLError, Exception => e raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})" end end if info[:key] if sig_alg.nil? signed = info[:key].ssh_do_sign(data.to_s) sig_alg = identity.ssh_signature_type else signed = info[:key].ssh_do_sign(data.to_s, sig_alg) end return Net::SSH::Buffer.from(:string, sig_alg, :mstring, signed).to_s end if info[:from] == :agent raise KeyManagerError, "the agent is no longer available" unless agent case sig_alg when "rsa-sha2-512" return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_512) when "rsa-sha2-256" return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_256) else return agent.sign(info[:identity], data.to_s) end end raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})" end # Identifies whether the ssh-agent will be used or not. def use_agent? @use_agent end # Toggles whether the ssh-agent will be used or not. If true, an # attempt will be made to use the ssh-agent. If false, any existing # connection to an agent is closed and the agent will not be used. def use_agent=(use_agent) finish if !use_agent @use_agent = use_agent end # Returns an Agent instance to use for communicating with an SSH # agent process. Returns nil if use of an SSH agent has been disabled, # 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 nil end def no_keys? key_files.empty? && key_data.empty? end private # Load keycerts from files and data. def keycerts keycert_files.map { |keycert_file| KeyFactory.load_public_key(keycert_file) } + keycert_data.map { |data| KeyFactory.load_data_public_key(data) } end # Prepares identities from user key_files for loading, preserving their order and sources. def prepare_identities_from_files key_files.map do |file| if readable_file?(file) identity = {} cert_file = file + "-cert.pub" public_key_file = file + ".pub" if readable_file?(cert_file) identity[:load_from] = :pubkey_file identity[:pubkey_file] = cert_file elsif readable_file?(public_key_file) identity[:load_from] = :pubkey_file identity[:pubkey_file] = public_key_file else identity[:load_from] = :privkey_file end identity.merge(privkey_file: file) end end.compact end def readable_file?(path) File.file?(path) && File.readable?(path) end # Prepared identities from user key_data, preserving their order and sources. def prepare_identities_from_data key_data.map do |data| { load_from: :data, data: data } end end # 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| 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, "", 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 def process_identity_loading_error(identity, e) case identity[:load_from] when :pubkey_file error { "could not load public key file `#{identity[:pubkey_file]}': #{e.class} (#{e.message})" } when :privkey_file error { "could not load private key file `#{identity[:privkey_file]}': #{e.class} (#{e.message})" } else raise e end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/methods/000077500000000000000000000000001454036133000217145ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/authentication/methods/abstract.rb000066400000000000000000000053061454036133000240500ustar00rootroot00000000000000require 'net/ssh/buffer' require 'net/ssh/errors' require 'net/ssh/loggable' require 'net/ssh/authentication/constants' 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 include Loggable include Constants # The authentication session object attr_reader :session # The key manager object. Not all authentication methods will require # this. attr_reader :key_manager # So far only affects algorithms used for rsa keys, but can be # extended to other keys, e.g after reading of # PubkeyAcceptedAlgorithms option from ssh_config file is implemented. attr_reader :pubkey_algorithms # Instantiates a new authentication method. def initialize(session, options = {}) @session = session @key_manager = options[:key_manager] @options = options @prompt = options[:password_prompt] @pubkey_algorithms = options[:pubkey_algorithms] \ || %w[rsa-sha2-256-cert-v01@openssh.com ssh-rsa-cert-v01@openssh.com rsa-sha2-256 ssh-rsa] self.logger = session.logger end # Returns the session-id, as generated during the first key exchange of # an SSH connection. def session_id session.transport.algorithms.session_id end # Sends a message via the underlying transport layer abstraction. This # will block until the message is completely sent. def send_message(msg) session.transport.send_message(msg) end # Creates a new USERAUTH_REQUEST packet. The extra arguments on the end # must be either boolean values or strings, and are tacked onto the end # of the packet. The new packet is returned, ready for sending. def userauth_request(username, next_service, auth_method, *others) buffer = Net::SSH::Buffer.from(:byte, USERAUTH_REQUEST, :string, username, :string, next_service, :string, auth_method) others.each do |value| case value when true, false then buffer.write_bool(value) when String then buffer.write_string(value) else raise ArgumentError, "don't know how to write #{value.inspect}" end end buffer end private attr_reader :prompt end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/methods/hostbased.rb000066400000000000000000000050141454036133000242150ustar00rootroot00000000000000require 'net/ssh/authentication/methods/abstract' module Net module SSH module Authentication module Methods # Implements the host-based SSH authentication method. class Hostbased < Abstract include Constants # Attempts to perform host-based authorization of the user by trying # all known keys. def authenticate(next_service, username, password = nil) return false unless key_manager key_manager.each_identity do |identity| return true if authenticate_with(identity, next_service, username, key_manager) end return false end private # Returns the hostname as reported by the underlying socket. def hostname session.transport.socket.client_name end # Attempts to perform host-based authentication of the user, using # the given host identity (key). def authenticate_with(identity, next_service, username, key_manager) debug { "trying hostbased (#{identity.fingerprint})" } client_username = ENV['USER'] || username req = build_request(identity, next_service, username, "#{hostname}.", client_username) sig_data = Buffer.from(:string, session_id, :raw, req) sig = key_manager.sign(identity, sig_data.to_s) message = Buffer.from(:raw, req, :string, sig) send_message(message) message = session.next_message case message.type when USERAUTH_SUCCESS info { "hostbased succeeded (#{identity.fingerprint})" } return true when USERAUTH_FAILURE info { "hostbased failed (#{identity.fingerprint})" } raise Net::SSH::Authentication::DisallowedMethod unless message[:authentications].split(/,/).include? 'hostbased' return false else raise Net::SSH::Exception, "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})" end end # Build the "core" hostbased request string. def build_request(identity, next_service, username, hostname, client_username) userauth_request(username, next_service, "hostbased", identity.ssh_type, Buffer.from(:key, identity).to_s, hostname, client_username).to_s end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/methods/keyboard_interactive.rb000066400000000000000000000054531454036133000264450ustar00rootroot00000000000000require 'net/ssh/prompt' require 'net/ssh/authentication/methods/abstract' module Net module SSH module Authentication module Methods # Implements the "keyboard-interactive" SSH authentication method. class KeyboardInteractive < Abstract USERAUTH_INFO_REQUEST = 60 USERAUTH_INFO_RESPONSE = 61 # Attempt to authenticate the given user for the given service. def authenticate(next_service, username, password = nil) debug { "trying keyboard-interactive" } send_message(userauth_request(username, next_service, "keyboard-interactive", "", "")) prompter = nil loop do message = session.next_message case message.type when USERAUTH_SUCCESS debug { "keyboard-interactive succeeded" } prompter.success if prompter return true when USERAUTH_FAILURE debug { "keyboard-interactive failed" } raise Net::SSH::Authentication::DisallowedMethod unless 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", "", "")) when USERAUTH_INFO_REQUEST name = message.read_string instruction = message.read_string debug { "keyboard-interactive info request" } if password.nil? && interactive? && prompter.nil? prompter = prompt.start(type: 'keyboard-interactive', name: name, instruction: instruction) end _ = message.read_string # lang_tag responses = [] message.read_long.times do text = message.read_string echo = message.read_bool password_to_send = password || (prompter && prompter.ask(text, echo)) responses << password_to_send end # if the password failed the first time around, don't try # and use it on subsequent requests. password = nil msg = Buffer.from(:byte, USERAUTH_INFO_RESPONSE, :long, responses.length, :string, responses) send_message(msg) else raise Net::SSH::Exception, "unexpected reply in keyboard interactive: #{message.type} (#{message.inspect})" end end end def interactive? options = session.transport.options || {} !options[:non_interactive] end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/methods/none.rb000066400000000000000000000020201454036133000231720ustar00rootroot00000000000000require 'net/ssh/errors' require 'net/ssh/authentication/methods/abstract' 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")) 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 net-ssh-7.2.1/lib/net/ssh/authentication/methods/password.rb000066400000000000000000000051661454036133000241130ustar00rootroot00000000000000require 'net/ssh/errors' require 'net/ssh/prompt' require 'net/ssh/authentication/methods/abstract' module Net module SSH module Authentication module Methods # Implements the "password" SSH authentication method. class Password < Abstract # Attempt to authenticate the given user for the given service. If # the password parameter is nil, this will ask for password def authenticate(next_service, username, password = nil) clear_prompter! retries = 0 max_retries = get_max_retries return false if !password && max_retries == 0 begin password_to_send = password || ask_password(username) send_message(userauth_request(username, next_service, "password", false, password_to_send)) message = session.next_message retries += 1 if message.type == USERAUTH_FAILURE debug { "password failed" } raise Net::SSH::Authentication::DisallowedMethod unless message[:authentications].split(/,/).include? 'password' password = nil end end until (message.type != USERAUTH_FAILURE || retries >= max_retries) case message.type when USERAUTH_SUCCESS debug { "password succeeded" } @prompter.success if @prompter return true when USERAUTH_FAILURE return false when USERAUTH_PASSWD_CHANGEREQ debug { "password change request received, failing" } return false else raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})" end end private NUMBER_OF_PASSWORD_PROMPTS = 3 def clear_prompter! @prompt_info = nil @prompter = nil end def ask_password(username) host = session.transport.host prompt_info = { type: 'password', user: username, host: host } if @prompt_info != prompt_info @prompt_info = prompt_info @prompter = prompt.start(prompt_info) end echo = false @prompter.ask("#{username}@#{host}'s password:", echo) end def get_max_retries options = session.transport.options || {} result = options[:number_of_password_prompts] || NUMBER_OF_PASSWORD_PROMPTS options[:non_interactive] ? 0 : result end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/methods/publickey.rb000066400000000000000000000121441454036133000242320ustar00rootroot00000000000000require 'net/ssh/buffer' require 'net/ssh/errors' require 'net/ssh/authentication/methods/abstract' module Net module SSH module Authentication module Methods # Implements the "publickey" SSH authentication method. class Publickey < Abstract # Attempts to perform public-key authentication for the given # username, trying each identity known to the key manager. If any of # them succeed, returns +true+, otherwise returns +false+. This # requires the presence of a key manager. def authenticate(next_service, username, password = nil) return false unless key_manager key_manager.each_identity do |identity| return true if authenticate_with(identity, next_service, username) end return false end private # Builds a packet that contains the request formatted for sending # a public-key request to the server. def build_request(pub_key, username, next_service, alg, has_sig) blob = Net::SSH::Buffer.new blob.write_key pub_key userauth_request(username, next_service, "publickey", has_sig, alg, blob.to_s) end # Builds and sends a request formatted for a public-key # authentication request. def send_request(pub_key, username, next_service, alg, signature = nil) msg = build_request(pub_key, username, next_service, alg, !signature.nil?) msg.write_string(signature) if signature send_message(msg) end def authenticate_with_alg(identity, next_service, username, alg, sig_alg = nil) debug { "trying publickey (#{identity.fingerprint}) alg #{alg}" } send_request(identity, username, next_service, alg) message = session.next_message case message.type when USERAUTH_PK_OK buffer = build_request(identity, username, next_service, alg, true) sig_data = Net::SSH::Buffer.new sig_data.write_string(session_id) sig_data.append(buffer.to_s) sig_blob = key_manager.sign(identity, sig_data, sig_alg) send_request(identity, username, next_service, alg, sig_blob.to_s) message = session.next_message case message.type when USERAUTH_SUCCESS debug { "publickey succeeded (#{identity.fingerprint})" } return true when USERAUTH_FAILURE debug { "publickey failed (#{identity.fingerprint})" } raise Net::SSH::Authentication::DisallowedMethod unless message[:authentications].split(/,/).include? 'publickey' return false else raise Net::SSH::Exception, "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})" end when USERAUTH_FAILURE return false when USERAUTH_SUCCESS return true else raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})" end end # Attempts to perform public-key authentication for the given # username, with the given identity (public key). Returns +true+ if # successful, or +false+ otherwise. def authenticate_with(identity, next_service, username) type = identity.ssh_type if type == "ssh-rsa" pubkey_algorithms.each do |pk_alg| case pk_alg when "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa" if authenticate_with_alg(identity, next_service, username, pk_alg, pk_alg) # success return true end end end elsif type == "ssh-rsa-cert-v01@openssh.com" pubkey_algorithms.each do |pk_alg| case pk_alg when "rsa-sha2-512-cert-v01@openssh.com" if authenticate_with_alg(identity, next_service, username, pk_alg, "rsa-sha2-512") # success return true end when "rsa-sha2-256-cert-v01@openssh.com" if authenticate_with_alg(identity, next_service, username, pk_alg, "rsa-sha2-256") # success return true end when "ssh-rsa-cert-v01@openssh.com" if authenticate_with_alg(identity, next_service, username, pk_alg) # success return true end end end elsif authenticate_with_alg(identity, next_service, username, type) # success return true end # failure return false end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/pageant.rb000066400000000000000000000417401454036133000222230ustar00rootroot00000000000000if RUBY_VERSION < "1.9" require 'dl/import' require 'dl/struct' elsif RUBY_VERSION < "2.1" require 'dl/import' require 'dl/types' require 'dl' else require 'fiddle' require 'fiddle/types' require 'fiddle/import' # For now map DL to Fiddler versus updating all the code below module DL CPtr ||= Fiddle::Pointer if RUBY_PLATFORM != "java" RUBY_FREE ||= Fiddle::RUBY_FREE end end end require 'net/ssh/errors' 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. # # This code is a slightly modified version of the original implementation # 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 dlload 'user32.dll', 'kernel32.dll', 'advapi32.dll' include Fiddle::Win32Types SIZEOF_DWORD = Fiddle::SIZEOF_LONG end if RUBY_ENGINE == "jruby" typealias("HANDLE", "void *") # From winnt.h typealias("PHANDLE", "void *") # From winnt.h typealias("ULONG_PTR", "unsigned long*") end typealias("LPCTSTR", "char *") # From winnt.h typealias("LPVOID", "void *") # From winnt.h typealias("LPCVOID", "const void *") # From windef.h typealias("LRESULT", "long") # From windef.h typealias("WPARAM", "unsigned int *") # From windef.h 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 %w[findWindow getCurrentProcess initializeSecurityDescriptor setSecurityDescriptorOwner isValidSecurityDescriptor openProcessToken getTokenInformation getLastError getCurrentThreadId createFileMapping mapViewOfFile sendMessageTimeout unmapViewOfFile closeHandle].each do |name| new_name = name[0].chr.upcase + name[1..name.length] 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 elsif RUBY_ENGINE == "jruby" %w[FindWindow CreateFileMapping SendMessageTimeout].each do |name| alias_method name, name + "A" module_function name end # :nodoc: module LibC extend FFI::Library ffi_lib FFI::Library::LIBC 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 else 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, Win::REVISION) ) raise_error_if_zero( Win.SetSecurityDescriptorOwner(psd_information, get_sid_ptr(user), 0) ) 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 else def self.get_sid(user) user.SID end def self.ptr_to_handle(phandle) phandle.ptr.to_i end def self.to_struct_ptr(ptr) ptr end def self.ptr_to_dword(ptr) ptr.to_s(Win::SIZEOF_DWORD).unpack('L')[0] end def self.to_token_user(ptoken_information) TOKEN_USER.new(ptoken_information) end def self.get_sid_ptr(user) user.SID end end def self.get_current_user token_handle = open_process_token(Win.GetCurrentProcess, Win::TOKEN_QUERY) token_user = get_token_information(token_handle, Win::TOKEN_USER_INFORMATION_CLASS) 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) ) 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. Win.GetTokenInformation(token_handle, 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( Win.GetTokenInformation(token_handle, token_information_class, ptoken_information, 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. def send_query(query) res = nil 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 cds.cbData = mapname.size + 1 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? Win.CloseHandle(filemap) if filemap != 0 end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/pub_key_fingerprint.rb000066400000000000000000000035601454036133000246470ustar00rootroot00000000000000require 'openssl' module Net module SSH module Authentication # Public key fingerprinting utility module - internal not part of API. # This is included in pubkey classes and called from there. All RSA, DSA, and ECC keys # are supported. # # require 'net/ssh' # my_pubkey_text = File.read('/path/to/id_ed25519.pub') # #=> "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDB2NBh4GJPPUN1kXPMu8b633Xcv55WoKC3OkBjFAbzJ alice@example.com" # my_pubkey = Net::SSH::KeyFactory.load_data_public_key(my_pubkey_text) # #=> # "2f:7f:97:21:76:a4:0f:38:c4:fe:d8:b4:6a:39:72:30" # my_pubkey.fingerprint('SHA256') # #=> "SHA256:u6mXnY8P1b0FODGp8mckqOB33u8+jvkSCtJbD5Q9klg" module PubKeyFingerprint # :nodoc: # Return the key's fingerprint. Algorithm may be either +MD5+ (default), # or +SHA256+. For +SHA256+, fingerprints are in the same format # returned by OpenSSH's `ssh-add -l -E SHA256`, i.e., # trailing base64 padding '=' characters are stripped and the # literal string +SHA256:+ is prepended. def fingerprint(algorithm = 'MD5') @fingerprint ||= {} @fingerprint[algorithm] ||= PubKeyFingerprint.fingerprint(to_blob, algorithm) end def self.fingerprint(blob, algorithm = 'MD5') case algorithm.to_s.upcase when 'MD5' OpenSSL::Digest.hexdigest(algorithm, blob).scan(/../).join(":") when 'SHA256' "SHA256:#{Base64.encode64(OpenSSL::Digest.digest(algorithm, blob)).chomp.gsub(/=+\z/, '')}" else raise OpenSSL::Digest::DigestError, "unsupported ssh key digest #{algorithm}" end end end end end end net-ssh-7.2.1/lib/net/ssh/authentication/session.rb000066400000000000000000000145071454036133000222700ustar00rootroot00000000000000require 'net/ssh/loggable' require 'net/ssh/transport/constants' require 'net/ssh/authentication/constants' require 'net/ssh/authentication/key_manager' require 'net/ssh/authentication/methods/none' require 'net/ssh/authentication/methods/publickey' require 'net/ssh/authentication/methods/hostbased' require 'net/ssh/authentication/methods/password' 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 # Represents an authentication session. It manages the authentication of # a user over an established connection (the "transport" object, see # Net::SSH::Transport::Session). # # The use of an authentication session to manage user authentication is # internal to Net::SSH (specifically Net::SSH.start). Consumers of the # Net::SSH library will never need to access this class directly. class Session include Loggable include Constants include Transport::Constants # transport layer abstraction attr_reader :transport # the list of authentication methods to try attr_reader :auth_methods # the list of authentication methods that are allowed attr_reader :allowed_auth_methods # a hash of options, given at construction time attr_reader :options # Instantiates a new Authentication::Session object over the given # transport layer abstraction. def initialize(transport, options = {}) self.logger = transport.logger @transport = transport @auth_methods = options[:auth_methods] || Net::SSH::Config.default_auth_methods @options = options @allowed_auth_methods = @auth_methods end # Attempts to authenticate the given user, in preparation for the next # service request. Returns true if an authentication method succeeds in # authenticating the user, and false otherwise. def authenticate(next_service, username, password = nil) debug { "beginning authentication of `#{username}'" } transport.send_message(transport.service_request("ssh-userauth")) expect_message(SERVICE_ACCEPT) key_manager = KeyManager.new(logger, options) keys.each { |key| key_manager.add(key) } unless keys.empty? keycerts.each { |keycert| key_manager.add_keycert(keycert) } unless keycerts.empty? keycert_data.each { |data| key_manager.add_keycert_data(data) } unless keycert_data.empty? key_data.each { |key2| key_manager.add_key_data(key2) } unless key_data.empty? default_keys.each { |key| key_manager.add(key) } unless options.key?(:keys) || options.key?(:key_data) attempted = [] @auth_methods.each do |name| 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], pubkey_algorithms: options[:pubkey_algorithms] || nil) rescue NameError debug {"Mechanism #{name} was requested, but isn't a known type. Ignoring it."} next end return true if method.authenticate(next_service, username, password) rescue Net::SSH::Authentication::DisallowedMethod end error { "all authorization methods failed (tried #{attempted.join(', ')})" } return false ensure key_manager.finish if key_manager end # Blocks until a packet is received. It silently handles USERAUTH_BANNER # packets, and will raise an error if any packet is received that is not # valid during user authentication. def next_message loop do packet = transport.next_message case packet.type when USERAUTH_BANNER info { packet[:message] } # TODO add a hook for people to retrieve the banner when it is sent when USERAUTH_FAILURE @allowed_auth_methods = packet[:authentications].split(/,/) debug { "allowed methods: #{packet[:authentications]}" } return packet when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT return packet when USERAUTH_SUCCESS transport.hint :authenticated return packet else raise Net::SSH::Exception, "unexpected message #{packet.type} (#{packet})" end end end # Blocks until a packet is received, and returns it if it is of the given # type. If it is not, an exception is raised. def expect_message(type) message = next_message raise Net::SSH::Exception, "expected #{type}, got #{message.type} (#{message})" unless message.type == type message end private # Returns an array of paths to the key files usually defined # by system default. def default_keys %w[~/.ssh/id_ed25519 ~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa ~/.ssh2/id_ed25519 ~/.ssh2/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_ecdsa] end # Returns an array of paths to the key files that should be used when # attempting any key-based authentication mechanism. def keys Array(options[:keys]) end # Returns an array of paths to the keycert files that should be used when # attempting any key-based authentication mechanism. def keycerts Array(options[:keycerts]) end # Returns an array of the keycert data that should be used when # attempting any key-based authentication mechanism. def keycert_data Array(options[:keycert_data]) end # Returns an array of the key data that should be used when # attempting any key-based authentication mechanism. def key_data Array(options[:key_data]) end end end end end net-ssh-7.2.1/lib/net/ssh/buffer.rb000066400000000000000000000371301454036133000170340ustar00rootroot00000000000000require 'net/ssh/transport/openssl' require 'net/ssh/authentication/certificate' 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 # for building binary packets given a signature. # # Writing to a buffer always appends to the end, regardless of where the # read cursor is. Reading, on the other hand, always begins at the first # byte of the buffer and increments the read cursor, with subsequent reads # taking up where the last left off. # # As a consumer of the Net::SSH library, you will rarely come into contact # with these buffer objects directly, but it could happen. Also, if you # are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer # class can be quite handy. class Buffer # This is a convenience method for creating and populating a new buffer # from a single command. The arguments must be even in length, with the # first of each pair of arguments being a symbol naming the type of the # data that follows. If the type is :raw, the value is written directly # to the hash. # # b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4") # #-> "\1\0\0\0\5hello\1\2\3\4" # # The supported data types are: # # * :raw => write the next value verbatim (#write) # * :int64 => write an 8-byte integer (#write_int64) # * :long => write a 4-byte integer (#write_long) # * :byte => write a single byte (#write_byte) # * :string => write a 4-byte length followed by character data (#write_string) # * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved) # * :bool => write a single byte, interpreted as a boolean (#write_bool) # * :bignum => write an SSH-encoded bignum (#write_bignum) # * :key => write an SSH-encoded key value (#write_key) # # Any of these, except for :raw, accepts an Array argument, to make it # easier to write multiple values of the same type in a briefer manner. def self.from(*args) raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0 buffer = new 0.step(args.length - 1, 2) do |index| type = args[index] value = args[index + 1] if type == :raw buffer.append(value.to_s) elsif Array === value buffer.send("write_#{type}", *value) else buffer.send("write_#{type}", value) end end buffer end # exposes the raw content of the buffer attr_reader :content # the current position of the pointer in the buffer attr_accessor :position # Creates a new buffer, initialized to the given content. The position # is initialized to the beginning of the buffer. def initialize(content = String.new) @content = content.to_s @position = 0 end # Returns the length of the buffer's content. def length @content.length end # Returns the number of bytes available to be read (e.g., how many bytes # remain between the current position and the end of the buffer). def available length - position end # Returns a copy of the buffer's content. def to_s (@content || "").dup end # Compares the contents of the two buffers, returning +true+ only if they # are identical in size and content. def ==(buffer) to_s == buffer.to_s end # Returns +true+ if the buffer contains no data (e.g., it is of zero length). def empty? @content.empty? end # Resets the pointer to the start of the buffer. Subsequent reads will # begin at position 0. def reset! @position = 0 end # Returns true if the pointer is at the end of the buffer. Subsequent # reads will return nil, in this case. def eof? @position >= length end # Resets the buffer, making it empty. Also, resets the read position to # 0. def clear! @content = String.new @position = 0 end # Consumes n bytes from the buffer, where n is the current position # unless otherwise specified. This is useful for removing data from the # buffer that has previously been read, when you are expecting more data # to be appended. It helps to keep the size of buffers down when they # would otherwise tend to grow without bound. # # Returns the buffer object itself. def consume!(n = position) if n >= length # optimize for a fairly common case clear! elsif n > 0 @content = @content[n..-1] || String.new @position -= n @position = 0 if @position < 0 end self end # Appends the given text to the end of the buffer. Does not alter the # read position. Returns the buffer object itself. def append(text) @content << text self end # Returns all text from the current pointer to the end of the buffer as # a new Net::SSH::Buffer object. def remainder_as_buffer Buffer.new(@content[@position..-1]) end # Reads all data up to and including the given pattern, which may be a # String, Fixnum, or Regexp and is interpreted exactly as String#index # does. Returns nil if nothing matches. Increments the position to point # immediately after the pattern, if it does match. Returns all data up to # and including the text that matched the pattern. def read_to(pattern) index = @content.index(pattern, @position) or return nil length = case pattern when String then pattern.length when Integer then 1 when Regexp then $&.length end index && read(index + length) end # Reads and returns the next +count+ bytes from the buffer, starting from # the read position. If +count+ is +nil+, this will return all remaining # text in the buffer. This method will increment the pointer. def read(count = nil) count ||= length count = length - @position if @position + count > length @position += count @content[@position - count, count] end # Reads (as #read) and returns the given number of bytes from the buffer, # and then consumes (as #consume!) all data up to the new read position. def read!(count = nil) data = read(count) consume! data end # Calls block(self) until the buffer is empty, and returns all results. def read_all(&block) Enumerator.new { |e| e << yield(self) until eof? }.to_a end # Return the next 8 bytes as a 64-bit integer (in network byte order). # Returns nil if there are less than 8 bytes remaining to be read in the # buffer. def read_int64 hi = read_long or return nil lo = read_long or return nil return (hi << 32) + lo end # Return the next four bytes as a long integer (in network byte order). # Returns nil if there are less than 4 bytes remaining to be read in the # buffer. def read_long b = read(4) or return nil b.unpack("N").first end # Read and return the next byte in the buffer. Returns nil if called at # the end of the buffer. def read_byte b = read(1) or return nil b.getbyte(0) end # Read and return an SSH2-encoded string. The string starts with a long # integer that describes the number of bytes remaining in the string. # Returns nil if there are not enough bytes to satisfy the request. def read_string length = read_long or return nil read(length) end # Read a single byte and convert it into a boolean, using 'C' rules # (i.e., zero is false, non-zero is true). def read_bool b = read_byte or return nil b != 0 end # Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is # essentially just a string, which is reinterpreted to be a bignum in # binary format. def read_bignum data = read_string return unless data OpenSSL::BN.new(data, 2) end # Read a key from the buffer. The key will start with a string # describing its type. The remainder of the key is defined by the # type that was read. def read_key type = read_string return (type ? read_keyblob(type) : nil) end def read_private_keyblob(type) case type when /^ssh-rsa$/ n = read_bignum e = read_bignum d = read_bignum iqmp = read_bignum p = read_bignum q = read_bignum _unkown1 = read_bignum _unkown2 = read_bignum dmp1 = d % (p - 1) dmq1 = d % (q - 1) # Public key data_sequence = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(n), OpenSSL::ASN1::Integer(e) ]) if d && p && q && dmp1 && dmq1 && iqmp data_sequence = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(n), OpenSSL::ASN1::Integer(e), OpenSSL::ASN1::Integer(d), OpenSSL::ASN1::Integer(p), OpenSSL::ASN1::Integer(q), OpenSSL::ASN1::Integer(dmp1), OpenSSL::ASN1::Integer(dmq1), OpenSSL::ASN1::Integer(iqmp) ]) end asn1 = OpenSSL::ASN1::Sequence(data_sequence) OpenSSL::PKey::RSA.new(asn1.to_der) when /^ecdsa\-sha2\-(\w*)$/ OpenSSL::PKey::EC.read_keyblob($1, self) else raise Exception, "Cannot decode private key of type #{type}" end end # Read a keyblob of the given type from the buffer, and return it as # a key. Only RSA, DSA, and ECDSA keys are supported. def read_keyblob(type) case type when /^(.*)-cert-v01@openssh\.com$/ key = Net::SSH::Authentication::Certificate.read_certblob(self, $1) when /^ssh-dss$/ p = read_bignum q = read_bignum g = read_bignum pub_key = read_bignum asn1 = OpenSSL::ASN1::Sequence.new( [ OpenSSL::ASN1::Sequence.new( [ OpenSSL::ASN1::ObjectId.new('DSA'), OpenSSL::ASN1::Sequence.new( [ OpenSSL::ASN1::Integer.new(p), OpenSSL::ASN1::Integer.new(q), OpenSSL::ASN1::Integer.new(g) ] ) ] ), OpenSSL::ASN1::BitString.new(OpenSSL::ASN1::Integer.new(pub_key).to_der) ] ) key = OpenSSL::PKey::DSA.new(asn1.to_der) when /^ssh-rsa$/ e = read_bignum n = read_bignum asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Integer(n), OpenSSL::ASN1::Integer(e) ] ) key = OpenSSL::PKey::RSA.new(asn1.to_der) when /^ssh-ed25519$/ Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("unsupported key type `#{type}'") key = Net::SSH::Authentication::ED25519::PubKey.read_keyblob(self) when /^ecdsa\-sha2\-(\w*)$/ key = OpenSSL::PKey::EC.read_keyblob($1, self) else raise NotImplementedError, "unsupported key type `#{type}'" end return key end # Reads the next string from the buffer, and returns a new Buffer # object that wraps it. def read_buffer Buffer.new(read_string) end # Writes the given data literally into the string. Does not alter the # read position. Returns the buffer object. def write(*data) data.each { |datum| @content << datum.dup.force_encoding('BINARY') } self end # 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 << if string.frozen? string.dup.force_encoding('BINARY') else string.force_encoding('BINARY') end self end # Writes each argument to the buffer as a network-byte-order-encoded # 64-bit integer (8 bytes). Does not alter the read position. Returns the # buffer object. def write_int64(*n) n.each do |i| hi = (i >> 32) & 0xFFFFFFFF lo = i & 0xFFFFFFFF @content << [hi, lo].pack("N2") end self end # Writes each argument to the buffer as a network-byte-order-encoded # long (4-byte) integer. Does not alter the read position. Returns the # buffer object. def write_long(*n) @content << n.pack("N*") self end # Writes each argument to the buffer as a byte. Does not alter the read # position. Returns the buffer object. def write_byte(*n) n.each { |b| @content << b.chr } self end # Writes each argument to the buffer as an SSH2-encoded string. Each # string is prefixed by its length, encoded as a 4-byte long integer. # Does not alter the read position. Returns the buffer object. def write_string(*text) text.each do |string| s = string.to_s write_long(s.bytesize) write(s) end self end # Writes each argument to the buffer as an SSH2-encoded string. Each # string is prefixed by its length, encoded as a 4-byte long integer. # Does not alter the read position. Returns the buffer object. # Might alter arguments see write_moved def write_mstring(*text) text.each do |string| s = string.to_s write_long(s.bytesize) write_moved(s) end self end # Writes each argument to the buffer as a (C-style) boolean, with 1 # meaning true, and 0 meaning false. Does not alter the read position. # Returns the buffer object. def write_bool(*b) b.each { |v| @content << (v ? "\1" : "\0") } self end # Writes each argument to the buffer as a bignum (SSH2-style). No # checking is done to ensure that the arguments are, in fact, bignums. # Does not alter the read position. Returns the buffer object. def write_bignum(*n) @content << n.map { |b| b.to_ssh }.join self end # Writes the given arguments to the buffer as SSH2-encoded keys. Does not # alter the read position. Returns the buffer object. def write_key(*key) key.each { |k| append(k.to_blob) } self end end end end; net-ssh-7.2.1/lib/net/ssh/buffered_io.rb000066400000000000000000000144551454036133000200410ustar00rootroot00000000000000require 'net/ssh/buffer' require 'net/ssh/loggable' 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 # (see Net::SSH::Connection::Session#listen_to). # # The general idea is that instead of calling #read directly on an IO that # has been extended with this module, you call #fill (to add pending input # to the internal read buffer), and then #read_available (to read from that # buffer). Likewise, you don't call #write directly, you call #enqueue to # add data to the write buffer, and then #send_pending or #wait_for_pending_sends # to actually send the data across the wire. # # In this way you can easily use the object as an argument to IO.select, # calling #fill when it is available for read, or #send_pending when it is # available for write, and then call #enqueue and #read_available during # the idle times. # # socket = TCPSocket.new(address, port) # socket.extend(Net::SSH::BufferedIo) # # ssh.listen_to(socket) # # ssh.loop do # if socket.available > 0 # puts socket.read_available # socket.enqueue("response\n") # end # end # # Note that this module must be used to extend an instance, and should not # be included in a class. If you do want to use it via an include, then you # must make sure to invoke the private #initialize_buffered_io method in # your class' #initialize method: # # class Foo < IO # include Net::SSH::BufferedIo # # def initialize # initialize_buffered_io # # ... # end # end module BufferedIo include Loggable # Called when the #extend is called on an object, with this module as the # argument. It ensures that the modules instance variables are all properly # initialized. def self.extended(object) # :nodoc: # need to use __send__ because #send is overridden in Socket object.__send__(:initialize_buffered_io) end # Tries to read up to +n+ bytes of data from the remote end, and appends # the data to the input buffer. It returns the number of bytes read, or 0 # if no data was available to be read. def fill(n = 8192) input.consume! data = recv(n) || "" debug { "read #{data.length} bytes" } input.append(data) return data.length rescue EOFError => e @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 if output.length > 0 sent = send(output.to_s, 0) debug { "sent #{sent} bytes" } output.consume!(sent) return sent > 0 else return false end end # Calls #send_pending repeatedly, if necessary, blocking until the output # buffer is empty. def wait_for_pending_sends send_pending 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 # explicitly in the +initialize+ method of any class that uses # Module#include to add this module. def initialize_buffered_io @input = Net::SSH::Buffer.new @input_errors = [] @output = Net::SSH::Buffer.new @output_errors = [] end end # Fixes for two issues by Miklós Fazekas: # # * if client closes a forwarded connection, but the server is # reading, net-ssh terminates with IOError socket closed. # * if client force closes (RST) a forwarded connection, but # server is reading, net-ssh terminates with [an exception] # # See: # # http://net-ssh.lighthouseapp.com/projects/36253/tickets/7 # http://github.com/net-ssh/net-ssh/tree/portfwfix # module ForwardedBufferedIo def fill(n = 8192) begin super(n) rescue Errno::ECONNRESET => e debug { "connection was reset => shallowing exception:#{e}" } return 0 rescue IOError => e if e.message =~ /closed/ then debug { "connection was reset => shallowing exception:#{e}" } return 0 else raise end end end def send_pending begin super rescue Errno::ECONNRESET => e debug { "connection was reset => shallowing exception:#{e}" } return 0 rescue IOError => e if e.message =~ /closed/ then debug { "connection was reset => shallowing exception:#{e}" } return 0 else raise end end end end end end net-ssh-7.2.1/lib/net/ssh/config.rb000066400000000000000000000362701454036133000170340ustar00rootroot00000000000000module 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 # some extent) from OpenSSH configuration files (~/.ssh/config, /etc/ssh_config, # and so forth). # # Only a subset of OpenSSH configuration options are understood: # # * ChallengeResponseAuthentication => maps to the :auth_methods option challenge-response (then coleasced into keyboard-interactive) # * KbdInteractiveAuthentication => maps to the :auth_methods keyboard-interactive # * CertificateFile => maps to the :keycerts option # * Ciphers => maps to the :encryption option # * Compression => :compression # * CompressionLevel => :compression_level # * ConnectTimeout => maps to the :timeout option # * ForwardAgent => :forward_agent # * GlobalKnownHostsFile => :global_known_hosts_file # * HostBasedAuthentication => maps to the :auth_methods option # * HostKeyAlgorithms => maps to :host_key option # * HostKeyAlias => :host_key_alias # * HostName => :host_name # * IdentityFile => maps to the :keys option # * IdentityAgent => :identity_agent # * IdentitiesOnly => :keys_only # * CheckHostIP => :check_host_ip # * Macs => maps to the :hmac option # * PasswordAuthentication => maps to the :auth_methods option password # * Port => :port # * PreferredAuthentications => maps to the :auth_methods option # * ProxyCommand => maps to the :proxy option # * ProxyJump => maps to the :proxy option # * PubKeyAuthentication => maps to the :auth_methods option # * RekeyLimit => :rekey_limit # * StrictHostKeyChecking => :verify_host_key # * User => :user # * UserKnownHostsFile => :user_known_hosts_file # * NumberOfPasswordPrompts => :number_of_password_prompts # * FingerprintHash => :fingerprint_hash # # Note that you will never need to use this class directly--you can control # whether the OpenSSH configuration files are read by passing the :config # option to Net::SSH.start. (They are, by default.) class Config class << self @@default_files = %w[~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config] # The following defaults follow the openssh client ssh_config defaults. # http://lwn.net/Articles/544640/ # "hostbased" is off and "none" is not supported but we allow it since # it's used by some clients to query the server for allowed auth methods @@default_auth_methods = %w[none publickey password keyboard-interactive] # Returns an array of locations of OpenSSH configuration files # to parse by default. def default_files @@default_files.clone end def default_auth_methods @@default_auth_methods.clone end # Loads the configuration data for the given +host+ from all of the # given +files+ (defaulting to the list of files returned by # #default_files), translates the resulting hash into the options # recognized by Net::SSH, and returns them. def for(host, files = expandable_default_files) translate(files.inject({}) { |settings, file| load(file, host, settings) }) end # Load the OpenSSH configuration settings in the given +file+ for the # given +host+. If +settings+ is given, the options are merged into # that hash, with existing values taking precedence over newly parsed # ones. Returns a hash containing the OpenSSH options. (See # #translate for how to convert the OpenSSH options into Net::SSH # options.) def load(path, host, settings = {}, base_dir = nil) file = File.expand_path(path) base_dir ||= File.dirname(file) return settings unless File.readable?(file) globals = {} block_matched = false block_seen = false IO.foreach(file) do |line| next if line =~ /^\s*(?:#.*)?$/ if line =~ /^\s*(\S+)\s*=(.*)$/ key, value = $1, $2 else key, value = line.strip.split(/\s+/, 2) end # silently ignore malformed entries next if value.nil? key.downcase! value = unquote(value) value = case value.strip when /^\d+$/ then value.to_i when /^no$/i then false when /^yes$/i then true else value end if key == 'host' # Support "Host host1 host2 hostN". # See http://github.com/net-ssh/net-ssh/issues#issue/6 negative_hosts, positive_hosts = value.to_s.split(/\s+/).partition { |h| h.start_with?('!') } # Check for negative patterns first. If the host matches, that overrules any other positive match. # The host substring code is used to strip out the starting "!" so the regexp will be correct. negative_matched = negative_hosts.any? { |h| host =~ pattern2regex(h[1..-1]) } if negative_matched block_matched = false else block_matched = positive_hosts.any? { |h| host =~ pattern2regex(h) } end block_seen = true settings[key] = host elsif key == 'match' block_matched = eval_match_conditions(value, host, settings) block_seen = true elsif !block_seen case key when 'identityfile', 'certificatefile' (globals[key] ||= []) << value when 'include' included_file_paths(base_dir, value).each do |file_path| globals = load(file_path, host, globals, base_dir) end else globals[key] = value unless settings.key?(key) end elsif block_matched case key when 'identityfile', 'certificatefile' (settings[key] ||= []) << value when 'include' included_file_paths(base_dir, value).each do |file_path| settings = load(file_path, host, settings, base_dir) end else settings[key] = value unless settings.key?(key) end end # ProxyCommand and ProxyJump override each other so they need to be tracked togeather %w[proxyjump proxycommand].each do |proxy_key| if (proxy_value = settings.delete(proxy_key)) settings['proxy'] ||= [proxy_key, proxy_value] end end end globals.merge(settings) do |key, oldval, newval| case key when 'identityfile', 'certificatefile' oldval + newval else newval end end end # Given a hash of OpenSSH configuration options, converts them into # a hash of Net::SSH options. Unrecognized options are ignored. The # +settings+ hash must have Strings for keys, all downcased, and # the returned hash will have Symbols for keys. def translate(settings) auth_methods = default_auth_methods.clone (auth_methods << 'challenge-response').uniq! ret = settings.each_with_object({ auth_methods: auth_methods }) do |(key, value), hash| translate_config_key(hash, key.to_sym, value, settings) end merge_challenge_response_with_keyboard_interactive(ret) end # Filters default_files down to the files that are expandable. def expandable_default_files default_files.keep_if do |path| File.expand_path(path) true rescue ArgumentError false end end private def translate_verify_host_key(value) case value when false :never when true :always when 'accept-new' :accept_new end end def translate_keepalive(hash, value) if value && value.to_i > 0 hash[:keepalive] = true hash[:keepalive_interval] = value.to_i else hash[:keepalive] = false end end TRANSLATE_CONFIG_KEY_RENAME_MAP = { bindaddress: :bind_address, compression: :compression, compressionlevel: :compression_level, certificatefile: :keycerts, connecttimeout: :timeout, forwardagent: :forward_agent, identitiesonly: :keys_only, identityagent: :identity_agent, globalknownhostsfile: :global_known_hosts_file, hostkeyalias: :host_key_alias, identityfile: :keys, fingerprinthash: :fingerprint_hash, port: :port, user: :user, userknownhostsfile: :user_known_hosts_file, checkhostip: :check_host_ip }.freeze def translate_config_key(hash, key, value, settings) case key when :stricthostkeychecking hash[:verify_host_key] = translate_verify_host_key(value) when :ciphers hash[:encryption] = value.split(/,/) when :hostbasedauthentication if value (hash[:auth_methods] << "hostbased").uniq! else hash[:auth_methods].delete("hostbased") end when :hostkeyalgorithms hash[:host_key] = value.split(/,/) when :hostname hash[:host_name] = value.gsub(/%h/, settings['host']) when :macs hash[:hmac] = value.split(/,/) when :serveralivecountmax hash[:keepalive_maxcount] = value.to_i if value when :serveraliveinterval translate_keepalive(hash, value) when :passwordauthentication if value (hash[:auth_methods] << 'password').uniq! else hash[:auth_methods].delete('password') end when :challengeresponseauthentication if value (hash[:auth_methods] << 'challenge-response').uniq! else hash[:auth_methods].delete('challenge-response') end when :kbdinteractiveauthentication if value (hash[:auth_methods] << 'keyboard-interactive').uniq! else hash[:auth_methods].delete('keyboard-interactive') end when :preferredauthentications hash[:auth_methods] = value.split(/,/) # TODO we should place to preferred_auth_methods rather than auth_methods when :proxy if (proxy = setup_proxy(*value)) hash[:proxy] = proxy end when :pubkeyauthentication if value (hash[:auth_methods] << 'publickey').uniq! else hash[:auth_methods].delete('publickey') end when :rekeylimit hash[:rekey_limit] = interpret_size(value) when :sendenv multi_send_env = value.to_s.split(/\s+/) hash[:send_env] = multi_send_env.map { |e| Regexp.new pattern2regex(e).source, false } when :setenv hash[:set_env] = Shellwords.split(value.to_s).map { |e| e.split '=', 2 }.to_h when :numberofpasswordprompts hash[:number_of_password_prompts] = value.to_i when *TRANSLATE_CONFIG_KEY_RENAME_MAP.keys hash[TRANSLATE_CONFIG_KEY_RENAME_MAP[key]] = value end end def setup_proxy(type, value) case type when 'proxycommand' if value !~ /^none$/ require 'net/ssh/proxy/command' Net::SSH::Proxy::Command.new(value) end when 'proxyjump' require 'net/ssh/proxy/jump' Net::SSH::Proxy::Jump.new(value) end end # Converts an ssh_config pattern into a regex for matching against # host names. def pattern2regex(pattern) tail = pattern prefix = String.new while !tail.empty? do head, sep, tail = tail.partition(/[\*\?]/) prefix = prefix + Regexp.quote(head) case sep when '*' prefix += '.*' when '?' prefix += '.' when '' else fail "Unpexpcted sep:#{sep}" end end Regexp.new("^" + prefix + "$", true) end # Converts the given size into an integer number of bytes. def interpret_size(size) case size when /k$/i then size.to_i * 1024 when /m$/i then size.to_i * 1024 * 1024 when /g$/i then size.to_i * 1024 * 1024 * 1024 else size.to_i end end def merge_challenge_response_with_keyboard_interactive(hash) if hash[:auth_methods].include?('challenge-response') hash[:auth_methods].delete('challenge-response') (hash[:auth_methods] << 'keyboard-interactive').uniq! end hash end def included_file_paths(base_dir, config_paths) tokenize_config_value(config_paths).flat_map do |path| Dir.glob(File.expand_path(path, base_dir)).select { |f| File.file?(f) } end end # Tokenize string into tokens. # A token is a word or a quoted sequence of words, separated by whitespaces. def tokenize_config_value(str) str.scan(/([^"\s]+)?(?:"([^"]+)")?\s*/).map(&:join) end def eval_match_conditions(condition, host, settings) # Not using `\s` for whitespace matching as canonical # ssh_config parser implementation (OpenSSH) has specific character set. # Ref: https://github.com/openssh/openssh-portable/blob/2581333d564d8697837729b3d07d45738eaf5a54/misc.c#L237-L239 conditions = condition.split(/[ \t\r\n]+|(? 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! @remote_window_size -= length else break end end end # Invoked when the server confirms that a channel has been opened. # The remote_id is the id of the channel as assigned by the remote host, # and max_window and max_packet are the maximum window and maximum # packet sizes, respectively. If an open-confirmation callback was # given when the channel was created, it is invoked at this time with # the channel itself as the sole argument. def do_open_confirmation(remote_id, max_window, max_packet) # :nodoc: @remote_id = remote_id @remote_window_size = @remote_maximum_window_size = max_window @remote_maximum_packet_size = max_packet connection.forward.agent(self) if connection.options[:forward_agent] && type == "session" forward_local_env(connection.options[:send_env]) if connection.options[:send_env] 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 # will be raised. def do_open_failed(reason_code, description) if @on_open_failed @on_open_failed.call(self, reason_code, description) else raise ChannelOpenFailed.new(reason_code, description) end end # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and # causes the remote window size to be adjusted upwards by the given # number of bytes. This has the effect of allowing more data to be sent # from the local end to the remote end of the channel. def do_window_adjust(bytes) # :nodoc: @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 # either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback # to handle the request, CHANNEL_FAILURE will be sent. Otherwise, # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The # callback should accept the channel as the first argument, and the # request-specific data as the second. def do_request(request, want_reply, data) # :nodoc: result = true begin callback = @on_request[request] or raise ChannelRequestFailed callback.call(self, data) rescue ChannelRequestFailed result = false end if want_reply msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id) connection.send_message(msg) end end # Invokes the #on_data callback when the server sends data to the # channel. This will reduce the available window size on the local end, # but does not actually throttle requests that come in illegally when # the window size is too small. The callback is invoked with the channel # as the first argument, and the data as the second. def do_data(data) # :nodoc: 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, # type, and data. def do_extended_data(type, data) 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 if callback = pending_requests.shift callback.call(self, false) else 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 if callback = pending_requests.shift callback.call(self, true) else error { "channel success received with no pending request to handle it (bug?)" } end end private # Runs the SSH event loop until the remote confirmed channel open # experimental api def wait_until_open_confirmed connection.loop { !remote_id } end LOCAL_WINDOW_SIZE_INCREMENT = 0x20000 GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE = 10 * LOCAL_WINDOW_SIZE_INCREMENT # Updates the local window size by the given amount. If the window # size drops to less than half of the local maximum (an arbitrary # threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the # server telling it that the window size has grown. def update_local_window_size(size) @local_window_size -= size if local_window_size < local_maximum_window_size / 2 connection.send_message( Buffer.from(:byte, CHANNEL_WINDOW_ADJUST, :long, remote_id, :long, LOCAL_WINDOW_SIZE_INCREMENT) ) @local_window_size += LOCAL_WINDOW_SIZE_INCREMENT if @local_maximum_window_size < @local_window_size || @local_maximum_window_size < GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE @local_maximum_window_size += LOCAL_WINDOW_SIZE_INCREMENT end end end # Gets an +Array+ of local environment variables in the remote process' # environment. # A variable name can either be described by a +Regexp+ or +String+. # # channel.forward_local_env [/^GIT_.*$/, "LANG"] def forward_local_env(env_variable_patterns) Array(env_variable_patterns).each do |env_variable_pattern| matched_variables = ENV.find_all do |env_name, _| case env_variable_pattern when Regexp then env_name =~ env_variable_pattern when String then env_name == env_variable_pattern end end matched_variables.each do |env_name, env_value| self.env(env_name, env_value) end end end # Set a +Hash+ of environment variables in the remote process' environment. # # channel.set_remote_env foo: 'bar', baz: 'whale' def set_remote_env(env) env.each { |key, value| self.env(key, value) } end end end end end net-ssh-7.2.1/lib/net/ssh/connection/constants.rb000066400000000000000000000015611454036133000217350ustar00rootroot00000000000000module 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 #++ GLOBAL_REQUEST = 80 REQUEST_SUCCESS = 81 REQUEST_FAILURE = 82 #-- # Channel related messages #++ CHANNEL_OPEN = 90 CHANNEL_OPEN_CONFIRMATION = 91 CHANNEL_OPEN_FAILURE = 92 CHANNEL_WINDOW_ADJUST = 93 CHANNEL_DATA = 94 CHANNEL_EXTENDED_DATA = 95 CHANNEL_EOF = 96 CHANNEL_CLOSE = 97 CHANNEL_REQUEST = 98 CHANNEL_SUCCESS = 99 CHANNEL_FAILURE = 100 end end end end net-ssh-7.2.1/lib/net/ssh/connection/event_loop.rb000066400000000000000000000072431454036133000220760ustar00rootroot00000000000000require 'net/ssh/loggable' module Net module SSH module Connection # EventLoop can be shared across multiple sessions # # one issue is with blocks passed to loop, etc. # they should get current session as parameter, but in # case you're using multiple sessions in an event loop it doesnt makes sense # 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 = [] w = [] minwait = nil @sessions.each do |session| sr, sw, actwait = session.ev_do_calculate_rw_wait(wait) minwait = actwait if actwait && (minwait.nil? || actwait < minwait) r.push(*sr) w.push(*sw) sr.each { |ri| owners[ri] = session } sw.each { |wi| owners[wi] = session } end readers, writers, = IO.select(r, w, nil, minwait) fired_sessions = {} if readers readers.each do |reader| session = owners[reader] (fired_sessions[session] ||= { r: [], w: [] })[:r] << reader end end if writers writers.each do |writer| session = owners[writer] (fired_sessions[session] ||= { r: [], w: [] })[:w] << writer end end fired_sessions.each do |s, rw| s.ev_do_handle_events(rw[:r], rw[:w]) end @sessions.each { |s| s.ev_do_postprocess(fired_sessions.key?(s)) } true end end # optimized version for a single session class SingleSessionEventLoop < EventLoop # Compatibility for original single session event loops: # 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 end end end end net-ssh-7.2.1/lib/net/ssh/connection/keepalive.rb000066400000000000000000000033361454036133000216700ustar00rootroot00000000000000require 'net/ssh/loggable' 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" } @unresponded_keepalive_count = 0 } @last_keepalive_sent_at = Time.now if keepalive_maxcount > 0 && @unresponded_keepalive_count > keepalive_maxcount error { "Timeout, server #{@session.host} not responding. Missed #{@unresponded_keepalive_count - 1} timeouts." } @unresponded_keepalive_count = 0 raise Net::SSH::Timeout, "Timeout, server #{@session.host} not responding." end end end end end end net-ssh-7.2.1/lib/net/ssh/connection/session.rb000066400000000000000000000677321454036133000214200ustar00rootroot00000000000000require 'net/ssh/loggable' require 'net/ssh/connection/channel' require 'net/ssh/connection/constants' require 'net/ssh/service/forward' require 'net/ssh/connection/keepalive' require 'net/ssh/connection/event_loop' 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. # It also encapsulates the SSH event loop (via #loop and #process), # and serves as a central point-of-reference for all SSH-related services (e.g. # port forwarding, SFTP, SCP, etc.). # # You will rarely (if ever) need to instantiate this class directly; rather, # you'll almost always use Net::SSH.start to initialize a new network # connection, authenticate a user, and return a new connection session, # all in one call. # # Net::SSH.start("localhost", "user") do |ssh| # # 'ssh' is an instance of Net::SSH::Connection::Session # ssh.exec! "/etc/init.d/some_process start" # end 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 } @pending_requests = [] @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 # until the next operation on the socket. Nevertheless, this method can # be useful if you just want to know if _you_ have closed the connection. def closed? transport.closed? end # Closes the session gracefully, blocking until all channels have # successfully closed, and then closes the underlying transport layer # connection. def close info { "closing remaining channels (#{channels.length} open)" } channels.each { |id, channel| channel.close } begin loop(0.1) { channels.any? } rescue Net::SSH::Disconnect raise unless channels.empty? 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 # underlying protocol's state). 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 # a +true+ value for +include_invisible+, then those will be counted. # # This can be useful for determining whether the event loop should continue # to be run. # # ssh.loop { ssh.busy? } def busy?(include_invisible = false) if include_invisible channels.any? else channels.any? { |id, ch| !ch[:invisible] } end end # The main event loop. Calls #process until #process returns false. If a # block is given, it is passed to #process, otherwise a default proc is # used that just returns true if there are any channels active (see #busy?). # The # +wait+ parameter is also passed through to #process (where it is # interpreted as the maximum number of seconds to wait for IO.select to return). # # # loop for as long as there are any channels active # ssh.loop # # # loop for as long as there are any channels active, but make sure # # the event loop runs at least once per 0.1 second # ssh.loop(0.1) # # # loop until ctrl-C is pressed # int_pressed = false # trap("INT") { int_pressed = true } # ssh.loop(0.1) { not int_pressed } def loop(wait = nil, &block) running = block || Proc.new { busy? } loop_forever { break unless process(wait, &running) } begin process(0) rescue IOError => e if e.message =~ /closed/ debug { "stream was closed after loop => shallowing exception so it will be re-raised in next loop" } else raise 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, # #process returns true. The session itself is yielded to the block as its # only argument. # # If +wait+ is nil (the default), this method will block until any of the # monitored IO objects are ready to be read from or written to. If you want # it to not block, you can pass 0, or you can pass any other numeric value # to indicate that it should block for no more than that many seconds. # Passing 0 is a good way to poll the connection, but if you do it too # frequently it can make your CPU quite busy! # # This will also cause all active channels to be processed once each (see # Net::SSH::Connection::Channel#on_process). # # TODO revise example # # # process multiple Net::SSH connections in parallel # connections = [ # Net::SSH.start("host1", ...), # Net::SSH.start("host2", ...) # ] # # connections.each do |ssh| # ssh.exec "grep something /in/some/files" # end # # condition = Proc.new { |s| s.busy? } # # loop do # connections.delete_if { |ssh| !ssh.process(0.1, &condition) } # break if connections.empty? # end def process(wait = nil, &block) @event_loop.process(wait, &block) rescue StandardError force_channel_cleanup_on_close if closed? raise end # This is called internally as part of #process. It dispatches any # available incoming packets, and then runs Net::SSH::Connection::Channel#process # for any active channels. If a block is given, it is invoked at the # start of the method and again at the end, and if the block ever returns # false, this method returns false. Otherwise, it returns true. def preprocess(&block) return false if block_given? && !yield(self) ev_preprocess(&block) return false if block_given? && !yield(self) return true end # Called by event loop to process available data before going to # event multiplexing def ev_preprocess(&block) dispatch_incoming_packets(raise_disconnect_errors: false) each_channel { |id, channel| channel.process unless channel.local_closed? } end # Returns the file descriptors the event loop should wait for read/write events, # we also return the max wait def ev_do_calculate_rw_wait(wait) r = listeners.keys w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? } [r, w, io_select_wait(wait)] 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 # transport layer to rekey. Then returns true. def ev_do_handle_events(readers, writers) Array(readers).each do |reader| if listeners[reader] listeners[reader].call(reader) else if reader.fill.zero? reader.close stop_listening_to(reader) 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) @keepalive.send_as_needed(was_events) 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 # not require a response from the server, otherwise the server is required # to respond and indicate whether the request was successful or not. This # success or failure is indicated by the callback being invoked, with the # first parameter being true or false (success, or failure), and the second # being the packet itself. # # Generally, Net::SSH will manage global requests that need to be sent # (e.g. port forward requests and such are handled in the Net::SSH::Service::Forward # class, for instance). However, there may be times when you need to # send a global request that isn't explicitly handled by Net::SSH, and so # this method is available to you. # # ssh.send_global_request("keep-alive@openssh.com") def send_global_request(type, *extra, &callback) info { "sending global request #{type}" } msg = Buffer.from(:byte, GLOBAL_REQUEST, :string, type.to_s, :bool, !callback.nil?, *extra) send_message(msg) 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 # must be even in number and conform to the same format as described for # Net::SSH::Buffer.from. If a callback is given, it will be invoked when # the server confirms that the channel opened successfully. The sole parameter # for the callback is the channel object itself. # # In general, you'll use #open_channel without any arguments; the only # time you'd want to set the channel type or pass additional initialization # data is if you were implementing an SSH extension. # # channel = ssh.open_channel do |ch| # ch.exec "grep something /some/files" do |ch, success| # ... # end # end # # 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 # arguments: the channel object, a symbol indicating the data type # (:stdout or :stderr), and the data (as a string). # # Note that this method returns immediately, and requires an event loop # (see Session#loop) in order for the command to actually execute. # # This is effectively identical to calling #open_channel, and then # Net::SSH::Connection::Channel#exec, and then setting up the channel # callbacks. However, for most uses, this will be sufficient. # # ssh.exec "grep something /some/files" do |ch, stream, data| # if stream == :stderr # puts "ERROR: #{data}" # else # puts data # end # end def exec(command, status: nil, &block) 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) else $stdout.print(data) end end channel.on_extended_data do |ch2, type, data| if block block.call(ch2, :stderr, data) else $stderr.print(data) end end 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. # # matches = ssh.exec!("grep something /some/files") # # the returned string has an exitstatus method to query its exit status def exec!(command, status: nil, &block) block_or_concat = block || Proc.new do |ch, type, data| ch[:result] ||= String.new ch[:result] << data end status ||= {} channel = exec(command, status: status, &block_or_concat) channel.wait 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 # need to send a packet that Net::SSH does not directly support, you can # use this to send it. # # ssh.send_message(Buffer.from(:byte, REQUEST_SUCCESS).to_s) 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. # # Any +io+ value passed to this method _must_ have mixed into it the # Net::SSH::BufferedIo functionality, typically by calling #extend on the # object. # # The following example executes a process on the remote server, opens # a socket to somewhere, and then pipes data from that socket to the # remote process' stdin stream: # # channel = ssh.open_channel do |ch| # ch.exec "/some/process/that/wants/input" do |ch, success| # abort "can't execute!" unless success # # io = TCPSocket.new(somewhere, port) # io.extend(Net::SSH::BufferedIo) # ssh.listen_to(io) # # ch.on_process do # if io.available > 0 # ch.send_data(io.read_available) # end # end # # ch.on_close do # ssh.stop_listening_to(io) # io.close # end # end # end # # channel.wait 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 # raise ChannelOpenFailed if it is unable to open the channel for some # reason. Otherwise, the channel will be opened and a confirmation message # sent to the server. # # This is used by the Net::SSH::Service::Forward service to open a channel # when a remote forwarded port receives a connection. However, you are # welcome to register handlers for other channel types, as needed. 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 # is required). If the callback sends the response, it should return # :sent. Otherwise, if it returns true, REQUEST_SUCCESS will be sent, and # if it returns false, REQUEST_FAILURE will be sent. def on_global_request(type, &block) 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. def global_request(packet) info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" } callback = @on_global_request[packet[:request_type]] result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false 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] rescue ChannelOpenFailed => err failure = [err.code, err.reason] else channels[local_id] = channel msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long, channel.local_id, :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size) end 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 net-ssh-7.2.1/lib/net/ssh/connection/term.rb000066400000000000000000000101401454036133000206610ustar00rootroot00000000000000module 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"), # http://tools.ietf.org/html/rfc4254. module Term # 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 net-ssh-7.2.1/lib/net/ssh/errors.rb000066400000000000000000000101111454036133000170650ustar00rootroot00000000000000module Net module SSH # A general exception class, to act as the ancestor of all other Net::SSH # exception classes. class Exception < ::RuntimeError; end # This exception is raised when authentication fails (whether it be # public key authentication, password authentication, or whatever). class AuthenticationFailed < Net::SSH::Exception; end # This exception is raised when a connection attempt times out. class ConnectionTimeout < Net::SSH::Exception; end # This exception is raised when the remote host has disconnected # unexpectedly. class Disconnect < Net::SSH::Exception; end # This exception is raised when the remote host has disconnected/ # timeouted unexpectedly. class Timeout < Disconnect; end # This exception is primarily used internally, but if you have a channel # request handler (see Net::SSH::Connection::Channel#on_request) that you # want to fail in such a way that the server knows it failed, you can # raise this exception in the handler and Net::SSH will translate that into # a "channel failure" message. class ChannelRequestFailed < Net::SSH::Exception; end # This is exception is primarily used internally, but if you have a channel # open handler (see Net::SSH::Connection::Session#on_open_channel) and you # want to fail in such a way that the server knows it failed, you can # raise this exception in the handler and Net::SSH will translate that into # 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})" end end # Base class for host key exceptions. When rescuing this exception, you can # inspect the key fingerprint and, if you want to proceed anyway, simply call # the remember_host! method on the exception, and then retry. class HostKeyError < Net::SSH::Exception # the callback to use when #remember_host! is called attr_writer :callback # :nodoc: # 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! @callback.call end end # Raised when the cached key for a particular host does not match the # key given by the host, which can be indicative of a man-in-the-middle # attack. When rescuing this exception, you can inspect the key fingerprint # and, if you want to proceed anyway, simply call the remember_host! # method on the exception, and then retry. class HostKeyMismatch < HostKeyError; end # Raised when there is no cached key for a particular host, which probably # means that the host has simply not been seen before. # When rescuing this exception, you can inspect the key fingerprint and, if # you want to proceed anyway, simply call the remember_host! method on the # exception, and then retry. class HostKeyUnknown < HostKeyError; end end end net-ssh-7.2.1/lib/net/ssh/key_factory.rb000066400000000000000000000174021454036133000201020ustar00rootroot00000000000000require 'net/ssh/transport/openssl' require 'net/ssh/prompt' 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 # will rarely (if ever) be directly used by consumers of the library. # # klass = Net::SSH::KeyFactory.get("rsa") # assert klass.is_a?(OpenSSL::PKey::RSA) # # key = Net::SSH::KeyFactory.load_public_key("~/.ssh/id_dsa.pub") class KeyFactory # Specifies the mapping of SSH names to OpenSSL key classes. MAP = { 'dh' => OpenSSL::PKey::DH, 'rsa' => OpenSSL::PKey::RSA, 'dsa' => OpenSSL::PKey::DSA, 'ecdsa' => OpenSSL::PKey::EC } MAP["ed25519"] = Net::SSH::Authentication::ED25519::PrivKey if defined? Net::SSH::Authentication::ED25519 class << self # Fetch an OpenSSL key instance by its SSH name. It will be a new, # empty key of the given type. def get(name) MAP.fetch(name).new end # Loads a private key from a file. It will correctly determine # whether the file describes an RSA or DSA key, and will load it # appropriately. The new key is returned. If the key itself is # encrypted (requiring a passphrase to use), the user will be # prompted to enter their password unless passphrase works. def load_private_key(filename, passphrase = nil, ask_passphrase = true, prompt = Prompt.default) data = File.read(File.expand_path(filename)) load_data_private_key(data, passphrase, ask_passphrase, filename, prompt) end # Loads a private key. It will correctly determine # whether the file describes an RSA or DSA key, and will load it # appropriately. The new key is returned. If the key itself is # encrypted (requiring a passphrase to use), the user will be # prompted to enter their password unless passphrase works. def load_data_private_key(data, passphrase = nil, ask_passphrase = true, filename = "", prompt = Prompt.default) key_type = classify_key(data, filename) encrypted_key = nil tries = 0 prompter = nil result = begin key_type.read(data, passphrase || 'invalid') rescue *key_type.error_classes => e encrypted_key = !!key_type.encrypted_key?(data, e) if encrypted_key.nil? if encrypted_key && ask_passphrase tries += 1 if tries <= 3 prompter ||= prompt.start(type: 'private_key', filename: filename, sha: Digest::SHA256.digest(data)) passphrase = prompter.ask("Enter passphrase for #{filename}:", false) retry else raise end else raise end end prompter.success if prompter result end # Loads a public key from a file. It will correctly determine whether # the file describes an RSA or DSA key, and will load it # appropriately. The new public key is returned. def load_public_key(filename) data = File.read(File.expand_path(filename)) load_data_public_key(data, filename) end # Loads a public key. It will correctly determine whether # the file describes an RSA or DSA key, and will load it # appropriately. The new public key is returned. def load_data_public_key(data, filename = "") fields = data.split(/ /) blob = nil begin blob = fields.shift end while !blob.nil? && !/^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp\d+)(-cert-v01@openssh\.com)?$/.match(blob) blob = fields.shift raise Net::SSH::Exception, "public key at #{filename} is not valid" if blob.nil? blob = blob.unpack("m*").first reader = Net::SSH::Buffer.new(blob) reader.read_key or raise OpenSSL::PKey::PKeyError, "not a public key #{filename.inspect}" end private # rubocop:disable Style/Documentation, Lint/DuplicateMethods class KeyType def self.read(key_data, passphrase) raise Exception, "TODO subclasses should implement read" end def self.error_classes raise Exception, "TODO subclasses should implement read" end def self.encrypted_key?(data, error) raise Exception, "TODO subclasses should implement is_encrypted_key" end end class OpenSSHPrivateKeyType < KeyType def self.read(key_data, passphrase) Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader.read(key_data, passphrase) end def self.error_classes [Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader::DecryptError] end def self.encrypted_key?(key_data, decode_error) decode_error.is_a?(Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader::DecryptError) && decode_error.encrypted_key? end end class OpenSSLKeyTypeBase < KeyType def self.open_ssl_class raise Exception, "TODO: subclasses should implement" end def self.read(key_data, passphrase) open_ssl_class.new(key_data, passphrase) end def self.encrypted_key?(key_data, error) key_data.match(/ENCRYPTED/) end end class OpenSSLPKeyType < OpenSSLKeyTypeBase def self.read(key_data, passphrase) open_ssl_class.read(key_data, passphrase) end def self.open_ssl_class OpenSSL::PKey end def self.error_classes [ArgumentError, OpenSSL::PKey::PKeyError] end end class OpenSSLDSAKeyType < OpenSSLKeyTypeBase def self.open_ssl_class OpenSSL::PKey::DSA end def self.error_classes [OpenSSL::PKey::DSAError] end end class OpenSSLRSAKeyType < OpenSSLKeyTypeBase def self.open_ssl_class OpenSSL::PKey::RSA end def self.error_classes [OpenSSL::PKey::RSAError] end end class OpenSSLECKeyType < OpenSSLKeyTypeBase def self.open_ssl_class OpenSSL::PKey::EC end def self.error_classes [OpenSSL::PKey::ECError] end end # rubocop:enable Style/Documentation, Lint/DuplicateMethods # Determine whether the file describes an RSA or DSA key, and return how load it # appropriately. def classify_key(data, filename) if data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/) Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("OpenSSH keys only supported if ED25519 is available") return OpenSSHPrivateKeyType elsif OpenSSL::PKey.respond_to?(:read) return OpenSSLPKeyType elsif data.match(/-----BEGIN DSA PRIVATE KEY-----/) return OpenSSLDSAKeyType elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/) return OpenSSLRSAKeyType elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) return OpenSSLECKeyType elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/) raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'" else raise OpenSSL::PKey::PKeyError, "not a private key (#{filename})" end end end end end end net-ssh-7.2.1/lib/net/ssh/known_hosts.rb000066400000000000000000000211141454036133000201320ustar00rootroot00000000000000require 'strscan' require 'openssl' require 'base64' require 'delegate' require 'net/ssh/buffer' 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 class HostKeys include Enumerable attr_reader :host def initialize(host_keys, host, known_hosts, options = {}) @host_keys = host_keys @host = host @known_hosts = known_hosts @options = options end def add_host_key(key) @known_hosts.add(@host, key, @options) @host_keys.push(key) end def each(&block) @host_keys.each(&block) end def empty? @host_keys.empty? end end # Searches an OpenSSH-style known-host file for a given host, and returns all # matching keys. This is used to implement host-key verification, as well as # to determine what key a user prefers to use for a given host. # # This is used internally by Net::SSH, and will never need to be used directly # by consumers of the library. class KnownHosts SUPPORTED_TYPE = %w[ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521] SUPPORTED_TYPE.push('ssh-ed25519') if Net::SSH::Authentication::ED25519Loader::LOADED class << self # Searches all known host files (see KnownHosts.hostfiles) for all keys # of the given host. Returns an enumerable of keys found. def search_for(host, options = {}) HostKeys.new(search_in(hostfiles(options), host, options), host, self, options) end # Search for all known keys for the given host, in every file given in # the +files+ array. Returns the list of keys. def search_in(files, host, options = {}) files.flat_map { |file| KnownHosts.new(file).keys_for(host, options) } end # Looks in the given +options+ hash for the :user_known_hosts_file and # :global_known_hosts_file keys, and returns an array of all known # hosts files. If the :user_known_hosts_file key is not set, the # default is returned (~/.ssh/known_hosts and ~/.ssh/known_hosts2). If # :global_known_hosts_file is not set, the default is used # (/etc/ssh/ssh_known_hosts and /etc/ssh/ssh_known_hosts2). # # If you only want the user known host files, you can pass :user as # the second option. def hostfiles(options, which = :all) files = [] files += Array(options[:user_known_hosts_file] || %w[~/.ssh/known_hosts ~/.ssh/known_hosts2]) if which == :all || which == :user if which == :all || which == :global files += Array(options[:global_known_hosts_file] || %w[/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2]) end return files end # Looks in all user known host files (see KnownHosts.hostfiles) and tries to # add an entry for the given host and key to the first file it is able # to. def add(host, key, options = {}) hostfiles(options, :user).each do |file| KnownHosts.new(file).add(host, key) return rescue SystemCallError # try the next hostfile end end end # The host-key file name that this KnownHosts instance will use to search # for keys. attr_reader :source # Instantiate a new KnownHosts instance that will search the given known-hosts # file. The path is expanded file File.expand_path. def initialize(source) @source = File.expand_path(source) end # Returns an array of all keys that are known to be associatd with the # given host. The +host+ parameter is either the domain name or ip address # of the host, or both (comma-separated). Additionally, if a non-standard # port is being used, it may be specified by putting the host (or ip, or # both) in square brackets, and appending the port outside the brackets # after a colon. Possible formats for +host+, then, are; # # "net.ssh.test" # "1.2.3.4" # "net.ssh.test,1.2.3.4" # "[net.ssh.test]:5555" # "[1,2,3,4]:5555" # "[net.ssh.test]:5555,[1.2.3.4]:5555 def keys_for(host, options = {}) keys = [] return keys unless File.readable?(source) entries = host.split(/,/) host_name = entries[0] host_ip = entries[1] File.open(source) do |file| file.each_line do |line| 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?('#') hostlist = hosts.split(',') next unless SUPPORTED_TYPE.include?(type) found = hostlist.any? { |pattern| match(host_name, pattern) } || known_host_hash?(hostlist, entries) next unless found found = hostlist.include?(host_ip) if options[:check_host_ip] && entries.size > 1 && hostlist.size > 1 next unless found blob = key_content.unpack("m*").first 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 keys end def match(host, pattern) if pattern.include?('*') || pattern.include?('?') # see man 8 sshd for pattern details pattern_regexp = pattern.split('*', -1).map do |x| x.split('?', -1).map do |y| Regexp.escape(y) end.join('.') end.join('.*') host =~ Regexp.new("\\A#{pattern_regexp}\\z") else host == pattern end end # Indicates whether one of the entries matches an hostname that has been # stored as a HMAC-SHA1 hash in the known hosts. def known_host_hash?(hostlist, entries) if hostlist.size == 1 && hostlist.first =~ /\A\|1(\|.+){2}\z/ chunks = hostlist.first.split(/\|/) salt = Base64.decode64(chunks[2]) digest = OpenSSL::Digest.new('sha1') entries.each do |entry| hmac = OpenSSL::HMAC.digest(digest, salt, entry) return true if Base64.encode64(hmac).chomp == chunks[3] end end false end # Tries to append an entry to the current source file for the given host # and key. If it is unable to (because the file is not writable, for # instance), an exception will be raised. def add(host, key) File.open(source, "a") do |file| blob = [Net::SSH::Buffer.from(:key, key).to_s].pack("m*").gsub(/\s/, "") file.puts "#{host} #{key.ssh_type} #{blob}" end end end end end net-ssh-7.2.1/lib/net/ssh/loggable.rb000066400000000000000000000041001454036133000173260ustar00rootroot00000000000000module 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 # can easily be used to add Net::SSH-like logging to your own programs. # # class MyClass # include Net::SSH::Loggable # end # # Net::SSH.start(...) do |ssh| # obj = MyClass.new # obj.logger = ssh.logger # ... # end module Loggable # 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. def facility @facility ||= self.class.to_s.gsub(/::/, ".").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + "[%x]" % object_id end end end end net-ssh-7.2.1/lib/net/ssh/packet.rb000066400000000000000000000112541454036133000170310ustar00rootroot00000000000000require 'net/ssh/buffer' require 'net/ssh/transport/constants' require 'net/ssh/authentication/constants' 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. # # data = some_channel_request_packet # packet = Net::SSH::Packet.new(data) # # p packet.type #-> 98 (CHANNEL_REQUEST) # p packet[:request] # p packet[:want_reply] # # This is used exclusively internally by Net::SSH, and unless you're doing # protocol-level manipulation or are extending Net::SSH in some way, you'll # never need to use this class directly. class Packet < Buffer @@types = {} # Register a new packet type that should be recognized and auto-parsed by # Net::SSH::Packet. Note that any packet type that is not preregistered # will not be autoparsed. # # The +pairs+ parameter must be either empty, or an array of two-element # tuples, where the first element of each tuple is the name of the field, # and the second is the type. # # register DISCONNECT, [:reason_code, :long], [:description, :string], [:language, :string] def self.register(type, *pairs) @@types[type] = pairs end include Connection::Constants include Authentication::Constants include Transport::Constants #-- # These are the recognized packet types. All other packet types will be # accepted, but not auto-parsed, requiring the client to parse the # fields using the methods provided by Net::SSH::Buffer. #++ register DISCONNECT, %i[reason_code long], %i[description string], %i[language string] register IGNORE, %i[data string] register UNIMPLEMENTED, %i[number long] register DEBUG, %i[always_display bool], %i[message string], %i[language string] register SERVICE_ACCEPT, %i[service_name string] register USERAUTH_BANNER, %i[message string], %i[language string] register USERAUTH_FAILURE, %i[authentications string], %i[partial_success bool] register GLOBAL_REQUEST, %i[request_type string], %i[want_reply bool], %i[request_data buffer] register CHANNEL_OPEN, %i[channel_type string], %i[remote_id long], %i[window_size long], %i[packet_size long] register CHANNEL_OPEN_CONFIRMATION, %i[local_id long], %i[remote_id long], %i[window_size long], %i[packet_size long] register CHANNEL_OPEN_FAILURE, %i[local_id long], %i[reason_code long], %i[description string], %i[language string] register CHANNEL_WINDOW_ADJUST, %i[local_id long], %i[extra_bytes long] register CHANNEL_DATA, %i[local_id long], %i[data string] register CHANNEL_EXTENDED_DATA, %i[local_id long], %i[data_type long], %i[data string] register CHANNEL_EOF, %i[local_id long] register CHANNEL_CLOSE, %i[local_id long] register CHANNEL_REQUEST, %i[local_id long], %i[request string], %i[want_reply bool], %i[request_data buffer] register CHANNEL_SUCCESS, %i[local_id long] register CHANNEL_FAILURE, %i[local_id long] # The (integer) type of this packet. attr_reader :type # Create a new packet from the given payload. This will automatically # parse the packet if it is one that has been previously registered with # Packet.register; otherwise, the packet will need to be manually parsed # using the methods provided in the Net::SSH::Buffer superclass. def initialize(payload) @named_elements = {} super @type = read_byte instantiate! end # Access one of the auto-parsed fields by name. Raises an error if no # element by the given name exists. def [](name) name = name.to_sym raise ArgumentError, "no such element #{name}" unless @named_elements.key?(name) @named_elements[name] end private # Parse the packet's contents and assign the named elements, as described # by the registered format for the packet. def instantiate! (@@types[type] || []).each do |name, datatype| @named_elements[name.to_sym] = if datatype == :buffer remainder_as_buffer else send("read_#{datatype}") end end end end end end net-ssh-7.2.1/lib/net/ssh/prompt.rb000066400000000000000000000040541454036133000171030ustar00rootroot00000000000000require 'io/console' 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. # # A custom prompt objects can implement caching, or different UI. The prompt # object should implemnted a start method, which should return something implementing # ask and success. Net::SSH uses it like: # # prompter = options[:password_prompt].start({type:'password'}) # while !ok && max_retries < 3 # user = prompter.ask("user: ", true) # password = prompter.ask("password: ", false) # ok = send(user, password) # prompter.sucess if ok # end # class Prompt # factory def self.default(options = {}) @default ||= new(options) end def initialize(options = {}); end # default prompt object implementation. More sophisticated implemenetations # might implement caching. class Prompter def initialize(info) if info[:type] == 'keyboard-interactive' $stdout.puts(info[:name]) unless info[:name].empty? $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) $stdout.print(prompt) $stdout.flush ret = $stdin.noecho(&:gets).chomp $stdout.print("\n") ret end # success method will be called when the password was accepted # It's a good time to save password asked to a cache. def success; end end # start password session. Multiple questions might be asked multiple times # on the returned object. Info hash tries to uniquely identify the password # session, so caching implementations can save passwords properly. def start(info) Prompter.new(info) end end end end net-ssh-7.2.1/lib/net/ssh/proxy/000077500000000000000000000000001454036133000164135ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/proxy/command.rb000066400000000000000000000073141454036133000203630ustar00rootroot00000000000000require 'socket' require 'rubygems' 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: # # require 'net/ssh/proxy/command' # # proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p') # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh| # ... # end class Command # The command line template attr_reader :command_line_template # The command line for the session attr_reader :command_line # Timeout in seconds in open, defaults to 60 attr_accessor :timeout # Create a new socket factory that tunnels via a command executed # with the user's shell, which is composed from the given command # template. In the command template, `%h' will be substituted by # the host name to connect and `%p' by the port. def initialize(command_line_template) @command_line_template = command_line_template @command_line = nil @timeout = 60 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 = nil) command_line = @command_line_template.gsub(/%(.)/) { case $1 when 'h' host when 'p' port.to_s when 'r' remote_user = connection_options && connection_options[:remote_user] if remote_user remote_user else raise ArgumentError, "remote user name not available" end when '%' '%' else raise ArgumentError, "unknown key: #{$1}" end } begin io = IO.popen(command_line, "r+") begin if result = IO.select([io], nil, [io], @timeout) if result.last.any? || io.eof? raise "command failed" end else raise "command timed out" end rescue StandardError close_on_error(io) raise end rescue StandardError => e raise ConnectError, "#{e}: #{command_line}" end @command_line = command_line if Gem.win_platform? # read_nonblock and write_nonblock are not available on Windows # pipe. Use sysread and syswrite as a replacement works. def io.send(data, flag) syswrite(data) end def io.recv(size) sysread(size) end else def io.send(data, flag) begin result = write_nonblock(data) rescue IO::WaitWritable, Errno::EINTR IO.select(nil, [self]) retry end result end def io.recv(size) begin result = read_nonblock(size) rescue IO::WaitReadable, Errno::EINTR timeout_in_seconds = 20 if IO.select([self], nil, [self], timeout_in_seconds) == nil raise "Unexpected spurious read wakeup" end retry end result end end io end def close_on_error(io) Process.kill('TERM', io.pid) Thread.new { io.close } end end end end end net-ssh-7.2.1/lib/net/ssh/proxy/errors.rb000066400000000000000000000006101454036133000202510ustar00rootroot00000000000000require 'net/ssh/errors' module Net module SSH module Proxy # A general exception class for all Proxy errors. class Error < Net::SSH::Exception; end # Used for reporting proxy connection errors. class ConnectError < Error; end # Used when the server doesn't recognize the user's credentials. class UnauthorizedError < Error; end end end end net-ssh-7.2.1/lib/net/ssh/proxy/http.rb000066400000000000000000000063611454036133000177250ustar00rootroot00000000000000require 'socket' require 'net/ssh/proxy/errors' 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: # # require 'net/ssh/proxy/http' # # proxy = Net::SSH::Proxy::HTTP.new('proxy_host', proxy_port) # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh| # ... # end # # If the proxy requires authentication, you can pass :user and :password # to the proxy's constructor: # # proxy = Net::SSH::Proxy::HTTP.new('proxy_host', proxy_port, # :user => "user", :password => "password") # # Note that HTTP digest authentication is not supported; Basic only at # this point. 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 # options are supported: # # * :user => the user name to use when authenticating to the proxy # * :password => the password to use when authenticating def initialize(proxy_host, proxy_port = 80, options = {}) @proxy_host = proxy_host @proxy_port = proxy_port @options = options end # Return a new socket connected to the given host and port via the # proxy that was requested when the socket factory was instantiated. def open(host, port, connection_options) socket = establish_connection(connection_options[:timeout]) socket.write "CONNECT #{host}:#{port} HTTP/1.1\r\n" socket.write "Host: #{host}:#{port}\r\n" if options[:user] credentials = ["#{options[:user]}:#{options[:password]}"].pack("m*").gsub(/\s/, "") socket.write "Proxy-Authorization: Basic #{credentials}\r\n" end socket.write "\r\n" resp = parse_response(socket) return socket if resp[:code] == 200 socket.close raise ConnectError, resp.inspect end protected def establish_connection(connect_timeout) Socket.tcp(proxy_host, proxy_port, nil, nil, connect_timeout: connect_timeout) end def parse_response(socket) version, code, reason = socket.gets.chomp.split(/ /, 3) headers = {} while (line = socket.gets) && (line.chomp! != "") name, value = line.split(/:/, 2) headers[name.strip] = value.strip end body = socket.read(headers["Content-Length"].to_i) if headers["Content-Length"] return { version: version, code: code.to_i, reason: reason, headers: headers, body: body } end end end end end net-ssh-7.2.1/lib/net/ssh/proxy/https.rb000066400000000000000000000033161454036133000201050ustar00rootroot00000000000000require 'socket' require 'openssl' require 'net/ssh/proxy/errors' require 'net/ssh/proxy/http' 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. class HTTPS < HTTP # 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. In addition to the options # taken by Net::SSH::Proxy::HTTP it supports: # # * :ssl_context => the SSL configuration to use for the connection def initialize(proxy_host, proxy_port = 80, options = {}) @ssl_context = options.delete(:ssl_context) || OpenSSL::SSL::SSLContext.new super(proxy_host, proxy_port, options) end protected # Shim to make OpenSSL::SSL::SSLSocket behave like a regular TCPSocket # for all intents and purposes of Net::SSH::BufferedIo module SSLSocketCompatibility def self.extended(object) # :nodoc: 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| socket.extend(SSLSocketCompatibility) socket.connect end end end end end end net-ssh-7.2.1/lib/net/ssh/proxy/jump.rb000066400000000000000000000034711454036133000177200ustar00rootroot00000000000000require 'uri' require 'net/ssh/proxy/command' 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: # # require 'net/ssh/proxy/jump' # # proxy = Net::SSH::Proxy::Jump.new('user@proxy') # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh| # ... # end 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".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 net-ssh-7.2.1/lib/net/ssh/proxy/socks4.rb000066400000000000000000000042431454036133000201510ustar00rootroot00000000000000require 'socket' require 'resolv' require 'ipaddr' 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: # # require 'net/ssh/proxy/socks4' # # proxy = Net::SSH::Proxy::SOCKS4.new('proxy.host', proxy_port, :user => 'user') # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh| # ... # end class SOCKS4 # The SOCKS protocol version used by this class VERSION = 4 # The packet type for connection requests CONNECT = 1 # The status code for a successful connection GRANTED = 90 # The proxy's host name or IP address, as given to the constructor. attr_reader :proxy_host # The proxy's port number. attr_reader :proxy_port # The additional options that were given to the proxy's constructor. attr_reader :options # Create a new proxy connection to the given proxy host and port. # Optionally, a :user key may be given to identify the username # with which to authenticate. def initialize(proxy_host, proxy_port = 1080, options = {}) @proxy_host = proxy_host @proxy_port = proxy_port @options = options end # Return a new socket connected to the given host and port via the # proxy that was requested when the socket factory was instantiated. def open(host, port, connection_options) socket = 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 version, status, port, ip = socket.recv(8).unpack("CCnN") if status != GRANTED socket.close raise ConnectError, "error connecting to proxy (#{status})" end return socket end end end end end net-ssh-7.2.1/lib/net/ssh/proxy/socks5.rb000066400000000000000000000106141454036133000201510ustar00rootroot00000000000000require 'socket' 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: # # require 'net/ssh/proxy/socks5' # # proxy = Net::SSH::Proxy::SOCKS5.new('proxy.host', proxy_port, # :user => 'user', :password => "password") # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh| # ... # end class SOCKS5 # The SOCKS protocol version used by this class VERSION = 5 # The SOCKS authentication type for requests without authentication METHOD_NO_AUTH = 0 # The SOCKS authentication type for requests via username/password METHOD_PASSWD = 2 # The SOCKS authentication type for when there are no supported # authentication methods. METHOD_NONE = 0xFF # The SOCKS packet type for requesting a proxy connection. CMD_CONNECT = 1 # The SOCKS address type for connections via IP address. ATYP_IPV4 = 1 # The SOCKS address type for connections via domain name. ATYP_DOMAIN = 3 # The SOCKS response code for a successful operation. SUCCESS = 0 # The proxy's host name or IP address attr_reader :proxy_host # The proxy's port number attr_reader :proxy_port # The map of options given at initialization attr_reader :options # Create a new proxy connection to the given proxy host and port. # Optionally, :user and :password options may be given to # identify the username and password with which to authenticate. def initialize(proxy_host, proxy_port = 1080, options = {}) @proxy_host = proxy_host @proxy_port = proxy_port @options = options end # Return a new socket connected to the given host and port via the # proxy that was requested when the socket factory was instantiated. def open(host, port, connection_options) socket = Socket.tcp(proxy_host, proxy_port, nil, nil, connect_timeout: connection_options[:timeout]) methods = [METHOD_NO_AUTH] methods << METHOD_PASSWD if options[:user] packet = [VERSION, methods.size, *methods].pack("C*") socket.send packet, 0 version, method = socket.recv(2).unpack("CC") if version != VERSION socket.close raise Net::SSH::Proxy::Error, "invalid SOCKS version (#{version})" end if method == METHOD_NONE socket.close raise Net::SSH::Proxy::Error, "no supported authorization methods" end negotiate_password(socket) if method == METHOD_PASSWD packet = [VERSION, CMD_CONNECT, 0].pack("C*") if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ packet << [ATYP_IPV4, $1.to_i, $2.to_i, $3.to_i, $4.to_i].pack("C*") else packet << [ATYP_DOMAIN, host.length, host].pack("CCA*") end 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) case address_type when 1 socket.recv(4) # get four bytes for IPv4 address when 3 len = socket.recv(1).getbyte(0) hostname = socket.recv(len) when 4 ipv6addr hostname = socket.recv(16) else socket.close raise ConnectError, "Illegal response type" end portnum = socket.recv(2) unless reply == SUCCESS socket.close raise ConnectError, "#{reply}" end return socket end private # Simple username/password negotiation with the SOCKS5 server. def negotiate_password(socket) packet = [0x01, options[:user].length, options[:user], options[:password].length, options[:password]].pack("CCA*CA*") socket.send packet, 0 version, status = socket.recv(2).unpack("CC") if status != SUCCESS socket.close raise UnauthorizedError, "could not authorize user" end end end end end end net-ssh-7.2.1/lib/net/ssh/service/000077500000000000000000000000001454036133000166725ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/service/forward.rb000066400000000000000000000437451454036133000207000ustar00rootroot00000000000000require '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 # returned by Connection::Session#forward: # # ssh.forward.local(1234, "www.capify.org", 80) class Forward include Loggable # The underlying connection service instance that the port-forwarding # services employ. attr_reader :session # A simple class for representing a requested remote forwarded port. Remote = Struct.new(:host, :port) # :nodoc: # Instantiates a new Forward service instance atop the given connection # service session. This will register new channel open handlers to handle # the specialized channels that the SSH port forwarding protocols employ. def initialize(session) @session = session self.logger = session.logger @remote_forwarded_ports = {} @local_forwarded_ports = {} @agent_forwarded = false @local_forwarded_sockets = {} session.on_open_channel('forwarded-tcpip', &method(:forwarded_tcpip)) session.on_open_channel('auth-agent', &method(:auth_agent_channel)) session.on_open_channel('auth-agent@openssh.com', &method(:auth_agent_channel)) end # Starts listening for connections on the local host, and forwards them # to the specified remote host/port via the SSH connection. This method # accepts either three or four arguments. When four arguments are given, # they are: # # * the local address to bind to # * the local port to listen on # * the remote host to forward connections to # * the port on the remote host to connect to # # If three arguments are given, it is as if the local bind address is # "127.0.0.1", and the rest are applied as above. # # To request an ephemeral port on the remote server, provide 0 (zero) for # the port number. In all cases, this method will return the port that # has been assigned. # # ssh.forward.local(1234, "www.capify.org", 80) # assigned_port = ssh.forward.local("0.0.0.0", 0, "www.capify.org", 80) def local(*args) if args.length < 3 || args.length > 4 raise ArgumentError, "expected 3 or 4 parameters, got #{args.length}" end local_port_type = :long socket = begin if defined?(UNIXServer) and args.first.class == UNIXServer local_port_type = :string args.shift else bind_address = "127.0.0.1" bind_address = args.shift if args.first.is_a?(String) && args.first =~ /\D/ local_port = args.shift.to_i local_port_type = :long TCPServer.new(bind_address, local_port) end end local_port = socket.addr[1] if local_port == 0 # ephemeral port was requested remote_host = args.shift remote_port = args.shift.to_i @local_forwarded_ports[[local_port, bind_address]] = socket session.listen_to(socket) do |server| client = server.accept debug { "received connection on #{socket}" } channel = session.open_channel("direct-tcpip", :string, remote_host, :long, remote_port, :string, bind_address, local_port_type, local_port) do |achannel| achannel.info { "direct channel established" } end prepare_client(client, channel, :local) channel.on_open_failed do |ch, code, description| channel.error { "could not establish direct channel: #{description} (#{code})" } session.stop_listening_to(channel[:socket]) channel[:socket].close end end local_port end # Terminates an active local forwarded port. # # ssh.forward.cancel_local(1234) # ssh.forward.cancel_local(1234, "0.0.0.0") def cancel_local(port, bind_address = "127.0.0.1") socket = @local_forwarded_ports.delete([port, bind_address]) socket.shutdown rescue nil socket.close rescue nil session.stop_listening_to(socket) end # Returns a list of all active locally forwarded ports. The returned value # is an array of arrays, where each element is a two-element tuple # consisting of the local port and bind address corresponding to the # forwarding port. def active_locals @local_forwarded_ports.keys end # Starts listening for connections on the local host, and forwards them # to the specified remote socket via the SSH connection. This will # (re)create the local socket file. The remote server needs to have the # socket file already available. # # ssh.forward.local_socket('/tmp/local.sock', '/tmp/remote.sock') def local_socket(local_socket_path, remote_socket_path) File.delete(local_socket_path) if File.exist?(local_socket_path) socket = Socket.unix_server_socket(local_socket_path) @local_forwarded_sockets[local_socket_path] = socket session.listen_to(socket) do |server| client = server.accept[0] debug { "received connection on #{socket}" } channel = session.open_channel("direct-streamlocal@openssh.com", :string, remote_socket_path, :string, nil, :long, 0) do |achannel| achannel.info { "direct channel established" } end prepare_client(client, channel, :local) channel.on_open_failed do |ch, code, description| channel.error { "could not establish direct channel: #{description} (#{code})" } session.stop_listening_to(channel[:socket]) channel[:socket].close end end local_socket_path end # Terminates an active local forwarded socket. # # ssh.forward.cancel_local_socket('/tmp/foo.sock') def cancel_local_socket(local_socket_path) socket = @local_forwarded_sockets.delete(local_socket_path) socket.shutdown rescue nil socket.close rescue nil session.stop_listening_to(socket) end # Returns a list of all active locally forwarded sockets. The returned value # is an array of Unix domain socket file paths. def active_local_sockets @local_forwarded_sockets.keys end # Requests that all connections on the given remote-port be forwarded via # the local host to the given port/host. The last argument describes the # bind address on the remote host, and defaults to 127.0.0.1. # # This method will return immediately, but the port will not actually be # forwarded immediately. If the remote server is not able to begin the # listener for this request, an exception will be raised asynchronously. # # To request an ephemeral port on the remote server, provide 0 (zero) for # the port number. The assigned port will show up in the # #active_remotes # list. # # remote_host is interpreted by the server per RFC 4254, which has these # special values: # # - "" means that connections are to be accepted on all protocol # families supported by the SSH implementation. # - "0.0.0.0" means to listen on all IPv4 addresses. # - "::" means to listen on all IPv6 addresses. # - "localhost" means to listen on all protocol families supported by # the SSH implementation on loopback addresses only ([RFC3330] and # [RFC3513]). # - "127.0.0.1" and "::1" indicate listening on the loopback # interfaces for IPv4 and IPv6, respectively. # # You may pass a block that will be called when the the port forward # request receives a response. This block will be passed the remote_port # that was actually bound to, or nil if the binding failed. If the block # returns :no_exception, the "failed binding" exception will not be thrown. # # If you want to block until the port is active, you could do something # like this: # # got_remote_port = nil # remote(port, host, remote_port, remote_host) do |actual_remote_port| # got_remote_port = actual_remote_port || :error # :no_exception # will yield the exception on my own thread # end # session.loop { !got_remote_port } # if got_remote_port == :error # raise Net::SSH::Exception, "remote forwarding request failed" # end # def remote(port, host, remote_port, remote_host = "127.0.0.1") session.send_global_request("tcpip-forward", :string, remote_host, :long, remote_port) do |success, response| if success remote_port = response.read_long if remote_port == 0 debug { "remote forward from remote #{remote_host}:#{remote_port} to #{host}:#{port} established" } @remote_forwarded_ports[[remote_port, remote_host]] = Remote.new(host, port) yield remote_port, remote_host if block_given? else instruction = if block_given? yield :error end unless instruction == :no_exception error { "remote forwarding request failed" } raise Net::SSH::Exception, "remote forwarding request failed" end end end end # an alias, for token backwards compatibility with the 1.x API alias :remote_to :remote # Requests that a remote forwarded port be cancelled. The remote forwarded # port on the remote host, bound to the given address on the remote host, # will be terminated, but not immediately. This method returns immediately # after queueing the request to be sent to the server. If for some reason # the port cannot be cancelled, an exception will be raised (asynchronously). # # If you want to know when the connection has been cancelled, it will no # longer be present in the #active_remotes list. If you want to block until # the port is no longer active, you could do something like this: # # ssh.forward.cancel_remote(1234, "0.0.0.0") # ssh.loop { ssh.forward.active_remotes.include?([1234, "0.0.0.0"]) } def cancel_remote(port, host = "127.0.0.1") session.send_global_request("cancel-tcpip-forward", :string, host, :long, port) do |success, response| if success @remote_forwarded_ports.delete([port, host]) else raise Net::SSH::Exception, "could not cancel remote forward request on #{host}:#{port}" end end end # Returns all active forwarded remote ports. The returned value is an # array of two-element tuples, where the first element is the port on the # remote host and the second is the bind address. def active_remotes @remote_forwarded_ports.keys end # Returns all active remote forwarded ports and where they forward to. The # returned value is a hash from [, ] # to [, ]. def active_remote_destinations @remote_forwarded_ports.each_with_object({}) do |(remote, local), result| result[[local.port, local.host]] = remote end end # Enables SSH agent forwarding on the given channel. The forwarded agent # will remain active even after the channel closes--the channel is only # used as the transport for enabling the forwarded connection. You should # never need to call this directly--it is called automatically the first # time a session channel is opened, when the connection was created with # :forward_agent set to true: # # Net::SSH.start("remote.host", "me", :forward_agent => true) do |ssh| # ssh.open_channel do |ch| # # agent will be automatically forwarded by this point # end # ssh.loop # end def agent(channel) return if @agent_forwarded @agent_forwarded = true channel.send_channel_request("auth-agent-req@openssh.com") do |achannel, success| if success debug { "authentication agent forwarding is active" } else achannel.send_channel_request("auth-agent-req") do |a2channel, success2| if success2 debug { "authentication agent forwarding is active" } else error { "could not establish forwarding of authentication agent" } end end end end end private # Perform setup operations that are common to all forwarded channels. # +client+ is a socket, +channel+ is the channel that was just created, # and +type+ is an arbitrary string describing the type of the channel. def prepare_client(client, channel, type) client.extend(Net::SSH::BufferedIo) client.extend(Net::SSH::ForwardedBufferedIo) client.logger = logger session.listen_to(client) channel[:socket] = client channel.on_data do |ch, data| debug { "data:#{data.length} on #{type} forwarded channel" } ch[:socket].enqueue(data) end channel.on_eof do |ch| debug { "eof #{type} on #{type} forwarded channel" } begin ch[:socket].send_pending ch[:socket].shutdown Socket::SHUT_WR rescue IOError => e if e.message =~ /closed/ then debug { "epipe in on_eof => shallowing exception:#{e}" } else raise end rescue Errno::EPIPE => e debug { "epipe in on_eof => shallowing exception:#{e}" } rescue Errno::ENOTCONN => e debug { "enotconn in on_eof => shallowing exception:#{e}" } end end channel.on_close do |ch| debug { "closing #{type} forwarded channel" } ch[:socket].close if !client.closed? session.stop_listening_to(ch[:socket]) end channel.on_process do |ch| if ch[:socket].closed? ch.info { "#{type} forwarded connection closed" } ch.close elsif ch[:socket].available > 0 data = ch[:socket].read_available(8192) ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" } ch.send_data(data) end end end # not a real socket, so use a simpler behaviour def prepare_simple_client(client, channel, type) channel[:socket] = client channel.on_data do |ch, data| ch.debug { "data:#{data.length} on #{type} forwarded channel" } ch[:socket].send(data) end channel.on_process do |ch| data = ch[:socket].read(8192) if data ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" } ch.send_data(data) end end end # The callback used when a new "forwarded-tcpip" channel is requested # by the server. This will open a new socket to the host/port specified # when the forwarded connection was first requested. def forwarded_tcpip(session, channel, packet) connected_address = packet.read_string connected_port = packet.read_long 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}" } prepare_client(client, channel, :remote) rescue SocketError => err raise Net::SSH::ChannelOpenFailed.new(2, "could not connect to remote host (#{remote.host}:#{remote.port}): #{err.message}") end # The callback used when an auth-agent channel is requested by the server. def auth_agent_channel(session, channel, packet) info { "opening auth-agent channel" } channel[:invisible] = true begin agent = Authentication::Agent.connect(logger, session.options[:agent_socket_factory]) if (agent.socket.is_a? ::IO) prepare_client(agent.socket, channel, :agent) else prepare_simple_client(agent.socket, channel, :agent) end rescue Exception => e error { "attempted to connect to agent but failed: #{e.class.name} (#{e.message})" } raise Net::SSH::ChannelOpenFailed.new(2, "could not connect to authentication agent") end end end end end end net-ssh-7.2.1/lib/net/ssh/test.rb000066400000000000000000000071411454036133000165410ustar00rootroot00000000000000require 'net/ssh/transport/session' require 'net/ssh/connection/session' require 'net/ssh/test/kex' 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 # "story" of expected sends and receives: # # require 'minitest/autorun' # require 'net/ssh/test' # # class MyTest < Minitest::Test # include Net::SSH::Test # # def test_exec_via_channel_works # story do |session| # channel = session.opens_channel # channel.sends_exec "ls" # channel.gets_data "result of ls" # channel.gets_close # channel.sends_close # end # # assert_scripted do # result = nil # # connection.open_channel do |ch| # ch.exec("ls") do |success| # ch.on_data { |c, data| result = data } # ch.on_close { |c| c.close } # end # end # # connection.loop # assert_equal "result of ls", result # end # end # end # # See Net::SSH::Test::Channel and Net::SSH::Test::Script for more options. # # Note that the Net::SSH::Test system is rather finicky yet, and can be kind # of frustrating to get working. Any suggestions for improvement will be # welcome! module Test # If a block is given, yields the script for the test socket (#socket). # Otherwise, simply returns the socket's script. See Net::SSH::Test::Script. def story Net::SSH::Test::Extensions::IO.with_test_extension { yield socket.script if block_given? } return socket.script end # Returns the test socket instance to use for these tests (see # Net::SSH::Test::Socket). def socket(options = {}) @socket ||= Net::SSH::Test::Socket.new end # Returns the connection session (Net::SSH::Connection::Session) for use # in these tests. It is a fully functional SSH session, operating over # a mock socket (#socket). def connection(options = {}) @connection ||= Net::SSH::Connection::Session.new(transport(options), options) end # Returns the transport session (Net::SSH::Transport::Session) for use # in these tests. It is a fully functional SSH transport session, operating # over a mock socket (#socket). def transport(options = {}) @transport ||= Net::SSH::Transport::Session.new( options[:host] || "localhost", options.merge(kex: "test", host_key: "ssh-rsa", append_all_supported_algorithms: true, verify_host_key: :never, proxy: socket(options)) ) end # First asserts that a story has been described (see #story). Then yields, # and then asserts that all items described in the script have been # processed. Typically, this is called immediately after a story has # been built, and the SSH commands being tested are then executed within # 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 net-ssh-7.2.1/lib/net/ssh/test/000077500000000000000000000000001454036133000162115ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/test/channel.rb000066400000000000000000000124541454036133000201540ustar00rootroot00000000000000module 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 # a convenience for scripting channel-related activity for later comparison # in a unit test. # # story do |session| # channel = session.opens_channel # channel.sends_exec "ls" # channel.gets_data "result of ls" # channel.gets_extended_data "some error coming from ls" # channel.gets_close # channel.sends_close # end 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 # essentially just mimics receiving an empty data packet): # # channel.gets_data "abcdefg" # channel.inject_remote_delay! # channel.gets_data "hijklmn" def inject_remote_delay! gets_data("") end # Scripts the sending of an "exec" channel request packet to the mock # server. If +reply+ is true, then the server is expected to reply to the # request, otherwise no response to this request will be sent. If +success+ # is +true+, then the request will be successful, otherwise a failure will # be scripted. # # channel.sends_exec "ls -l" def sends_exec(command, reply = true, success = true) script.sends_channel_request(self, "exec", reply, command, success) end # Scripts the sending of a "subsystem" channel request packet to the mock # server. See #sends_exec for a discussion of the meaning of the +reply+ # and +success+ arguments. # # channel.sends_subsystem "sftp" def sends_subsystem(subsystem, reply = true, success = true) 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. # # channel.gets_extended_data "whoops" 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 def gets_close script.gets_channel_close(self) end end end end end net-ssh-7.2.1/lib/net/ssh/test/extensions.rb000066400000000000000000000151141454036133000207370ustar00rootroot00000000000000require 'net/ssh/buffer' require 'net/ssh/packet' require 'net/ssh/buffered_io' require 'net/ssh/connection/channel' require 'net/ssh/connection/constants' require 'net/ssh/transport/constants' require 'net/ssh/transport/packet_stream' 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 # Returns +true+ if the position in the stream is less than the total # length of the stream. 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 # (rather than concatenated) data packets. def send_data_for_test(data) send_data_for_real(data) 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 begin yield ensure @extension_enabled = orig_value end end def self.extension_enabled? @extension_enabled end module ClassMethods def self.extended(obj) # :nodoc: class << obj alias_method :select_for_real, :select alias_method :select, :select_for_test end end # The testing version of ::IO.select. Assumes that all readers, # writers, and errors arrays are either nil, or contain only objects # that mix in Net::SSH::Test::Extensions::BufferedIo. def select_for_test(readers = nil, writers = nil, errors = nil, wait = nil) 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 [[], [], []] end end end end end end end Net::SSH::BufferedIo.send(:include, Net::SSH::Test::Extensions::BufferedIo) Net::SSH::Transport::PacketStream.send(:include, Net::SSH::Test::Extensions::PacketStream) Net::SSH::Connection::Channel.send(:include, Net::SSH::Test::Extensions::Channel) IO.send(:include, Net::SSH::Test::Extensions::IO) net-ssh-7.2.1/lib/net/ssh/test/kex.rb000066400000000000000000000030201454036133000173200ustar00rootroot00000000000000require 'openssl' require 'net/ssh/errors' require 'net/ssh/transport/algorithms' require 'net/ssh/transport/constants' require 'net/ssh/transport/kex' 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.) # # This registers itself with the transport key-exchange system as the # "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 Net::SSH::Transport::Algorithms::ALGORITHMS[:kex] << "test" Net::SSH::Transport::Kex::MAP["test"] = Net::SSH::Test::Kex net-ssh-7.2.1/lib/net/ssh/test/local_packet.rb000066400000000000000000000036471454036133000211710ustar00rootroot00000000000000require 'net/ssh/packet' 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 # of the sends_* methods. class LocalPacket < Packet attr_reader :init # Extend the default Net::SSH::Test::Packet constructor to also accept an # optional block, which is used to finalize the initialization of the # packet when #process is first called. def initialize(type, *args, &block) super(type, *args) @init = block end # Returns +true+; this is a local packet. def local? true end # Called by Net::SSH::Test::Extensions::PacketStream#test_enqueue_packet # to mimic remote processing of a locally-sent packet. It compares the # packet it was given with the contents of this LocalPacket's data, to see # if what was sent matches what was scripted. If it differs in any way, # an exception is raised. def process(packet) @init.call(Net::SSH::Packet.new(packet.to_s)) if @init type = packet.read_byte raise "expected #{@type}, but got #{type}" if @type != type @data.zip(types).each do |expected, _type| _type ||= case expected when nil then break when Numeric then :long when String then :string when TrueClass, FalseClass then :bool end actual = packet.send("read_#{_type}") next if expected.nil? raise "expected #{_type} #{expected.inspect} but got #{actual.inspect}" unless expected == actual end end end end end end net-ssh-7.2.1/lib/net/ssh/test/packet.rb000066400000000000000000000112371454036133000200110ustar00rootroot00000000000000require 'net/ssh/connection/constants' 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. # # These packets are not true packets, in that they don't represent what was # actually sent between the hosst; rather, they represent what was expected # to be sent, as dictated by the script (Net::SSH::Test::Script). Thus, # though they are defined with data elements, these data elements are used # to either validate data that was sent by the local host (Net::SSH::Test::LocalPacket) # or to mimic the sending of data by the remote host (Net::SSH::Test::RemotePacket). class Packet include Net::SSH::Transport::Constants include Net::SSH::Connection::Constants # Register a custom channel request. extra_parts is an array of types # of extra parameters def self.register_channel_request(request, extra_parts) @registered_requests ||= {} @registered_requests[request] = { extra_parts: extra_parts } end def self.registered_channel_requests(request) @registered_requests && @registered_requests[request] end # Ceate a new packet of the given +type+, and with +args+ being a list of # data elements in the order expected for packets of the given +type+ # (see #types). def initialize(type, *args) @type = self.class.const_get(type.to_s.upcase) @data = args end # The default for +remote?+ is false. Subclasses should override as necessary. def remote? false end # The default for +local?+ is false. Subclasses should override as necessary. def local? false end # Instantiates the packets data elements. When the packet was first defined, # some elements may not have been fully realized, and were described as # Proc objects rather than atomic types. This invokes those Proc objects # and replaces them with their returned values. This allows for values # like Net::SSH::Test::Channel#remote_id to be used in scripts before # the remote_id is known (since it is only known after a channel has been # confirmed open). def instantiate! @data.map! { |i| i.respond_to?(:call) ? i.call : i } end # Returns an array of symbols describing the data elements for packets of # the same type as this packet. These types are used to either validate # sent packets (Net::SSH::Test::LocalPacket) or build received packets # (Net::SSH::Test::RemotePacket). # # Not all packet types are defined here. As new packet types are required # (e.g., a unit test needs to test that the remote host sent a packet that # is not implemented here), the description of that packet should be # added. Unsupported packet types will otherwise raise an exception. def types @types ||= case @type when KEXINIT %i[long long long long string string string string string string string string string string bool] when NEWKEYS then [] when CHANNEL_OPEN then %i[string long long long] when CHANNEL_OPEN_CONFIRMATION then %i[long long long long] when CHANNEL_DATA then %i[long string] when CHANNEL_EXTENDED_DATA then %i[long long string] when CHANNEL_EOF, CHANNEL_CLOSE, CHANNEL_SUCCESS, CHANNEL_FAILURE then [:long] when CHANNEL_REQUEST parts = %i[long string bool] case @data[1] when "exec", "subsystem", "shell" then parts << :string when "exit-status" then parts << :long when "pty-req" then parts.concat(%i[string long long long long string]) when "env" then parts.contact(%i[string string]) else request = Packet.registered_channel_requests(@data[1]) raise "don't know what to do about #{@data[1]} channel request" unless request parts.concat(request[:extra_parts]) end else raise "don't know how to parse packet type #{@type}" end end end end end end net-ssh-7.2.1/lib/net/ssh/test/remote_packet.rb000066400000000000000000000030241454036133000213570ustar00rootroot00000000000000require 'net/ssh/buffer' 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 received by the local (client) host. These are created # automatically by Net::SSH::Test::Script and Net::SSH::Test::Channel by any # of the gets_* methods. class RemotePacket < Packet # Returns +true+; this is a remote packet. 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 # to be sent). This will happen when either your test script # (Net::SSH::Test::Script) or your program are wrong. 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 # 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. def to_s @to_s ||= begin instantiate! string = Net::SSH::Buffer.from(:byte, @type, *types.zip(@data).flatten).to_s [string.length, string].pack("NA*") end end end end end end net-ssh-7.2.1/lib/net/ssh/test/script.rb000066400000000000000000000173431454036133000200520ustar00rootroot00000000000000require 'net/ssh/test/channel' require 'net/ssh/test/local_packet' require 'net/ssh/test/remote_packet' 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 # events for packets being received by the local from the remote host. # # A reference to a script. is generally obtained in a unit test via the # Net::SSH::Test#story helper method: # # story do |script| # channel = script.opens_channel # ... # end class Script # The list of scripted events. These will be Net::SSH::Test::LocalPacket # and Net::SSH::Test::RemotePacket instances. attr_reader :events # Create a new, empty script. def initialize @events = [] end # Scripts the opening of a channel by adding a local packet sending the # channel open request, and if +confirm+ is true (the default), also # adding a remote packet confirming the new channel. # # A new Net::SSH::Test::Channel instance is returned, which can be used # to script additional channel operations. def opens_channel(confirm = true) 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 # indicating whether a response to this packet is required , and +data+ # is any additional request-specific data that this packet should send. # +success+ indicates whether the response (if one is required) should be # success or failure. If +data+ is an array it will be treated as multiple # data. # # If a reply is desired, a remote packet will also be queued, :channel_success # if +success+ is true, or :channel_failure if +success+ is false. # # This will typically be called via Net::SSH::Test::Channel#sends_exec or # Net::SSH::Test::Channel#sends_subsystem. def sends_channel_request(channel, request, reply, data, success = true) if data.is_a? Array events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, *data) else events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, data) end if reply if success events << RemotePacket.new(:channel_success, channel.local_id) else events << RemotePacket.new(:channel_failure, channel.local_id) 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. # # This will typically be called via Net::SSH::Test::Channel#sends_data. 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. def sends_channel_request_pty(channel) data = ['pty-req', false] 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. # # Currently the only extended data type is stderr == 1. 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. # # # remove the next event and return it # event = script.next # # # peek at the next event # event = script.next(:first) 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. def process(packet) event = events.shift or raise "end of script reached, but got a packet type #{packet.read_byte}" event.process(packet) end end end end end net-ssh-7.2.1/lib/net/ssh/test/socket.rb000066400000000000000000000037501454036133000200330ustar00rootroot00000000000000require 'socket' require 'stringio' require 'net/ssh/test/extensions' require 'net/ssh/test/script' 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 net-ssh-7.2.1/lib/net/ssh/transport/000077500000000000000000000000001454036133000172665ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/transport/algorithms.rb000066400000000000000000000515111454036133000217670ustar00rootroot00000000000000require 'net/ssh/buffer' require 'net/ssh/known_hosts' require 'net/ssh/loggable' require 'net/ssh/transport/cipher_factory' require 'net/ssh/transport/constants' require 'net/ssh/transport/hmac' require 'net/ssh/transport/kex' require 'net/ssh/transport/kex/curve25519_sha256_loader' require 'net/ssh/transport/server_version' require 'net/ssh/authentication/ed25519_loader' module Net module SSH module Transport # Implements the higher-level logic behind an SSH key-exchange. It handles # both the initial exchange, as well as subsequent re-exchanges (as needed). # It also encapsulates the negotiation of the algorithms, and provides a # single point of access to the negotiated algorithms. # # You will never instantiate or reference this directly. It is used # internally by the transport layer. class Algorithms include Loggable include Constants # Define the default algorithms, in order of preference, supported by Net::SSH. DEFAULT_ALGORITHMS = { host_key: %w[ecdsa-sha2-nistp521-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp521 ecdsa-sha2-nistp384 ecdsa-sha2-nistp256 ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512], kex: %w[ecdh-sha2-nistp521 ecdh-sha2-nistp384 ecdh-sha2-nistp256 diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1], encryption: %w[aes256-ctr aes192-ctr aes128-ctr], hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512 hmac-sha2-256 hmac-sha1] }.freeze if Net::SSH::Transport::ChaCha20Poly1305CipherLoader::LOADED DEFAULT_ALGORITHMS[:encryption].unshift( 'chacha20-poly1305@openssh.com' ) end if Net::SSH::Authentication::ED25519Loader::LOADED DEFAULT_ALGORITHMS[:host_key].unshift( 'ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519' ) end if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED DEFAULT_ALGORITHMS[:kex].unshift( 'curve25519-sha256', 'curve25519-sha256@libssh.org' ) end # Define all algorithms, with the deprecated, supported by Net::SSH. ALGORITHMS = { host_key: DEFAULT_ALGORITHMS[:host_key] + %w[ssh-dss], kex: DEFAULT_ALGORITHMS[:kex] + %w[diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1], encryption: DEFAULT_ALGORITHMS[:encryption] + %w[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], hmac: DEFAULT_ALGORITHMS[:hmac] + %w[hmac-sha2-512-96 hmac-sha2-256-96 hmac-sha1-96 hmac-ripemd160 hmac-ripemd160@openssh.com hmac-md5 hmac-md5-96 none], compression: %w[none zlib@openssh.com zlib], language: %w[] }.freeze # The underlying transport layer session that supports this object attr_reader :session # The hash of options used to initialize this object attr_reader :options # The kex algorithm to use settled on between the client and server. attr_reader :kex # The type of host key that will be used for this session. attr_reader :host_key # The type of the cipher to use to encrypt packets sent from the client to # the server. attr_reader :encryption_client # The type of the cipher to use to decrypt packets arriving from the server. attr_reader :encryption_server # The type of HMAC to use to sign packets sent by the client. attr_reader :hmac_client # The type of HMAC to use to validate packets arriving from the server. attr_reader :hmac_server # The type of compression to use to compress packets being sent by the client. attr_reader :compression_client # The type of compression to use to decompress packets arriving from the server. attr_reader :compression_server # The language that will be used in messages sent by the client. attr_reader :language_client # The language that will be used in messages sent from the server. attr_reader :language_server # The hash of algorithms preferred by the client, which will be told to # the server during algorithm negotiation. attr_reader :algorithms # The session-id for this session, as decided during the initial key exchange. attr_reader :session_id # Returns true if the given packet can be processed during a key-exchange. def self.allowed_packet?(packet) (1..4).include?(packet.type) || (6..19).include?(packet.type) || (21..49).include?(packet.type) end # Instantiates a new Algorithms object, and prepares the hash of preferred # algorithms based on the options parameter and the ALGORITHMS constant. def initialize(session, options = {}) @session = session @logger = session.logger @options = options @algorithms = {} @pending = @initialized = false @client_packet = @server_packet = nil prepare_preferred_algorithms! end # Start the algorithm negotation def start raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized send_kexinit end # Request a rekey operation. This will return immediately, and does not # actually perform the rekey operation. It does cause the session to change # state, however--until the key exchange finishes, no new packets will be # processed. def rekey! @client_packet = @server_packet = nil @initialized = false send_kexinit end # Called by the transport layer when a KEXINIT packet is received, indicating # that the server wants to exchange keys. This can be spontaneous, or it # can be in response to a client-initiated rekey request (see #rekey!). Either # way, this will block until the key exchange completes. def accept_kexinit(packet) info { "got KEXINIT from server" } @server_data = parse_server_algorithm_packet(packet) @server_packet = @server_data[:raw] if !pending? send_kexinit else proceed! end end # A convenience method for accessing the list of preferred types for a # specific algorithm (see #algorithms). def [](key) algorithms[key] end # Returns +true+ if a key-exchange is pending. This will be true from the # moment either the client or server requests the key exchange, until the # exchange completes. While an exchange is pending, only a limited number # of packets are allowed, so event processing essentially stops during this # period. def pending? @pending end # Returns true if no exchange is pending, and otherwise returns true or # false depending on whether the given packet is of a type that is allowed # during a key exchange. def allow?(packet) !pending? || Algorithms.allowed_packet?(packet) end # Returns true if the algorithms have been negotiated at all. def initialized? @initialized end def host_key_format case host_key when /^([a-z0-9-]+)-cert-v\d{2}@openssh.com$/ Regexp.last_match[1] else host_key end end private # Sends a KEXINIT packet to the server. If a server KEXINIT has already # been received, this will then invoke #proceed! to proceed with the key # exchange, otherwise it returns immediately (but sets the object to the # pending state). def send_kexinit info { "sending KEXINIT" } @pending = true packet = build_client_algorithm_packet @client_packet = packet.to_s session.send_message(packet) proceed! if @server_packet end # After both client and server have sent their KEXINIT packets, this # will do the algorithm negotiation and key exchange. Once both finish, # the object leaves the pending state and the method returns. def proceed! info { "negotiating algorithms" } negotiate_algorithms exchange_keys @pending = false end # Prepares the list of preferred algorithms, based on the options hash # that was given when the object was constructed, and the ALGORITHMS # constant. Also, when determining the host_key type to use, the known # hosts files are examined to see if the host has ever sent a host_key # before, and if so, that key type is used as the preferred type for # communicating with this server. def prepare_preferred_algorithms! options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true ALGORITHMS.each do |algorithm, supported| algorithms[algorithm] = compose_algorithm_list( supported, options[algorithm] || DEFAULT_ALGORITHMS[algorithm], options[:append_all_supported_algorithms] ) end # for convention, make sure our list has the same keys as the server # list algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption] algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac] algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression] algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language] if !options.key?(:host_key) # make sure the host keys are specified in preference order, where any # existing known key for the host has preference. existing_keys = session.host_keys 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 algorithms[:host_key] = host_keys end end # Composes the list of algorithms by taking supported algorithms and matching with supplied options. def compose_algorithm_list(supported, option, append_all_supported_algorithms = false) return supported.dup unless option list = [] option = Array(option).compact.uniq if option.first && option.first.start_with?('+', '-') list = supported.dup appends = option.select { |opt| opt.start_with?('+') }.map { |opt| opt[1..-1] } deletions = option.select { |opt| opt.start_with?('-') }.map { |opt| opt[1..-1] } list.concat(appends) deletions.each do |opt| if opt.include?('*') opt_escaped = Regexp.escape(opt) algo_re = /\A#{opt_escaped.gsub('\*', '[A-Za-z\d\-@\.]*')}\z/ list.delete_if { |existing_opt| algo_re.match(existing_opt) } else list.delete(opt) end end list.uniq! else list = option if append_all_supported_algorithms supported.each { |name| list << name unless list.include?(name) } end end unsupported = [] list.select! do |name| is_supported = supported.include?(name) unsupported << name unless is_supported is_supported end lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty? list end # Parses a KEXINIT packet from the server. def parse_server_algorithm_packet(packet) data = { raw: packet.content } packet.read(16) # skip the cookie value data[:kex] = packet.read_string.split(/,/) data[:host_key] = packet.read_string.split(/,/) data[:encryption_client] = packet.read_string.split(/,/) data[:encryption_server] = packet.read_string.split(/,/) data[:hmac_client] = packet.read_string.split(/,/) data[:hmac_server] = packet.read_string.split(/,/) data[:compression_client] = packet.read_string.split(/,/) data[:compression_server] = packet.read_string.split(/,/) data[:language_client] = packet.read_string.split(/,/) data[:language_server] = packet.read_string.split(/,/) # TODO: if first_kex_packet_follows, we need to try to skip the # actual kexinit stuff and try to guess what the server is doing... # need to read more about this scenario. # first_kex_packet_follows = packet.read_bool return data end # Given the #algorithms map of preferred algorithm types, this constructs # a KEXINIT packet to send to the server. It does not actually send it, # it simply builds the packet and returns it. def build_client_algorithm_packet kex = algorithms[:kex].join(",") host_key = algorithms[:host_key].join(",") encryption = algorithms[:encryption].join(",") hmac = algorithms[:hmac].join(",") compression = algorithms[:compression].join(",") language = algorithms[:language].join(",") Net::SSH::Buffer.from(:byte, KEXINIT, :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)], :mstring, [kex, host_key, encryption, encryption, hmac, hmac], :mstring, [compression, compression, language, language], :bool, false, :long, 0) end # Given the parsed server KEX packet, and the client's preferred algorithm # lists in #algorithms, determine which preferred algorithms each has # in common and set those as the selected algorithms. If, for any algorithm, # no type can be settled on, an exception is raised. def negotiate_algorithms @kex = negotiate(:kex) @host_key = negotiate(:host_key) @encryption_client = negotiate(:encryption_client) @encryption_server = negotiate(:encryption_server) @hmac_client = negotiate(:hmac_client) @hmac_server = negotiate(:hmac_server) @compression_client = negotiate(:compression_client) @compression_server = negotiate(:compression_server) @language_client = negotiate(:language_client) rescue "" @language_server = negotiate(:language_server) rescue "" debug do "negotiated:\n" + %i[kex host_key encryption_server encryption_client hmac_client hmac_server compression_client compression_server language_client language_server].map do |key| "* #{key}: #{instance_variable_get("@#{key}")}" end.join("\n") end end # Negotiates a single algorithm based on the preferences reported by the # server and those set by the client. This is called by # #negotiate_algorithms. def negotiate(algorithm) match = self[algorithm].find { |item| @server_data[algorithm].include?(item) } if match.nil? raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm\n"\ "Server #{algorithm} preferences: #{@server_data[algorithm].join(',')}\n"\ "Client #{algorithm} preferences: #{self[algorithm].join(',')}" end return match end # Considers the sizes of the keys and block-sizes for the selected ciphers, # and the lengths of the hmacs, and returns the largest as the byte requirement # for the key-exchange algorithm. def kex_byte_requirement sizes = [8] # require at least 8 bytes sizes.concat(CipherFactory.get_lengths(encryption_client)) sizes.concat(CipherFactory.get_lengths(encryption_server)) sizes << HMAC.key_length(hmac_client) sizes << HMAC.key_length(hmac_server) sizes.max end # Instantiates one of the Transport::Kex classes (based on the negotiated # kex algorithm), and uses it to exchange keys. Then, the ciphers and # HMACs are initialized and fed to the transport layer, to be used in # further communication with the server. def exchange_keys debug { "exchanging keys" } need_bytes = kex_byte_requirement algorithm = Kex::MAP[kex].new(self, session, client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION, server_version_string: session.server_version.version, server_algorithm_packet: @server_packet, client_algorithm_packet: @client_packet, need_bytes: need_bytes, minimum_dh_bits: options[:minimum_dh_bits], logger: logger) result = algorithm.exchange_keys secret = result[:shared_secret].to_ssh hash = result[:session_id] digester = result[:hashing_algorithm] @session_id ||= hash key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) } iv_client = key["A"] iv_server = key["B"] key_client = key["C"] key_server = key["D"] mac_key_client = key["E"] mac_key_server = key["F"] parameters = { shared: secret, hash: hash, digester: digester } cipher_client = CipherFactory.get( encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true) ) cipher_server = CipherFactory.get( encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true) ) mac_client = if cipher_client.implicit_mac? cipher_client.implicit_mac else HMAC.get(hmac_client, mac_key_client, parameters) end mac_server = if cipher_server.implicit_mac? cipher_server.implicit_mac else HMAC.get(hmac_server, mac_key_server, parameters) end session.configure_client cipher: cipher_client, hmac: mac_client, compression: normalize_compression_name(compression_client), compression_level: options[:compression_level], rekey_limit: options[:rekey_limit], max_packets: options[:rekey_packet_limit], max_blocks: options[:rekey_blocks_limit] session.configure_server cipher: cipher_server, hmac: mac_server, compression: normalize_compression_name(compression_server), rekey_limit: options[:rekey_limit], max_packets: options[:rekey_packet_limit], max_blocks: options[:rekey_blocks_limit] @initialized = true end # Given the SSH name for some compression algorithm, return a normalized # name as a symbol. def normalize_compression_name(name) case name when "none" then false when "zlib" then :standard when "zlib@openssh.com" then :delayed else raise ArgumentError, "unknown compression type `#{name}'" end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/chacha20_poly1305_cipher.rb000066400000000000000000000061621454036133000240770ustar00rootroot00000000000000require 'rbnacl' require 'net/ssh/loggable' module Net module SSH module Transport ## Implements the chacha20-poly1305@openssh cipher class ChaCha20Poly1305Cipher include Net::SSH::Loggable # Implicit HMAC, no need to do anything class ImplicitHMac def etm # TODO: ideally this shouln't be called true end def key_length 64 end end def initialize(encrypt:, key:) @chacha_hdr = OpenSSL::Cipher.new("chacha20") key_len = @chacha_hdr.key_len @chacha_main = OpenSSL::Cipher.new("chacha20") @poly = RbNaCl::OneTimeAuths::Poly1305 if key.size < key_len * 2 error { "chacha20_poly1305: keylength doesn't match" } raise "chacha20_poly1305: keylength doesn't match" end if encrypt @chacha_hdr.encrypt @chacha_main.encrypt else @chacha_hdr.decrypt @chacha_main.decrypt end main_key = key[0...key_len] @chacha_main.key = main_key hdr_key = key[key_len...(2 * key_len)] @chacha_hdr.key = hdr_key end def update_cipher_mac(payload, sequence_number) iv_data = [0, 0, 0, sequence_number].pack("NNNN") @chacha_main.iv = iv_data poly_key = @chacha_main.update(([0] * 32).pack('C32')) packet_length = payload.size length_data = [packet_length].pack("N") @chacha_hdr.iv = iv_data packet = @chacha_hdr.update(length_data) iv_data[0] = 1.chr @chacha_main.iv = iv_data unencrypted_data = payload packet += @chacha_main.update(unencrypted_data) packet += @poly.auth(poly_key, packet) return packet end def read_length(data, sequence_number) iv_data = [0, 0, 0, sequence_number].pack("NNNN") @chacha_hdr.iv = iv_data @chacha_hdr.update(data).unpack1("N") end def read_and_mac(data, mac, sequence_number) iv_data = [0, 0, 0, sequence_number].pack("NNNN") @chacha_main.iv = iv_data poly_key = @chacha_main.update(([0] * 32).pack('C32')) iv_data[0] = 1.chr @chacha_main.iv = iv_data unencrypted_data = @chacha_main.update(data[4..]) begin ok = @poly.verify(poly_key, mac, data[0..]) raise Net::SSH::Exception, "corrupted hmac detected #{name}" unless ok rescue RbNaCl::BadAuthenticatorError raise Net::SSH::Exception, "corrupted hmac detected #{name}" end return unencrypted_data end def mac_length 16 end def block_size 8 end def name "chacha20-poly1305@openssh.com" end def implicit_mac? true end def implicit_mac return ImplicitHMac.new end def self.block_size 8 end def self.key_length 64 end end end end end net-ssh-7.2.1/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb000066400000000000000000000006261454036133000254240ustar00rootroot00000000000000module Net module SSH module Transport # Loads chacha20 poly1305 support which requires optinal dependency rbnacl module ChaCha20Poly1305CipherLoader begin require 'net/ssh/transport/chacha20_poly1305_cipher' LOADED = true ERROR = nil rescue LoadError => e ERROR = e LOADED = false end end end end end net-ssh-7.2.1/lib/net/ssh/transport/cipher_factory.rb000066400000000000000000000112621454036133000226160ustar00rootroot00000000000000require 'openssl' require 'net/ssh/transport/ctr.rb' require 'net/ssh/transport/key_expander' require 'net/ssh/transport/identity_cipher' require 'net/ssh/transport/chacha20_poly1305_cipher_loader' require 'net/ssh/transport/openssl_cipher_extensions' module Net module SSH module Transport # Implements a factory of OpenSSL cipher algorithms. class CipherFactory # Maps the SSH name of a cipher to it's corresponding OpenSSL name SSH_TO_OSSL = { "3des-cbc" => "des-ede3-cbc", "blowfish-cbc" => "bf-cbc", "aes256-cbc" => "aes-256-cbc", "aes192-cbc" => "aes-192-cbc", "aes128-cbc" => "aes-128-cbc", "idea-cbc" => "idea-cbc", "cast128-cbc" => "cast-cbc", "rijndael-cbc@lysator.liu.se" => "aes-256-cbc", "3des-ctr" => "des-ede3", "blowfish-ctr" => "bf-ecb", "aes256-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-256-ctr") ? "aes-256-ctr" : "aes-256-ecb", "aes192-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-192-ctr") ? "aes-192-ctr" : "aes-192-ecb", "aes128-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-128-ctr") ? "aes-128-ctr" : "aes-128-ecb", 'cast128-ctr' => 'cast5-ecb', 'none' => 'none' } SSH_TO_CLASS = if Net::SSH::Transport::ChaCha20Poly1305CipherLoader::LOADED { 'chacha20-poly1305@openssh.com' => Net::SSH::Transport::ChaCha20Poly1305Cipher } else { } end # Returns true if the underlying OpenSSL library supports the given cipher, # and false otherwise. def self.supported?(name) return true if SSH_TO_CLASS.key?(name) ossl_name = SSH_TO_OSSL[name] or raise NotImplementedError, "unimplemented cipher `#{name}'" return true if ossl_name == "none" return SSH_TO_CLASS.key?(name) || OpenSSL::Cipher.ciphers.include?(ossl_name) end # Retrieves a new instance of the named algorithm. The new instance # will be initialized using an iv and key generated from the given # iv, key, shared, hash and digester values. Additionally, the # cipher will be put into encryption or decryption mode, based on the # value of the +encrypt+ parameter. def self.get(name, options = {}) klass = SSH_TO_CLASS[name] unless klass.nil? key_len = klass.key_length key = Net::SSH::Transport::KeyExpander.expand_key(key_len, options[:key], options) return klass.new(encrypt: options[:encrypt], key: key) end 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 cipher.extend(Net::SSH::Transport::OpenSSLCipherExtensions) if name =~ /-ctr(@openssh.org)?$/ if ossl_name !~ /-ctr/ cipher.extend(Net::SSH::Transport::CTR) else cipher = Net::SSH::Transport::OpenSSLAESCTR.new(cipher) 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 # of the tuple. # if :iv_len option is supplied the third return value will be ivlen def self.get_lengths(name, options = {}) klass = SSH_TO_CLASS[name] return [klass.key_length, klass.block_size] unless klass.nil? ossl_name = SSH_TO_OSSL[name] if ossl_name.nil? || ossl_name == "none" result = [0, 0] result << 0 if options[:iv_len] else cipher = OpenSSL::Cipher.new(ossl_name) key_len = cipher.key_len cipher.key_len = key_len block_size = case ossl_name when /\-ctr/ Net::SSH::Transport::OpenSSLAESCTR.block_size 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 net-ssh-7.2.1/lib/net/ssh/transport/constants.rb000066400000000000000000000016351454036133000216340ustar00rootroot00000000000000module Net module SSH module Transport module Constants #-- # Transport layer generic messages #++ DISCONNECT = 1 IGNORE = 2 UNIMPLEMENTED = 3 DEBUG = 4 SERVICE_REQUEST = 5 SERVICE_ACCEPT = 6 #-- # Algorithm negotiation messages #++ KEXINIT = 20 NEWKEYS = 21 #-- # Key exchange method specific messages #++ KEXDH_INIT = 30 KEXDH_REPLY = 31 KEXECDH_INIT = 30 KEXECDH_REPLY = 31 KEXDH_GEX_GROUP = 31 KEXDH_GEX_INIT = 32 KEXDH_GEX_REPLY = 33 KEXDH_GEX_REQUEST = 34 end end end end net-ssh-7.2.1/lib/net/ssh/transport/ctr.rb000066400000000000000000000046551454036133000204150ustar00rootroot00000000000000require 'openssl' require 'delegate' module Net::SSH::Transport # :nodoc: class OpenSSLAESCTR < SimpleDelegator def initialize(original) super @was_reset = false end def block_size 16 end def self.block_size 16 end def reset @was_reset = true end def iv=(iv_s) super unless @was_reset end end # :nodoc: # Pure-Ruby implementation of Stateful Decryption Counter(SDCTR) Mode # for Block Ciphers. See RFC4344 for detail. module CTR def self.extended(orig) orig.instance_eval { @remaining = String.new @counter = nil @counter_len = orig.block_size orig.encrypt orig.padding = 0 singleton_class.send(:alias_method, :_update, :update) singleton_class.send(:private, :_update) singleton_class.send(:undef_method, :update) def iv @counter end def iv_len block_size end def iv=(iv_s) @counter = iv_s if @counter.nil? end def encrypt # DO NOTHING (always set to "encrypt") end def decrypt # DO NOTHING (always set to "encrypt") end def padding=(pad) # DO NOTHING (always 0) end def reset @remaining = String.new end def update(data) @remaining += data encrypted = String.new offset = 0 while (@remaining.bytesize - offset) >= block_size encrypted += xor!(@remaining.slice(offset, block_size), _update(@counter)) increment_counter! offset += block_size end @remaining = @remaining.slice(offset..-1) encrypted end def final s = @remaining.empty? ? '' : xor!(@remaining, _update(@counter)) @remaining = String.new s end def xor!(s1, s2) s = [] s1.unpack('Q*').zip(s2.unpack('Q*')) {|a, b| s.push(a ^ b) } s.pack('Q*') end singleton_class.send(:private, :xor!) def increment_counter! c = @counter_len while ((c -= 1) > 0) if @counter.setbyte(c, (@counter.getbyte(c) + 1) & 0xff) != 0 break end end end singleton_class.send(:private, :increment_counter!) } end end end net-ssh-7.2.1/lib/net/ssh/transport/hmac.rb000066400000000000000000000034451454036133000205310ustar00rootroot00000000000000require 'net/ssh/transport/key_expander' require 'net/ssh/transport/hmac/md5' require 'net/ssh/transport/hmac/md5_96' require 'net/ssh/transport/hmac/sha1' require 'net/ssh/transport/hmac/sha1_96' require 'net/ssh/transport/hmac/sha2_256' require 'net/ssh/transport/hmac/sha2_256_96' require 'net/ssh/transport/hmac/sha2_512' require 'net/ssh/transport/hmac/sha2_512_96' require 'net/ssh/transport/hmac/sha2_256_etm' require 'net/ssh/transport/hmac/sha2_512_etm' require 'net/ssh/transport/hmac/ripemd160' require 'net/ssh/transport/hmac/none' # Implements a simple factory interface for fetching hmac implementations, or # for finding the key lengths for hmac implementations.s module Net::SSH::Transport::HMAC # The mapping of SSH hmac algorithms to their implementations MAP = { 'hmac-md5' => MD5, 'hmac-md5-96' => MD5_96, 'hmac-sha1' => SHA1, 'hmac-sha1-96' => SHA1_96, 'hmac-sha2-256' => SHA2_256, 'hmac-sha2-256-96' => SHA2_256_96, 'hmac-sha2-512' => SHA2_512, 'hmac-sha2-512-96' => SHA2_512_96, 'hmac-sha2-256-etm@openssh.com' => SHA2_256_Etm, 'hmac-sha2-512-etm@openssh.com' => SHA2_512_Etm, 'hmac-ripemd160' => RIPEMD160, 'hmac-ripemd160@openssh.com' => RIPEMD160, 'none' => None } # Retrieves a new hmac instance of the given SSH type (+name+). If +key+ is # given, the new instance will be initialized with that key. def self.get(name, key = "", parameters = {}) impl = MAP[name] or raise ArgumentError, "hmac not found: #{name.inspect}" impl.new(Net::SSH::Transport::KeyExpander.expand_key(impl.key_length, key, parameters)) end # Retrieves the key length for the hmac of the given SSH type (+name+). def self.key_length(name) impl = MAP[name] or raise ArgumentError, "hmac not found: #{name.inspect}" impl.key_length end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/000077500000000000000000000000001454036133000201765ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/transport/hmac/abstract.rb000066400000000000000000000056041454036133000223330ustar00rootroot00000000000000require 'openssl' require 'openssl/digest' module Net module SSH module Transport module HMAC # The base class of all OpenSSL-based HMAC algorithm wrappers. class Abstract class << self def etm(*v) @etm = false if !defined?(@etm) if v.empty? @etm = superclass.etm if @etm.nil? && superclass.respond_to?(:etm) return @etm elsif v.length == 1 @etm = v.first else raise ArgumentError, "wrong number of arguments (#{v.length} for 1)" end end def key_length(*v) @key_length = nil if !defined?(@key_length) if v.empty? @key_length = superclass.key_length if @key_length.nil? && superclass.respond_to?(:key_length) return @key_length elsif v.length == 1 @key_length = v.first else raise ArgumentError, "wrong number of arguments (#{v.length} for 1)" end end def mac_length(*v) @mac_length = nil if !defined?(@mac_length) if v.empty? @mac_length = superclass.mac_length if @mac_length.nil? && superclass.respond_to?(:mac_length) return @mac_length elsif v.length == 1 @mac_length = v.first else raise ArgumentError, "wrong number of arguments (#{v.length} for 1)" end end def digest_class(*v) @digest_class = nil if !defined?(@digest_class) if v.empty? @digest_class = superclass.digest_class if @digest_class.nil? && superclass.respond_to?(:digest_class) return @digest_class elsif v.length == 1 @digest_class = v.first else raise ArgumentError, "wrong number of arguments (#{v.length} for 1)" end end end def etm self.class.etm end def key_length self.class.key_length end def mac_length self.class.mac_length end def digest_class self.class.digest_class end # The key in use for this instance. attr_reader :key def initialize(key = nil) self.key = key end # Sets the key to the given value, truncating it so that it is the correct # length. def key=(value) @key = value ? value.to_s[0, key_length] : nil end # Compute the HMAC digest for the given data string. def digest(data) OpenSSL::HMAC.digest(digest_class.new, key, data)[0, mac_length] end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/md5.rb000066400000000000000000000003271454036133000212120ustar00rootroot00000000000000require '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 net-ssh-7.2.1/lib/net/ssh/transport/hmac/md5_96.rb000066400000000000000000000003171454036133000215270ustar00rootroot00000000000000require '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 net-ssh-7.2.1/lib/net/ssh/transport/hmac/none.rb000066400000000000000000000003651454036133000214660ustar00rootroot00000000000000require '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 mac_length 0 def digest(data) "" end end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/ripemd160.rb000066400000000000000000000004751454036133000222400ustar00rootroot00000000000000require '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 mac_length 20 key_length 20 digest_class OpenSSL::Digest::RIPEMD160 end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha1.rb000066400000000000000000000004471454036133000213640ustar00rootroot00000000000000require '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 mac_length 20 key_length 20 digest_class OpenSSL::Digest::SHA1 end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha1_96.rb000066400000000000000000000003231454036133000216730ustar00rootroot00000000000000require '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 net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_256.rb000066400000000000000000000004631454036133000217570ustar00rootroot00000000000000require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC # The SHA-256 HMAC algorithm. This has a mac and key length of 32, and # uses the SHA-256 digest algorithm. class SHA2_256 < Abstract mac_length 32 key_length 32 digest_class OpenSSL::Digest::SHA256 end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_256_96.rb000066400000000000000000000003411454036133000222700ustar00rootroot00000000000000require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC # The SHA256-96 HMAC algorithm. This returns only the first 12 bytes of # the digest. class SHA2_256_96 < SHA2_256 mac_length 12 end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_256_etm.rb000066400000000000000000000005361454036133000226250ustar00rootroot00000000000000require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC # The SHA-256 Encrypt-Then-Mac HMAC algorithm. This has a mac and # key length of 32, and uses the SHA-256 digest algorithm. class SHA2_256_Etm < Abstract etm true mac_length 32 key_length 32 digest_class OpenSSL::Digest::SHA256 end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_512.rb000066400000000000000000000004631454036133000217520ustar00rootroot00000000000000require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC # The SHA-512 HMAC algorithm. This has a mac and key length of 64, and # uses the SHA-512 digest algorithm. class SHA2_512 < Abstract mac_length 64 key_length 64 digest_class OpenSSL::Digest::SHA512 end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_512_96.rb000066400000000000000000000003431454036133000222650ustar00rootroot00000000000000require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC # The SHA2-512-96 HMAC algorithm. This returns only the first 12 bytes of # the digest. class SHA2_512_96 < SHA2_512 mac_length 12 end end net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_512_etm.rb000066400000000000000000000005361454036133000226200ustar00rootroot00000000000000require 'net/ssh/transport/hmac/abstract' module Net::SSH::Transport::HMAC # The SHA-512 Encrypt-Then-Mac HMAC algorithm. This has a mac and # key length of 64, and uses the SHA-512 digest algorithm. class SHA2_512_Etm < Abstract etm true mac_length 64 key_length 64 digest_class OpenSSL::Digest::SHA512 end end net-ssh-7.2.1/lib/net/ssh/transport/identity_cipher.rb000066400000000000000000000025221454036133000227770ustar00rootroot00000000000000module Net module SSH module Transport # A cipher that does nothing but pass the data through, unchanged. This # keeps things in the code nice and clean when a cipher has not yet been # determined (i.e., during key exchange). class IdentityCipher class << self # A default block size of 8 is required by the SSH2 protocol. def block_size 8 end def key_length 0 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 def implicit_mac? false end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex.rb000066400000000000000000000025711454036133000204070ustar00rootroot00000000000000require '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' require 'net/ssh/transport/kex/ecdh_sha2_nistp384' require 'net/ssh/transport/kex/ecdh_sha2_nistp521' require 'net/ssh/transport/kex/curve25519_sha256_loader' module Net::SSH::Transport module Kex # Maps the supported key-exchange algorithms as named by the SSH protocol # to their corresponding implementors. MAP = { 'diffie-hellman-group1-sha1' => DiffieHellmanGroup1SHA1, 'diffie-hellman-group14-sha1' => DiffieHellmanGroup14SHA1, 'diffie-hellman-group14-sha256' => DiffieHellmanGroup14SHA256, 'diffie-hellman-group-exchange-sha1' => DiffieHellmanGroupExchangeSHA1, 'diffie-hellman-group-exchange-sha256' => DiffieHellmanGroupExchangeSHA256, 'ecdh-sha2-nistp256' => EcdhSHA2NistP256, 'ecdh-sha2-nistp384' => EcdhSHA2NistP384, 'ecdh-sha2-nistp521' => EcdhSHA2NistP521 } if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED MAP['curve25519-sha256'] = Curve25519Sha256 MAP['curve25519-sha256@libssh.org'] = Curve25519Sha256 end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/000077500000000000000000000000001454036133000200555ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/transport/kex/abstract.rb000066400000000000000000000111561454036133000222110ustar00rootroot00000000000000require 'net/ssh/buffer' require 'net/ssh/errors' require 'net/ssh/loggable' require 'net/ssh/transport/openssl' require 'net/ssh/transport/constants' module Net module SSH module Transport module Kex # Abstract class that implement Diffie-Hellman Key Exchange # See https://tools.ietf.org/html/rfc4253#page-21 class Abstract include Loggable include Constants attr_reader :algorithms attr_reader :connection attr_reader :data attr_reader :dh # Create a new instance of the Diffie-Hellman Key Exchange algorithm. # The Diffie-Hellman (DH) key exchange provides a shared secret that # cannot be determined by either party alone. The key exchange is # combined with a signature with the host key to provide host # authentication. def initialize(algorithms, connection, data) @algorithms = algorithms @connection = connection @data = data.dup @dh = generate_key @logger = @data.delete(:logger) end # Perform the key-exchange for the given session, with the given # data. This method will return a hash consisting of the # following keys: # # * :session_id # * :server_key # * :shared_secret # * :hashing_algorithm # # The caller is expected to be able to understand how to use these # deliverables. def exchange_keys result = send_kexinit verify_server_key(result[:server_key]) session_id = verify_signature(result) confirm_newkeys { session_id: session_id, server_key: result[:server_key], shared_secret: result[:shared_secret], hashing_algorithm: digester } end def digester raise NotImplementedError, 'abstract class: digester not implemented' end private def matching?(key_ssh_type, host_key_alg) return true if key_ssh_type == host_key_alg return true if key_ssh_type == 'ssh-rsa' && ['rsa-sha2-512', 'rsa-sha2-256'].include?(host_key_alg) end # Verify that the given key is of the expected type, and that it # really is the key for the session's host. Raise Net::SSH::Exception # if it is not. def verify_server_key(key) # :nodoc: unless matching?(key.ssh_type, algorithms.host_key) raise Net::SSH::Exception, "host key algorithm mismatch '#{key.ssh_type}' != '#{algorithms.host_key}'" end blob, fingerprint = generate_key_fingerprint(key) unless connection.host_key_verifier.verify(key: key, key_blob: blob, fingerprint: fingerprint, session: connection) raise Net::SSH::Exception, 'host key verification failed' end end def generate_key_fingerprint(key) blob = Net::SSH::Buffer.from(:key, key).to_s fingerprint = Net::SSH::Authentication::PubKeyFingerprint.fingerprint(blob, @connection.options[:fingerprint_hash] || 'SHA256') [blob, fingerprint] rescue StandardError => e [nil, "(could not generate fingerprint: #{e.message})"] end # Verify the signature that was received. Raise Net::SSH::Exception # if the signature could not be verified. Otherwise, return the new # session-id. def verify_signature(result) # :nodoc: response = build_signature_buffer(result) hash = digester.digest(response.to_s) server_key = result[:server_key] server_sig = result[:server_sig] unless connection.host_key_verifier.verify_signature { server_key.ssh_do_verify(server_sig, hash, host_key: algorithms.host_key) } raise Net::SSH::Exception, 'could not verify server signature' end hash end # Send the NEWKEYS message, and expect the NEWKEYS message in # reply. def confirm_newkeys # :nodoc: # send own NEWKEYS message first (the wodSSHServer won't send first) response = Net::SSH::Buffer.new response.write_byte(NEWKEYS) connection.send_message(response) # wait for the server's NEWKEYS message buffer = connection.next_message raise Net::SSH::Exception, 'expected NEWKEYS' unless buffer.type == NEWKEYS end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/abstract5656.rb000066400000000000000000000052241454036133000225360ustar00rootroot00000000000000require 'net/ssh/transport/kex/abstract' module Net module SSH module Transport module Kex # Implement key-exchange algorithm from Elliptic Curve Algorithm Integration # in the Secure Shell Transport Layer (RFC 5656) class Abstract5656 < Abstract alias ecdh dh def curve_name raise NotImplementedError, 'abstract class: curve_name not implemented' end private def get_message_types [KEXECDH_INIT, KEXECDH_REPLY] end def build_signature_buffer(result) response = Net::SSH::Buffer.new response.write_string data[:client_version_string], data[:server_version_string], data[:client_algorithm_packet], data[:server_algorithm_packet], result[:key_blob], ecdh_public_key_bytes, result[:server_ecdh_pubkey] response.write_bignum result[:shared_secret] response end def send_kexinit # :nodoc: init, reply = get_message_types # send the KEXECDH_INIT message ## byte SSH_MSG_KEX_ECDH_INIT ## string Q_C, client's ephemeral public key octet string buffer = Net::SSH::Buffer.from(:byte, init, :mstring, ecdh_public_key_bytes) connection.send_message(buffer) # expect the following KEXECDH_REPLY message ## byte SSH_MSG_KEX_ECDH_REPLY ## string K_S, server's public host key ## string Q_S, server's ephemeral public key octet string ## string the signature on the exchange hash buffer = connection.next_message raise Net::SSH::Exception, 'expected REPLY' unless buffer.type == reply result = {} result[:key_blob] = buffer.read_string result[:server_key] = Net::SSH::Buffer.new(result[:key_blob]).read_key result[:server_ecdh_pubkey] = buffer.read_string result[:shared_secret] = compute_shared_secret(result[:server_ecdh_pubkey]) sig_buffer = Net::SSH::Buffer.new(buffer.read_string) sig_type = sig_buffer.read_string if sig_type != algorithms.host_key_format raise Net::SSH::Exception, "host key algorithm mismatch for signature '#{sig_type}' != '#{algorithms.host_key_format}'" end result[:server_sig] = sig_buffer.read_string result end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/curve25519_sha256.rb000066400000000000000000000021361454036133000232260ustar00rootroot00000000000000gem 'x25519' # raise if the gem x25519 is not installed require 'x25519' require 'net/ssh/transport/constants' require 'net/ssh/transport/kex/abstract5656' module Net module SSH module Transport module Kex # A key-exchange service implementing the "curve25519-sha256@libssh.org" # key-exchange algorithm. (defined in https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-06) class Curve25519Sha256 < Abstract5656 def digester OpenSSL::Digest::SHA256 end private def generate_key # :nodoc: ::X25519::Scalar.generate end ## string Q_C, client's ephemeral public key octet string def ecdh_public_key_bytes ecdh.public_key.to_bytes end # compute shared secret from server's public key and client's private key def compute_shared_secret(server_ecdh_pubkey) pk = ::X25519::MontgomeryU.new(server_ecdh_pubkey) OpenSSL::BN.new(ecdh.diffie_hellman(pk).to_bytes, 2) end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb000066400000000000000000000016401454036133000245530ustar00rootroot00000000000000module Net module SSH module Transport module Kex # Loads Curve25519Sha256 support which requires optinal dependencies module Curve25519Sha256Loader begin require 'net/ssh/transport/kex/curve25519_sha256' LOADED = true ERROR = nil rescue LoadError => e ERROR = e LOADED = false end def self.raiseUnlessLoaded(message) description = ERROR.is_a?(LoadError) ? dependenciesRequiredForX25519 : '' description << "#{ERROR.class} : \"#{ERROR.message}\"\n" if ERROR raise NotImplementedError, "#{message}\n#{description}" unless LOADED end def self.dependenciesRequiredForX25519 result = "net-ssh requires the following gems for x25519 support:\n" result << " * x25519\n" end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb000066400000000000000000000030041454036133000256220ustar00rootroot00000000000000require 'net/ssh/transport/kex/diffie_hellman_group1_sha1' 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) class DiffieHellmanGroup14SHA1 < DiffieHellmanGroup1SHA1 # The value of 'P', as a string, in hexadecimal P_s = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" + "C4C6628B" "80DC1CD1" "29024E08" "8A67CC74" + "020BBEA6" "3B139B22" "514A0879" "8E3404DD" + "EF9519B3" "CD3A431B" "302B0A6D" "F25F1437" + "4FE1356D" "6D51C245" "E485B576" "625E7EC6" + "F44C42E9" "A637ED6B" "0BFF5CB6" "F406B7ED" + "EE386BFB" "5A899FA5" "AE9F2411" "7C4B1FE6" + "49286651" "ECE45B3D" "C2007CB8" "A163BF05" + "98DA4836" "1C55D39A" "69163FA8" "FD24CF5F" + "83655D23" "DCA3AD96" "1C62F356" "208552BB" + "9ED52907" "7096966D" "670C354E" "4ABC9804" + "F1746C08" "CA18217C" "32905E46" "2E36CE3B" + "E39E772C" "180E8603" "9B2783A2" "EC07A28F" + "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 # The group constant G = 2 end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb000066400000000000000000000005041454036133000260000ustar00rootroot00000000000000require '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 net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb000066400000000000000000000106671454036133000255530ustar00rootroot00000000000000require 'net/ssh/transport/kex/abstract' module Net module SSH module Transport module Kex # A key-exchange service implementing the "diffie-hellman-group1-sha1" # key-exchange algorithm. class DiffieHellmanGroup1SHA1 < Abstract # The value of 'P', as a string, in hexadecimal P_s = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" + "C4C6628B" "80DC1CD1" "29024E08" "8A67CC74" + "020BBEA6" "3B139B22" "514A0879" "8E3404DD" + "EF9519B3" "CD3A431B" "302B0A6D" "F25F1437" + "4FE1356D" "6D51C245" "E485B576" "625E7EC6" + "F44C42E9" "A637ED6B" "0BFF5CB6" "F406B7ED" + "EE386BFB" "5A899FA5" "AE9F2411" "7C4B1FE6" + "49286651" "ECE65381" "FFFFFFFF" "FFFFFFFF" # The radix in which P_s represents the value of P P_r = 16 # The group constant G = 2 def digester OpenSSL::Digest::SHA1 end private # Returns the DH key parameters for the current connection. [p, q] def get_parameters [ OpenSSL::BN.new(self.class::P_s, self.class::P_r), self.class::G ] end # Returns the INIT/REPLY constants used by this algorithm. def get_message_types [KEXDH_INIT, KEXDH_REPLY] end # Build the signature buffer to use when verifying a signature from # the server. def build_signature_buffer(result) response = Net::SSH::Buffer.new response.write_string data[:client_version_string], data[:server_version_string], data[:client_algorithm_packet], data[:server_algorithm_packet], result[:key_blob] response.write_bignum dh.pub_key, result[:server_dh_pubkey], result[:shared_secret] response end # Generate a DH key with a private key consisting of the given # number of bytes. def generate_key # :nodoc: p, g = get_parameters asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Integer(p), OpenSSL::ASN1::Integer(g) ] ) dh_params = OpenSSL::PKey::DH.new(asn1.to_der) # XXX No private key size check! In theory the latter call should work but fails on OpenSSL 3.0 as # dh_paramgen_subprime_len is now reserved for DHX algorithm # key = OpenSSL::PKey.generate_key(dh_params, "dh_paramgen_subprime_len" => data[:need_bytes]/8) if OpenSSL::PKey.respond_to?(:generate_key) OpenSSL::PKey.generate_key(dh_params) else dh_params.generate_key! dh_params end end # Send the KEXDH_INIT message, and expect the KEXDH_REPLY. Return the # resulting buffer. # # Parse the buffer from a KEXDH_REPLY message, returning a hash of # the extracted values. def send_kexinit # :nodoc: init, reply = get_message_types # send the KEXDH_INIT message buffer = Net::SSH::Buffer.from(:byte, init, :bignum, dh.pub_key) connection.send_message(buffer) # expect the KEXDH_REPLY message buffer = connection.next_message raise Net::SSH::Exception, "expected REPLY" unless buffer.type == reply result = Hash.new result[:key_blob] = buffer.read_string result[:server_key] = Net::SSH::Buffer.new(result[:key_blob]).read_key result[:server_dh_pubkey] = buffer.read_bignum result[:shared_secret] = OpenSSL::BN.new(dh.compute_key(result[:server_dh_pubkey]), 2) sig_buffer = Net::SSH::Buffer.new(buffer.read_string) sig_type = sig_buffer.read_string if sig_type != algorithms.host_key_format raise Net::SSH::Exception, "host key algorithm mismatch for signature " + "'#{sig_type}' != '#{algorithms.host_key_format}'" end result[:server_sig] = sig_buffer.read_string return result end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb000066400000000000000000000046351454036133000273320ustar00rootroot00000000000000require 'net/ssh/errors' require 'net/ssh/transport/constants' require 'net/ssh/transport/kex/diffie_hellman_group1_sha1' module Net::SSH::Transport::Kex # A key-exchange service implementing the # "diffie-hellman-group-exchange-sha1" key-exchange algorithm. class DiffieHellmanGroupExchangeSHA1 < DiffieHellmanGroup1SHA1 MINIMUM_BITS = 1024 MAXIMUM_BITS = 8192 private # Compute the number of bits needed for the given number of bytes. def compute_need_bits # for Compatibility: OpenSSH requires (need_bits * 2 + 1) length of parameter need_bits = data[:need_bytes] * 8 * 2 + 1 data[:minimum_dh_bits] ||= MINIMUM_BITS if need_bits < data[:minimum_dh_bits] need_bits = data[:minimum_dh_bits] elsif need_bits > MAXIMUM_BITS need_bits = MAXIMUM_BITS end data[:need_bits] = need_bits data[:need_bytes] = need_bits / 8 end # Returns the DH key parameters for the given session. def get_parameters compute_need_bits # request the DH key parameters for the given number of bits. buffer = Net::SSH::Buffer.from(:byte, KEXDH_GEX_REQUEST, :long, data[:minimum_dh_bits], :long, data[:need_bits], :long, MAXIMUM_BITS) connection.send_message(buffer) buffer = connection.next_message raise Net::SSH::Exception, "expected KEXDH_GEX_GROUP, got #{buffer.type}" unless buffer.type == KEXDH_GEX_GROUP p = buffer.read_bignum g = buffer.read_bignum [p, g] end # Returns the INIT/REPLY constants used by this algorithm. def get_message_types [KEXDH_GEX_INIT, KEXDH_GEX_REPLY] end # Build the signature buffer to use when verifying a signature from # the server. def build_signature_buffer(result) response = Net::SSH::Buffer.new response.write_string data[:client_version_string], data[:server_version_string], data[:client_algorithm_packet], data[:server_algorithm_packet], result[:key_blob] response.write_long MINIMUM_BITS, data[:need_bits], MAXIMUM_BITS response.write_bignum dh.p, dh.g, dh.pub_key, result[:server_dh_pubkey], result[:shared_secret] response end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb000066400000000000000000000005361454036133000275020ustar00rootroot00000000000000require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1' module Net::SSH::Transport::Kex # A key-exchange service implementing the # "diffie-hellman-group-exchange-sha256" key-exchange algorithm. class DiffieHellmanGroupExchangeSHA256 < DiffieHellmanGroupExchangeSHA1 def digester OpenSSL::Digest::SHA256 end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb000066400000000000000000000022111454036133000236700ustar00rootroot00000000000000require 'net/ssh/transport/kex/abstract5656' module Net module SSH module Transport module Kex # A key-exchange service implementing the "ecdh-sha2-nistp256" # key-exchange algorithm. (defined in RFC 5656) class EcdhSHA2NistP256 < Abstract5656 def digester OpenSSL::Digest::SHA256 end def curve_name OpenSSL::PKey::EC::CurveNameAlias['nistp256'] end private def generate_key # :nodoc: OpenSSL::PKey::EC.generate(curve_name) end # compute shared secret from server's public key and client's private key def compute_shared_secret(server_ecdh_pubkey) pk = OpenSSL::PKey::EC::Point.new(OpenSSL::PKey::EC.new(curve_name).group, OpenSSL::BN.new(server_ecdh_pubkey, 2)) OpenSSL::BN.new(ecdh.dh_compute_key(pk), 2) end ## string Q_C, client's ephemeral public key octet string def ecdh_public_key_bytes ecdh.public_key.to_bn.to_s(2) end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb000066400000000000000000000007661454036133000237070ustar00rootroot00000000000000require 'net/ssh/transport/kex/ecdh_sha2_nistp256' module Net module SSH module Transport module Kex # A key-exchange service implementing the "ecdh-sha2-nistp256" # key-exchange algorithm. (defined in RFC 5656) class EcdhSHA2NistP384 < EcdhSHA2NistP256 def digester OpenSSL::Digest::SHA384 end def curve_name OpenSSL::PKey::EC::CurveNameAlias['nistp384'] end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb000066400000000000000000000007661454036133000237000ustar00rootroot00000000000000require 'net/ssh/transport/kex/ecdh_sha2_nistp256' module Net module SSH module Transport module Kex # A key-exchange service implementing the "ecdh-sha2-nistp521" # key-exchange algorithm. (defined in RFC 5656) class EcdhSHA2NistP521 < EcdhSHA2NistP256 def digester OpenSSL::Digest::SHA512 end def curve_name OpenSSL::PKey::EC::CurveNameAlias['nistp521'] end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/key_expander.rb000066400000000000000000000015101454036133000222660ustar00rootroot00000000000000module 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 end end end net-ssh-7.2.1/lib/net/ssh/transport/openssl.rb000066400000000000000000000215131454036133000213000ustar00rootroot00000000000000require '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. class BN # Converts a BN object to a string. The format used is that which is # required by the SSH2 protocol. def to_ssh if zero? return [0].pack("N") else buf = to_s(2) if buf.getbyte(0)[7] == 1 return [buf.length + 1, 0, buf].pack("NCA*") else return [buf.length, buf].pack("NA*") end end end end module PKey class PKey include Net::SSH::Authentication::PubKeyFingerprint end # 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. class DH # Determines whether the pub_key for this key is valid. (This algorithm # 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) end end # 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. class RSA # Returns "ssh-rsa", which is the description of this key type used by the # SSH2 protocol. def ssh_type "ssh-rsa" end alias ssh_signature_type ssh_type # Converts the key to a blob, according to the SSH2 protocol. def to_blob @blob ||= Net::SSH::Buffer.from(:string, ssh_type, :bignum, e, :bignum, n).to_s end # Verifies the given signature matches the given data. def ssh_do_verify(sig, data, options = {}) digester = if options[:host_key] == "rsa-sha2-512" OpenSSL::Digest::SHA512.new elsif options[:host_key] == "rsa-sha2-256" OpenSSL::Digest::SHA256.new else OpenSSL::Digest::SHA1.new end verify(digester, sig, data) end # Returns the signature for the given data. def ssh_do_sign(data, sig_alg = nil) digester = if sig_alg == "rsa-sha2-512" OpenSSL::Digest::SHA512.new elsif sig_alg == "rsa-sha2-256" OpenSSL::Digest::SHA256.new else OpenSSL::Digest::SHA1.new end sign(digester, data) end end # 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. class DSA # Returns "ssh-dss", which is the description of this key type used by the # SSH2 protocol. def ssh_type "ssh-dss" end alias ssh_signature_type ssh_type # Converts the key to a blob, according to the SSH2 protocol. def to_blob @blob ||= Net::SSH::Buffer.from(:string, ssh_type, :bignum, p, :bignum, q, :bignum, g, :bignum, pub_key).to_s end # Verifies the given signature matches the given data. def ssh_do_verify(sig, data, options = {}) sig_r = sig[0, 20].unpack("H*")[0].to_i(16) sig_s = sig[20, 20].unpack("H*")[0].to_i(16) a1sig = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(sig_r), OpenSSL::ASN1::Integer(sig_s) ]) return verify(OpenSSL::Digest::SHA1.new, a1sig.to_der, data) end # Signs the given data. def ssh_do_sign(data, sig_alg = nil) sig = sign(OpenSSL::Digest::SHA1.new, data) a1sig = OpenSSL::ASN1.decode(sig) sig_r = a1sig.value[0].value.to_s(2) sig_s = a1sig.value[1].value.to_s(2) sig_size = params["q"].num_bits / 8 raise OpenSSL::PKey::DSAError, "bad sig size" if sig_r.length > sig_size || sig_s.length > sig_size sig_r = "\0" * (20 - sig_r.length) + sig_r if sig_r.length < 20 sig_s = "\0" * (20 - sig_s.length) + sig_s if sig_s.length < 20 return sig_r + sig_s end end # 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. class EC CurveNameAlias = { 'nistp256' => 'prime256v1', 'nistp384' => 'secp384r1', 'nistp521' => 'secp521r1' }.freeze CurveNameAliasInv = { 'prime256v1' => 'nistp256', 'secp384r1' => 'nistp384', 'secp521r1' => 'nistp521' }.freeze def self.read_keyblob(curve_name_in_type, buffer) curve_name_in_key = buffer.read_string unless curve_name_in_type == curve_name_in_key raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')" end public_key_oct = buffer.read_string begin curvename = OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key] group = OpenSSL::PKey::EC::Group.new(curvename) point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2)) asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::ObjectId("id-ecPublicKey"), OpenSSL::ASN1::ObjectId(curvename) ] ), OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed)) ] ) key = OpenSSL::PKey::EC.new(asn1.to_der) return key rescue OpenSSL::PKey::ECError raise NotImplementedError, "unsupported key type `#{type}'" end end # Returns the description of this key type used by the # SSH2 protocol, like "ecdsa-sha2-nistp256" def ssh_type "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}" end alias ssh_signature_type ssh_type def digester if group.curve_name =~ /^[a-z]+(\d+)\w*\z/ curve_size = Regexp.last_match(1).to_i if curve_size <= 256 OpenSSL::Digest::SHA256.new elsif curve_size <= 384 OpenSSL::Digest::SHA384.new else OpenSSL::Digest::SHA512.new end else OpenSSL::Digest::SHA256.new end end private :digester # Converts the key to a blob, according to the SSH2 protocol. def to_blob @blob ||= Net::SSH::Buffer.from(:string, ssh_type, :string, CurveNameAliasInv[group.curve_name], :mstring, public_key.to_bn.to_s(2)).to_s @blob end # Verifies the given signature matches the given data. def ssh_do_verify(sig, data, options = {}) digest = digester.digest(data) a1sig = nil begin sig_r_len = sig[0, 4].unpack('H*')[0].to_i(16) sig_l_len = sig[4 + sig_r_len, 4].unpack('H*')[0].to_i(16) sig_r = sig[4, sig_r_len].unpack('H*')[0] sig_s = sig[4 + sig_r_len + 4, sig_l_len].unpack('H*')[0] a1sig = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(sig_r.to_i(16)), OpenSSL::ASN1::Integer(sig_s.to_i(16)) ]) rescue StandardError end if a1sig.nil? return false else dsa_verify_asn1(digest, a1sig.to_der) end end # Returns the signature for the given data. def ssh_do_sign(data, sig_alg = nil) digest = digester.digest(data) sig = dsa_sign_asn1(digest) a1sig = OpenSSL::ASN1.decode(sig) sig_r = a1sig.value[0].value sig_s = a1sig.value[1].value Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s end class Point # Returns the description of this key type used by the # SSH2 protocol, like "ecdsa-sha2-nistp256" def ssh_type "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}" end alias ssh_signature_type ssh_type # Converts the key to a blob, according to the SSH2 protocol. def to_blob @blob ||= Net::SSH::Buffer.from(:string, ssh_type, :string, CurveNameAliasInv[group.curve_name], :mstring, to_bn.to_s(2)).to_s @blob end end end end end net-ssh-7.2.1/lib/net/ssh/transport/openssl_cipher_extensions.rb000066400000000000000000000002471454036133000251120ustar00rootroot00000000000000module Net::SSH::Transport # we add those mehtods to OpenSSL::Chipher instances module OpenSSLCipherExtensions def implicit_mac? false end end end net-ssh-7.2.1/lib/net/ssh/transport/packet_stream.rb000066400000000000000000000265761454036133000224550ustar00rootroot00000000000000require 'net/ssh/buffered_io' require 'net/ssh/errors' require 'net/ssh/packet' require 'net/ssh/transport/cipher_factory' require 'net/ssh/transport/hmac' 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, # to allow for both blocking and non-blocking reads. module PacketStream # rubocop:disable Metrics/ModuleLength PROXY_COMMAND_HOST_IP = ''.freeze include BufferedIo def self.extended(object) object.__send__(:initialize_ssh) end # The map of "hints" that can be used to modify the behavior of the packet # stream. For instance, when authentication succeeds, an "authenticated" # hint is set, which is used to determine whether or not to compress the # data when using the "delayed" compression algorithm. attr_reader :hints # The server state object, which encapsulates the algorithms used to interpret # packets coming from the server. attr_reader :server # The client state object, which encapsulates the algorithms used to build # packets to send to the server. attr_reader :client # The name of the client (local) end of the socket, as reported by the # socket. def client_name @client_name ||= begin sockaddr = getsockname begin Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first rescue StandardError begin Socket.getnameinfo(sockaddr).first rescue StandardError begin Socket.gethostbyname(Socket.gethostname).first rescue StandardError lwarn { "the client ipaddr/name could not be determined" } "unknown" end end end end end # The IP address of the peer (remote) end of the socket, as reported by # the socket. def peer_ip @peer_ip ||= if respond_to?(:getpeername) addr = getpeername Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first else PROXY_COMMAND_HOST_IP end end # Returns true if the IO is available for reading, and false otherwise. def available_for_read? result = IO.select([self], nil, nil, 0) result && result.first.any? end # Returns the next full packet. If the mode parameter is :nonblock (the # default), then this will return immediately, whether a packet is # available or not, and will return nil if there is no packet ready to be # returned. If the mode parameter is :block, then this method will block # until a packet is available or timeout seconds have passed. def next_packet(mode = :nonblock, timeout = nil) case mode when :nonblock then packet = poll_next_packet return packet if packet if available_for_read? if fill <= 0 result = poll_next_packet if result.nil? raise Net::SSH::Disconnect, "connection closed by remote host" else return result end end end poll_next_packet when :block then loop do packet = poll_next_packet return packet if packet result = IO.select([self], nil, nil, timeout) raise Net::SSH::ConnectionTimeout, "timeout waiting for next packet" unless result raise Net::SSH::Disconnect, "connection closed by remote host" if fill <= 0 end else raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}" end end # Enqueues a packet to be sent, and blocks until the entire packet is # sent. def send_packet(payload) enqueue_packet(payload) wait_for_pending_sends end # Enqueues a packet to be sent, but does not immediately send the packet. # The given payload is pre-processed according to the algorithms specified # in the client state (compression, cipher, and hmac). def enqueue_packet(payload) # rubocop:disable Metrics/AbcSize # try to compress the packet payload = client.compress(payload) # the length of the packet, minus the padding actual_length = (client.hmac.etm ? 0 : 4) + payload.bytesize + 1 # compute the padding length padding_length = client.block_size - (actual_length % client.block_size) padding_length += client.block_size if padding_length < 4 # compute the packet length (sans the length field itself) packet_length = payload.bytesize + padding_length + 1 if packet_length < 16 padding_length += client.block_size packet_length = payload.bytesize + padding_length + 1 end padding = Array.new(padding_length) { rand(256) }.pack("C*") if client.cipher.implicit_mac? unencrypted_data = [padding_length, payload, padding].pack("CA*A*") message = client.cipher.update_cipher_mac(unencrypted_data, client.sequence_number) elsif client.hmac.etm debug { "using encrypt-then-mac" } # Encrypt padding_length, payload, and padding. Take MAC # from the unencrypted packet_lenght and the encrypted # data. length_data = [packet_length].pack("N") unencrypted_data = [padding_length, payload, padding].pack("CA*A*") encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher mac_data = length_data + encrypted_data mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*")) message = mac_data + mac else unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*") mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*")) encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher message = encrypted_data + mac end debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" } enqueue(message) client.increment(packet_length) self end # Performs any pending cleanup necessary on the IO and its associated # state objects. (See State#cleanup). def cleanup client.cleanup server.cleanup end # If the IO object requires a rekey operation (as indicated by either its # client or server state objects, see State#needs_rekey?), this will # yield. Otherwise, this does nothing. def if_needs_rekey? if client.needs_rekey? || server.needs_rekey? yield client.reset! if client.needs_rekey? server.reset! if server.needs_rekey? end end protected # Called when this module is used to extend an object. It initializes # the states and generally prepares the object for use as a packet stream. def initialize_ssh @hints = {} @server = State.new(self, :server) @client = State.new(self, :client) @packet = nil initialize_buffered_io end # Tries to read the next packet. If there is insufficient data to read # an entire packet, this returns immediately, otherwise the packet is # read, post-processed according to the cipher, hmac, and compression # algorithms specified in the server state object, and returned as a # new Packet object. # rubocop:disable Metrics/AbcSize def poll_next_packet aad_length = server.hmac.etm ? 4 : 0 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 if server.cipher.implicit_mac? @packet_length = server.cipher.read_length(data[0...4], server.sequence_number) @packet = Net::SSH::Buffer.new @mac_data = data elsif server.hmac.etm @packet_length = data.unpack("N").first @mac_data = data @packet = Net::SSH::Buffer.new(server.update_cipher(data[aad_length..-1])) else @packet = Net::SSH::Buffer.new(server.update_cipher(data)) @packet_length = @packet.read_long end end need = @packet_length + 4 - aad_length - server.block_size raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0 if server.cipher.implicit_mac? return nil if available < need + server.cipher.mac_length else return nil if available < need + server.hmac.mac_length # rubocop:disable Style/IfInsideElse end if need > 0 # read the remainder of the packet and decrypt it. data = read_available(need) @mac_data += data if server.hmac.etm || server.cipher.implicit_mac? unless server.cipher.implicit_mac? @packet.append( server.update_cipher(data) ) end end if server.cipher.implicit_mac? real_hmac = read_available(server.cipher.mac_length) || "" @packet = Net::SSH::Buffer.new(server.cipher.read_and_mac(@mac_data, real_hmac, server.sequence_number)) padding_length = @packet.read_byte payload = @packet.read(@packet_length - padding_length - 1) else # get the hmac from the tail of the packet (if one exists), and # then validate it. real_hmac = read_available(server.hmac.mac_length) || "" @packet.append(server.final_cipher) padding_length = @packet.read_byte payload = @packet.read(@packet_length - padding_length - 1) my_computed_hmac = if server.hmac.etm server.hmac.digest([server.sequence_number, @mac_data].pack("NA*")) else server.hmac.digest([server.sequence_number, @packet.content].pack("NA*")) end raise Net::SSH::Exception, "corrupted hmac detected #{server.hmac.class}" if real_hmac != my_computed_hmac end # try to decompress the payload, in case compression is active payload = server.decompress(payload) debug { "received packet nr #{server.sequence_number} type #{payload.getbyte(0)} len #{@packet_length}" } server.increment(@packet_length) @packet = nil return Packet.new(payload) end end # rubocop:enable Metrics/AbcSize end end end net-ssh-7.2.1/lib/net/ssh/transport/server_version.rb000066400000000000000000000052571454036133000226770ustar00rootroot00000000000000require 'net/ssh/errors' require 'net/ssh/loggable' require 'net/ssh/version' 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 # layer. # # Note that this class also encapsulates the negotiated version, and acts as # 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 = 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 = String.new loop do begin b = socket.readpartial(1) raise Net::SSH::Disconnect, "connection closed by remote host" if b.nil? rescue EOFError raise Net::SSH::Disconnect, "connection closed by remote host" end @version << b 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 end end end net-ssh-7.2.1/lib/net/ssh/transport/session.rb000066400000000000000000000310411454036133000212750ustar00rootroot00000000000000require 'socket' require 'net/ssh/errors' require 'net/ssh/loggable' require 'net/ssh/version' require 'net/ssh/transport/algorithms' require 'net/ssh/transport/constants' require 'net/ssh/transport/packet_stream' require 'net/ssh/transport/server_version' require 'net/ssh/verifiers/accept_new_or_local_tunnel' require 'net/ssh/verifiers/accept_new' require 'net/ssh/verifiers/always' 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), # but will instead be created for you automatically when you create a new # SSH session via Net::SSH.start. class Session include Loggable include Constants # The standard port for the SSH protocol. DEFAULT_PORT = 22 # The host to connect to, as given to the constructor. attr_reader :host # The port number to connect to, as given in the options to the constructor. # If no port number was given, this will default to DEFAULT_PORT. attr_reader :port # The underlying socket object being used to communicate with the remote # host. attr_reader :socket # The ServerVersion instance that encapsulates the negotiated protocol # version. attr_reader :server_version # The Algorithms instance used to perform key exchanges. attr_reader :algorithms # The host-key verifier object used to verify host keys, to ensure that # the connection is not being spoofed. attr_reader :host_key_verifier # The hash of options that were given to the object at initialization. attr_reader :options # Instantiates a new transport layer abstraction. This will block until # the initial key exchange completes, leaving you with a ready-to-use # transport session. def initialize(host, options = {}) self.logger = options[:logger] @host = host @port = options[:port] || DEFAULT_PORT @bind_address = options[:bind_address] || nil @options = options @socket = if (factory = options[:proxy]) debug { "establishing connection to #{@host}:#{@port} through proxy" } factory.open(@host, @port, options) else debug { "establishing connection to #{@host}:#{@port}" } Socket.tcp(@host, @port, @bind_address, nil, connect_timeout: options[:timeout]) end @socket.extend(PacketStream) @socket.logger = @logger debug { "connection established" } @queue = [] @host_key_verifier = select_host_key_verifier(options[:verify_host_key]) @server_version = ServerVersion.new(socket, logger, options[:timeout]) @algorithms = Algorithms.new(self, options) @algorithms.start wait { algorithms.initialized? } rescue Errno::ETIMEDOUT raise Net::SSH::ConnectionTimeout end def host_keys @host_keys ||= begin known_hosts = options.fetch(:known_hosts, KnownHosts) known_hosts.search_for(options[:host_key_alias] || host_as_string, options) end end # Returns the host (and possibly IP address) in a format compatible with # SSH known-host files. def host_as_string @host_as_string ||= begin string = "#{host}" string = "[#{string}]:#{port}" if port != DEFAULT_PORT peer_ip = socket.peer_ip if peer_ip != Net::SSH::Transport::PacketStream::PROXY_COMMAND_HOST_IP && peer_ip != host string2 = peer_ip string2 = "[#{string2}]:#{port}" if port != DEFAULT_PORT string << "," << string2 end string end end # Returns true if the underlying socket has been closed. def closed? socket.closed? end # Cleans up (see PacketStream#cleanup) and closes the underlying socket. def close socket.cleanup socket.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 # underlying protocol's state). def shutdown! error { "forcing connection closed" } socket.close end # Returns a new service_request packet for the given service name, ready # for sending to the server. def service_request(service) Net::SSH::Buffer.from(:byte, SERVICE_REQUEST, :string, service) end # Requests a rekey operation, and blocks until the operation completes. # If a rekey is already pending, this returns immediately, having no # effect. def rekey! if !algorithms.pending? algorithms.rekey! wait { algorithms.initialized? } end end # Returns immediately if a rekey is already in process. Otherwise, if a # rekey is needed (as indicated by the socket, see PacketStream#if_needs_rekey?) # 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 # Returns a hash of information about the peer (remote) side of the socket, # including :ip, :port, :host, and :canonized (see #host_as_string). def peer @peer ||= { ip: socket.peer_ip, port: @port.to_i, host: @host, canonized: host_as_string } end # Blocks until a new packet is available to be read, and returns that # packet. See #poll_message. def next_message poll_message(:block) end # Tries to read the next packet from the socket. If mode is :nonblock (the # default), this will not block and will return nil if there are no packets # waiting to be read. Otherwise, this will block until a packet is # available. Note that some packet types (DISCONNECT, IGNORE, UNIMPLEMENTED, # DEBUG, and KEXINIT) are handled silently by this method, and will never # be returned. # # If a key-exchange is in process and a disallowed packet type is # received, it will be enqueued and otherwise ignored. When a key-exchange # is not in process, and consume_queue is true, packets will be first # read from the queue before the socket is queried. def poll_message(mode = :nonblock, consume_queue = true) loop do return @queue.shift if consume_queue && @queue.any? && algorithms.allow?(@queue.first) packet = socket.next_packet(mode, options[:timeout]) return nil if packet.nil? case packet.type when DISCONNECT raise Net::SSH::Disconnect, "disconnected: #{packet[:description]} (#{packet[:reason_code]})" when IGNORE debug { "IGNORE packet received: #{packet[:data].inspect}" } when UNIMPLEMENTED lwarn { "UNIMPLEMENTED: #{packet[:number]}" } when DEBUG send(packet[:always_display] ? :fatal : :debug) { packet[:message] } when KEXINIT algorithms.accept_kexinit(packet) else return packet if algorithms.allow?(packet) push(packet) end end end # Waits (blocks) until the given block returns true. If no block is given, # this just waits long enough to see if there are any pending packets. Any # packets read are enqueued (see #push). def wait loop do break if block_given? && yield message = poll_message(:nonblock, false) push(message) if message break if !block_given? end end # Adds the given packet to the packet queue. If the queue is non-empty, # #poll_message will return packets from the queue in the order they # were received. def push(packet) @queue.push(packet) end # Sends the given message via the packet stream, blocking until the # entire message has been sent. def send_message(message) socket.send_packet(message) end # Enqueues the given message, such that it will be sent at the earliest # opportunity. This does not block, but returns immediately. def enqueue_message(message) socket.enqueue_packet(message) end # Configure's the packet stream's client state with the given set of # options. This is typically used to define the cipher, compression, and # hmac algorithms to use when sending packets to the server. def configure_client(options = {}) socket.client.set(options) end # Configure's the packet stream's server state with the given set of # options. This is typically used to define the cipher, compression, and # hmac algorithms to use when reading packets from the server. def configure_server(options = {}) socket.server.set(options) end # Sets a new hint for the packet stream, which the packet stream may use # to change its behavior. (See PacketStream#hints). def hint(which, value = true) socket.hints[which] = value end public # this method is primarily for use in tests attr_reader :queue # :nodoc: private # Compatibility verifier which allows users to keep using # custom verifier code without adding new :verify_signature # method. class CompatibleVerifier def initialize(verifier) @verifier = verifier end def verify(arguments) @verifier.verify(arguments) end def verify_signature(&block) yield end end # Instantiates a new host-key verification class, based on the value of # the parameter. # # Usually, the argument is a symbol like `:never` which corresponds to # a verifier, like `::Net::SSH::Verifiers::Never`. # # - :never (very insecure) # - :accept_new_or_local_tunnel (insecure) # - :accept_new (insecure) # - :always (secure) # # If the argument happens to respond to :verify and :verify_signature, # it is returned directly. Otherwise, an exception is raised. # # Values false, true, and :very were deprecated in # [#595](https://github.com/net-ssh/net-ssh/pull/595) def select_host_key_verifier(verifier) case verifier when false Kernel.warn('verify_host_key: false is deprecated, use :never') Net::SSH::Verifiers::Never.new when :never then Net::SSH::Verifiers::Never.new when true Kernel.warn('verify_host_key: true is deprecated, use :accept_new_or_local_tunnel') Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new when :accept_new_or_local_tunnel, nil then Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new when :very Kernel.warn('verify_host_key: :very is deprecated, use :accept_new') Net::SSH::Verifiers::AcceptNew.new when :accept_new then Net::SSH::Verifiers::AcceptNew.new when :secure then Kernel.warn('verify_host_key: :secure is deprecated, use :always') Net::SSH::Verifiers::Always.new when :always then Net::SSH::Verifiers::Always.new else if verifier.respond_to?(:verify) if verifier.respond_to?(:verify_signature) verifier else Kernel.warn("Warning: verifier without :verify_signature is deprecated") CompatibleVerifier.new(verifier) end else raise( ArgumentError, "Invalid argument to :verify_host_key (or deprecated " \ ":paranoid): #{verifier.inspect}" ) end end end end end end end net-ssh-7.2.1/lib/net/ssh/transport/state.rb000066400000000000000000000155451454036133000207450ustar00rootroot00000000000000require 'zlib' require 'net/ssh/transport/cipher_factory' require 'net/ssh/transport/hmac' 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 # forth. This class will never be instantiated directly, but is used as # part of the internal state of the PacketStream module. 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) @socket = socket @role = role @sequence_number = @packets = @blocks = 0 @cipher = CipherFactory.get("none") @block_size = 8 @hmac = HMAC.get("none") @compression = nil @compressor = @decompressor = nil @next_iv = String.new end # A convenience method for quickly setting multiple values in a single # command. def set(values) values.each do |key, value| instance_variable_set("@#{key}", value) 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. def increment(packet_length) @sequence_number = (@sequence_number + 1) & 0xFFFFFFFF @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 # by the socket. 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 # limit for small blocksizes." if @block_size >= 16 @max_blocks = 1 << (@block_size * 2) 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 if @compressor @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. def needs_rekey? 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 net-ssh-7.2.1/lib/net/ssh/verifiers/000077500000000000000000000000001454036133000172305ustar00rootroot00000000000000net-ssh-7.2.1/lib/net/ssh/verifiers/accept_new.rb000066400000000000000000000017201454036133000216650ustar00rootroot00000000000000require 'net/ssh/errors' require 'net/ssh/known_hosts' 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 # server. If the server does appear at least once, but the key given does # not match any known for the server, an exception will be raised (HostKeyMismatch). # Otherwise, this returns true. class AcceptNew < Always def verify(arguments) begin super rescue HostKeyUnknown => err err.remember_host! return true end end def verify_signature(&block) yield rescue HostKeyUnknown => err err.remember_host! return true end end end end end net-ssh-7.2.1/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb000066400000000000000000000022031454036133000251210ustar00rootroot00000000000000require '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 # connection is being tunnelled through a forwarded port, so the known-hosts # file will not be helpful (in general). class AcceptNewOrLocalTunnel < AcceptNew # Tries to determine if the connection is being tunnelled, and if so, # returns true. Otherwise, performs the standard strict verification. def verify(arguments) return true if tunnelled?(arguments) super end private # A connection is potentially being tunnelled if the port is not 22, # and the ip refers to the localhost. def tunnelled?(args) return false if args[:session].port == Net::SSH::Transport::Session::DEFAULT_PORT ip = args[:session].peer[:ip] return ip == "127.0.0.1" || ip == "::1" end end end end end net-ssh-7.2.1/lib/net/ssh/verifiers/always.rb000066400000000000000000000040631454036133000210600ustar00rootroot00000000000000require 'net/ssh/errors' 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 # (HostKeyUnknown). This is in contrast to the "Strict" class, which will # silently add the key to your known_hosts file. If the server does appear at # least once, but the key given does not match any known for the server, an # exception will be raised (HostKeyMismatch). # Otherwise, this returns true. class Always def verify(arguments) host_keys = arguments[:session].host_keys # We've never seen this host before, so raise an exception. process_cache_miss(host_keys, arguments, HostKeyUnknown, "is unknown") if host_keys.empty? # If we found any matches, check to see that the key type and # blob also match. found = host_keys.any? do |key| 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 # indicating that the key was not recognized. process_cache_miss(host_keys, arguments, HostKeyMismatch, "does not match") unless found found end def verify_signature(&block) yield end private def process_cache_miss(host_keys, args, exc_class, message) exception = exc_class.new("fingerprint #{args[:fingerprint]} " + "#{message} for #{host_keys.host.inspect}") exception.data = args exception.callback = Proc.new do host_keys.add_host_key(args[:key]) end raise exception end end end end end net-ssh-7.2.1/lib/net/ssh/verifiers/never.rb000066400000000000000000000006411454036133000206750ustar00rootroot00000000000000module 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. class Never # Returns true. def verify(arguments) true end def verify_signature(&block) true end end end end end net-ssh-7.2.1/lib/net/ssh/version.rb000066400000000000000000000042051454036133000172450ustar00rootroot00000000000000module Net module SSH # A class for describing the current version of a library. The version # consists of three parts: the +major+ number, the +minor+ number, and the # +tiny+ (or +patch+) number. # # Two Version instances may be compared, so that you can test that a version # of a library is what you require: # # require 'net/ssh/version' # # if Net::SSH::Version::CURRENT < Net::SSH::Version[2,1,0] # abort "your software is too old!" # end class Version include Comparable # A convenience method for instantiating a new Version instance with the # given +major+, +minor+, and +tiny+ components. def self.[](major, minor, tiny, pre = nil) new(major, minor, tiny, pre) end attr_reader :major, :minor, :tiny # Create a new Version object with the given components. def initialize(major, minor, tiny, pre = nil) @major, @minor, @tiny, @pre = major, minor, tiny, pre end # Compare this version to the given +version+ object. def <=>(version) to_i <=> version.to_i end # Converts this version object to a string, where each of the three # version components are joined by the '.' character. E.g., 2.0.0. def to_s @to_s ||= [@major, @minor, @tiny, @pre].compact.join(".") end # Converts this version to a canonical integer that may be compared # against other version objects. def to_i @to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny end # The major component of this version of the Net::SSH library MAJOR = 7 # The minor component of this version of the Net::SSH library MINOR = 2 # The tiny component of this version of the Net::SSH library TINY = 1 # The prerelease component of this version of the Net::SSH library # nil allowed PRE = nil # The current version of the Net::SSH library as a Version instance CURRENT = new(*[MAJOR, MINOR, TINY, PRE].compact) # The current version of the Net::SSH library as a String STRING = CURRENT.to_s end end end net-ssh-7.2.1/net-ssh-public_cert.pem000066400000000000000000000022441454036133000174600ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDQDCCAiigAwIBAgIBATANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpuZXRz c2gvREM9c29sdXRpb3VzL0RDPWNvbTAeFw0yMzAxMjQwMzE3NTVaFw0yNDAxMjQw MzE3NTVaMCUxIzAhBgNVBAMMGm5ldHNzaC9EQz1zb2x1dGlvdXMvREM9Y29tMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxieE22fR/qmdPKUHyYTyUx2g wskLwrCkxay+Tvc97ZZUOwf85LDDDPqhQaTWLvRwnIOMgQE2nBPzwalVclK6a+pW x/18KDeZY15vm3Qn5p42b0wi9hUxOqPm3J2hdCLCcgtENgdX21nVzejn39WVqFJO lntgSDNW5+kCS8QaRsmIbzj17GKKkrsw39kiQw7FhWfJFeTjddzoZiWwc59KA/Bx fBbmDnsMLAtAtauMOxORrbx3EOY7sHku/kSrMg3FXFay7jc6BkbbUij+MjJ/k82l 4o8o0YO4BAnya90xgEmgOG0LCCxRhuXQFnMDuDjK2XnUe0h4/6NCn94C+z9GsQID AQABo3sweTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUBfKiwO2e M4NEiRrVG793qEPLYyMwHwYDVR0RBBgwFoEUbmV0c3NoQHNvbHV0aW91cy5jb20w HwYDVR0SBBgwFoEUbmV0c3NoQHNvbHV0aW91cy5jb20wDQYJKoZIhvcNAQELBQAD ggEBAHyOSaOUji+EJFWZ46g+2EZ/kG7EFloFtIQUz8jDJIWGE+3NV5po1M0Z6EqH XmG3BtMLfgOV9NwMQRqIdKnZDfKsqM/FOu+9IqrP+OieAde5OrXR2pzQls60Xft7 3qNVaQS99woQRqiUiDQQ7WagOYrZjuVANqTDNt4myzGSjS5sHcKlz3PRn0LJRMe5 ouuLwQ7BCXityv5RRXex2ibCOyY7pB5ris6xDnPe1WdlyCfUf1Fb+Yqxpy6a8QmH v84waVXQ2i5M7pJaHVBF7DxxeW/q8W3VCnsq8vmmvULSThD18QqYGaFDJeN8sTR4 6tfjgZ6OvGSScvbCMHkCE9XjonE= -----END CERTIFICATE----- net-ssh-7.2.1/net-ssh.gemspec000066400000000000000000000036101454036133000160270ustar00rootroot00000000000000require_relative 'lib/net/ssh/version' Gem::Specification.new do |spec| spec.name = "net-ssh" spec.version = Net::SSH::Version::STRING spec.authors = ["Jamis Buck", "Delano Mandelbaum", "Mikl\u{f3}s Fazekas"] spec.email = ["net-ssh@solutious.com"] if ENV['NET_SSH_BUILDGEM_SIGNED'] spec.cert_chain = ["net-ssh-public_cert.pem"] spec.signing_key = "/mnt/gem/net-ssh-private_key.pem" end spec.summary = %q{Net::SSH: a pure-Ruby implementation of the SSH2 client protocol.} 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.6") spec.metadata = { "changelog_uri" => "https://github.com/net-ssh/net-ssh/blob/master/CHANGES.txt" } spec.extra_rdoc_files = [ "LICENSE.txt", "README.md" ] spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] unless ENV['NET_SSH_NO_ED25519'] spec.add_development_dependency("bcrypt_pbkdf", "~> 1.0") unless RUBY_PLATFORM == "java" spec.add_development_dependency("ed25519", "~> 1.2") spec.add_development_dependency('x25519') unless RUBY_PLATFORM == 'java' end spec.add_development_dependency('rbnacl', '~> 7.1') unless ENV['NET_SSH_NO_RBNACL'] spec.add_development_dependency "bundler", ">= 1.17" spec.add_development_dependency "minitest", "~> 5.19" spec.add_development_dependency "mocha", "~> 2.1.0" spec.add_development_dependency "rake", "~> 12.0" spec.add_development_dependency "rubocop", "~> 1.28.0" end net-ssh-7.2.1/support/000077500000000000000000000000001454036133000146155ustar00rootroot00000000000000net-ssh-7.2.1/support/ssh_tunnel_bug.rb000077500000000000000000000046631454036133000201750ustar00rootroot00000000000000#!/usr/bin/ruby # SSH TUNNEL CONNECTION BUG # from: http://net-ssh.lighthouseapp.com/projects/36253/tickets/7-an-existing-connection-was-forcibly-closed-by-the-remote-host#ticket-7-3 # # Steps to reproduce: # # * Start HTTP Proxy # * If running debian in EC2: # * apt-get install squid # * Add the following to /etc/squid/squid.conf: # acl localnet src 1.2.3.0/255.255.255.0 # http_access allow localnet # icp_access allow localnet # visible_hostname netsshtest # * Start squid squid -N -d 1 -D # * Run this script # * 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. # require 'highline/import' require 'net/ssh' LOCAL_PORT = 8080 PROXY_PORT = 3128 host, user = *ARGV abort "Usage: #{$0} host user" unless ARGV.size == 2 puts "Connecting to #{user}@#{host}..." 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.forward.local(LOCAL_PORT, host, PROXY_PORT) session.loop {true} rescue StandardError => e puts e.message puts e.backtrace end __END__ $ ruby support/ssh_tunnel.rb host user Connecting to user@host... Password: ****** Configure your browser proxy to localhost:8080 closed stream /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/buffered_io.rb:99:in `send' /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/buffered_io.rb:99:in `send_pending' /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/connection/session.rb:236:in `block in postprocess' /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/connection/session.rb:235:in `each' /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/connection/session.rb:235:in `postprocess' /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/connection/session.rb:203:in `process' /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/connection/session.rb:161:in `block in loop' /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/connection/session.rb:161:in `loop' /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.0.15/lib/net/ssh/connection/session.rb:161:in `loop' net-ssh-7.2.1/test/000077500000000000000000000000001454036133000140605ustar00rootroot00000000000000net-ssh-7.2.1/test/.rubocop.yml000066400000000000000000000000761454036133000163350ustar00rootroot00000000000000inherit_from: ../.rubocop.yml Metrics/ClassLength: Max: 800net-ssh-7.2.1/test/authentication/000077500000000000000000000000001454036133000170775ustar00rootroot00000000000000net-ssh-7.2.1/test/authentication/methods/000077500000000000000000000000001454036133000205425ustar00rootroot00000000000000net-ssh-7.2.1/test/authentication/methods/common.rb000066400000000000000000000013501454036133000223560ustar00rootroot00000000000000module 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)) def sess.next_message transport.next_message end sess end end def reset_session(options = {}) @transport = nil @session = nil session(options) end end end end net-ssh-7.2.1/test/authentication/methods/test_abstract.rb000066400000000000000000000034561454036133000237410ustar00rootroot00000000000000require 'common' require 'authentication/methods/common' require 'net/ssh/authentication/methods/abstract' module Authentication module Methods class TestAbstract < NetSSHTest include Common def test_constructor_should_set_defaults assert_nil subject.key_manager end def test_constructor_should_honor_options assert_equal :manager, subject(key_manager: :manager).key_manager end def test_session_id_should_query_session_id_from_key_exchange transport.stubs(:algorithms).returns(stub("algorithms", session_id: "abcxyz123")) assert_equal "abcxyz123", subject.session_id end def test_send_message_should_delegate_to_transport transport.expects(:send_message).with("abcxyz123") subject.send_message("abcxyz123") end def test_userauth_request_should_build_well_formed_userauth_packet packet = subject.userauth_request("jamis", "ssh-connection", "password") assert_equal "\062\0\0\0\005jamis\0\0\0\016ssh-connection\0\0\0\010password", packet.to_s end def test_userauth_request_should_translate_extra_booleans_onto_end packet = subject.userauth_request("jamis", "ssh-connection", "password", true, false) assert_equal "\062\0\0\0\005jamis\0\0\0\016ssh-connection\0\0\0\010password\1\0", packet.to_s end def test_userauth_request_should_translate_extra_strings_onto_end packet = subject.userauth_request("jamis", "ssh-connection", "password", "foo", "bar") assert_equal "\062\0\0\0\005jamis\0\0\0\016ssh-connection\0\0\0\010password\0\0\0\3foo\0\0\0\3bar", packet.to_s end private def subject(options = {}) @subject ||= Net::SSH::Authentication::Methods::Abstract.new(session(options), options) end end end end net-ssh-7.2.1/test/authentication/methods/test_hostbased.rb000066400000000000000000000100221454036133000240750ustar00rootroot00000000000000require 'common' require 'net/ssh/authentication/methods/hostbased' require 'authentication/methods/common' module Authentication module Methods class TestHostbased < NetSSHTest include Common def test_authenticate_should_return_false_when_no_key_manager_has_been_set assert_equal false, subject(key_manager: nil).authenticate("ssh-connection", "jamis") end def test_authenticate_should_return_false_when_key_manager_has_no_keys assert_equal false, subject(keys: []).authenticate("ssh-connection", "jamis") end def test_authenticate_should_return_false_if_no_keys_can_authenticate ENV.stubs(:[]).with('USER').returns(nil) key_manager.expects(:sign).with(&signature_parameters(keys.first)).returns("sig-one") key_manager.expects(:sign).with(&signature_parameters(keys.last)).returns("sig-two") transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert verify_userauth_request_packet(packet, keys.first) assert_equal "sig-one", packet.read_string t.return(USERAUTH_FAILURE, :string, "hostbased,password") t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.last) assert_equal "sig-two", packet2.read_string t2.return(USERAUTH_FAILURE, :string, "hostbased,password") end end assert_equal false, subject.authenticate("ssh-connection", "jamis") end def test_authenticate_should_return_true_if_any_key_can_authenticate ENV.stubs(:[]).with('USER').returns(nil) key_manager.expects(:sign).with(&signature_parameters(keys.first)).returns("sig-one") transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert verify_userauth_request_packet(packet, keys.first) assert_equal "sig-one", packet.read_string t.return(USERAUTH_SUCCESS) end assert subject.authenticate("ssh-connection", "jamis") end private def signature_parameters(key) Proc.new do |given_key, data| next false unless given_key.to_blob == key.to_blob buffer = Net::SSH::Buffer.new(data) buffer.read_string == "abcxyz123" && # session-id buffer.read_byte == USERAUTH_REQUEST && # type verify_userauth_request_packet(buffer, key) 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 end @@keys = nil def keys @@keys ||= [OpenSSL::PKey::RSA.new(512), OpenSSL::PKey::DSA.new(1024)] end def key_manager(options = {}) @key_manager ||= begin manager = stub("key_manager") manager.stubs(:each_identity).multiple_yields(*(options[:keys] || keys)) manager end end def subject(options = {}) options[:key_manager] = key_manager(options) unless options.key?(:key_manager) @subject ||= Net::SSH::Authentication::Methods::Hostbased.new(session(options), options) end def socket(options = {}) @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)) def sess.next_message transport.next_message end sess end end end end end net-ssh-7.2.1/test/authentication/methods/test_keyboard_interactive.rb000066400000000000000000000121051454036133000263220ustar00rootroot00000000000000require_relative '../../common' require 'net/ssh/authentication/methods/keyboard_interactive' require_relative 'common' 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 assert_equal "jamis", packet.read_string assert_equal "ssh-connection", packet.read_string assert_equal "keyboard-interactive", packet.read_string assert_equal "", packet.read_string # language tags assert_equal "", packet.read_string # submethods t.return(USERAUTH_FAILURE, :string, "password") end assert_raises Net::SSH::Authentication::DisallowedMethod do subject.authenticate("ssh-connection", "jamis") end end def test_authenticate_should_be_false_if_given_password_is_not_accepted reset_subject(non_interactive: true) transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 1, :string, "Password:", :bool, false) t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type assert_equal 1, packet2.read_long assert_equal "the-password", packet2.read_string t2.return(USERAUTH_FAILURE, :string, "keyboard-interactive") end end assert_equal false, subject.authenticate("ssh-connection", "jamis", "the-password") end def test_authenticate_should_be_true_if_given_password_is_accepted transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 1, :string, "Password:", :bool, false) t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type t2.return(USERAUTH_SUCCESS) end end assert subject.authenticate("ssh-connection", "jamis", "the-password") end def test_authenticate_should_duplicate_password_as_needed_to_fill_request transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 2, :string, "Password:", :bool, false, :string, "Again:", :bool, false) t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type assert_equal 2, packet2.read_long assert_equal "the-password", packet2.read_string assert_equal "the-password", packet2.read_string 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| assert_equal USERAUTH_REQUEST, packet.type t.return(USERAUTH_INFO_REQUEST, :string, "", :string, "", :string, "", :long, 2, :string, "Name:", :bool, true, :string, "Password:", :bool, false) t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type assert_equal 2, packet2.read_long assert_equal "", packet2.read_string assert_equal "", packet2.read_string t2.return(USERAUTH_SUCCESS) end end assert subject.authenticate("ssh-connection", "jamis", nil) 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) t.expect do |t2, packet2| assert_equal USERAUTH_INFO_RESPONSE, packet2.type assert_equal 2, packet2.read_long assert_equal "name", packet2.read_string assert_equal "password", packet2.read_string 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 net-ssh-7.2.1/test/authentication/methods/test_none.rb000066400000000000000000000022231454036133000230640ustar00rootroot00000000000000require 'common' require 'net/ssh/authentication/methods/none' require 'authentication/methods/common' 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 net-ssh-7.2.1/test/authentication/methods/test_password.rb000066400000000000000000000073471454036133000240030ustar00rootroot00000000000000require 'common' require 'net/ssh/authentication/methods/password' require 'net/ssh/authentication/session' require 'authentication/methods/common' 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 assert_equal "jamis", packet.read_string assert_equal "ssh-connection", packet.read_string assert_equal "password", packet.read_string assert_equal false, packet.read_bool assert_equal "the-password", packet.read_string t.return(USERAUTH_FAILURE, :string, "publickey") end assert_raises Net::SSH::Authentication::DisallowedMethod do subject.authenticate("ssh-connection", "jamis", "the-password") end end def test_authenticate_ask_for_password_for_second_time_when_password_is_incorrect transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert_equal "jamis", packet.read_string assert_equal "ssh-connection", packet.read_string assert_equal "password", packet.read_string assert_equal false, packet.read_bool assert_equal "the-password", packet.read_string t.return(USERAUTH_FAILURE, :string, "publickey,password") t.expect do |_t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert_equal "jamis", packet2.read_string assert_equal "ssh-connection", packet2.read_string assert_equal "password", packet2.read_string assert_equal false, packet2.read_bool assert_equal "the-password-2", packet2.read_string 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 assert_equal "bill", packet.read_string assert_equal "ssh-connection", packet.read_string assert_equal "password", packet.read_string assert_equal false, packet.read_bool 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 net-ssh-7.2.1/test/authentication/methods/test_publickey.rb000066400000000000000000000206101454036133000241140ustar00rootroot00000000000000require 'common' require 'net/ssh/authentication/methods/publickey' require 'authentication/methods/common' module Authentication module Methods class TestPublickey < NetSSHTest include Common def test_authenticate_should_return_false_when_no_key_manager_has_been_set assert_equal false, subject(key_manager: nil).authenticate("ssh-connection", "jamis") end def test_authenticate_should_return_false_when_key_manager_has_no_keys assert_equal false, subject(keys: []).authenticate("ssh-connection", "jamis") end def test_authenticate_should_return_false_if_no_keys_can_authenticate transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert verify_userauth_request_packet(packet, keys.first, false) t.return(USERAUTH_FAILURE, :string, "hostbased,password") t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.last, false) t2.return(USERAUTH_FAILURE, :string, "hostbased,password") end end assert_equal false, subject.authenticate("ssh-connection", "jamis") end def test_authenticate_should_raise_if_publickey_disallowed key_manager.expects(:sign).with(&signature_parameters(keys.first)).returns("sig-one") transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert verify_userauth_request_packet(packet, keys.first, false) t.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first)) t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.first, true) assert_equal "sig-one", packet2.read_string t2.return(USERAUTH_FAILURE, :string, "hostbased,password") end end assert_raises Net::SSH::Authentication::DisallowedMethod do subject.authenticate("ssh-connection", "jamis") end end def test_authenticate_should_return_false_if_signature_exchange_fails key_manager.expects(:sign).with(&signature_parameters(keys.first)).returns("sig-one") key_manager.expects(:sign).with(&signature_parameters(keys.last)).returns("sig-two") transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert verify_userauth_request_packet(packet, keys.first, false) t.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first)) t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.first, true) assert_equal "sig-one", packet2.read_string t2.return(USERAUTH_FAILURE, :string, "publickey") t2.expect do |t3, packet3| assert_equal USERAUTH_REQUEST, packet3.type assert verify_userauth_request_packet(packet3, keys.last, false) t3.return(USERAUTH_PK_OK, :string, keys.last.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.last)) t3.expect do |t4, packet4| assert_equal USERAUTH_REQUEST, packet4.type assert verify_userauth_request_packet(packet4, keys.last, true) assert_equal "sig-two", packet4.read_string t4.return(USERAUTH_FAILURE, :string, "publickey") end end end end assert !subject.authenticate("ssh-connection", "jamis") end def test_authenticate_should_return_true_if_any_key_can_authenticate key_manager.expects(:sign).with(&signature_parameters(keys.first)).returns("sig-one") transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert verify_userauth_request_packet(packet, keys.first, false) t.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first)) t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.first, true) assert_equal "sig-one", packet2.read_string t2.return(USERAUTH_SUCCESS) end end assert subject.authenticate("ssh-connection", "jamis") end def test_authenticate_rsa_sha2 key_manager.expects(:sign).with(&signature_parameters_with_alg(keys.first, "rsa-sha2-256")).returns("sig-one") transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert verify_userauth_request_packet(packet, keys.first, false, "rsa-sha2-256") t.return(USERAUTH_PK_OK, :string, "rsa-sha2-256", :string, Net::SSH::Buffer.from(:key, keys.first)) t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.first, true, "rsa-sha2-256") assert_equal "sig-one", packet2.read_string t2.return(USERAUTH_SUCCESS) end end assert subject(pubkey_algorithms: %w[rsa-sha2-256]).authenticate("ssh-connection", "jamis") end def test_authenticate_rsa_sha2_fallback key_manager.expects(:sign).with(&signature_parameters(keys.first)).returns("sig-one") transport.expect do |t, packet| assert_equal USERAUTH_REQUEST, packet.type assert verify_userauth_request_packet(packet, keys.first, false, "rsa-sha2-256") t.return(USERAUTH_FAILURE, :string, "publickey") t.expect do |t2, packet2| assert_equal USERAUTH_REQUEST, packet2.type assert verify_userauth_request_packet(packet2, keys.first, false) t2.return(USERAUTH_PK_OK, :string, keys.first.ssh_type, :string, Net::SSH::Buffer.from(:key, keys.first)) t2.expect do |t3, packet3| assert_equal USERAUTH_REQUEST, packet3.type assert verify_userauth_request_packet(packet3, keys.first, true) assert_equal "sig-one", packet3.read_string t3.return(USERAUTH_SUCCESS) end end end assert subject(pubkey_algorithms: %w[rsa-sha2-256 ssh-rsa]).authenticate("ssh-connection", "jamis") end private def signature_parameters(key) Proc.new do |given_key, data| next false unless given_key.to_blob == key.to_blob buffer = Net::SSH::Buffer.new(data) buffer.read_string == "abcxyz123" && # session-id buffer.read_byte == USERAUTH_REQUEST && # type verify_userauth_request_packet(buffer, key, true) end end def signature_parameters_with_alg(key, alg) Proc.new do |given_key, data, given_alg| next false unless given_key.to_blob == key.to_blob next false unless given_alg == alg buffer = Net::SSH::Buffer.new(data) buffer.read_string == "abcxyz123" && # session-id buffer.read_byte == USERAUTH_REQUEST && # type verify_userauth_request_packet(buffer, key, true, alg) end end def verify_userauth_request_packet(packet, key, has_sig, alg = nil) packet.read_string == "jamis" && # user-name packet.read_string == "ssh-connection" && # next service packet.read_string == "publickey" && # auth-method packet.read_bool == has_sig && # whether a signature is appended packet.read_string == (alg || key.ssh_type) && # ssh key type packet.read_buffer.read_key.to_blob == key.to_blob # key end @@keys = nil def keys @@keys ||= [OpenSSL::PKey::RSA.new(512), OpenSSL::PKey::DSA.new(1024)] end def key_manager(options = {}) @key_manager ||= begin manager = stub("key_manager") manager.stubs(:each_identity).multiple_yields(*(options[:keys] || keys)) manager end end def subject(options = {}) options[:key_manager] = key_manager(options) unless options.key?(:key_manager) options[:pubkey_algorithms] = %w[ssh-rsa] unless options.key?(:pubkey_algorithms) @subject ||= Net::SSH::Authentication::Methods::Publickey.new(session(options), options) end end end end net-ssh-7.2.1/test/authentication/test_agent.rb000066400000000000000000000465301454036133000215710ustar00rootroot00000000000000require_relative '../common' require 'net/ssh/authentication/agent' module Authentication class TestAgent < NetSSHTest SSH2_AGENT_REQUEST_VERSION = 1 SSH2_AGENT_REQUEST_IDENTITIES = 11 SSH2_AGENT_IDENTITIES_ANSWER = 12 SSH2_AGENT_SIGN_REQUEST = 13 SSH2_AGENT_SIGN_RESPONSE = 14 SSH2_AGENT_ADD_IDENTITY = 17 SSH2_AGENT_REMOVE_IDENTITY = 18 SSH2_AGENT_REMOVE_ALL_IDENTITIES = 19 SSH2_AGENT_LOCK = 22 SSH2_AGENT_UNLOCK = 23 SSH2_AGENT_ADD_ID_CONSTRAINED = 25 SSH2_AGENT_FAILURE = 30 SSH2_AGENT_VERSION_RESPONSE = 103 SSH_COM_AGENT2_FAILURE = 102 SSH_AGENT_REQUEST_RSA_IDENTITIES = 1 SSH_AGENT_RSA_IDENTITIES_ANSWER = 2 SSH_AGENT_FAILURE = 5 SSH_AGENT_SUCCESS = 6 SSH_AGENT_CONSTRAIN_LIFETIME = 1 SSH_AGENT_CONSTRAIN_CONFIRM = 2 ED25519 = <<~EOF -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACDuVIPDUXcVkXOyNAaFsotbySHLNG/Gw6gc3j2k2zcRVAAAAKD6bG5++mxu fgAAAAtzc2gtZWQyNTUxOQAAACDuVIPDUXcVkXOyNAaFsotbySHLNG/Gw6gc3j2k2zcRVA AAAEAydU4FtZ9+5o5Y/m1aPNHFda37Fm0Us5FlUKx50tWw+e5Ug8NRdxWRc7I0BoWyi1vJ 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".dup.force_encoding('BINARY') def setup @original, ENV['SSH_AUTH_SOCK'] = ENV['SSH_AUTH_SOCK'], "/path/to/ssh.agent.sock" end def teardown ENV['SSH_AUTH_SOCK'] = @original end def test_connect_should_use_agent_factory_to_determine_connection_type factory.expects(:open).with("/path/to/ssh.agent.sock").returns(socket) agent(false).connect! end def test_connect_should_expand_path_to_identity_agent factory.expects(:open).with("#{Dir.home}/path/to/ssh.agent.sock").returns(socket) agent(false).connect! nil, "~/path/to/ssh.agent.sock" end def test_connect_should_use_agent_socket_factory_instead_of_factory assert_equal agent.connect!, socket assert_equal agent.connect!(agent_socket_factory), "/foo/bar.sock" end def test_connect_should_raise_error_if_connection_could_not_be_established factory.expects(:open).raises(SocketError) assert_raises(Net::SSH::Authentication::AgentNotAvailable) { agent(false).connect! } end def test_negotiate_should_raise_error_if_ssh2_agent_response_received socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_REQUEST_VERSION, type assert_equal Net::SSH::Transport::ServerVersion::PROTO_VERSION, buffer.read_string s.return(SSH2_AGENT_VERSION_RESPONSE) end assert_raises(Net::SSH::Authentication::AgentNotAvailable) { agent.negotiate! } end def test_negotiate_should_raise_error_if_response_was_unexpected socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_VERSION, type s.return(255) end assert_raises(Net::SSH::Authentication::AgentNotAvailable) { agent.negotiate! } end def test_negotiate_should_be_successful_with_expected_response socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_VERSION, type s.return(SSH_AGENT_RSA_IDENTITIES_ANSWER) end assert_nothing_raised { agent(:connect).negotiate! } end def test_identities_should_fail_if_SSH_AGENT_FAILURE_received socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH_AGENT_FAILURE) end assert_raises(Net::SSH::Authentication::AgentError) { agent.identities } end def test_identities_should_fail_if_SSH2_AGENT_FAILURE_received socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH2_AGENT_FAILURE) end assert_raises(Net::SSH::Authentication::AgentError) { agent.identities } end def test_identities_should_fail_if_SSH_COM_AGENT2_FAILURE_received socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH_COM_AGENT2_FAILURE) end assert_raises(Net::SSH::Authentication::AgentError) { agent.identities } end def test_identities_should_fail_if_response_is_not_SSH2_AGENT_IDENTITIES_ANSWER socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(255) end assert_raises(Net::SSH::Authentication::AgentError) { agent.identities } end def test_identities_should_augment_identities_with_comment_field key1 = key key2 = OpenSSL::PKey::DSA.new(1024) 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 result = agent.identities assert_equal key1.to_blob, result.first.to_blob assert_equal key2.to_blob, result.last.to_blob assert_equal "My favorite key", result.first.comment assert_equal "Okay, but not the best", result.last.comment end def test_identities_should_ignore_unimplemented_ones key1 = key key2 = OpenSSL::PKey::DSA.new(1024) key2.to_blob[0..5] = 'badkey' key3 = OpenSSL::PKey::DSA.new(1024) socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH2_AGENT_IDENTITIES_ANSWER, :long, 3, :string, Net::SSH::Buffer.from(:key, key1), :string, "My favorite key", :string, Net::SSH::Buffer.from(:key, key2), :string, "bad", :string, Net::SSH::Buffer.from(:key, key3), :string, "Okay, but not the best") end result = agent.identities assert_equal 2, result.size assert_equal key1.to_blob, result.first.to_blob assert_equal key3.to_blob, result.last.to_blob assert_equal "My favorite key", result.first.comment assert_equal "Okay, but not the best", result.last.comment end def test_identities_should_ignore_invalid_ones key1 = key key2_bad = Net::SSH::Buffer.new(String.new) key3 = OpenSSL::PKey::DSA.new(1024) socket.expect do |s, type, _buffer| assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type s.return(SSH2_AGENT_IDENTITIES_ANSWER, :long, 3, :string, Net::SSH::Buffer.from(:key, key1), :string, "My favorite key", :string, key2_bad, :string, "bad", :string, Net::SSH::Buffer.from(:key, key3), :string, "Okay, but not the best") end result = agent.identities assert_equal 2, result.size assert_equal key1.to_blob, result.first.to_blob assert_equal key3.to_blob, result.last.to_blob assert_equal "My favorite key", result.first.comment assert_equal "Okay, but not the best", result.last.comment end def test_close_should_close_socket socket.expects(:close) agent.close end def test_sign_should_fail_if_response_is_SSH_AGENT_FAILURE socket.expect { |s,| s.return(SSH_AGENT_FAILURE) } assert_raises(Net::SSH::Authentication::AgentError) { agent.sign(key, "hello world") } end def test_sign_should_fail_if_response_is_SSH2_AGENT_FAILURE socket.expect { |s,| s.return(SSH2_AGENT_FAILURE) } assert_raises(Net::SSH::Authentication::AgentError) { agent.sign(key, "hello world") } end def test_sign_should_fail_if_response_is_SSH_COM_AGENT2_FAILURE socket.expect { |s,| s.return(SSH_COM_AGENT2_FAILURE) } assert_raises(Net::SSH::Authentication::AgentError) { agent.sign(key, "hello world") } end def test_sign_should_fail_if_response_is_not_SSH2_AGENT_SIGN_RESPONSE socket.expect { |s,| s.return(255) } assert_raises(Net::SSH::Authentication::AgentError) { agent.sign(key, "hello world") } end def test_sign_should_return_signed_data_from_agent socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_SIGN_REQUEST, type assert_equal key.to_blob, Net::SSH::Buffer.new(buffer.read_string).read_key.to_blob assert_equal "hello world", buffer.read_string assert_equal 0, buffer.read_long s.return(SSH2_AGENT_SIGN_RESPONSE, :string, "abcxyz123") end assert_equal "abcxyz123", agent.sign(key, "hello world") end def test_add_rsa_identity_with_constraints rsa = OpenSSL::PKey::RSA.new(512) socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_ID_CONSTRAINED, type assert_equal buffer.read_string, "ssh-rsa" assert_equal buffer.read_bignum.to_s, rsa.n.to_s assert_equal buffer.read_bignum.to_s, rsa.e.to_s assert_equal buffer.read_bignum.to_s, rsa.d.to_s assert_equal buffer.read_bignum.to_s, rsa.iqmp.to_s assert_equal buffer.read_bignum.to_s, rsa.p.to_s assert_equal buffer.read_bignum.to_s, rsa.q.to_s assert_equal 'foobar', buffer.read_string assert_equal SSH_AGENT_CONSTRAIN_LIFETIME, buffer.read_byte assert_equal 42, buffer.read_long assert_equal SSH_AGENT_CONSTRAIN_CONFIRM, buffer.read_byte assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.add_identity(rsa, "foobar", lifetime: 42, confirm: true) end def test_add_rsa_cert_identity cert = make_cert(OpenSSL::PKey::RSA.new(512)) socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ssh-rsa-cert-v01@openssh.com" assert_equal buffer.read_string, cert.to_blob assert_equal buffer.read_bignum.to_s, cert.key.d.to_s assert_equal buffer.read_bignum.to_s, cert.key.iqmp.to_s assert_equal buffer.read_bignum.to_s, cert.key.p.to_s assert_equal buffer.read_bignum.to_s, cert.key.q.to_s assert_equal 'foobar', buffer.read_string assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.add_identity(cert, "foobar") end def test_add_dsa_identity dsa = OpenSSL::PKey::DSA.new(1024) socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ssh-dss" assert_equal buffer.read_bignum.to_s, dsa.p.to_s assert_equal buffer.read_bignum.to_s, dsa.q.to_s assert_equal buffer.read_bignum.to_s, dsa.g.to_s assert_equal buffer.read_bignum.to_s, dsa.pub_key.to_s assert_equal buffer.read_bignum.to_s, dsa.priv_key.to_s assert_equal 'foobar', buffer.read_string assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.add_identity(dsa, "foobar") end def test_add_dsa_cert_identity cert = make_cert(OpenSSL::PKey::DSA.new(1024)) socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ssh-dss-cert-v01@openssh.com" assert_equal buffer.read_string, cert.to_blob assert_equal buffer.read_bignum.to_s, cert.key.priv_key.to_s assert_equal 'foobar', buffer.read_string assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.add_identity(cert, "foobar") end def test_add_ecdsa_identity ecdsa = OpenSSL::PKey::EC.generate("prime256v1") socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ecdsa-sha2-nistp256" assert_equal buffer.read_string, "nistp256" assert_equal buffer.read_string, ecdsa.public_key.to_bn.to_s(2) assert_equal buffer.read_bignum, ecdsa.private_key assert_equal 'foobar', buffer.read_string assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.add_identity(ecdsa, "foobar") end def test_add_ecdsa_cert_identity cert = make_cert(OpenSSL::PKey::EC.generate("prime256v1")) socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_ADD_IDENTITY, type assert_equal buffer.read_string, "ecdsa-sha2-nistp256-cert-v01@openssh.com" assert_equal buffer.read_string, cert.to_blob assert_equal buffer.read_bignum, cert.key.private_key assert_equal 'foobar', buffer.read_string assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.add_identity(cert, "foobar") end 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 assert_equal buffer.read_string, "ssh-ed25519" assert_equal buffer.read_string, ed25519.public_key.verify_key.to_bytes assert_equal buffer.read_string, ed25519.sign_key.keypair assert_equal 'foobar', buffer.read_string assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.add_identity(ed25519, "foobar") end 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 assert_equal buffer.read_string, "ssh-ed25519-cert-v01@openssh.com" assert_equal buffer.read_string, cert.to_blob assert_equal buffer.read_string, cert.key.public_key.verify_key.to_bytes assert_equal buffer.read_string, cert.key.sign_key.keypair assert_equal 'foobar', buffer.read_string assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.add_identity(cert, "foobar") end def test_add_identity_should_raise_error_on_failure socket.expect do |s, _type, _buffer| s.return(SSH_AGENT_FAILURE) end assert_raises(Net::SSH::Authentication::AgentError) do agent.add_identity(key, "foobar") end end def test_remove_identity socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_REMOVE_IDENTITY, type assert_equal buffer.read_string, key.to_blob assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.remove_identity(key) end def test_remove_identity_should_raise_error_on_failure socket.expect do |s, _type, _buffer| s.return(SSH_AGENT_FAILURE) end assert_raises(Net::SSH::Authentication::AgentError) do agent.remove_identity(key) end end def test_remove_all_identities socket.expect do |s, type, buffer| assert_equal SSH2_AGENT_REMOVE_ALL_IDENTITIES, type assert buffer.eof? s.return(SSH_AGENT_SUCCESS) end agent.remove_all_identities end def test_remove_all_identities_should_raise_error_on_failure socket.expect do |s, _type, _buffer| s.return(SSH_AGENT_FAILURE) end assert_raises(Net::SSH::Authentication::AgentError) do agent.remove_all_identities end end private def make_cert(key) cert = Net::SSH::Buffer.new(CERT).read_key cert.key = key cert.sign!(key) end class MockSocket def initialize @expectation = nil @buffer = Net::SSH::Buffer.new end def expect(&block) @expectation = block end def return(type, *args) data = Net::SSH::Buffer.from(*args) @buffer.append([data.length + 1, type, data.to_s].pack("NCA*")) end 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 @expectation.call(self, type, buffer) @expectation = nil end def read(length) @buffer.read(length) end end def key @key ||= OpenSSL::PKey::RSA.new(512) end def socket @socket ||= MockSocket.new end def factory @factory ||= stub("socket factory", open: socket) end def agent(auto = :connect) @agent ||= begin agent = Net::SSH::Authentication::Agent.new agent.stubs(:unix_socket_class).returns(factory) agent.connect! if auto == :connect agent end end def agent_socket_factory @agent_socket_factory ||= -> { "/foo/bar.sock" } end end end net-ssh-7.2.1/test/authentication/test_certificate.rb000066400000000000000000000361361454036133000227560ustar00rootroot00000000000000require_relative '../common' require 'net/ssh/authentication/certificate' CA_KEY = <<~EOF -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAnVJVDoOOYn2Bdk9uyroBJf6HgGK1mFIlqSjBru9xfIJMrVE/ HcZvuNhwSRv8+P5erSqkdTuZUwxjEb79BDdCHPJourGwChJGGxb3Wo3T8mbAHNi+ zIKFUWthJLa9HCmFQqpmyPNWKsMcqtzDSd1l73UCTRIa4n07aGWdtaQ35BKP4PGl kS/76gkPIB60QkArNh+9p6l1gBmqrP9LXEaM2XUMP7kjW03fsPzo9kqYpJmP+V2I HXxBJasbDk6q0yDPp31j3vW6NMjSgSgTs5RAZkPc3/2hGyQ/E6lt6yrKJ7MZGfDS s1AAlm916TDELR/PGncDNMbfp4zKXkl4FfqaKwIDAQABAoIBAAZVIBNbfEm+n41x mRYT8qPi4PVsA79D3zw15cXy4XCPliKL2KyMJkccfziSJdan9oul4cTOR1eucfZu 56RZzRF5OHn7WQiuv5+rhv1gJB3nwOfoWZXF0zP5zIk7ydTuXuzWCxkfomJKREck Z7/7Z3UCErujdO2U+OU04epD1/QYMwWZC+S1HT45zGbQtTA0M0EnZ+3kNuWP3DKk GdZ4kGQU3n8gvD6ygyYgP33tMlVFZrkSPFgZn/s0Tq5f/7dsiBg/wuDkDU63JXm0 YXsltGAJ059ptOtEQkWPorCJQ/SRDdIo99VCVmwvkJh5BX4kpTcgSBqR8fSosCkb bt2QBdECgYEAzMauVPgByL8zkoWnrv8BVoICVDyO/YjcMJeA/DzyOc6KdAzJ37a9 17aUOoA+hOAI74RusOnnjoHhNwTa8n4Fxo8oLhGxcQhimwIcQ9GA2bB3LyU2te91 m6dGl2UIZPDbaPLRE4KDzAKSHsJdU+bZE035ZhvHpuc4+g+RfvDwfD8CgYEAxKzK pHezJ2RgZEhz6jI5pKIAMZbz9ogmNbeGzWrKPsuFnQ0pjRADVVtRCj8/fCQUzNtR TJdYIowVA2mnJOH8/7QBD7KWK8q60egPlCZ5Jhq0c2IlBLIR3ICUf/HbT+GKZ3Ja XJKZxhd02JqJ4hcZkt5lO8cWdgYi0wto+5I5VxUCgYEAtmw8e6dgd4SVne8BPa0g dP9mscItJAGbHpKpLovgwcyUnOOTp381Sgj1rdP3XgnzC+Tvcx080kAz8P6bSjEo VgXMJpZOe8KbjTlpEqV9YvCIjHBbd+J15A81nMM9oibLX4gI55d6b/DOWSaPW6Io OcFZ7zPKPY54vJPH6s0bf6MCgYAf5DD72FkZoyIqQMFjEX/dXVOQtvyaVltzzG20 c4OWCSSCYfcB473WoncSpUzjEWq6CTo2pDfrajGiGwi6Z1bCE+s0I25MbZQ7o1ib Wl28uwnVx+1exI025zatRIeefWEXAyj559+9imItGWoQWlSQRzW9KrxOqRIOjMQa PwzDPQKBgBT0PXS9xtWOJrSjM14OfgUwv6N0jRLknknKLHma7tyXbFKw1py107Dy XptT2M+GhtBGbjCw4sz6GLkgLQLBmKmm0Ktr0BulAIl33j/gYL7UWLeU3703e2Xx CAHisz08DFRX3OkU2bsRDhyhJVjydWHhQSlokF9WPR69Lho6Y1HU -----END RSA PRIVATE KEY----- EOF KEY = <<~EOF -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAs1K8+OqjMJCHhfZtgPt/liXAaIUkBQVKm0XZ3oHAycLADyfR VFLLYs3Qb6AVUYvyNnTJITiF0gwnxhR1HWWQcXlYbAcGp9C4IOGzSVDetb4ZDpct Tf1KVIHijj7NGJxKehy1fUxzT/Osqg2r+dSDjVGC50aknxyaxcNZhGuGG2bXhOML DWxHFXlhsD3fEY0PdFovcLu3Z/XrRjj1BX192/qjNGR35YC8IT0OlhgIRhAHe/+d MsqqbnWCgrotRowSuwQrbmjpTq8MZRYAUZwcy5QCjFH7LEhblvFaNApZXUDgCHOb aA6qfhA1mVyMp3EaPamdumJ49WBbineACOB2eQIDAQABAoIBAGM9XcFvsQJWafn0 R+PCy3gfylzNmgKBTCmkPY+LRVMjSUDZ61n8O/yhJEIyWLn5dgE3HnwZGM4G1hgk CDBNneN+oTWfqcpDkzL3VU40yBvSaXGOro7jpzgfbW8FSGHfVMRBkRsXrRVJKHwv 9sXbGzahLo2ppb88iFb75lWHX/9XO2Hm9ozY+wPwPrOxqLumcVefBTUbc8nUIC2j Qx81wUhPf4xXAnxdtYURhsJOjnznzINBfA0X5p7Ecamy6i1o4n2AgMWdXaPrJiIB xNBuFRDdKcDjP4qKbcwXFkARtsy45WPK3gz+Mm61sdUE3XZIGa+oRLSkzMDM81Oh e4pmaVECgYEA7ixbkPW67oXX1+DzoTBrKs77s0vM/RaBmDOQrVHqmJN19A5bFEuj bqc5z+6cwQmHQz/J8x2WyicrCzROeTDSvQf+AjL4noEjMdeX/YU14Gaz74ntWJ5T tvOtFl5iF1ffpoHDFckXTJ1fveF7YudiQ8E7rkw53D7TWXCJbRbQp/UCgYEAwL7A zUM9OP1SwyxsMk7+ovtEUh4qfBUiWTq9HGxbEUjhPAjnIQRL1mdLJko3k2oHzNT3 hTSwu82cd5Zws5+vuNMG2dFQqW62J3d2uIzPrWm8pNlZQsuME/rZBIi212a5UcYZ oRYIO7OkVJrbj/m99pimLH4tdV2RazXeTB6CNfUCgYEAgjhOeBticTdMpAOiMOdA MM+qXoV7NoUvpf/LgnffRDybqSyQL7CLUtyrhzx3CDQleGdQC1SKNUzlA+M9ZJWF I0VTY/Bqbn88tuuhdkN3CZIdn0JSOrmWG9lvMWO5TfoFlgwslaS00Hba+f5mb9UC rPjhoJKcsAbJl4UoHjTzMGECgYACQvq+LdjND2PmOGI4oOaqAOrHT+VNuW3CwEax y6+x3zoNW0ljAMrnBCVEmMBYMXlP9PvGi1y7h2kbmh9ObERCle9RpPweUNdAVU2G Utio/0GgaZB7kSneniXnwLbshh8Mj5eDZV/JW41FFOAYq2SIPThN81kTNHrdWC94 ky8R9QKBgQDXzGNrXay1Qn4TewdlevK7PNiB9xzkdRGf7pYCh5PWMFWDDJUwo17S gTn5Kf77XhIZskNDA12mVZgR+EnDmybHqWybRG3pKc4hmKogKmDTHjnG4M/cVbNE zlh8zP2fVxwrdNFkJxn9a+9/qIhkh/if9JhCecajaE9mD3xvOJ1/iA== -----END RSA PRIVATE KEY----- EOF # Generated via `ssh-keygen -s ca -I foobar -V +52w -n root -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".b # Generated same as above but with -t rsa-sha2-512. SIGNED_CERT_SHA512 = "\x00\x00\x00\x1Cssh-rsa-cert-v01@openssh.com\x00\x00\x00 \x19\xA1\xDA\xE6'\a\xA5[\eA\xE3\xE4X\x80\xB0\x19 \xD8\x18\xD7\x98*\xC2x\xAD\x00\b\x9A\xD5\xA0n=\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\x00cE\xAE\xE4\x00\x00\x00\x00e%\x91'\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\x14\x00\x00\x00\frsa-sha2-512\x00\x00\x01\x00{\xB9\xB2\xFF\x92\xC2%\"/\xBB\xE39\xB1\xCE\x83\x1AW\x173\xCFb\t\xC7i "/bin/false" }, cert.critical_options) 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+".b, 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".b assert_equal expected_signature, cert.signature assert cert.signature_valid? assert_equal SIGNED_CERT, cert.to_blob end def test_certificate_sha512 cert = Net::SSH::Buffer.new(SIGNED_CERT_SHA512).read_key assert_equal "\x19\xA1\xDA\xE6'\a\xA5[\eA\xE3\xE4X\x80\xB0\x19 \xD8\x18\xD7\x98*\xC2x\xAD\x00\b\x9A\xD5\xA0n=".b, cert.nonce assert_equal 99, cert.serial assert_equal :user, cert.type assert_equal "foobar", cert.key_id assert_equal ["root"], cert.valid_principals assert_equal Time.at(1665511140), cert.valid_after assert_equal Time.at(1696960807), cert.valid_before assert_equal({ "force-command" => "/bin/false" }, cert.critical_options) 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+".b, cert.signature_key.to_blob expected_signature = "\x00\x00\x00\frsa-sha2-512\x00\x00\x01\x00{\xB9\xB2\xFF\x92\xC2%\"/\xBB\xE39\xB1\xCE\x83\x1AW\x173\xCFb\t\xC7i Host *+* ProxyCommand ssh $(echo %h | sed 's/+[^+]*$//;s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/;s/:/ -p /') PATH=.:\$PATH nc -w1 $(echo %h | sed 's/^.*+//;/:/!s/$/ %p/;s/:/ /') Host office-offsite HostName work-gateway+office-workstation Host test.host Compression yesnet-ssh-7.2.1/test/configs/identity_agent000066400000000000000000000000611454036133000204370ustar00rootroot00000000000000Host use-agent IdentityAgent /path/to/auth.socknet-ssh-7.2.1/test/configs/include000066400000000000000000000001251454036133000170540ustar00rootroot00000000000000Include subset1 "subset ws" IdentityFile ~/.ssh/id2.pem Host xyz Include conf.d/* net-ssh-7.2.1/test/configs/match000066400000000000000000000002011454036133000165200ustar00rootroot00000000000000Host test.host ForwardAgent yes Match all Compression yes Match unsupported Port 2345 Host * Port 22 Compression no net-ssh-7.2.1/test/configs/multihost000066400000000000000000000001101454036133000174530ustar00rootroot00000000000000Host other.host test.host Compression yes Port 1980 RekeyLimit 2G net-ssh-7.2.1/test/configs/negative_match000066400000000000000000000001171454036133000204100ustar00rootroot00000000000000Host test.* !test.host Port 1234 Compression no Host test.host Port 9876net-ssh-7.2.1/test/configs/nohost000066400000000000000000000002301454036133000167400ustar00rootroot00000000000000 IdentityFile ~/.ssh/id_dsa IdentityFile ~/.ssh/id_rsa Port 1980 Host test.host Port 1985 net-ssh-7.2.1/test/configs/numeric_host000066400000000000000000000000701454036133000201270ustar00rootroot00000000000000Host 1234 Compression yes Port 1980 RekeyLimit 2G net-ssh-7.2.1/test/configs/proxy_command_proxy_jump_mix000066400000000000000000000002411454036133000234600ustar00rootroot00000000000000Host test.mix1 ProxyJump jump2 Host *.mix1 ProxyCommand ssh -W %h:%p jump1 Host test.mix2 ProxyCommand ssh -W %h:%p jump2 Host *.mix2 ProxyJump jump1 net-ssh-7.2.1/test/configs/proxy_jump000066400000000000000000000001671454036133000176530ustar00rootroot00000000000000Host behind-proxy ProxyJump user@proxy Host behind-three-proxies ProxyJump user1@proxy1,user2@proxy2,user3@proxy3 net-ssh-7.2.1/test/configs/proxy_remote_user000066400000000000000000000000671454036133000212300ustar00rootroot00000000000000Host behind-proxy ProxyCommand ssh %r@proxy -W %h:%p net-ssh-7.2.1/test/configs/send_env000066400000000000000000000000431454036133000172310ustar00rootroot00000000000000Host 1234 SendEnv GIT_* LANG LC_* net-ssh-7.2.1/test/configs/set_env000066400000000000000000000000671454036133000171010ustar00rootroot00000000000000Host 1234 SetEnv foo="bar" baz=whale cat="black hole" net-ssh-7.2.1/test/configs/subset ws000066400000000000000000000000201454036133000173420ustar00rootroot00000000000000Compression yes net-ssh-7.2.1/test/configs/subset1000066400000000000000000000000451454036133000170200ustar00rootroot00000000000000IdentityFile ~/.ssh/id.pem Port 2345 net-ssh-7.2.1/test/configs/substitutes000066400000000000000000000001311454036133000200240ustar00rootroot00000000000000Host test HostName %h.sufix Host 1234 HostName prefix.%h.sufix Host * HostName %hnet-ssh-7.2.1/test/configs/wild_cards000066400000000000000000000002611454036133000175450ustar00rootroot00000000000000Host test.* Port 1234 Compression no Host tes?.host Port 4321 ForwardAgent yes Host *.hos? IdentityFile ~/.ssh/id_dsa Compression yes Host k*.host RekeyLimit 1Gnet-ssh-7.2.1/test/connection/000077500000000000000000000000001454036133000162175ustar00rootroot00000000000000net-ssh-7.2.1/test/connection/test_channel.rb000066400000000000000000000420021454036133000212110ustar00rootroot00000000000000require 'common' require 'net/ssh/connection/channel' module Connection class TestChannel < NetSSHTest include Net::SSH::Connection::Constants def teardown connection.test! end def test_constructor_should_set_defaults assert_equal 0x8000, channel.local_maximum_packet_size assert_equal 0x20000, channel.local_maximum_window_size assert channel.pending_requests.empty? end def test_channel_properties channel[:hello] = "some value" assert_equal "some value", channel[:hello] end def test_exec_should_be_syntactic_sugar_for_a_channel_request channel.expects(:send_channel_request).with("exec", :string, "ls").yields found_block = false channel.exec("ls") { found_block = true } assert found_block, "expected block to be passed to send_channel_request" end def test_subsystem_should_be_syntactic_sugar_for_a_channel_request channel.expects(:send_channel_request).with("subsystem", :string, "sftp").yields found_block = false channel.subsystem("sftp") { found_block = true } assert found_block, "expected block to be passed to send_channel_request" end def test_request_pty_with_invalid_option_should_raise_error assert_raises(ArgumentError) do channel.request_pty(bogus: "thing") end end def test_request_pty_without_options_should_use_defaults channel.expects(:send_channel_request).with("pty-req", :string, "xterm", :long, 80, :long, 24, :long, 640, :long, 480, :string, "\0").yields found_block = false channel.request_pty { found_block = true } assert found_block, "expected block to be passed to send_channel_request" end def test_request_pty_with_options_should_honor_options channel.expects(:send_channel_request).with("pty-req", :string, "vanilla", :long, 60, :long, 15, :long, 400, :long, 200, :string, "\5\0\0\0\1\0") channel.request_pty term: "vanilla", chars_wide: 60, chars_high: 15, pixels_wide: 400, pixels_high: 200, modes: { 5 => 1 } end def test_send_data_should_append_to_channels_output_buffer channel.send_data("hello") assert_equal "hello", channel.output.to_s channel.send_data("world") assert_equal "helloworld", channel.output.to_s end def test_close_before_channel_has_been_confirmed_should_set_closing assert !channel.closing? channel.close assert channel.closing? end def test_close_should_set_closing_and_send_message channel.do_open_confirmation(0, 100, 100) assert !channel.closing? connection.expect { |_t, packet| assert_equal CHANNEL_CLOSE, packet.type } connection.expects(:cleanup_channel).with(channel) channel.close channel.process assert channel.closing? end def test_close_while_closing_should_do_nothing test_close_should_set_closing_and_send_message assert_nothing_raised { channel.close } end def test_process_when_process_callback_is_not_set_should_just_enqueue_data channel.expects(:enqueue_pending_output) channel.process end def test_process_when_process_callback_is_set_should_yield_self_before_enqueuing_data channel.expects(:enqueue_pending_output).never channel.on_process { |ch| ch.expects(:enqueue_pending_output).once } channel.process end def test_enqueue_pending_output_should_have_no_effect_if_channel_has_not_been_confirmed channel.send_data("hello") assert_nothing_raised { channel.enqueue_pending_output } end def test_enqueue_pending_output_should_have_no_effect_if_there_is_no_output channel.do_open_confirmation(0, 100, 100) assert_nothing_raised { channel.enqueue_pending_output } end def test_enqueue_pending_output_should_not_enqueue_more_than_output_length channel.do_open_confirmation(0, 100, 100) channel.send_data("hello world") connection.expect do |_t, packet| assert_equal CHANNEL_DATA, packet.type assert_equal 0, packet[:local_id] assert_equal 11, packet[:data].length end channel.enqueue_pending_output end def test_enqueue_pending_output_should_not_enqueue_more_than_max_packet_length_at_once channel.do_open_confirmation(0, 100, 8) channel.send_data("hello world") connection.expect do |t, packet| assert_equal CHANNEL_DATA, packet.type assert_equal 0, packet[:local_id] assert_equal "hello wo", packet[:data] t.expect do |_t2, packet2| assert_equal CHANNEL_DATA, packet2.type assert_equal 0, packet2[:local_id] assert_equal "rld", packet2[:data] end end channel.enqueue_pending_output end def test_enqueue_pending_output_should_not_enqueue_more_than_max_window_size channel.do_open_confirmation(0, 8, 100) channel.send_data("hello world") connection.expect do |_t, packet| assert_equal CHANNEL_DATA, packet.type assert_equal 0, packet[:local_id] assert_equal "hello wo", packet[:data] end channel.enqueue_pending_output end def test_on_data_with_block_should_set_callback flag = false channel.on_data { flag = !flag } channel.do_data("") assert(flag, "callback should have been invoked") channel.on_data channel.do_data("") assert(flag, "callback should have been removed") end def test_on_extended_data_with_block_should_set_callback flag = false channel.on_extended_data { flag = !flag } channel.do_extended_data(0, "") assert(flag, "callback should have been invoked") channel.on_extended_data channel.do_extended_data(0, "") assert(flag, "callback should have been removed") end def test_on_process_with_block_should_set_callback flag = false channel.on_process { flag = !flag } channel.process assert(flag, "callback should have been invoked") channel.on_process channel.process assert(flag, "callback should have been removed") end def test_on_close_with_block_should_set_callback flag = false channel.on_close { flag = !flag } channel.do_close assert(flag, "callback should have been invoked") channel.on_close channel.do_close assert(flag, "callback should have been removed") end def test_on_eof_with_block_should_set_callback flag = false channel.on_eof { flag = !flag } channel.do_eof assert(flag, "callback should have been invoked") channel.on_eof channel.do_eof assert(flag, "callback should have been removed") end def test_do_request_for_unhandled_request_should_do_nothing_if_not_wants_reply channel.do_open_confirmation(0, 100, 100) assert_nothing_raised { channel.do_request "exit-status", false, nil } end 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 } channel.do_request "keepalive@openssh.com", true, nil end def test_do_request_for_handled_request_should_invoke_callback_and_do_nothing_if_returns_true_and_not_wants_reply channel.do_open_confirmation(0, 100, 100) flag = false channel.on_request("exit-status") { flag = true; true } assert_nothing_raised { channel.do_request "exit-status", false, nil } assert flag, "callback should have been invoked" end def test_do_request_for_handled_request_should_invoke_callback_and_do_nothing_if_fails_and_not_wants_reply channel.do_open_confirmation(0, 100, 100) flag = false channel.on_request("exit-status") { flag = true; raise Net::SSH::ChannelRequestFailed } assert_nothing_raised { channel.do_request "exit-status", false, nil } assert flag, "callback should have been invoked" end def test_do_request_for_handled_request_should_invoke_callback_and_send_CHANNEL_SUCCESS_if_returns_true_and_wants_reply 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 } assert_nothing_raised { channel.do_request "exit-status", true, nil } assert flag, "callback should have been invoked" end def test_do_request_for_handled_request_should_invoke_callback_and_send_CHANNEL_FAILURE_if_returns_false_and_wants_reply 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 } 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| assert_equal CHANNEL_REQUEST, p.type assert_equal 0, p[:local_id] assert_equal "exec", p[:request] assert_equal false, p[:want_reply] assert_equal "ls", p[:request_data].read_string end channel.send_channel_request("exec", :string, "ls") assert channel.pending_requests.empty? end def test_send_channel_request_should_wait_for_remote_id channel.expects(:remote_id).times(1).returns(nil) msg = nil begin channel.send_channel_request("exec", :string, "ls") rescue RuntimeError => e msg = e.message end assert_equal "Channel open not yet confirmed, please call send_channel_request(or exec) from block of open_channel", msg assert channel.pending_requests.empty? end def test_send_channel_request_with_callback_should_want_reply channel.do_open_confirmation(0, 100, 100) connection.expect do |_t, p| assert_equal CHANNEL_REQUEST, p.type assert_equal 0, p[:local_id] assert_equal "exec", p[:request] assert_equal true, p[:want_reply] assert_equal "ls", p[:request_data].read_string end callback = Proc.new {} channel.send_channel_request("exec", :string, "ls", &callback) assert_equal [callback], channel.pending_requests end def test_do_open_confirmation_should_set_remote_parameters channel.do_open_confirmation(1, 2, 3) assert_equal 1, channel.remote_id assert_equal 2, channel.remote_window_size assert_equal 2, channel.remote_maximum_window_size assert_equal 3, channel.remote_maximum_packet_size end def test_do_open_confirmation_should_call_open_confirmation_callback flag = false channel { flag = true } assert !flag, "callback should not have been invoked yet" channel.do_open_confirmation(1, 2, 3) assert flag, "callback should have been invoked" end def test_do_open_confirmation_with_session_channel_should_invoke_agent_forwarding_if_agent_forwarding_requested connection forward_agent: true forward = mock("forward") forward.expects(:agent).with(channel) connection.expects(:forward).returns(forward) channel.do_open_confirmation(1, 2, 3) end def test_do_open_confirmation_with_non_session_channel_should_not_invoke_agent_forwarding_even_if_agent_forwarding_requested connection forward_agent: true channel type: "direct-tcpip" connection.expects(:forward).never channel.do_open_confirmation(1, 2, 3) end def test_do_window_adjust_should_adjust_remote_window_size_by_the_given_amount channel.do_open_confirmation(0, 1000, 1000) assert_equal 1000, channel.remote_window_size assert_equal 1000, channel.remote_maximum_window_size channel.do_window_adjust(500) assert_equal 1500, channel.remote_window_size assert_equal 1500, channel.remote_maximum_window_size end def test_do_data_should_update_local_window_size assert_equal 0x20000, channel.local_maximum_window_size assert_equal 0x20000, channel.local_window_size channel.do_data("here is some data") assert_equal 0x20000, channel.local_maximum_window_size assert_equal 0x1FFEF, channel.local_window_size end def test_do_extended_data_should_update_local_window_size assert_equal 0x20000, channel.local_maximum_window_size assert_equal 0x20000, channel.local_window_size channel.do_extended_data(1, "here is some data") assert_equal 0x20000, channel.local_maximum_window_size assert_equal 0x1FFEF, channel.local_window_size end def test_do_data_when_local_window_size_drops_below_threshold_should_trigger_WINDOW_ADJUST_message channel.do_open_confirmation(0, 1000, 1000) assert_equal 0x20000, channel.local_maximum_window_size assert_equal 0x20000, channel.local_window_size connection.expect do |_t, p| assert_equal CHANNEL_WINDOW_ADJUST, p.type assert_equal 0, p[:local_id] assert_equal 0x20000, p[:extra_bytes] end channel.do_data("." * 0x10001) assert_equal 0x40000, channel.local_maximum_window_size assert_equal 0x2FFFF, channel.local_window_size end def test_do_failure_should_grab_next_pending_request_and_call_it result = nil channel.pending_requests << Proc.new { |*args| result = args } channel.do_failure assert_equal [channel, false], result assert channel.pending_requests.empty? end def test_do_success_should_grab_next_pending_request_and_call_it result = nil channel.pending_requests << Proc.new { |*args| result = args } channel.do_success assert_equal [channel, true], result assert channel.pending_requests.empty? end def test_active_should_be_true_when_channel_appears_in_channel_list connection.channels[channel.local_id] = channel assert channel.active? end def test_active_should_be_false_when_channel_is_not_in_channel_list assert !channel.active? end def test_wait_should_block_while_channel_is_active? channel.expects(:active?).times(3).returns(true, true, false) channel.wait end def test_wait_until_open_confirmed_should_block_while_remote_id_nil channel.expects(:remote_id).times(3).returns(nil, nil, 3) channel.send(:wait_until_open_confirmed) end def test_eof_bang_should_send_eof_to_server channel.do_open_confirmation(0, 1000, 1000) connection.expect { |_t, p| assert_equal CHANNEL_EOF, p.type } 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 } channel.eof! assert_nothing_raised { channel.eof! } channel.process end 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 } assert !channel.eof? channel.eof! assert channel.eof? channel.process end 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 } channel.eof! channel.process assert_raises(EOFError) { channel.send_data("die! die! die!") } end def test_data_should_precede_eof channel.do_open_confirmation(0, 1000, 1000) connection.expect do |_t, p| assert_equal CHANNEL_DATA, p.type connection.expect { |_t, p2| assert_equal CHANNEL_EOF, p2.type } end channel.send_data "foo" channel.eof! channel.process end private class MockConnection attr_reader :logger attr_reader :options attr_reader :channels def initialize(options = {}) @expectation = nil @options = options @channels = {} end def expect(&block) @expectation = block end 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) end alias loop_forever loop def loop(&block) loop_forever { break unless block.call } end def test! raise "expected a packet but none were sent" if @expectation end end def connection(options = {}) @connection ||= MockConnection.new(options) end def channel(options = {}, &block) @channel ||= Net::SSH::Connection::Channel.new(connection(options), options[:type] || "session", options[:local_id] || 0, &block) end end end net-ssh-7.2.1/test/connection/test_session.rb000066400000000000000000000555141454036133000213000ustar00rootroot00000000000000require_relative '../common' require 'net/ssh/connection/session' module Connection class TestSession < NetSSHTest include Net::SSH::Connection::Constants def test_constructor_should_set_defaults assert session.channels.empty? assert session.pending_requests.empty? assert_equal({ socket => nil }, session.listeners) end def test_on_open_channel_should_register_block_with_given_channel_type flag = false session.on_open_channel("testing") { flag = true } assert_not_nil session.channel_open_handlers["testing"] session.channel_open_handlers["testing"].call assert flag, "callback should have been invoked" end def test_forward_should_create_and_cache_instance_of_forward_service assert_instance_of Net::SSH::Service::Forward, session.forward assert_equal session.forward.object_id, session.forward.object_id end def test_listen_to_without_callback_should_add_argument_as_listener io = stub("io") session.listen_to(io) assert session.listeners.key?(io) assert_nil session.listeners[io] end def test_listen_to_should_add_argument_to_listeners_list_if_block_is_given io = stub("io", pending_write?: true) flag = false session.listen_to(io) { flag = true } assert !flag, "callback should not be invoked immediately" assert session.listeners.key?(io) session.listeners[io].call assert flag, "callback should have been invoked" end def test_stop_listening_to_should_remove_argument_from_listeners io = stub("io", pending_write?: true) session.listen_to(io) assert session.listeners.key?(io) session.stop_listening_to(io) assert !session.listeners.key?(io) end def test_send_message_should_enqueue_message_at_transport_layer packet = P(:byte, REQUEST_SUCCESS) session.send_message(packet) assert_equal packet.to_s, socket.write_buffer end def test_open_channel_defaults_should_use_session_channel flag = false channel = session.open_channel { flag = true } assert !flag, "callback should not be invoked immediately" channel.do_open_confirmation(1, 2, 3) assert flag, "callback should have been invoked" assert_equal "session", channel.type assert_equal 0, channel.local_id assert_equal channel, session.channels[channel.local_id] packet = P(:byte, CHANNEL_OPEN, :string, "session", :long, channel.local_id, :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size) assert_equal packet.to_s, socket.write_buffer end def test_open_channel_with_type_should_use_type channel = session.open_channel("direct-tcpip") assert_equal "direct-tcpip", channel.type packet = P(:byte, CHANNEL_OPEN, :string, "direct-tcpip", :long, channel.local_id, :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size) assert_equal packet.to_s, socket.write_buffer end def test_open_channel_with_extras_should_append_extras_to_packet channel = session.open_channel("direct-tcpip", :string, "other.host", :long, 1234) packet = P(:byte, CHANNEL_OPEN, :string, "direct-tcpip", :long, channel.local_id, :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size, :string, "other.host", :long, 1234) assert_equal packet.to_s, socket.write_buffer end def test_send_global_request_without_callback_should_not_expect_reply packet = P(:byte, GLOBAL_REQUEST, :string, "testing", :bool, false) session.send_global_request("testing") assert_equal packet.to_s, socket.write_buffer assert session.pending_requests.empty? end def test_send_global_request_with_callback_should_expect_reply packet = P(:byte, GLOBAL_REQUEST, :string, "testing", :bool, true) proc = Proc.new {} session.send_global_request("testing", &proc) assert_equal packet.to_s, socket.write_buffer assert_equal [proc], session.pending_requests end def test_send_global_request_with_extras_should_append_extras_to_packet packet = P(:byte, GLOBAL_REQUEST, :string, "testing", :bool, false, :string, "other.host", :long, 1234) session.send_global_request("testing", :string, "other.host", :long, 1234) assert_equal packet.to_s, socket.write_buffer end def test_process_should_exit_immediately_if_block_is_false session.channels[0] = stub("channel", closing?: false) session.channels[0].expects(:process).never process_times(0) end def test_can_open_channels_in_process # see #110 chid = session.send(:get_next_channel_id) session.channels[chid] = stub("channel", local_closed?: false) session.channels[chid].expects(:process).with do session.open_channel true end IO.expects(:select).never process_times(2) end def test_process_should_exit_after_processing_if_block_is_true_then_false session.channels[0] = stub("channel", local_closed?: false) session.channels[0].expects(:process) IO.expects(:select).never process_times(2) end def test_process_should_not_process_channels_that_are_closing session.channels[0] = stub("channel", local_closed?: true) session.channels[0].expects(:process).never IO.expects(:select).never process_times(2) end def test_global_request_packets_should_be_silently_handled_if_no_handler_exists_for_them transport.return(GLOBAL_REQUEST, :string, "testing", :bool, false) process_times(2) assert transport.queue.empty? assert !socket.pending_write? end def test_global_request_packets_should_be_auto_replied_to_even_if_no_handler_exists transport.return(GLOBAL_REQUEST, :string, "testing", :bool, true) process_times(2) assert_equal P(:byte, REQUEST_FAILURE).to_s, socket.write_buffer end def test_global_request_handler_should_not_trigger_auto_reply_if_no_reply_is_wanted flag = false session.on_global_request("testing") { flag = true } assert !flag, "callback should not be invoked yet" transport.return(GLOBAL_REQUEST, :string, "testing", :bool, false) process_times(2) assert transport.queue.empty? assert !socket.pending_write? assert flag, "callback should have been invoked" end def test_global_request_handler_returning_true_should_trigger_success_auto_reply flag = false session.on_global_request("testing") { flag = true } transport.return(GLOBAL_REQUEST, :string, "testing", :bool, true) process_times(2) assert_equal P(:byte, REQUEST_SUCCESS).to_s, socket.write_buffer assert flag end def test_global_request_handler_returning_false_should_trigger_failure_auto_reply flag = false session.on_global_request("testing") { flag = true; false } transport.return(GLOBAL_REQUEST, :string, "testing", :bool, true) process_times(2) assert_equal P(:byte, REQUEST_FAILURE).to_s, socket.write_buffer assert flag end def test_global_request_handler_returning_sent_should_not_trigger_auto_reply flag = false session.on_global_request("testing") { flag = true; :sent } transport.return(GLOBAL_REQUEST, :string, "testing", :bool, true) process_times(2) assert !socket.pending_write? assert flag end def test_global_request_handler_returning_other_value_should_raise_error transport.expects(:closed?).at_least_once.returns(false) session.on_global_request("testing") { "bug" } transport.return(GLOBAL_REQUEST, :string, "testing", :bool, true) assert_raises(RuntimeError) { process_times(2) } end def test_request_success_packets_should_invoke_next_pending_request_with_true result = nil session.pending_requests << Proc.new { |*args| result = args } transport.return(REQUEST_SUCCESS) process_times(2) assert_equal [true, P(:byte, REQUEST_SUCCESS)], result assert session.pending_requests.empty? end def test_request_failure_packets_should_invoke_next_pending_request_with_false result = nil session.pending_requests << Proc.new { |*args| result = args } transport.return(REQUEST_FAILURE) process_times(2) assert_equal [false, P(:byte, REQUEST_FAILURE)], result assert session.pending_requests.empty? end def test_channel_open_packet_without_corresponding_channel_open_handler_should_result_in_channel_open_failure transport.return(CHANNEL_OPEN, :string, "auth-agent", :long, 14, :long, 0x20000, :long, 0x10000) process_times(2) assert_equal P(:byte, CHANNEL_OPEN_FAILURE, :long, 14, :long, 3, :string, "unknown channel type auth-agent", :string, "").to_s, socket.write_buffer end 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| raise Net::SSH::ChannelOpenFailed.new(1234, "we iz in ur channelz!") end process_times(2) assert_equal P(:byte, CHANNEL_OPEN_FAILURE, :long, 14, :long, 1234, :string, "we iz in ur channelz!", :string, "").to_s, socket.write_buffer end def test_channel_open_packet_with_corresponding_handler_should_result_in_channel_open_confirmation_when_handler_succeeds transport.return(CHANNEL_OPEN, :string, "auth-agent", :long, 14, :long, 0x20001, :long, 0x10001) result = nil session.on_open_channel("auth-agent") { |*args| result = args } process_times(2) assert_equal P(:byte, CHANNEL_OPEN_CONFIRMATION, :long, 14, :long, 0, :long, 0x20000, :long, 0x8000).to_s, socket.write_buffer assert_not_nil(ch = session.channels[0]) assert_equal [session, ch, P(:byte, CHANNEL_OPEN, :string, "auth-agent", :long, 14, :long, 0x20001, :long, 0x10001)], result assert_equal 0, ch.local_id assert_equal 14, ch.remote_id assert_equal 0x20001, ch.remote_maximum_window_size assert_equal 0x10001, ch.remote_maximum_packet_size assert_equal 0x20000, ch.local_maximum_window_size assert_equal 0x8000, ch.local_maximum_packet_size assert_equal "auth-agent", ch.type end def test_channel_open_failure_should_remove_channel_and_tell_channel_that_open_failed session.channels[1] = stub("channel") session.channels[1].expects(:do_open_failed).with(1234, "some reason") transport.return(CHANNEL_OPEN_FAILURE, :long, 1, :long, 1234, :string, "some reason", :string, "lang tag") process_times(2) assert session.channels.empty? end def test_channel_open_confirmation_packet_should_be_routed_to_corresponding_channel channel_at(14).expects(:do_open_confirmation).with(1234, 0x20001, 0x10001) transport.return(CHANNEL_OPEN_CONFIRMATION, :long, 14, :long, 1234, :long, 0x20001, :long, 0x10001) process_times(2) end def test_channel_window_adjust_packet_should_be_routed_to_corresponding_channel channel_at(14).expects(:do_window_adjust).with(5000) transport.return(CHANNEL_WINDOW_ADJUST, :long, 14, :long, 5000) process_times(2) end def test_channel_request_for_nonexistant_channel_should_be_ignored transport.return(CHANNEL_REQUEST, :long, 14, :string, "testing", :bool, false) assert_nothing_raised { process_times(2) } end def test_channel_request_packet_should_be_routed_to_corresponding_channel channel_at(14).expects(:do_request).with("testing", false, Net::SSH::Buffer.new) transport.return(CHANNEL_REQUEST, :long, 14, :string, "testing", :bool, false) process_times(2) end def test_channel_data_packet_should_be_routed_to_corresponding_channel channel_at(14).expects(:do_data).with("bring it on down") transport.return(CHANNEL_DATA, :long, 14, :string, "bring it on down") process_times(2) end def test_channel_extended_data_packet_should_be_routed_to_corresponding_channel channel_at(14).expects(:do_extended_data).with(1, "bring it on down") transport.return(CHANNEL_EXTENDED_DATA, :long, 14, :long, 1, :string, "bring it on down") process_times(2) end def test_channel_eof_packet_should_be_routed_to_corresponding_channel channel_at(14).expects(:do_eof).with transport.return(CHANNEL_EOF, :long, 14) process_times(2) end def test_channel_success_packet_should_be_routed_to_corresponding_channel channel_at(14).expects(:do_success).with transport.return(CHANNEL_SUCCESS, :long, 14) process_times(2) end def test_channel_failure_packet_should_be_routed_to_corresponding_channel channel_at(14).expects(:do_failure).with transport.return(CHANNEL_FAILURE, :long, 14) process_times(2) end def test_channel_close_packet_should_be_routed_to_corresponding_channel_and_channel_should_be_closed_and_removed session.channels[14] = stub("channel").tap do |channel| # this simulates the case where we closed the channel first, sent # CHANNEL_CLOSE to server and are waiting for server's response. channel.expects(:local_closed?).returns(true) channel.expects(:do_close) channel.expects(:close).with channel.expects(:remote_closed!).with.at_least_once channel.expects(:remote_closed?).with.returns(true) channel.expects(:local_id).returns(14) end transport.return(CHANNEL_CLOSE, :long, 14) process_times(2) assert session.channels.empty? end def test_multiple_pending_dispatches_should_be_dispatched_together channel_at(14).expects(:do_eof).with session.channels[14].expects(:do_success).with transport.return(CHANNEL_SUCCESS, :long, 14) transport.return(CHANNEL_EOF, :long, 14) process_times(2) end def test_writers_without_pending_writes_should_not_be_considered_for_select IO.expects(:select).with([socket], [], nil, nil).returns([[], [], []]) session.process end def test_writers_with_pending_writes_should_be_considered_for_select socket.enqueue("laksdjflasdkf") IO.expects(:select).with([socket], [socket], nil, nil).returns([[], [], []]) session.process end def test_ready_readers_should_be_filled socket.expects(:recv).returns("this is some data") IO.expects(:select).with([socket], [], nil, nil).returns([[socket], [], []]) session.process assert_equal [socket], session.listeners.keys end def test_ready_readers_that_cant_be_filled_should_be_removed socket.expects(:recv).returns("") socket.expects(:close) IO.expects(:select).with([socket], [], nil, nil).returns([[socket], [], []]) session.process assert session.listeners.empty? end def test_ready_readers_that_are_registered_with_a_block_should_call_block_instead_of_fill io = stub("io", pending_write?: false) flag = false session.stop_listening_to(socket) # so that we only have to test the presence of a single IO object session.listen_to(io) { flag = true } IO.expects(:select).with([io], [], nil, nil).returns([[io], [], []]) session.process assert flag, "callback should have been invoked" end def test_ready_writers_should_call_send_pending socket.enqueue("laksdjflasdkf") socket.expects(:send).with("laksdjflasdkf", 0).returns(13) IO.expects(:select).with([socket], [socket], nil, nil).returns([[], [socket], []]) session.process end def test_process_should_call_rekey_as_needed transport.expects(:rekey_as_needed) IO.expects(:select).with([socket], [], nil, nil).returns([[], [], []]) session.process end def test_process_should_call_enqueue_message_if_io_select_timed_out timeout = Net::SSH::Connection::Session::DEFAULT_IO_SELECT_TIMEOUT options = { keepalive: true } expected_packet = P(:byte, Net::SSH::Packet::GLOBAL_REQUEST, :string, "keepalive@openssh.com", :bool, true) IO.stubs(:select).with([socket], [], nil, timeout).returns(nil) transport.expects(:enqueue_message).with { |msg| msg.content == expected_packet.content } session(options).process end def test_process_should_raise_if_keepalives_not_answered timeout = Net::SSH::Connection::Session::DEFAULT_IO_SELECT_TIMEOUT options = { keepalive: true, keepalive_interval: 300, keepalive_maxcount: 3 } expected_packet = P(:byte, Net::SSH::Packet::GLOBAL_REQUEST, :string, "keepalive@openssh.com", :bool, true) [1, 2, 3].each do |i| Time.stubs(:now).returns(Time.at(i * 300)) IO.stubs(:select).with([socket], [], nil, timeout).returns(nil) transport.expects(:enqueue_message).with { |msg| msg.content == expected_packet.content } session(options).process end Time.stubs(:now).returns(Time.at(4 * 300)) IO.stubs(:select).with([socket], [], nil, timeout).returns(nil) transport.expects(:enqueue_message).with { |msg| msg.content == expected_packet.content } assert_raises(Net::SSH::Timeout) { session(options).process } end def test_process_should_not_call_enqueue_message_unless_io_select_timed_out timeout = Net::SSH::Connection::Session::DEFAULT_IO_SELECT_TIMEOUT options = { keepalive: true } IO.stubs(:select).with([socket], [], nil, timeout).returns([[socket], [], []]) socket.stubs(:recv).returns("x") transport.expects(:enqueue_message).never session(options).process end def test_process_should_not_call_enqueue_message_unless_keepalive_interval_not_go_on timeout = 10 options = { keepalive: true, keepalive_interval: timeout } Time.stubs(:now).returns(Time.at(0), Time.at(9), Time.at(timeout)) IO.stubs(:select).with([socket], [], nil, timeout).returns(nil) transport.expects(:enqueue_message).times(2) 3.times { session(options).process } end def test_process_should_call_io_select_with_nil_as_last_arg_if_keepalive_disabled IO.expects(:select).with([socket], [], nil, nil).returns([[], [], []]) session.process end def test_process_should_call_io_select_with_interval_as_last_arg_if_keepalive_interval_passed timeout = 10 options = { keepalive: true, keepalive_interval: timeout } IO.expects(:select).with([socket], [], nil, timeout).returns([[], [], []]) session(options).process end def test_process_should_call_io_select_with_wait_if_provided_and_minimum timeout = 10 wait = 5 options = { keepalive: true, keepalive_interval: timeout } IO.expects(:select).with([socket], [], nil, wait).returns([[], [], []]) session(options).process(wait) end def test_loop_should_call_process_until_process_returns_false session.expects(:process).with(0) session.expects(:process).with(nil).times(4).returns(true, true, true, false).yields n = 0 session.loop { n += 1 } assert_equal 4, n end def test_exec_should_open_channel_and_configure_default_callbacks prep_exec("ls", :stdout, "data packet", :stderr, "extended data packet") call = :first session.exec "ls" do |_channel, type, data| if call == :first assert_equal :stdout, type assert_equal "data packet", data call = :second elsif call == :second assert_equal :stderr, type assert_equal "extended data packet", data call = :third else flunk "should never get here, call == #{call.inspect}" end end session.loop assert_equal :third, call end def test_exec_without_block_should_use_print_to_display_result prep_exec("ls", :stdout, "data packet", :stderr, "extended data packet") $stdout.expects(:print).with("data packet") $stderr.expects(:print).with("extended data packet") session.exec "ls" session.loop end def test_exec_bang_should_block_until_command_finishes prep_exec("ls", :stdout, "some data") called = false session.exec! "ls" do |_channel, type, data| called = true assert_equal :stdout, type assert_equal "some data", data end assert called end def test_exec_bang_without_block_should_return_data_as_string prep_exec("ls", :stdout, "some data") assert_equal "some data", session.exec!("ls") end def test_exec_bang_without_block_should_return_empty_string_for_empty_command_output prep_exec('ls', :stdout, '') assert_equal "", session.exec!('ls') end def test_max_select_wait_time_should_return_keepalive_interval_when_keepalive_enabled options = { keepalive: true, keepalive_interval: 5 } assert_equal 5, session(options).max_select_wait_time end def test_max_select_wait_time_should_return_nil_when_keepalive_disabled options = {} assert_nil session(options).max_select_wait_time end private def prep_exec(command, *data) IO.expects(:select).with([socket], [], nil, 0).returns([[], [], []]) transport.mock_enqueue = true transport.expect do |t, p| assert_equal CHANNEL_OPEN, p.type t.return(CHANNEL_OPEN_CONFIRMATION, :long, p[:remote_id], :long, 0, :long, 0x20000, :long, 0x10000) t.expect do |t2, p2| assert_equal CHANNEL_REQUEST, p2.type assert_equal "exec", p2[:request] assert_equal true, p2[:want_reply] assert_equal "ls", p2.read_string t2.return(CHANNEL_SUCCESS, :long, p[:remote_id]) 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 t2.return(CHANNEL_EXTENDED_DATA, :long, p[:remote_id], :long, 1, :string, datum) end end t2.return(CHANNEL_CLOSE, :long, p[:remote_id]) t2.expect { |_t3, p3| assert_equal CHANNEL_CLOSE, p3.type } end end end module MockSocket # so that we can easily test the contents that were enqueued, without # worrying about all the packet stream overhead def enqueue_packet(message) enqueue(message.to_s) end end def socket @socket ||= begin socket ||= Object.new socket.extend(Net::SSH::Transport::PacketStream) socket.extend(MockSocket) socket end end def channel_at(local_id) session.channels[local_id] = stub("channel", process: true, local_closed?: false) end def transport(options = {}) @transport ||= MockTransport.new(options.merge(socket: socket)) end def session(options = {}) @session ||= Net::SSH::Connection::Session.new(transport, options) end def process_times(n) i = 0 session.process { (i += 1) < n } end end end net-ssh-7.2.1/test/integration/000077500000000000000000000000001454036133000164035ustar00rootroot00000000000000net-ssh-7.2.1/test/integration/README.md000066400000000000000000000012771454036133000176710ustar00rootroot00000000000000# Integration tests with vagrant Requirements: * Vagrant (https://www.vagrantup.com/) * Ansible (http://docs.ansible.com/intro_installation.html) Setup: ansible-galaxy install rvm.ruby vagrant up ; vagrant ssh rvmsudo_secure_path=1 rvmsudo rvm all do gem install bundler rvm all do sh -c 'rm Gemfile.lock; bundle' rvm all do rake test # Debugging Checking the ssh logs might be useful: ```yml script: - #NET_SSH_RUN_INTEGRATION_TESTS=1 bundle exec rake test - sudo tail -n 3 /var/log/auth.log - bundle exec ruby -Ilib:test ./test/integration/test_forward.rb -n test_client_close_should_be_handled_remote - sudo tail -n 60 /var/log/auth.log - bundle exec rubocop ``` net-ssh-7.2.1/test/integration/Vagrantfile000066400000000000000000000006061454036133000205720ustar00rootroot00000000000000VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "ubuntu/jammy64" config.vm.provision "ansible" do |ansible| ansible.playbook = "./playbook.yml" ansible.become = true ansible.become_user = 'root' ansible.verbose = 'vvvv' ansible.compatibility_mode = "2.0" end config.vm.synced_folder "../..", "/net-ssh" end net-ssh-7.2.1/test/integration/common.rb000066400000000000000000000123601454036133000202220ustar00rootroot00000000000000$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib" require_relative '../common' require 'mocha/minitest' require 'pty' require 'expect' module IntegrationTestHelpers VERBOSE = false def sh(command) puts "$ #{command}" if VERBOSE res = system(command) status = $? raise "Command: #{command} failed:#{status.exitstatus}" unless res end def tmpdir(&block) Dir.mktmpdir do |dir| yield(dir) end end def sshd_8_or_later? !!(`sshd -v 2>&1 |grep 'OpenSSH_'` =~ /OpenSSH_8./) end def ssh_keygen(file, type = 'rsa', password = '') sh "rm -rf #{file} #{file}.pub" sh "ssh-keygen #{ssh_keygen_format} -q -f #{file} -t #{type} -N '#{password}'" end def ssh_keygen_format if Net::SSH::Authentication::ED25519Loader::LOADED "" else "-m PEM" end end def set_authorized_key(user, pubkey) authorized_key = "/home/#{user}/.ssh/authorized_keys" sh "sudo cp #{pubkey} #{authorized_key}" sh "sudo chown #{user} #{authorized_key}" sh "sudo chmod 0744 #{authorized_key}" end def sign_user_key(user, pubkey) cert = "/etc/ssh/users_ca" sh "sudo ssh-keygen -s #{cert} -I user_#{user} -n #{user} -V +52w #{pubkey}" end def with_agent(&block) puts "/usr/bin/ssh-agent -c" if VERBOSE agent_out = `/usr/bin/ssh-agent -c` agent_out.split("\n").each do |line| if line =~ /setenv (\S+) (\S+);/ ENV[$1] = $2 puts "ENV[#{$1}]=#{$2}" if VERBOSE end end begin yield ensure sh "/usr/bin/ssh-agent -k > /dev/null" end end def ssh_add(key, password) command = "ssh-add #{key}" status = nil PTY.spawn(command) do |reader, writer, pid| begin reader.expect(/Enter passphrase for .*:/) { |data| puts data } writer.puts(password) until reader.eof? do line = reader.readline puts line if VERBOSE end rescue Errno::EIO => _e end 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) f.close system("sudo cp -f #{f.path} /etc/ssh/sshd_config") 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") system("sudo service ssh restart") end end def with_lines_as_tempfile(lines = [], add_pid: true, debug: false, &block) Tempfile.open('sshd_config') do |f| 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 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, debug: false) pid = nil sshpidfile = nil sshlogpath = nil if config 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 = [] sshlogpath = "#{path}-log.txt" extra_params = ['-E', sshlogpath] pid = spawn('sudo', '/opt/net-ssh-openssh/sbin/sshd', '-D', '-f', path, '-p', port, *extra_params) sshpidfile = pidpath yield pid, port end else 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 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 sshpidfile if !File.exist?(sshpidfile) && !sshlogpath.nil? loglines = `sudo tail -n 10 #{sshlogpath}`.split("\n") puts "ERROR: sshpidfile #{sshpidfile} does not exist\n\nSSH server logs:\n#{loglines.join("\n")}" end 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(20) do Process.wait(pid) end rescue Timeout::Error warn "Failed to kill openssh process: #{pid}" system('sudo', 'kill', '-9', pid.to_s) raise end end end def localhost 'localhost' end def user 'net_ssh_1' end end net-ssh-7.2.1/test/integration/mitm_server.rb000066400000000000000000000044651454036133000212750ustar00rootroot00000000000000# Simple Man in the middle server # # server = MitmServer.new('localhost', 22) # Net.SSH.start('localhost', ENV['USER'], port: server.port) # server.run class MitmServer < TCPServer attr_accessor :local_read_size attr_accessor :target_read_size def initialize(remote_host = 'localhost', remote_port = 22) @remote_host = remote_host @remote_port = remote_port @server = TCPServer.open(0) @local_read_size = 2048 @target_read_size = 2048 end def port @server.addr[1] end def host 'localhost' end def run start(@server, @remote_host, @remote_port) end private def start_server(server, &block) server ||= TCPServer.open(0) Thread.start do loop do Thread.start(server.accept) do |client| yield(client) end end end return server end def dlog(message); end def start(server, remote_host, remote_port) err = nil server = start_server(server) do |local| remote = TCPSocket.new(remote_host, remote_port) loop do r, _w, _e = IO.select([local, remote], nil, nil) if r.include? local begin data = local.recv local_read_size rescue StandardError => e data = nil dlog "Local closed: #{e}" break end if data.empty? dlog "Local closed: #{data.inspect}" break end dlog "Forwarding: #{data.length} to remote" begin remote.write data rescue StandardError => e dlog "remote closed: #{e}" break end end if r.include? remote # rubocop:disable Style/Next begin data = remote.recv target_read_size rescue StandardError => e dlog "remote closed: #{e}" break end if data.nil? || data.empty? dlog "Remote closed: #{data.inspect} #{err.inspect}" break end dlog "Forwarding: #{data.length} to local" begin local.write data rescue StandardError => e dlog "local closed: #{e}" break end end end dlog "Closing..." local.close remote.close end @server = server return server end end net-ssh-7.2.1/test/integration/playbook.yml000066400000000000000000000160101454036133000207440ustar00rootroot00000000000000--- - hosts: all become: true vars: no_rvm: no myuser: vagrant mygroup: vagrant homedir: /home/vagrant ruby_version: '2.7.7' ruby_versions_ssl1: - '2.6.10' - '2.7.7' - '3.0.6' ruby_versions: - '3.2.1' - '3.1.3' rvm_install_path: '/usr/local/rvm' foopwd: "$6$mhOzf/yapZwS$3RwDl4GfWZ5VcfcsHrK9xNNTxyzLOJBsmMttDNaegIbXxMahV86.v/5HsNtit16MEl0EFf5CSW8Dz2yXV.8GB0" foo2pwd: "$6$JiB7y7.M0yI$Abt.ZGIc4DwkRWeI6nKxzzPUZcux7hLRXSdpoKoZvswJz1SZyg5GRQWn9pGID0dgC6e4wFglfW6ev/qZoTqGk/" openssh_version: '9.3p1' openssh_with_ssl1: False pre_tasks: - name: get currently installed ruby version command: "{{rvm_install_path}}/rubies/ruby-{{ruby_version}}/bin/ruby -e 'puts \"#{RUBY_VERSION}\"'" register: current_ruby_version ignore_errors: true - name: check openssl version shell: "openssl version" ignore_errors: true register: openssl_version_query - name: Install openssl-1.1.1g block: - name: "Download openssl-1.1.1g sources" unarchive: src: https://www.openssl.org/source/openssl-1.1.1g.tar.gz dest: /tmp remote_src: True validate_certs: False - name: Install openssl 1.1 command: sh -c "./config --prefix=/opt/openssl-1.1.1g --openssldir=/opt/openssl-1.1.1g && make && sudo make install" args: chdir: /tmp/openssl-1.1.1g creates: /opt/openssl-1.1.1g/lib/libssl.so when: openssl_version_query.stdout.find('OpenSSL 3.') != -1 roles: - { role: rvm.ruby, tags: ruby, become: true, rvm1_user: 'root', rvm1_rubies: "{{ ruby_versions_ssl1 }}", rvm1_install_path: "{{rvm_install_path}}", rvm1_install_flags: '--auto-dotfiles', # Make sure RVM sets itself up so the user has access to it rvm1_ruby_install_flags: '--with-openssl-dir=/opt/openssl-1.1.1g', rvm1_gpg_key_server: 'hkp://keys.openpgp.org', when: "current_ruby_version.stdout|default() != ruby_version and not no_rvm and openssl_version_query.stdout.find('OpenSSL 3.') != -1" } tasks: - name: Install packages apt: pkg: - libssl-dev - build-essential - 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 - file: dest=/home/net_ssh_1/.ssh/ state=directory mode=0740 owner=net_ssh_1 - file: dest=/home/net_ssh_2/.ssh/ state=directory mode=0740 owner=net_ssh_2 - lineinfile: dest=/etc/sudoers.d/net_ssh_1 mode=0440 state=present create=yes line='net_ssh_1 ALL=(ALL) NOPASSWD:ALL' regexp=net_ssh_1 - 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://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-{{openssh_version}}.tar.gz dest: /tmp remote_src: True validate_certs: False - name: building and installing openssh {{openssh_version}} with OpenSSL 1 (used in forward test) command: sh -c "LD_LIBRARY_PATH=/opt/openssl-1.1.1g/lib ./configure --prefix=/opt/net-ssh-openssh --with-ssl-dir=/opt/openssl-1.1.1g --with-ldflags="-fcommon -L/opt/openssl-1.1.1g/lib" && make && sudo make install" args: chdir: /tmp/openssh-{{openssh_version}}/ creates: /opt/net-ssh-openssh when: openssh_with_ssl1 == True - name: building and installing openssh {{openssh_version}} (used in forward test) command: sh -c "./configure --prefix=/opt/net-ssh-openssh && make && sudo make install" args: chdir: /tmp/openssh-{{openssh_version}}/ creates: /opt/net-ssh-openssh when: openssh_with_ssl1 != True - name: drop installed openssh etc/ in favor of symlink file: state: absent path: /opt/net-ssh-openssh/etc - name: creating symlink between system etc/ssh/ and our etc/ file: src: /etc/ssh dest: /opt/net-ssh-openssh/etc state: link - command: ssh-keygen -A args: creates: /etc/ssh/ssh_host_ed25519_key notify: restart sshd - name: sshd debug lineinfile: dest='/etc/ssh/sshd_config' line='LogLevel DEBUG' regexp=LogLevel notify: restart sshd - name: sshd allow interactive lineinfile: dest='/etc/ssh/sshd_config' line='ChallengeResponseAuthentication yes' regexp='^ChallengeResponseAuthentication.+' notify: restart sshd - command: ssh-keygen -f /etc/ssh/users_ca -N '' args: creates: /etc/ssh/users_ca.pub notify: restart sshd - name: sshd cert auth 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=AllowTcpForwarding notify: restart sshd - name: sshd allow forward 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 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 force_apt_get: yes - name: Wait for locfile removal become: yes shell: while sudo fuser /var/lib/dpkg/lock >/dev/null 2>&1; do sleep 5; done; - name: Install packages apt: pkg: - pv - libgmp3-dev - git - libssl-dev state: present - copy: content='echo "cd /net-ssh ; rake integration-test"' dest=/etc/update-motd.d/99-net-ssh-tests mode=0755 - name: add user to rvm group so they can change gem wrappers user: name: "{{myuser}}" groups: rvm append: yes when: "not no_rvm" handlers: - name: restart sshd service: name=ssh state=restarted net-ssh-7.2.1/test/integration/test_agent.rb000066400000000000000000000112071454036133000210660ustar00rootroot00000000000000require_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".dup.force_encoding('BINARY') ED25519 = <<~EOF -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACDuVIPDUXcVkXOyNAaFsotbySHLNG/Gw6gc3j2k2zcRVAAAAKD6bG5++mxu fgAAAAtzc2gtZWQyNTUxOQAAACDuVIPDUXcVkXOyNAaFsotbySHLNG/Gw6gc3j2k2zcRVA AAAEAydU4FtZ9+5o5Y/m1aPNHFda37Fm0Us5FlUKx50tWw+e5Ug8NRdxWRc7I0BoWyi1vJ Ics0b8bDqBzePaTbNxFUAAAAGmJhcnRsZUBCYXJ0bGVzLU1hY0Jvb2stUHJvAQID -----END OPENSSH PRIVATE KEY----- EOF class TestAgent < NetSSHTest include IntegrationTestHelpers def setup @keys = [ OpenSSL::PKey::RSA.new(1024), OpenSSL::PKey::DSA.new(1024), OpenSSL::PKey::EC.generate("prime256v1") ] @keys << Net::SSH::Authentication::ED25519::PrivKey.read(ED25519, nil) if Net::SSH::Authentication::ED25519Loader::LOADED @keys += @keys.map do |key| cert = Net::SSH::Buffer.new(CERT).read_key cert.key = key cert.sign!(key) end end def test_ssh_agent_add_and_remove with_agent do agent = Net::SSH::Authentication::Agent.connect agent.remove_all_identities @keys.each do |key| agent.add_identity(key, "key") assert_equal [key.to_blob], agent.identities.map(&:to_blob) agent.remove_identity(key) assert agent.identities.empty? end end end def test_ssh_agent_add_and_remove_all_identities with_agent do agent = Net::SSH::Authentication::Agent.connect agent.remove_all_identities @keys.each do |key| agent.add_identity(key, "key") end assert_equal @keys.length, agent.identities.length agent.remove_all_identities assert agent.identities.empty? end end end net-ssh-7.2.1/test/integration/test_cert_host_auth.rb000066400000000000000000000065341454036133000230120ustar00rootroot00000000000000require_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 net-ssh-7.2.1/test/integration/test_cert_user_auth.rb000066400000000000000000000046551454036133000230150ustar00rootroot00000000000000require_relative 'common' require 'net/ssh' # environment: see playbook for full details. # 1. cert files: /etc/ssh/users_ca and /etc/ssh/users_ca.pub and # 2. /etc/ssh/sshd_config: TrustedUserCAKeys /etc/ssh/users_ca.pub unless ENV['NET_SSH_NO_ED25519'] class TestCertUserAuth < NetSSHTest include IntegrationTestHelpers def test_ed25519_with_implicit_cert Dir.mktmpdir do |dir| ssh_keygen "#{dir}/id_rsa_ed25519", "ed25519" sign_user_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") ret = Net::SSH.start("localhost", "net_ssh_1", keys: "#{dir}/id_rsa_ed25519") do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end def test_ed25519_with_explicit_cert Dir.mktmpdir do |dir| ssh_keygen "#{dir}/id_rsa_ed25519", "ed25519" sign_user_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") sh "mv #{dir}/id_rsa_ed25519-cert.pub #{dir}/cert" ret = Net::SSH.start("localhost", "net_ssh_1", keys: "#{dir}/id_rsa_ed25519", keycerts: "#{dir}/cert") do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end def test_ed25519_with_cert_in_agent Dir.mktmpdir do |dir| with_agent do ssh_keygen "#{dir}/id_rsa_ed25519", "ed25519", "pwd" sign_user_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") ssh_add("#{dir}/id_rsa_ed25519", "pwd") sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub #{dir}/id_rsa_ed25519-cert.pub" ret = Net::SSH.start("localhost", "net_ssh_1") do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end end def test_ed25519_with_key_in_agent_and_explicit_cert Dir.mktmpdir do |dir| with_agent do ssh_keygen "#{dir}/id_rsa_ed25519", "ed25519" # add key before signing cert ssh_add("#{dir}/id_rsa_ed25519", "pwd") sign_user_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") sh "rm -rf #{dir}/id_rsa_ed25519 #{dir}/id_rsa_ed25519.pub" ret = Net::SSH.start("localhost", "net_ssh_1", keycerts: "#{dir}/id_rsa_ed25519-cert.pub") do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end end end end net-ssh-7.2.1/test/integration/test_chacha20_poly1305_cipher.rb000066400000000000000000000027131454036133000242510ustar00rootroot00000000000000require_relative 'common' require 'fileutils' require 'tmpdir' require 'net/ssh' require 'timeout' unless ENV['NET_SSH_NO_RBNACL'] # 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 TestChacha20Poly1305Cipher < NetSSHTest include IntegrationTestHelpers def test_with_only_chacha20_cipher config_lines = File.read('/etc/ssh/sshd_config').split("\n") config_lines = config_lines.map do |line| if line =~ /^Ciphers/ "##{line}" else line end end config_lines.push("Ciphers chacha20-poly1305@openssh.com") Tempfile.open('empty_kh') do |f| f.close start_sshd_7_or_later(config: config_lines, debug: true) do |_pid, port| Timeout.timeout(4) do # We have our own sshd, give it a chance to come up before # listening. ret = Net::SSH.start("localhost", "net_ssh_1", encryption: "chacha20-poly1305@openssh.com", password: 'foopwd', port: port, user_known_hosts_file: [f.path], verbose: :debug) do |ssh| # assert_equal ssh.transport.algorithms.kex, "curve25519-sha256" ssh.exec! "echo 'foo'" end assert_equal "foo\n", ret rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH sleep 0.25 retry end end end end end end net-ssh-7.2.1/test/integration/test_channel.rb000066400000000000000000000077231454036133000214100ustar00rootroot00000000000000require_relative 'common' require 'net/ssh/buffer' require 'net/ssh' require 'net/ssh/proxy/command' require 'timeout' require 'tempfile' class TestChannel < NetSSHTest include IntegrationTestHelpers def localhost 'localhost' end def user 'net_ssh_1' end def ssh_start_params(options = {}) [localhost, user, { keys: @key_id_rsa }.merge(options)] end def setup_ssh_env(&block) tmpdir do |dir| @key_id_rsa = "#{dir}/id_rsa" ssh_keygen @key_id_rsa, "rsa" set_authorized_key(user, "#{@key_id_rsa}.pub") yield end end def ssh_exec(ssh, command, channel_success_handler, &block) 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) end channel.on_extended_data do |ch2, _type, data| yield(ch2, :stderr, data) end end end end def test_transport_close_before_channel_close_should_raise setup_ssh_env do proxy = Net::SSH::Proxy::Command.new("/bin/nc localhost 22") res = nil Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh| channel_success_handler = lambda do sleep(0.1) system("killall /bin/nc") end channel = ssh_exec(ssh, "echo Begin ; sleep 100 ; echo End", channel_success_handler) do |ch, _type, data| ch[:result] ||= String.new ch[:result] << data end assert_raises(IOError) { channel.wait } res = channel[:result] assert_equal(res, "Begin\n") end assert_equal(res, "Begin\n") end end def test_transport_close_after_channel_close_should_not_raise setup_ssh_env do proxy = Net::SSH::Proxy::Command.new("/bin/nc localhost 22") res = nil Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh| channel_success_handler = lambda do sleep(0.1) system("killall /bin/nc") end channel = ssh_exec(ssh, "echo Hello!", channel_success_handler) do |ch, _type, data| ch[:result] ||= "".dup ch[:result] << data end channel.wait res = channel[:result] assert_equal(res, "Hello!\n") end assert_equal(res, "Hello!\n") end end def test_transport_close_should_remote_close_channels setup_ssh_env do Net::SSH.start(*ssh_start_params) do |ssh| channel = ssh.open_channel do ssh.transport.socket.close end remote_closed = nil begin channel.wait rescue StandardError remote_closed = channel.remote_closed? end assert_equal remote_closed, true end end end def test_channel_should_set_environment_variables_on_remote setup_ssh_env do 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 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 end end net-ssh-7.2.1/test/integration/test_curve25519sha256.rb000066400000000000000000000025671454036133000224640ustar00rootroot00000000000000require_relative 'common' require 'fileutils' require 'tmpdir' require 'net/ssh' require 'timeout' unless ENV['NET_SSH_NO_ED25519'] # 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 TestCurve25519Sha256Keys < NetSSHTest include IntegrationTestHelpers def test_with_only_curve_kex config_lines = File.read('/etc/ssh/sshd_config').split("\n") config_lines = config_lines.map do |line| if line =~ /^KexAlgorithms/ "##{line}" else line end end config_lines.push("KexAlgorithms curve25519-sha256") Tempfile.open('empty_kh') do |f| f.close start_sshd_7_or_later(config: config_lines) do |_pid, port| Timeout.timeout(4) do # We have our own sshd, give it a chance to come up before # listening. ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd', port: port, user_known_hosts_file: [f.path]) do |ssh| assert_equal ssh.transport.algorithms.kex, "curve25519-sha256" ssh.exec! "echo 'foo'" end assert_equal "foo\n", ret rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH sleep 0.25 retry end end end end end end net-ssh-7.2.1/test/integration/test_ed25519_pkeys.rb000066400000000000000000000050631454036133000222040ustar00rootroot00000000000000require_relative 'common' require 'fileutils' require 'tmpdir' require 'net/ssh' unless ENV['NET_SSH_NO_ED25519'] # 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 TestED25519PKeys < NetSSHTest include IntegrationTestHelpers def test_in_file_no_password Dir.mktmpdir do |dir| ssh_keygen "#{dir}/id_rsa_ed25519", "ed25519" set_authorized_key('net_ssh_1', "#{dir}/id_rsa_ed25519.pub") ret = Net::SSH.start("localhost", "net_ssh_1", { keys: "#{dir}/id_rsa_ed25519" }) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end def test_ssh_agent Dir.mktmpdir do |dir| with_agent do ssh_keygen "#{dir}/id_rsa_ed25519", "ed25519" 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 assert_equal "hello from:net_ssh_1\n", ret end end end def test_in_file_with_password Dir.mktmpdir do |dir| ssh_keygen "#{dir}/id_rsa_ed25519", "ed25519" 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| 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| if (line =~ /^HostKey /) && line !~ /ed25519/ "##{line}" else line end end Tempfile.open('empty_kh') do |f| f.close with_sshd_config(config_lines.join("\n")) do ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd', user_known_hosts_file: [f.path]) do |ssh| ssh.exec! "echo 'foo'" end assert_equal "foo\n", ret end end end end end net-ssh-7.2.1/test/integration/test_encoding.rb000066400000000000000000000013151454036133000215550ustar00rootroot00000000000000require_relative 'common' require 'fileutils' require 'tmpdir' require 'net/ssh' class TestEncoding < NetSSHTest def test_unicode_character ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd') do |ssh| ssh.exec! "echo \"hello from:$USER\" \u2603" end assert_equal ret, "hello from:net_ssh_1 \u2603\n" end def test_long_command_with_unicode_in_it string = "eeeeeeeeeeeeeeeeeeeeeeeeeewwowowowìeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" command = "echo \"#{string}\"" ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd') do |ssh| ssh.exec! command end assert_equal ret, "#{string}\n" end end net-ssh-7.2.1/test/integration/test_exec.rb000066400000000000000000000010261454036133000207120ustar00rootroot00000000000000require_relative 'common' require 'net/ssh' class TestExec < NetSSHTest include IntegrationTestHelpers def test_error_exitstatus ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd') do |ssh| ssh.exec! "exit 42" end assert_equal "", ret assert_equal 42, ret.exitstatus end def test_ok_exitstatus ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd') do |ssh| ssh.exec! "echo 'foo'" end assert_equal "foo\n", ret assert_equal 0, ret.exitstatus end end net-ssh-7.2.1/test/integration/test_forward.rb000066400000000000000000000475541454036133000214520ustar00rootroot00000000000000# $ ruby -Ilib -Itest -rrubygems test/manual/test_forward.rb # Tests for the following patch: # # http://github.com/net-ssh/net-ssh/tree/portfwfix # # It fixes 3 issues, regarding closing forwarded ports: # # 1.) if client closes a forwarded connection, but the server is reading, net-ssh terminates with IOError socket closed. # 2.) if client force closes (RST) a forwarded connection, but server is reading, net-ssh terminates with # 3.) if server closes the sending side, the on_eof is not handled. # # More info: # # http://net-ssh.lighthouseapp.com/projects/36253/tickets/7 require_relative 'common' require 'net/ssh/buffer' require 'net/ssh' require 'net/ssh/proxy/command' require 'timeout' require 'tempfile' class ForwardTestBase < NetSSHTest include IntegrationTestHelpers def localhost 'localhost' end def user 'net_ssh_1' end def ssh_start_params(options = {}) [localhost, user, { keys: @key_id_rsa }.merge(options)] end def setup_ssh_env(&block) tmpdir do |dir| @key_id_rsa = "#{dir}/id_rsa" ssh_keygen @key_id_rsa, "rsa" set_authorized_key(user, "#{@key_id_rsa}.pub") yield end end def start_server_sending_lot_of_data(exceptions) server = TCPServer.open(0) Thread.start do loop do Thread.start(server.accept) do |client| 10000.times do |i| client.puts "item#{i}" end client.close rescue StandardError exceptions << $! raise end end end return server end end class TestForward < ForwardTestBase def start_server_closing_soon(exceptions = nil) server = TCPServer.open(0) Thread.start do loop do Thread.start(server.accept) do |client| client.recv(1024) client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii")) client.close rescue StandardError exceptions << $! raise end end end return server end def test_in_file_no_password setup_ssh_env do ret = Net::SSH.start(*ssh_start_params) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end def test_local_ephemeral_port_should_work_correctly setup_ssh_env do session = Net::SSH.start(*ssh_start_params) assert_nothing_raised do assigned_port = session.forward.local(0, localhost, 22) assert_not_nil assigned_port assert_operator assigned_port, :>, 0 end end end def test_remote_ephemeral_port_should_work_correctly setup_ssh_env do session = Net::SSH.start(*ssh_start_params) assert_nothing_raised do session.forward.remote(22, localhost, 0, localhost) session.loop { session.forward.active_remotes.length <= 0 } assigned_port = session.forward.active_remotes.first[0] assert_not_nil assigned_port assert_operator assigned_port, :>, 0 end end end def test_remote_callback_should_fire setup_ssh_env do session = Net::SSH.start(*ssh_start_params) assert_nothing_raised do got_port = nil session.forward.remote(22, localhost, 0, localhost) do |port| got_port = port end session.loop { session.forward.active_remotes.length <= 0 } assert_operator session.forward.active_remote_destinations.length, :==, 1 assert_operator session.forward.active_remote_destinations.keys.first, :==, [22, localhost] assert_operator session.forward.active_remote_destinations.values.first, :==, [got_port, localhost] assert_operator session.forward.active_remotes.first, :==, [got_port, localhost] assigned_port = session.forward.active_remotes.first[0] assert_operator got_port, :==, assigned_port assert_not_nil assigned_port assert_operator assigned_port, :>, 0 end end end def test_remote_callback_should_fire_on_error_and_still_throw_exception setup_ssh_env do session = Net::SSH.start(*ssh_start_params) assert_nothing_raised do session.forward.remote(22, localhost, 22, localhost) do |port| assert_operator port, :==, :error end end assert_raises(Net::SSH::Exception) do session.loop { true } end end end def test_remote_callback_should_fire_on_error_but_not_throw_exception_if_asked_not_to setup_ssh_env do session = Net::SSH.start(*ssh_start_params) assert_nothing_raised do got_port = nil session.forward.remote(22, localhost, 22, localhost) do |port| assert_operator port, :==, :error got_port = port :no_exception end session.loop { !got_port } assert_operator got_port, :==, :error assert_operator session.forward.active_remotes.length, :==, 0 end end end def test_loop_should_not_abort_when_local_side_of_forward_is_closed setup_ssh_env do session = Net::SSH.start(*ssh_start_params) server_exc = Queue.new server = start_server_sending_lot_of_data(server_exc) remote_port = server.addr[1] local_port = 0 # request ephemeral port session.forward.local(local_port, localhost, remote_port) client_done = Queue.new Thread.start do 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? end end def test_loop_should_not_abort_when_local_side_of_forward_is_reset setup_ssh_env do session = Net::SSH.start(*ssh_start_params) server_exc = Queue.new server = start_server_sending_lot_of_data(server_exc) remote_port = server.addr[1] local_port = 0 # request ephemeral port session.forward.local(local_port, localhost, remote_port) client_done = Queue.new Thread.start do 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? end end def test_loop_should_not_abort_when_server_side_of_forward_is_closed setup_ssh_env do session = Net::SSH.start(*ssh_start_params) server = start_server_closing_soon remote_port = server.addr[1] local_port = 0 # request ephemeral port session.forward.local(local_port, localhost, remote_port) client_done = Queue.new Thread.start do 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 end def start_server(server = nil, &block) server ||= TCPServer.open(0) Thread.start do loop do Thread.start(server.accept) do |client| yield(client) end end end return server end def test_client_close_should_be_handled_remote setup_ssh_env do message = "This is a small message!" * 1000 session = Net::SSH.start(*ssh_start_params) server_done = Queue.new server = start_server do |client| 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(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 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? } assert_equal message, server_done.pop end end end class TCPProxy def initialize @sockets = [] end attr_reader :sockets def open(host, port, connection_options = nil) socket = TCPSocket.new(host, port) @sockets << socket socket end def close_all sockets.each(&:close) end end def test_transport_close_should_closes_channels_with_tcps setup_ssh_env do server = start_server do |client| client.puts "Hello" sleep(100) client.puts "Hallo" end proxy = TCPProxy.new session = Net::SSH.start(*ssh_start_params(proxy: proxy)) remote_port = server.addr[1] local_port = session.forward.local(0, localhost, remote_port) # read on forwarded port client_done = Queue.new Thread.start do 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 session.loop(0.1) { true } rescue IOError, Errno::EBADF server_error = $! # puts "Error: #{$!} #{$!.backtrace.join("\n")}" end begin Timeout.timeout(5) do assert_equal true, client_done.pop end rescue StandardError puts "Server error: #{server_error.class} #{server_error} bt:#{server_error.backtrace.join("\n")}" raise end end end def todo_test_transport_close_should_closes_channels_with_proxy setup_ssh_env do server = start_server do |client| client.puts "Hello" sleep(100) client.puts "Hallo" end proxy = Net::SSH::Proxy::Command.new("/bin/nc localhost 22") session = Net::SSH.start(*ssh_start_params(proxy: proxy)) remote_port = server.addr[1] local_port = session.forward.local(0, localhost, remote_port) # read on forwarded port client_done = Queue.new Thread.start do 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 session.loop(0.1) { true } rescue EOFError begin session.close rescue StandardError end # puts "Error: #{$!} #{$!.backtrace.join("\n")}" end assert_equal true, client_done.pop end end end def test_client_close_should_be_handled setup_ssh_env do message = "This is a small message!" * 1000 session = Net::SSH.start(*ssh_start_params) server_done = Queue.new server = start_server do |client| 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 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? } assert_equal message, server_done.pop end end end 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(verbose: :debug)) server = start_server do |client| client.write message client.close end client_done = Queue.new got_remote_port = Queue.new local_port = server.addr[1] 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 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? } assert_equal message, client_done.pop end end end def test_server_eof_should_be_handled setup_ssh_env do message = "This is a small message!" session = Net::SSH.start(*ssh_start_params) server = start_server do |client| client.write message client.close end client_done = Queue.new remote_port = server.addr[1] local_port = session.forward.local(0, localhost, remote_port) Thread.start do 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? } assert_equal message, client_done.pop end end end def _run_reading_client(client_done, local_port) Thread.start do client = TCPSocket.new(localhost, local_port) data = client.read(4096) client.close client_done << data rescue StandardError client_done << $! end end def test_cannot_open_connection_should_allow_further_connections_on_different_forward setup_ssh_env do session = Net::SSH.start(*ssh_start_params) server = start_server do |client| _data = client.write "hello" client.close end # Forward to a non existing port non_existing_port = 1234 local_port = session.forward.local(0, localhost, non_existing_port) # should return connection refused client_done = Queue.new _run_reading_client(client_done, local_port) Timeout.timeout(5) do session.loop(0.1) { client_done.empty? } end assert_nil client_done.pop assert client_done.empty? # Forward to existing port remote_port = server.addr[1] local_port = session.forward.local(0, localhost, remote_port) _run_reading_client(client_done, local_port) Timeout.timeout(5) do session.loop(0.1) { client_done.empty? } end assert_equal "hello", client_done.pop assert client_done.empty? end end def test_cannot_open_connection_should_allow_further_connections_on_same setup_ssh_env do session = Net::SSH.start(*ssh_start_params) server = TCPServer.open(0) # Forward to a non existing port remote_port = server.addr[1] server.close local_port = session.forward.local(0, localhost, remote_port) # should return connection refused client_done = Queue.new _run_reading_client(client_done, local_port) Timeout.timeout(5) do session.loop(0.1) { client_done.empty? } end assert_nil client_done.pop assert client_done.empty? # start server server = TCPServer.open(remote_port) server = start_server(server) do |client| _data = client.write "hello" client.close end _run_reading_client(client_done, local_port) Timeout.timeout(5) do session.loop(0.1) { client_done.empty? } end assert_equal "hello", client_done.pop assert client_done.empty? end end def test_cancel_local setup_ssh_env do session = Net::SSH.start(*ssh_start_params) server = start_server(server) do |client| _data = client.write "hello" client.close end remote_port = server.addr[1] local_port = session.forward.local(0, localhost, remote_port) # run client client_done = Queue.new _run_reading_client(client_done, local_port) Timeout.timeout(5) do session.loop(0.1) { client_done.empty? } end assert_equal "hello", client_done.pop # cancel session.forward.cancel_local(local_port) session.loop(0.1) assert_equal({}, session.channels) end end end class TestForwardOnUnixSockets < ForwardTestBase if defined?(UNIXServer) && defined?(UNIXSocket) def create_local_socket(&blk) tempfile = Tempfile.new("net_ssh_forward_test") path = tempfile.path tempfile.delete yield UNIXServer.open(path) File.delete(path) end def test_forward_local_unix_socket_to_remote_port setup_ssh_env do session = Net::SSH.start(*ssh_start_params) server_exc = Queue.new server = start_server_sending_lot_of_data(server_exc) remote_port = server.addr[1] client_data = nil create_local_socket do |local_socket| session.forward.local(local_socket, localhost, remote_port) client_done = Queue.new Thread.start do client = UNIXSocket.new(local_socket.path) client_data = client.recv(1024) client.close sleep(0.2) ensure client_done << true end begin session.loop(0.1) { client_done.empty? } rescue Errno::EPIPE end end assert_not_nil(client_data, "client should have received data") assert(client_data.match(/item\d/), 'client should have received the string item') end end end def test_forward_local_unix_socket_to_remote_socket setup_ssh_env do start_sshd_7_or_later do |_pid, port| 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 create_local_socket do |remote_socket| # Make sure sshd can 'rw'. FileUtils.chmod(0o666, remote_socket.path) local_socket_path_file = Tempfile.new("net_ssh_forward_test_local") local_socket_path = local_socket_path_file.path session.forward.local_socket(local_socket_path, remote_socket.path) assert_equal([local_socket_path], session.forward.active_local_sockets) client_done = Queue.new Thread.start do begin # Ruby >= 2.4 Thread.current.report_on_exception = true rescue NoMethodError # Ruby <= 2.3 Thread.current.abort_on_exception = true end begin client = UNIXSocket.new(local_socket_path) client.puts "hi" assert_equal("hi", client.gets.strip) client.puts "bye" client_done << true ensure client.close end end Thread.start do begin # Ruby >= 2.4 Thread.current.report_on_exception = true rescue NoMethodError # Ruby <= 2.3 Thread.current.abort_on_exception = true end begin sock = remote_socket.accept assert_equal("hi", sock.gets.strip) sock.puts "hi" assert_equal("bye", sock.gets.strip) ensure sock.close end end session.loop(0.1) { client_done.empty? } session.forward.cancel_local_socket(local_socket_path) assert_equal([], session.forward.active_local_sockets) end end end end end net-ssh-7.2.1/test/integration/test_handshake_timeout.rb000066400000000000000000000015241454036133000234650ustar00rootroot00000000000000require_relative 'common' require 'net/ssh' class TestHandshakeTimeout < NetSSHTest include IntegrationTestHelpers def with_non_responding_server(&block) port = "4444" pipe = IO.popen("/bin/nc -l -k -p #{port}") begin yield(port) ensure Process.kill("TERM", pipe.pid) end end def nc_port_open?(port) Socket.tcp("localhost", port, connect_timeout: 1) { true } rescue false # rubocop:disable Style/RescueModifier end def test_error_exitstatus with_non_responding_server do |port| sleep(0.1) until nc_port_open?(port.to_i) assert_raises(Net::SSH::ConnectionTimeout, 'timeout during server version negotiating') do Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd', port: port, timeout: 1) do |ssh| ssh.exec! "exit 42" end end end end end net-ssh-7.2.1/test/integration/test_hmac_etm.rb000066400000000000000000000041721454036133000215500ustar00rootroot00000000000000require_relative 'common' require_relative 'mitm_server' 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 TestHMacEtm < NetSSHTest include IntegrationTestHelpers variants = { etm256: "hmac-sha2-256-etm@openssh.com", etm512: "hmac-sha2-512-etm@openssh.com" } def config_with_macs(macs) config_lines = File.read('/etc/ssh/sshd_config').split("\n") config_lines = config_lines.map do |line| if line =~ /^MACs/ "##{line}" else line end end config_lines.push("MACs #{macs}") end variants.each do |key, variant| define_method "test_with_only_hmac_etm#{key}" do start_sshd_7_or_later(config: config_with_macs(variant)) do |_pid, port| Timeout.timeout(4) do # 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 end variants.each do |key, variant| define_method "test_hmac_through_proxy_#{key}" do mitm = MitmServer.new('localhost', 22) mitm.local_read_size = 2 * 1024 mitm.target_read_size = 19 mitm.run # host, port = 'localhost', 22 host = mitm.host port = mitm.port hmac_options = { hmac: variant } ssh = Net::SSH.start(host, "net_ssh_1", hmac_options.merge(port: port, password: 'foopwd')) (1...20).each do |i| ret = ssh.exec!("echo \"#{'f' * i}\"") assert_includes ret, 'f' * i end ssh.close end end end net-ssh-7.2.1/test/integration/test_http_proxy.rb000066400000000000000000000036001454036133000222060ustar00rootroot00000000000000require_relative 'common' require 'net/ssh/buffer' require 'net/ssh' require 'timeout' require 'tempfile' require 'net/ssh/proxy/command' require 'net/ssh/proxy/http' require 'net/ssh/proxy/https' require 'webrick' require 'webrick/httpproxy' require 'webrick/https' class TestHTTPProxy < NetSSHTest include IntegrationTestHelpers def localhost 'localhost' end def user 'net_ssh_1' end def ssh_start_params(options) [localhost, user, { keys: @key_id_rsa }.merge(options)] end def setup_ssh_env(&block) tmpdir do |dir| @key_id_rsa = "#{dir}/id_rsa" ssh_keygen @key_id_rsa, "rsa" set_authorized_key(user, "#{@key_id_rsa}.pub") yield end end def with_http_proxy_server(&block) proxy = WEBrick::HTTPProxyServer.new Port: 0 Thread.start { proxy.start } begin yield(proxy) ensure proxy.shutdown end end def test_smoke setup_ssh_env do with_http_proxy_server do |http_proxy| msg = 'echo123' ret = Net::SSH.start(*ssh_start_params(proxy: Net::SSH::Proxy::HTTP.new('localhost', http_proxy.config[:Port]))) do |ssh| ssh.exec! "echo \"$USER:#{msg}\"" end assert_equal "net_ssh_1:#{msg}\n", ret end end end def with_http_tls_proxy_server(&block) cert_name = [%w[CN localhost]] proxy = WEBrick::HTTPProxyServer.new Port: 0, SSLEnable: true, SSLCertName: cert_name Thread.start { proxy.start } begin yield(proxy) ensure proxy.shutdown end end def test_smoke_tls setup_ssh_env do with_http_tls_proxy_server do |http_proxy| msg = 'echo123' ret = Net::SSH.start(*ssh_start_params(proxy: Net::SSH::Proxy::HTTPS.new('localhost', http_proxy.config[:Port]))) do |ssh| ssh.exec! "echo \"$USER:#{msg}\"" end assert_equal "net_ssh_1:#{msg}\n", ret end end end end net-ssh-7.2.1/test/integration/test_id_rsa_keys.rb000066400000000000000000000050531454036133000222660ustar00rootroot00000000000000require_relative 'common' require 'fileutils' require 'tmpdir' require 'net/ssh' # 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 TestIDRSAPKeys < NetSSHTest include IntegrationTestHelpers def test_in_file_no_password tmpdir do |dir| ssh_keygen "#{dir}/id_rsa", "rsa" set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") ret = Net::SSH.start("localhost", "net_ssh_1", { keys: "#{dir}/id_rsa" }) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end def test_ssh_agent tmpdir do |dir| with_agent do ssh_keygen "#{dir}/id_rsa", "rsa", 'pwd123' set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") ssh_add("#{dir}/id_rsa", "pwd123") ret = Net::SSH.start("localhost", "net_ssh_1") do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end end def test_ssh_agent_ignores_if_already_in_agent tmpdir do |dir| with_agent do ssh_keygen "#{dir}/id_rsa", "rsa", 'pwd123' set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") ssh_add("#{dir}/id_rsa", "pwd123") ret = Net::SSH.start("localhost", "net_ssh_1", keys: ["#{dir}/id_rsa"]) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end end def test_in_file_with_password tmpdir do |dir| ssh_keygen "#{dir}/id_rsa", "rsa", 'pwd12' set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") ret = Net::SSH.start("localhost", "net_ssh_1", { keys: "#{dir}/id_rsa", passphrase: 'pwd12' }) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal "hello from:net_ssh_1\n", ret end end def test_asks_for_passwords_when_read_from_memory tmpdir do |dir| ssh_keygen "#{dir}/id_rsa", "rsa", 'pwd12' set_authorized_key('net_ssh_1', "#{dir}/id_rsa.pub") private_key = File.read("#{dir}/id_rsa") options = { keys: [], key_data: [private_key] } prompt = MockPrompt.new sha = Digest::SHA256.digest(private_key) prompt.expects(:_ask).with('Enter passphrase for :', { type: 'private_key', filename: '', sha: sha }, false).returns('pwd12') Net::SSH.start("localhost", "net_ssh_1", options.merge(password_prompt: prompt)) do |ssh| ssh.exec! 'whoami' end end end end net-ssh-7.2.1/test/integration/test_key_exchange.rb000066400000000000000000000011021454036133000224130ustar00rootroot00000000000000require_relative 'common' require 'net/ssh' class TestKeyExchange < NetSSHTest include IntegrationTestHelpers Net::SSH::Transport::Algorithms::DEFAULT_ALGORITHMS[:kex].each do |kex| define_method("test_kex_#{kex}") do skip "diffie-hellman-group14-sha1 not supported on newer sshd" if kex == "diffie-hellman-group14-sha1" && sshd_8_or_later? ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd', kex: kex) do |ssh| ssh.exec! "echo 'foo'" end assert_equal "foo\n", ret assert_equal 0, ret.exitstatus end end end net-ssh-7.2.1/test/integration/test_password.rb000066400000000000000000000044551454036133000216410ustar00rootroot00000000000000require_relative 'common' require 'net/ssh' class TestPassword < NetSSHTest include IntegrationTestHelpers def test_with_password_parameter ret = Net::SSH.start("localhost", "net_ssh_1", password: 'foopwd') do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal ret, "hello from:net_ssh_1\n" end def test_keyboard_interactive_with_good_password skip "TODO keyboard-interactive on newer sshd" if sshd_8_or_later? ps = Object.new pt = Object.new pt.expects(:start).with(type: 'keyboard-interactive', name: '', instruction: '').returns(ps) ps.expects(:ask).with('password: ', false).returns("foopwd") ps.expects(:success) ret = Net::SSH.start("localhost", "net_ssh_1", auth_methods: ['keyboard-interactive'], password_prompt: pt) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal ret, "hello from:net_ssh_1\n" end def test_keyboard_interactive_with_one_failed_attempt skip "TODO keyboard-interactive on newer sshd" if sshd_8_or_later? ps = Object.new pt = Object.new pt.expects(:start).with(type: 'keyboard-interactive', name: '', instruction: '').returns(ps) ps.expects(:ask).twice.with('Password: ', false).returns("badpwd").then.with('Password: ', false).returns("foopwd") ps.expects(:success) ret = Net::SSH.start("localhost", "net_ssh_1", auth_methods: ['keyboard-interactive'], password_prompt: pt) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal ret, "hello from:net_ssh_1\n" end def test_password_with_good_password ps = Object.new pt = Object.new pt.expects(:start).with(type: 'password', user: 'net_ssh_1', host: 'localhost').returns(ps) ps.expects(:ask).with("net_ssh_1@localhost's password:", false).returns("foopwd") ps.expects(:success) ret = Net::SSH.start("localhost", "net_ssh_1", auth_methods: ['password'], password_prompt: pt) do |ssh| ssh.exec! 'echo "hello from:$USER"' end assert_equal ret, "hello from:net_ssh_1\n" end def test_bad_password_should_throw_auth_invalid assert_raises Net::SSH::AuthenticationFailed do Net::SSH.start("localhost", "net_ssh_1", password: "wrong_password", auth_methods: ['password'], non_interactive: true) do |ssh| ssh.exec! 'echo "hello from:$USER"' end end end end net-ssh-7.2.1/test/integration/test_proxy.rb000066400000000000000000000100671454036133000211540ustar00rootroot00000000000000require_relative 'common' require 'net/ssh/buffer' require 'net/ssh' require 'timeout' require 'tempfile' require 'fileutils' require 'net/ssh/proxy/command' require 'net/ssh/proxy/jump' class TestProxy < NetSSHTest include IntegrationTestHelpers def localhost 'localhost' end def user 'net_ssh_1' end def ssh_start_params(options) [localhost, user, { keys: @key_id_rsa }.merge(options)] end def setup_ssh_env(&block) tmpdir do |dir| @key_id_rsa = "#{dir}/id_rsa" ssh_keygen @key_id_rsa, "rsa" set_authorized_key(user, "#{@key_id_rsa}.pub") yield end end def setup_gateway(&block) gwhost = "gateway.netssh" gwuser = 'net_ssh_2' tmpdir do |dir| @gwkey_id_rsa = "#{dir}/id_rsa" ssh_keygen @gwkey_id_rsa, "rsa" set_authorized_key(gwuser, "#{@gwkey_id_rsa}.pub") config = "Host #{gwhost} IdentityFile #{@gwkey_id_rsa} StrictHostKeyChecking no " FileUtils.mkdir_p File.expand_path("~/.ssh") my_config = File.expand_path("~/.ssh/config") File.open(my_config, 'w') { |file| file.write(config) } begin FileUtils.chmod(0o600, my_config) yield gwuser, gwhost ensure FileUtils.rm(my_config) end end end def test_smoke setup_ssh_env do proxy = Net::SSH::Proxy::Command.new("/bin/nc localhost 22") msg = 'echo123' ret = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh| ssh.exec! "echo \"$USER:#{msg}\"" end assert_equal "net_ssh_1:#{msg}\n", ret end end def with_spurious_write_wakeup_emulate(rate = 99, &block) orig_io_select = IO.method(:select) count = 0 IO.singleton_class.send(:define_method, :select) do |*params| count += 1 if (count % rate != 0) if params && params[1] && !params[1].empty? return [[], params[1], []] end end orig_io_select.call(*params) end begin yield ensure IO.singleton_class.send(:define_method, :select, &orig_io_select) end end def test_with_rate_limit_and_spurious_wakeup system("sudo sh -c 'echo 4096 > /proc/sys/fs/pipe-max-size'") begin setup_ssh_env do proxy = Net::SSH::Proxy::Command.new("/usr/bin/pv --rate-limit 100k | /bin/nc localhost 22") large_msg = 'echo123' * 30000 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_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 end :ok end assert_equal :ok, ok end ensure system("sudo sh -c 'echo 1048576 > /proc/sys/fs/pipe-max-size'") end end def test_proxy_jump_through_localhost setup_ssh_env do setup_gateway do |gwuser, gwhost| proxy = Net::SSH::Proxy::Jump.new("#{gwuser}@#{gwhost}") output = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh| ssh.exec! "echo \"$USER:echo123\"" end assert_equal "net_ssh_1:echo123\n", output end end end class DbgProxy attr_reader :io def initialize(origin) @origin = origin end def open(*args) @io = @origin.open(*args) @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')) msg = 'echo123' assert_raises Errno::EPIPE, Net::SSH::Proxy::ConnectError do Net::SSH.start(*ssh_start_params(proxy: proxy, password: 'bad', non_interactive: true, auth_methods: ['password'], verbose: :debug)) do |ssh| ssh.exec! "echo \"$USER:#{msg}\"" end end assert proxy.io.nil? || proxy.io.closed?, "Process #proxy.io.pid not closed" end end end net-ssh-7.2.1/test/known_hosts/000077500000000000000000000000001454036133000164345ustar00rootroot00000000000000net-ssh-7.2.1/test/known_hosts/github000066400000000000000000000006071454036133000176440ustar00rootroot00000000000000github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==net-ssh-7.2.1/test/known_hosts/github_hash000066400000000000000000000006721454036133000206510ustar00rootroot00000000000000|1|eKp+6E0rZ3lONgsIziurXEnaIik=|rcQB/rlJMUquUyFta64KugPjX4o= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== net-ssh-7.2.1/test/known_hosts/gitlab000066400000000000000000000002731454036133000176230ustar00rootroot00000000000000gitlab.com,35.231.145.151 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= net-ssh-7.2.1/test/known_hosts/known_hosts_ignore000066400000000000000000000006611454036133000223010ustar00rootroot00000000000000# commented entry followed by empty line github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==net-ssh-7.2.1/test/known_hosts/misc000066400000000000000000000006121454036133000173110ustar00rootroot00000000000000*.git???.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== net-ssh-7.2.1/test/manual/000077500000000000000000000000001454036133000153355ustar00rootroot00000000000000net-ssh-7.2.1/test/manual/test_pageant.rb000066400000000000000000000017631454036133000203470ustar00rootroot00000000000000# # Tests for communication capability with Pageant (or KeeAgent) # process, to include the case where it is running in different UAC # context. # # To run: # - Ensure that Pageant is running (not as administrator). # - Open two command prompts, one as an administrator and one limited # (normal). # - Within each, from the root net/ssh project directory, execute: # ruby -Ilib -Itest -rrubygems test/manual/test_pageant.rb # require_relative '../common' require 'net/ssh/authentication/agent' module Authentication class TestPageapnt < NetSSHTest def test_agent_should_be_able_to_negotiate begin agent.negotiate! rescue Net::SSH::Authentication::AgentNotAvailable puts "Test failing connect now!.... :#{$!}" sleep 1800 raise end end private def agent(auto = :connect) @agent ||= begin agent = Net::SSH::Authentication::Agent.new agent.connect! if auto == :connect agent end end end end net-ssh-7.2.1/test/openssl3.conf000066400000000000000000000006421454036133000164770ustar00rootroot00000000000000openssl_conf = openssl_init [openssl_init] ssl_conf = ssl_sect providers = provider_sect [provider_sect] default = default_sect legacy = legacy_sect [default_sect] activate = 1 [legacy_sect] activate = 1 [ssl_sect] system_default = system_default_sect [system_default_sect] CipherString = DEFAULT@SECLEVEL=0 # system_default = system_default_sect # # [system_default_sect] # Options = UnsafeLegacyRenegotiation net-ssh-7.2.1/test/proxy/000077500000000000000000000000001454036133000152415ustar00rootroot00000000000000net-ssh-7.2.1/test/proxy/test_command.rb000066400000000000000000000013511454036133000202430ustar00rootroot00000000000000require_relative '../common' require 'net/ssh' require 'net/ssh/proxy/command' module NetSSH class TestProxy < NetSSHTest unless Gem.win_platform? def test_process_is_stopped_on_timeout 10.times do Process.waitpid(0, Process::WNOHANG) rescue true # rubocop:disable Style/RescueModifier end proxy = Net::SSH::Proxy::Command.new('sleep 10') proxy.timeout = 2 host = 'foo' port = 1 assert_raises Net::SSH::Proxy::ConnectError do proxy.open(host, port) end sleep 0.2 assert_raises Errno::ECHILD do Process.waitpid(0, Process::WNOHANG) skip "This test is fragile TODO revise" end end end end end net-ssh-7.2.1/test/start/000077500000000000000000000000001454036133000152155ustar00rootroot00000000000000net-ssh-7.2.1/test/start/test_connection.rb000066400000000000000000000032441454036133000207430ustar00rootroot00000000000000require_relative '../common' require 'net/ssh' module NetSSH class TestConnection < NetSSHTest attr_reader :connection_session def setup authentication_session = mock('authentication_session') authentication_session.stubs(:authenticate).returns(true) Net::SSH::Authentication::Session.stubs(:new).returns(authentication_session) @transport_session = mock('transport_session') Net::SSH::Transport::Session.stubs(:new).returns(@transport_session) @connection_session = mock('connection_session') Net::SSH::Connection::Session.expects(new: connection_session) end def test_close_connection_on_exception @connection_session.expects(:closed?).returns(false) @connection_session.expects(:close).once begin Net::SSH.start('localhost', 'testuser') { raise "error" } rescue RuntimeError # We aren't interested in the exception end end def test_close_connection_on_exception_only_if_still_open conn_open = states('conn').starts_as(true) @connection_session.expects(:close).then(conn_open.is(false)).once @connection_session.expects(:closed?).when(conn_open.is(false)).returns(true) begin Net::SSH.start('localhost', 'testuser') do |ssh| ssh.close raise "error" end rescue RuntimeError # We aren't interested in the exception end end def test_return_value_is_returned @connection_session.expects(:closed?).returns(false) @connection_session.expects(:close).once val = 1 retval = Net::SSH.start('localhost', 'testuser') { val } assert_equal(val, retval) end end end net-ssh-7.2.1/test/start/test_options.rb000066400000000000000000000074151454036133000203030ustar00rootroot00000000000000require_relative '../common' require 'net/ssh' module NetSSH class TestStartOptions < NetSSHTest def setup authentication_session = mock('authentication_session') authentication_session.stubs(:authenticate).returns(true) Net::SSH::Authentication::Session.stubs(:new).returns(authentication_session) Net::SSH::Transport::Session.stubs(:new).returns(mock('transport_session')) Net::SSH::Connection::Session.stubs(:new).returns(mock('connection_session')) end def test_start_should_accept_keepalive_option assert_nothing_raised do options = { keepalive: true } Net::SSH.start('localhost', 'testuser', options) end end def test_start_should_accept_keepalive_interval_option assert_nothing_raised do options = { keepalive_interval: 10 } Net::SSH.start('localhost', 'testuser', options) end end def test_start_should_accept_send_env_option assert_nothing_raised do options = { send_env: [/^LC_.*$/, "LANG"] } Net::SSH.start('localhost', 'testuser', options) end end def test_start_should_accept_set_env_option assert_nothing_raised do options = { set_env: { foo: 'bar', baz: 'whale will' } } Net::SSH.start('localhost', 'testuser', options) end end def test_start_should_accept_number_of_password_prompts_option assert_nothing_raised do options = { number_of_password_prompts: 2 } Net::SSH.start('localhost', 'testuser', options) end end def test_start_should_accept_append_all_supported_algorithms_option assert_nothing_raised do options = { append_all_supported_algorithms: true } Net::SSH.start('localhost', 'testuser', options) end end def test_start_should_accept_non_interactive_option assert_nothing_raised do options = { non_interactive: true } Net::SSH.start('localhost', 'testuser', options) end end def test_start_should_accept_pubkey_algorithms_option assert_nothing_raised do options = { pubkey_algorithms: %w[ssh-rsa] } Net::SSH.start('localhost', 'testuser', options) end end def test_start_should_accept_remote_user_option assert_nothing_raised do options = { remote_user: 'foo' } Net::SSH.start('localhost', 'testuser', options) end end def test_constructor_should_reject_options_set_to_nil Kernel.expects(:warn).with { |message| message =~ /remote_user/ }.once options = { remote_user: nil } Net::SSH.start('localhost', 'testuser', options) end def test_constructor_should_reject_options_set_to_array_of_nil Kernel.expects(:warn).with { |message| message =~ /keys/ }.once ENV.delete('no-such-env-variable') Net::SSH.start('localhost', 'testuser', keys: [ENV['no-such-env-variable']]) end def test_constructor_should_not_reject_nil_password_options_for_cap_v2_compatibility assert_nothing_raised do options = { password: nil } Net::SSH.start('localhost', 'testuser', options) end end def test_constructor_should_not_reject_nil_passpharse assert_nothing_raised do options = { passphrase: nil } Net::SSH.start('localhost', 'testuser', options) end end def test_constructor_should_reject_invalid_options assert_raises(ArgumentError) do options = { some_invalid_option: "some setting" } Net::SSH.start('localhost', 'testuser', options) end end def test_constructor_should_set_default_options options = { logger: nil, password_prompt: nil } Net::SSH.start('localhost', 'testuser', options) assert !options[:logger].nil? assert !options[:password_prompt].nil? end end end net-ssh-7.2.1/test/start/test_transport.rb000066400000000000000000000014531454036133000206400ustar00rootroot00000000000000require 'common' require 'net/ssh' 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 # Authentication should fail, as it is part of the context end end end end net-ssh-7.2.1/test/start/test_user_nil.rb000066400000000000000000000016261454036133000204260ustar00rootroot00000000000000require 'common' require 'net/ssh' module NetSSH class TestStartUserNil < NetSSHTest def setup @authentication_session = mock('authentication_session') Net::SSH::Authentication::Session.stubs(:new).returns(@authentication_session) Net::SSH::Transport::Session.stubs(:new).returns(mock('transport_session')) Net::SSH::Connection::Session.stubs(:new).returns(mock('connection_session')) end def test_start_should_accept_nil_user @authentication_session.stubs(:authenticate).returns(true) assert_nothing_raised do Net::SSH.start('localhost') end 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) assert_nothing_raised do Net::SSH.start('localhost', nil, config: false) end end end end net-ssh-7.2.1/test/test/000077500000000000000000000000001454036133000150375ustar00rootroot00000000000000net-ssh-7.2.1/test/test/test_test.rb000066400000000000000000000034411454036133000174040ustar00rootroot00000000000000require_relative '../common' require 'net/ssh/test' class TestNetSSHTest < NetSSHTest include Net::SSH::Test def test_example story do |session| channel = session.opens_channel channel.sends_exec "ls" channel.gets_data "result of ls" channel.gets_close channel.sends_close end assert_scripted do result = nil connection.open_channel do |ch| ch.exec("ls") do |_success| ch.on_data { |_c, data| result = data } ch.on_close(&:close) end end connection.loop assert_equal "result of ls", result end end def test_pty story do |session| channel = session.opens_channel channel.sends_request_pty session.sends_channel_request(channel, "shell", false, nil, true) channel.sends_exec "ls" channel.gets_data "result of ls" channel.gets_close channel.sends_close end assert_scripted do result = nil connection.open_channel do |ch| ch.request_pty ch.send_channel_request("shell") ch.exec("ls") do |_success| ch.on_data { |_c, data| result = data } ch.on_close(&:close) end end connection.loop assert_equal "result of ls", result end end def test_custom Packet.register_channel_request("custom", %i[string string long]) story do |session| channel = session.opens_channel session.sends_channel_request(channel, "custom", false, ["hello", "hello", 42], true) channel.gets_close channel.sends_close end assert_scripted do connection.open_channel do |ch| ch.send_channel_request("custom", :string, "hello", :string, "hello", :long, 42) ch.on_close(&:close) end connection.loop end end end net-ssh-7.2.1/test/test_all.rb000066400000000000000000000011651454036133000162170ustar00rootroot00000000000000$: << '.' Dir.chdir(File.dirname(__FILE__)) do test_files = Dir['**/test_*.rb'] - ['test_all.rb'] # prevent circular require test_files -= Dir['integration/test_*.rb'] unless ENV['NET_SSH_RUN_INTEGRATION_TESTS'] test_files -= Dir['win_integration/test_*.rb'] unless ENV['NET_SSH_RUN_WIN_INTEGRATION_TESTS'] test_files -= Dir['test/test_*.rb'] test_files = test_files.reject { |f| f =~ /^manual/ } test_files = test_files.select { |f| f =~ Regexp.new(ENV['ONLY']) } if ENV['ONLY'] test_files = test_files.reject { |f| f =~ Regexp.new(ENV['EXCEPT']) } if ENV['EXCEPT'] test_files.each { |file| require(file) } end net-ssh-7.2.1/test/test_buffer.rb000066400000000000000000000424601454036133000167230ustar00rootroot00000000000000# encoding: ASCII-8BIT # frozen_string_literal: false require_relative 'common' require 'net/ssh/buffer' class TestBuffer < NetSSHTest def test_constructor_should_initialize_buffer_to_empty_by_default buffer = new assert buffer.empty? assert_equal 0, buffer.position end def test_constructor_with_string_should_initialize_buffer_to_the_string buffer = new("hello") assert !buffer.empty? assert_equal "hello", buffer.to_s assert_equal 0, buffer.position end def test_from_should_require_an_even_number_of_arguments assert_raises(ArgumentError) { Net::SSH::Buffer.from("this") } end def test_from_should_build_new_buffer_from_definition buffer = Net::SSH::Buffer.from(:byte, 1, :long, 2, :int64, 3, :string, "4", :bool, true, :bool, false, :bignum, OpenSSL::BN.new("1234567890", 10), :raw, "something") assert_equal "\1\0\0\0\2\0\0\0\0\0\0\0\3\0\0\0\0014\1\0\000\000\000\004I\226\002\322something", buffer.to_s end def test_from_should_build_new_buffer_that_includes_utf8_string_128_characters length = 128 # letter A has a 1 byte UTF8 representation buffer = Net::SSH::Buffer.from(:long, 2, :string, 'A' * length) # long of 2 + length 128 as network endian + 128 A's expected = "\x00\x00\x00\x02" + [length].pack('N*') + ("\x41" * length) assert_equal expected, buffer.to_s end def test_from_should_build_new_buffer_with_frozen_strings foo = 'foo'.freeze buffer = Net::SSH::Buffer.from(:string, foo) assert_equal "\0\0\0\3foo", buffer.to_s end def test_from_should_build_new_buffer_with_utf8_frozen_strings foo = "\u2603".freeze buffer = Net::SSH::Buffer.from(:string, foo) assert_equal "\0\0\0\3\u2603".force_encoding('BINARY'), buffer.to_s end def test_from_should_not_change_regular_paramaters foo = "\u2603" buffer = Net::SSH::Buffer.from(:string, foo) assert_equal "\0\0\0\3\u2603".force_encoding('BINARY'), buffer.to_s assert_equal foo.encoding.to_s, "UTF-8" end def test_from_with_array_argument_should_write_multiple_of_the_given_type buffer = Net::SSH::Buffer.from(:byte, [1, 2, 3, 4, 5]) assert_equal "\1\2\3\4\5", buffer.to_s end def test_from_should_measure_bytesize_of_utf_8_string_correctly buffer = Net::SSH::Buffer.from(:string, "\u2603") # Snowman is 3 bytes assert_equal "\0\0\0\3\u2603".force_encoding('BINARY'), buffer.to_s end def test_read_without_argument_should_read_to_end buffer = new("hello world") assert_equal "hello world", buffer.read assert buffer.eof? assert_equal 11, buffer.position end def test_read_with_argument_that_is_less_than_length_should_read_that_many_bytes buffer = new "hello world" assert_equal "hello", buffer.read(5) assert_equal 5, buffer.position end def test_read_with_argument_that_is_more_than_length_should_read_no_more_than_length buffer = new "hello world" assert_equal "hello world", buffer.read(500) assert_equal 11, buffer.position end def test_read_at_eof_should_return_empty_string buffer = new "hello" buffer.position = 5 assert_equal "", buffer.read end def test_consume_without_argument_should_resize_buffer_to_start_at_position buffer = new "hello world" buffer.read(5) assert_equal 5, buffer.position assert_equal 11, buffer.length buffer.consume! assert_equal 0, buffer.position assert_equal 6, buffer.length assert_equal " world", buffer.to_s end def test_consume_with_argument_should_resize_buffer_starting_at_n buffer = new "hello world" assert_equal 0, buffer.position buffer.consume!(5) assert_equal 0, buffer.position assert_equal 6, buffer.length assert_equal " world", buffer.to_s end def test_read_bang_should_read_and_consume_and_return_read_portion buffer = new "hello world" assert_equal "hello", buffer.read!(5) assert_equal 0, buffer.position assert_equal 6, buffer.length assert_equal " world", buffer.to_s end def test_available_should_return_length_after_position_to_end_of_string buffer = new "hello world" buffer.read(5) assert_equal 6, buffer.available end def test_clear_bang_should_reset_buffer_contents_and_counters buffer = new "hello world" buffer.read(5) buffer.clear! assert_equal 0, buffer.length assert_equal 0, buffer.position assert_equal "", buffer.to_s end def test_append_should_append_argument_without_changing_position_and_should_return_self buffer = new "hello world" buffer.read(5) buffer.append(" again") assert_equal 5, buffer.position assert_equal 12, buffer.available assert_equal 17, buffer.length assert_equal "hello world again", buffer.to_s end def test_remainder_as_buffer_should_return_a_new_buffer_filled_with_the_text_after_the_current_position buffer = new "hello world" buffer.read(6) b2 = buffer.remainder_as_buffer assert_equal 6, buffer.position assert_equal 0, b2.position assert_equal "world", b2.to_s end def test_read_int64_should_return_8_byte_integer buffer = new "\xff\xee\xdd\xcc\xbb\xaa\x99\x88" assert_equal 0xffeeddccbbaa9988, buffer.read_int64 assert_equal 8, buffer.position end def test_read_int64_should_return_nil_on_partial_read buffer = new "\0\0\0\0\0\0\0" assert_nil buffer.read_int64 assert buffer.eof? end def test_read_long_should_return_4_byte_integer buffer = new "\xff\xee\xdd\xcc\xbb\xaa\x99\x88" assert_equal 0xffeeddcc, buffer.read_long assert_equal 4, buffer.position end def test_read_long_should_return_nil_on_partial_read buffer = new "\0\0\0" assert_nil buffer.read_long assert buffer.eof? end def test_read_byte_should_return_single_byte_integer buffer = new "\xfe\xdc" assert_equal 0xfe, buffer.read_byte assert_equal 1, buffer.position end def test_read_byte_should_return_nil_at_eof assert_nil new.read_byte end def test_read_string_should_read_length_and_data_from_buffer buffer = new "\0\0\0\x0bhello world" assert_equal "hello world", buffer.read_string end def test_read_string_should_return_nil_if_4_byte_length_cannot_be_read assert_nil new("\0\1").read_string end def test_read_bool_should_return_true_if_non_zero_byte_is_read buffer = new "\1\2\3\4\5\6" 6.times { assert_equal true, buffer.read_bool } end def test_read_bool_should_return_false_if_zero_byte_is_read buffer = new "\0" assert_equal false, buffer.read_bool end def test_read_bool_should_return_nil_at_eof assert_nil new.read_bool end def test_read_bignum_should_read_openssl_formatted_bignum buffer = new("\000\000\000\004I\226\002\322") assert_equal OpenSSL::BN.new("1234567890", 10), buffer.read_bignum end def test_read_bignum_should_return_nil_if_length_cannot_be_read assert_nil new("\0\1\2").read_bignum end def test_read_key_blob_should_read_dsa_keys random_dss { |buffer| buffer.read_keyblob("ssh-dss") } end def test_read_key_blob_should_read_rsa_keys random_rsa { |buffer| buffer.read_keyblob("ssh-rsa") } end def test_read_key_should_read_dsa_key_type_and_keyblob random_dss do |buffer| b2 = Net::SSH::Buffer.from(:string, "ssh-dss", :raw, buffer) b2.read_key end end def test_read_key_should_read_rsa_key_type_and_keyblob random_rsa do |buffer| b2 = Net::SSH::Buffer.from(:string, "ssh-rsa", :raw, buffer) b2.read_key end end def test_read_buffer_should_read_a_string_and_return_it_wrapped_in_a_buffer buffer = new("\0\0\0\x0bhello world") b2 = buffer.read_buffer assert_equal 0, b2.position assert_equal 11, b2.length assert_equal "hello world", b2.read end def test_read_to_should_return_nil_if_pattern_does_not_exist_in_buffer buffer = new("one two three") assert_nil buffer.read_to("\n") end def test_read_to_should_grok_string_patterns buffer = new("one two three") assert_equal "one tw", buffer.read_to("tw") assert_equal 6, buffer.position end def test_read_to_should_grok_regex_patterns buffer = new("one two three") assert_equal "one tw", buffer.read_to(/tw/) assert_equal 6, buffer.position end def test_read_to_should_grok_fixnum_patterns buffer = new("one two three") assert_equal "one tw", buffer.read_to(?w) assert_equal 6, buffer.position end def test_reset_bang_should_reset_position_to_0 buffer = new("hello world") buffer.read(5) assert_equal 5, buffer.position buffer.reset! assert_equal 0, buffer.position end def test_write_should_write_arguments_directly_to_end_buffer buffer = new("start") buffer.write "hello", " ", "world" assert_equal "starthello world", buffer.to_s assert_equal 0, buffer.position end def test_write_int64_should_write_arguments_as_8_byte_integers_to_end_of_buffer buffer = new("start") buffer.write_int64 0xffeeddccbbaa9988, 0x7766554433221100 assert_equal "start\xff\xee\xdd\xcc\xbb\xaa\x99\x88\x77\x66\x55\x44\x33\x22\x11\x00", buffer.to_s end def test_write_long_should_write_arguments_as_4_byte_integers_to_end_of_buffer buffer = new("start") buffer.write_long 0xffeeddcc, 0xbbaa9988 assert_equal "start\xff\xee\xdd\xcc\xbb\xaa\x99\x88", buffer.to_s end def test_write_byte_should_write_arguments_as_1_byte_integers_to_end_of_buffer buffer = new("start") buffer.write_byte 1, 2, 3, 4, 5 assert_equal "start\1\2\3\4\5", buffer.to_s end def test_write_bool_should_write_arguments_as_1_byte_boolean_values_to_end_of_buffer buffer = new("start") buffer.write_bool nil, false, true, 1, Object.new assert_equal "start\0\0\1\1\1", buffer.to_s end def test_write_bignum_should_write_arguments_as_ssh_formatted_bignum_values_to_end_of_buffer buffer = new("start") buffer.write_bignum OpenSSL::BN.new('1234567890', 10) assert_equal "start\000\000\000\004I\226\002\322", buffer.to_s end def test_write_dss_key_should_write_argument_to_end_of_buffer buffer = new("start") p = 0xffeeddccbbaa9988 q = 0x7766554433221100 g = 0xffddbb9977553311 pub_key = 0xeeccaa8866442200 asn1 = OpenSSL::ASN1::Sequence.new( [ OpenSSL::ASN1::Sequence.new( [ OpenSSL::ASN1::ObjectId.new('DSA'), OpenSSL::ASN1::Sequence.new( [ OpenSSL::ASN1::Integer.new(p), OpenSSL::ASN1::Integer.new(q), OpenSSL::ASN1::Integer.new(g) ] ) ] ), OpenSSL::ASN1::BitString.new(OpenSSL::ASN1::Integer.new(pub_key).to_der) ] ) key = OpenSSL::PKey::DSA.new(asn1.to_der) buffer.write_key(key) assert_equal "start\0\0\0\7ssh-dss\0\0\0\011\0\xff\xee\xdd\xcc\xbb\xaa\x99\x88\0\0\0\010\x77\x66\x55\x44\x33\x22\x11\x00\0\0\0\011\0\xff\xdd\xbb\x99\x77\x55\x33\x11\0\0\0\011\0\xee\xcc\xaa\x88\x66\x44\x22\x00", buffer.to_s end def test_write_rsa_key_should_write_argument_to_end_of_buffer buffer = new("start") n = 0x7766554433221100 e = 0xffeeddccbbaa9988 asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Integer(n), OpenSSL::ASN1::Integer(e) ] ) key = OpenSSL::PKey::RSA.new(asn1.to_der) buffer.write_key(key) assert_equal "start\0\0\0\7ssh-rsa\0\0\0\011\0\xff\xee\xdd\xcc\xbb\xaa\x99\x88\0\0\0\010\x77\x66\x55\x44\x33\x22\x11\x00", buffer.to_s end def test_read_key_blob_should_read_ecdsa_sha2_nistp256_keys random_ecdsa_sha2_nistp256 do |buffer| buffer.read_keyblob('ecdsa-sha2-nistp256') end end def test_read_key_blob_should_read_ecdsa_sha2_nistp384_keys random_ecdsa_sha2_nistp384 do |buffer| buffer.read_keyblob('ecdsa-sha2-nistp384') end end def test_read_key_blob_should_read_ecdsa_sha2_nistp521_keys random_ecdsa_sha2_nistp521 do |buffer| buffer.read_keyblob('ecdsa-sha2-nistp521') end end def test_read_key_should_read_ecdsa_sha2_nistp256_key_type_and_keyblob random_ecdsa_sha2_nistp256 do |buffer| b2 = Net::SSH::Buffer.from(:string, 'ecdsa-sha2-nistp256', :raw, buffer) b2.read_key end end def test_read_key_should_read_ecdsa_sha2_nistp384_key_type_and_keyblob random_ecdsa_sha2_nistp384 do |buffer| b2 = Net::SSH::Buffer.from(:string, 'ecdsa-sha2-nistp384', :raw, buffer) b2.read_key end end def test_read_key_should_read_ecdsa_sha2_nistp521_key_type_and_keyblob random_ecdsa_sha2_nistp521 do |buffer| b2 = Net::SSH::Buffer.from(:string, 'ecdsa-sha2-nistp521', :raw, buffer) b2.read_key end end def test_write_ecdsa_sha2_nistp256_key_should_write_argument_to_end_of_buffer buffer = new('start') key = OpenSSL::PKey::EC.new("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIISGj5vAJCWt2KPI8NwaWVDSNLl2vbRxDIOkY+n6O0VVoAoGCCqGSM49\nAwEHoUQDQgAEnKbs0yEogTKT4QRu8T9nb2svl2mEWXb6g224oCpD2o6TYNXNw54H\nmWkdCv+kFCqSlfSi5fqFhrXdfEY6zSzQYQ==\n-----END EC PRIVATE KEY-----\n") buffer.write_key(key) assert_equal "start\000\000\000\023ecdsa-sha2-nistp256\000\000\000\bnistp256\000\000\000A\004\234\246\354\323!(\2012\223\341\004n\361?gok/\227i\204Yv\372\203m\270\240*C\332\216\223`\325\315\303\236\a\231i\035\n\377\244\024*\222\225\364\242\345\372\205\206\265\335|F:\315,\320a", buffer.to_s end def test_write_ecdsa_sha2_nistp384_key_should_write_argument_to_end_of_buffer buffer = new('start') key = OpenSSL::PKey::EC.new("-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDBAfxJpzhsR7O+wMol6BcDgualR8rJBvYegUDYbBUrDnPzDx2/gD1lZ\nnwG1FuD2s9igBwYFK4EEACKhZANiAATsfiU4Kxyvvj1DdvFYsdDnZIT7loRlan9I\n8geCWPPl6x7NFRP+awrnTaarMgieGqxG8IQaIA0SsDOICfbDBkuatRi0S1Et/in4\nZwVEZvO81Ro5YSrjuUDAsytnI6OXS28=\n-----END EC PRIVATE KEY-----\n") buffer.write_key(key) assert_equal "start\000\000\000\023ecdsa-sha2-nistp384\000\000\000\bnistp384\000\000\000a\004\354~%8+\034\257\276=Cv\361X\261\320\347d\204\373\226\204ej\177H\362\a\202X\363\345\353\036\315\025\023\376k\n\347M\246\2532\b\236\032\254F\360\204\032 \r\022\2603\210\t\366\303\006K\232\265\030\264KQ-\376)\370g\005Df\363\274\325\0329a*\343\271@\300\263+g#\243\227Ko", buffer.to_s end def test_write_ecdsa_sha2_nistp521_key_should_write_argument_to_end_of_buffer buffer = new('start') key = OpenSSL::PKey::EC.new("-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEGhnQF/SFo4Vym88HnCfc6BR8WwYqDh9wNTPeqzR8auxIpp0GKQlCG2\nuHzyteJX5/YalV8empYhEzNmNLNn8x7j0aAHBgUrgQQAI6GBiQOBhgAEAYygOgV9\nVI8UyLQ3BDlv+rb3es+ufrIcj++cqcc9QcmRn237NiWRr/1NKy2AKijsEdACtZXo\nxPC0x9Vs9ieC2oR+ANOBubcxPl2giDnBYm8ywAmmlXsP5ByAM17k97CzW5O+Z/uO\nbxGUzzhoXTNcjqpAckhRVKdnh6FL/rKelT0tBYi+\n-----END EC PRIVATE KEY-----\n") buffer.write_key(key) assert_equal "start\000\000\000\023ecdsa-sha2-nistp521\000\000\000\bnistp521\000\000\000\205\004\001\214\240:\005}T\217\024\310\2647\0049o\372\266\367z\317\256~\262\034\217\357\234\251\307=A\311\221\237m\3736%\221\257\375M+-\200*(\354\021\320\002\265\225\350\304\360\264\307\325l\366'\202\332\204~\000\323\201\271\2671>]\240\2109\301bo2\300\t\246\225{\017\344\034\2003^\344\367\260\263[\223\276g\373\216o\021\224\3178h]3\\\216\252@rHQT\247g\207\241K\376\262\236\225=-\005\210\276", buffer.to_s end def test_write_ec_point_should_write_argument_to_end_of_buffer buffer = new('start') key = OpenSSL::PKey::EC::Point.new(OpenSSL::PKey::EC.new(OpenSSL::PKey::EC::CurveNameAlias['nistp256']).group) buffer.write_key(key) assert_equal "start\x00\x00\x00\x13ecdsa-sha2-nistp256\x00\x00\x00\bnistp256\x00\x00\x00\x00", buffer.to_s end private def random_rsa n1 = OpenSSL::BN.new(rand(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF).to_s, 10) n2 = OpenSSL::BN.new(rand(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF).to_s, 10) buffer = Net::SSH::Buffer.from(:bignum, [n1, n2]) key = yield(buffer) assert_equal "ssh-rsa", key.ssh_type assert_equal n1, key.e assert_equal n2, key.n end def random_dss n1 = OpenSSL::BN.new(rand(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF).to_s, 10) n2 = OpenSSL::BN.new(rand(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF).to_s, 10) n3 = OpenSSL::BN.new(rand(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF).to_s, 10) n4 = OpenSSL::BN.new(rand(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF).to_s, 10) buffer = Net::SSH::Buffer.from(:bignum, [n1, n2, n3, n4]) key = yield(buffer) assert_equal "ssh-dss", key.ssh_type assert_equal n1, key.p assert_equal n2, key.q assert_equal n3, key.g assert_equal n4, key.pub_key end def random_ecdsa_sha2_nistp256 k = OpenSSL::PKey::EC.generate('prime256v1') buffer = Net::SSH::Buffer.from(:string, 'nistp256', :string, k.public_key.to_bn.to_s(2)) key = yield(buffer) assert_equal 'ecdsa-sha2-nistp256', key.ssh_type assert_equal k.public_key, key.public_key end def random_ecdsa_sha2_nistp384 k = OpenSSL::PKey::EC.generate('secp384r1') buffer = Net::SSH::Buffer.from(:string, 'nistp384', :string, k.public_key.to_bn.to_s(2)) key = yield(buffer) assert_equal 'ecdsa-sha2-nistp384', key.ssh_type assert_equal k.public_key, key.public_key end def random_ecdsa_sha2_nistp521 k = OpenSSL::PKey::EC.generate('secp521r1') buffer = Net::SSH::Buffer.from(:string, 'nistp521', :string, k.public_key.to_bn.to_s(2)) key = yield(buffer) assert_equal 'ecdsa-sha2-nistp521', key.ssh_type assert_equal k.public_key, key.public_key end def new(*args) Net::SSH::Buffer.new(*args) end end net-ssh-7.2.1/test/test_buffered_io.rb000066400000000000000000000034751454036133000177260ustar00rootroot00000000000000require 'common' require 'net/ssh/buffered_io' class TestBufferedIo < NetSSHTest def test_fill_should_pull_from_underlying_io io.expects(:recv).with(8192).returns("here is some data") assert_equal 17, io.fill assert_equal 17, io.available assert_equal "here is some data", io.read_available(20) end def test_enqueue_should_not_write_to_underlying_io assert !io.pending_write? io.expects(:send).never io.enqueue("here is some data") assert io.pending_write? end def test_send_pending_should_not_fail_when_no_writes_are_pending assert !io.pending_write? io.expects(:send).never assert_nothing_raised { io.send_pending } end def test_send_pending_with_pending_writes_should_write_to_underlying_io io.enqueue("here is some data") io.expects(:send).with("here is some data", 0).returns(17) assert io.pending_write? assert_nothing_raised { io.send_pending } assert !io.pending_write? end def test_wait_for_pending_sends_should_write_only_once_if_all_can_be_written_at_once io.enqueue("here is some data") io.expects(:send).with("here is some data", 0).returns(17) assert io.pending_write? assert_nothing_raised { io.wait_for_pending_sends } assert !io.pending_write? end def test_wait_for_pending_sends_should_write_multiple_times_if_first_write_was_partial io.enqueue("here is some data") io.expects(:send).with("here is some data", 0).returns(10) io.expects(:send).with("me data", 0).returns(4) io.expects(:send).with("ata", 0).returns(3) IO.expects(:select).times(2).with(nil, [io]).returns([[], [io]]) assert_nothing_raised { io.wait_for_pending_sends } assert !io.pending_write? end private def io @io ||= begin io = mock("io") io.extend(Net::SSH::BufferedIo) io end end end net-ssh-7.2.1/test/test_config.rb000066400000000000000000000443171454036133000167220ustar00rootroot00000000000000require_relative './common' require 'net/ssh/config' require 'net/ssh' require 'pathname' require 'tempfile' class TestConfig < NetSSHTest def test_home_should_be_absolute_path assert Pathname.new(ENV['HOME']).absolute? end def test_load_for_non_existant_file_should_return_empty_hash bogus_file = File.expand_path("/bogus/file") File.expects(:readable?).with(bogus_file).returns(false) assert_equal({}, Net::SSH::Config.load(bogus_file, "host.name")) end def test_load_should_expand_path expected = File.expand_path("~/.ssh/config") File.expects(:readable?).with(expected).returns(false) Net::SSH::Config.load("~/.ssh/config", "host.name") end def test_load_with_exact_host_match_should_load_that_section config = Net::SSH::Config.load(config(:exact_match), "test.host") assert config['compression'] assert config['forwardagent'] assert_equal 1234, config['port'] end def test_load_with_wild_card_matches_should_load_all_matches_with_first_match_taking_precedence config = Net::SSH::Config.load(config(:wild_cards), "test.host") assert_equal 1234, config['port'] assert !config['compression'] assert config['forwardagent'] assert_equal %w(~/.ssh/id_dsa), config['identityfile'] assert !config.key?('rekeylimit') end def test_load_with_wild_card_and_negative_pattern_does_not_match config = Net::SSH::Config.load(config(:negative_match), "test.host") assert_equal 9876, config['port'] assert !config.key?('compression') end def test_load_with_pattern_does_match data = ' Host test.* Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "test.host") assert_equal 1234, config['port'] end end def test_check_host_ip data = ' Host * CheckHostIP no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, 'foo') assert_equal false, config['checkhostip'] config = Net::SSH::Config.for("foo", [f]) assert_equal false, config[:check_host_ip] end end def test_load_with_regex_chars data = ' Host | Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "test.host") assert_nil config['port'] config = Net::SSH::Config.load(f, "|") assert_equal 1234, config['port'] end end def test_for_should_load_all_files_and_translate_to_net_ssh_options config = Net::SSH::Config.for("test.host", [config(:exact_match), config(:wild_cards)]) assert_equal 1234, config[:port] assert config[:compression] assert config[:forward_agent] assert_equal %w(~/.ssh/id_dsa), config[:keys] assert_equal %w(~/.ssh/id_rsa-my-cert.pub ~/.ssh/cert.pub), config[:keycerts] assert !config.key?(:rekey_limit) end def test_load_with_no_host config = Net::SSH::Config.load(config(:nohost), "test.host") assert_equal %w(~/.ssh/id_dsa ~/.ssh/id_rsa), config['identityfile'] assert_equal 1985, config['port'] end def test_load_with_multiple_hosts config = Net::SSH::Config.load(config(:multihost), "test.host") assert config['compression'] assert_equal '2G', config['rekeylimit'] assert_equal 1980, config['port'] end def test_load_with_multiple_hosts_and_config_should_match_for_both aconfig = Net::SSH::Config.load(config(:multihost), "test.host") bconfig = Net::SSH::Config.load(config(:multihost), "other.host") assert_equal aconfig['port'], bconfig['port'] assert_equal aconfig['compression'], bconfig['compression'] assert_equal aconfig['rekeylimit'], bconfig['rekeylimit'] end def test_load_should_parse_equal_sign_delimiters config = Net::SSH::Config.load(config(:eqsign), "test.test") assert config['compression'] assert_equal 1234, config['port'] end 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, '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"', 'numberofpasswordprompts' => '123', 'serveraliveinterval' => '2', 'serveralivecountmax' => '4', 'fingerprinthash' => 'MD5', 'userknownhostsfile' => '/dev/null', 'stricthostkeychecking' => false } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal %w(a b c), net_ssh[:encryption] assert_equal true, net_ssh[:compression] assert_equal 6, net_ssh[:compression_level] assert_equal 100, net_ssh[:timeout] assert_equal true, net_ssh[:forward_agent] assert_equal %w(hostbased keyboard-interactive none password publickey), net_ssh[:auth_methods].sort assert_equal %w(d e f), net_ssh[:host_key] assert_equal %w(g h i), net_ssh[:keys] assert_equal %w(j k l), net_ssh[:hmac] assert_equal %w(m n o), net_ssh[:keycerts] assert_equal 1234, net_ssh[:port] assert_equal 1024, net_ssh[:rekey_limit] assert_equal "127.0.0.1", net_ssh[:bind_address] assert_equal [/^LC_.*$/], net_ssh[:send_env] assert_equal Hash['foo' => 'bar', 'baz' => 'whale', 'cat' => 'black hole'], net_ssh[:set_env] assert_equal 123, net_ssh[:number_of_password_prompts] assert_equal 4, net_ssh[:keepalive_maxcount] assert_equal 2, net_ssh[:keepalive_interval] assert_equal 'MD5', net_ssh[:fingerprint_hash] assert_equal true, net_ssh[:keepalive] assert_equal '/dev/null', net_ssh[:user_known_hosts_file] assert_equal :never, net_ssh[:verify_host_key] end def test_translate_should_turn_on_host_key_verification open_ssh = { 'stricthostkeychecking' => true } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal :always, net_ssh[:verify_host_key] end def test_translate_should_accept_new_host_key open_ssh = { 'stricthostkeychecking' => 'accept-new' } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal :accept_new, net_ssh[:verify_host_key] end def test_translate_should_turn_off_authentication_methods open_ssh = { 'hostbasedauthentication' => false, 'passwordauthentication' => false, 'pubkeyauthentication' => false, 'challengeresponseauthentication' => false, 'kbdinteractiveauthentication' => false } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal %w(none), net_ssh[:auth_methods].sort end def test_translate_should_turn_on_authentication_methods open_ssh = { 'hostbasedauthentication' => true, 'passwordauthentication' => true, 'pubkeyauthentication' => true, 'challengeresponseauthentication' => true, 'kbdinteractiveauthentication' => true } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal %w(hostbased keyboard-interactive none password publickey), net_ssh[:auth_methods].sort end def test_translate_should_not_disable_keyboard_interactive_when_challange_or_keyboardinterective_is_on open_ssh = { 'kbdinteractiveauthentication' => false } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal %w(keyboard-interactive none password publickey), net_ssh[:auth_methods].sort open_ssh = { 'challengeresponseauthentication' => false } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal %w(keyboard-interactive none password publickey), net_ssh[:auth_methods].sort end def test_should_ddisable_keyboard_interactive_when_challeng_and_keyboardinteractive_is_off open_ssh = { 'challengeresponseauthentication' => false, 'kbdinteractiveauthentication' => false } net_ssh = Net::SSH::Config.translate(open_ssh) assert_equal %w(none password publickey), net_ssh[:auth_methods].sort end def test_for_should_turn_off_authentication_methods config = Net::SSH::Config.for("test.host", [config(:empty), config(:auth_off), config(:auth_on)]) assert_equal %w(none), config[:auth_methods].sort end def test_for_should_turn_on_authentication_methods config = Net::SSH::Config.for("test.host", [config(:empty), config(:auth_on), config(:auth_off)]) assert_equal %w(hostbased keyboard-interactive none password publickey), config[:auth_methods].sort end def test_configuration_for_when_HOME_is_null_should_not_raise with_home_env(nil) do with_restored_default_files do Net::SSH.configuration_for("test.host", true) end end end def test_config_for_when_HOME_is_null_should_not_raise with_home_env(nil) do with_restored_default_files do Net::SSH::Config.for("test.host") end end end def test_load_with_plus_sign_hosts config = Net::SSH::Config.load(config(:host_plus), "test.host") assert config['compression'] end def test_load_with_numeric_host config = Net::SSH::Config.load(config(:numeric_host), "1234") assert config['compression'] assert_equal '2G', config['rekeylimit'] assert_equal 1980, config['port'] end def test_load_wildcar_with_substitutes config = Net::SSH::Config.load(config(:substitutes), "toto") net_ssh = Net::SSH::Config.translate(config) assert_equal 'toto', net_ssh[:host_name] end def test_load_sufix_with_substitutes config = Net::SSH::Config.load(config(:substitutes), "test") net_ssh = Net::SSH::Config.translate(config) assert_equal 'test.sufix', net_ssh[:host_name] end def test_load_prefix_and_sufix_with_substitutes config = Net::SSH::Config.load(config(:substitutes), "1234") net_ssh = Net::SSH::Config.translate(config) assert_equal 'prefix.1234.sufix', net_ssh[:host_name] end def test_load_with_send_env config = Net::SSH::Config.load(config(:send_env), "1234") net_ssh = Net::SSH::Config.translate(config) assert_equal [/^GIT_.*$/, /^LANG$/, /^LC_.*$/], net_ssh[:send_env] end def test_load_with_set_env config = Net::SSH::Config.load(config(:set_env), '1234') net_ssh = Net::SSH::Config.translate(config) assert_equal Hash['foo' => 'bar', 'baz' => 'whale', 'cat' => 'black hole'], net_ssh[:set_env] end def test_load_with_remote_user config = Net::SSH::Config.load(config(:proxy_remote_user), "behind-proxy") net_ssh = Net::SSH::Config.translate(config) assert net_ssh[:proxy] end def test_load_with_proxy_jump config = Net::SSH::Config.load(config(:proxy_jump), "behind-proxy") net_ssh = Net::SSH::Config.translate(config) assert net_ssh[:proxy] end def test_load_with_identity_agent config = Net::SSH::Config.load(config(:identity_agent), "use-agent") net_ssh = Net::SSH::Config.translate(config) assert_equal "/path/to/auth.sock", net_ssh[:identity_agent] end def test_load_with_include_keyword config = Net::SSH::Config.load(config(:include), "xyz") net_ssh = Net::SSH::Config.translate(config) assert_equal 'example.com', net_ssh[:host_name] assert_equal 'foo', net_ssh[:user] assert_equal 2345, net_ssh[:port] assert_equal true, net_ssh[:compression] assert net_ssh[:keys_only] assert_equal %w(~/.ssh/id.pem ~/.ssh/id2.pem ~/.ssh/id3.pem), net_ssh[:keys] end def test_default_files_not_mutable original_default_files = Net::SSH::Config.default_files.clone default_files = Net::SSH::Config.default_files default_files.push('garbage') assert_equal(original_default_files, Net::SSH::Config.default_files) end def test_default_auth_methods_not_mutable original_default_auth_methods = Net::SSH::Config.default_auth_methods.clone default_auth_methods = Net::SSH::Config.default_auth_methods default_auth_methods.push('garbage') assert_equal(original_default_auth_methods, Net::SSH::Config.default_auth_methods) end def test_load_with_match_block config = Net::SSH::Config.load(config(:match), "test.host") net_ssh = Net::SSH::Config.translate(config) assert_equal true, net_ssh[:forward_agent] assert_equal true, net_ssh[:compression] assert_equal 22, net_ssh[:port] end def test_load_with_match_block_with_host data = ' Match Host foo Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar") assert_nil config['port'] config = Net::SSH::Config.load(f, "foo") assert_equal 1234, config['port'] end end def test_load_with_match_block_with_hosts data = ' Match Host foo,bar Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar") assert_equal 1234, config['port'] config = Net::SSH::Config.load(f, "foo") assert_equal 1234, config['port'] end end def test_load_with_match_block_with_hosts_wildcard data = ' Match Host foo,*.baz.com Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bbaz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar.baz.com") assert_equal 1234, config['port'] config = Net::SSH::Config.load(f, "foo") assert_equal 1234, config['port'] end end def test_load_with_match_block_with_multi_space_separated_hosts_condition # Extra tabs are thrown in between, for good measure data = ' Match host foo,*.baz.com Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bbaz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar.baz.com") assert_equal 1234, config['port'] config = Net::SSH::Config.load(f, "foo") assert_equal 1234, config['port'] end end def test_load_with_match_block_with_quoted_hosts_condition data = ' Match host "foo,*.baz.com" Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bbaz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar.baz.com") assert_equal 1234, config['port'] config = Net::SSH::Config.load(f, "foo") assert_equal 1234, config['port'] end end def test_load_with_match_block_with_equal_signed_hosts_condition data = ' Match host=foo,*.baz.com Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bbaz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar.baz.com") assert_equal 1234, config['port'] config = Net::SSH::Config.load(f, "foo") assert_equal 1234, config['port'] end end def test_load_with_match_block_with_quoted_equal_signed_hosts_condition data = ' Match host="foo,*.baz.com" Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bbaz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar.baz.com") assert_equal 1234, config['port'] config = Net::SSH::Config.load(f, "foo") assert_equal 1234, config['port'] end end def test_load_with_match_block_with_whitespace_separated_equal_signed_hosts_condition data = ' Match host = foo,*.baz.com Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bbaz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar.baz.com") assert_equal 1234, config['port'] config = Net::SSH::Config.load(f, "foo") assert_equal 1234, config['port'] end end def test_load_with_match_block_with_multi_equal_signed_hosts_condition data = ' Match host==foo,*.baz.com Port 1234 Compression no ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bbaz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar.baz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "foo") assert_nil config['port'] end end def test_load_with_multiple_hosts_criteria data = ' Match host *.baz.com host !bar.baz.com Port 1234 ' with_config_from_data data do |f| config = Net::SSH::Config.load(f, "bar2") assert_nil config['port'] config = Net::SSH::Config.load(f, "bbaz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "bar.baz.com") assert_nil config['port'] config = Net::SSH::Config.load(f, "meh.baz.com") assert_equal 1234, config['port'] end end def test_mix_of_proxy_command_and_proxy_jump %w(test.mix1 test.mix2).each do |host| config = Net::SSH::Config.for(host, [config(:proxy_command_proxy_jump_mix)]) proxy = config[:proxy] proxy.build_proxy_command_equivalent if proxy.is_a? Net::SSH::Proxy::Jump assert_equal 'ssh -W %h:%p jump2', config[:proxy].command_line_template end end private def with_home_env(value, &block) env_home_before = ENV['HOME'] begin ENV['HOME'] = value yield ensure ENV['HOME'] = env_home_before end end def config(name) "test/configs/#{name}" end def with_config_from_data(data, &block) Tempfile.open('config') do |f| f.write(data) f.close yield(f.path) end end end net-ssh-7.2.1/test/test_key_factory.rb000066400000000000000000000442661454036133000177770ustar00rootroot00000000000000require_relative 'common' require 'net/ssh/key_factory' class TestKeyFactory < NetSSHTest def setup @key_file = File.expand_path("/key-file") end def test_load_unencrypted_private_RSA_key_should_return_key File.expects(:read).with(@key_file).returns(rsa_key.export) assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der end def test_load_unencrypted_private_RSA_key_should_have_fp_md5 File.expects(:read).with(@key_file).returns(rsa_key.export) assert_equal rsa_key_fingerprint_md5, Net::SSH::KeyFactory.load_private_key(@key_file).fingerprint end def test_load_unencrypted_private_RSA_key_should_have_fp_sha256 File.expects(:read).with(@key_file).returns(rsa_key.export) assert_equal rsa_key_fingerprint_sha256, Net::SSH::KeyFactory.load_private_key(@key_file).fingerprint('sha256') end def test_load_unencrypted_private_DSA_key_should_return_key File.expects(:read).with(@key_file).returns(dsa_key.export) assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der end def test_load_unencrypted_private_DSA_key_should_have_fp_md5 File.expects(:read).with(@key_file).returns(dsa_key.export) assert_equal dsa_key_fingerprint_md5, Net::SSH::KeyFactory.load_private_key(@key_file).fingerprint end def test_load_unencrypted_private_DSA_key_should_have_fp_sha256 File.expects(:read).with(@key_file).returns(dsa_key.export) assert_equal dsa_key_fingerprint_sha256, Net::SSH::KeyFactory.load_private_key(@key_file).fingerprint('sha256') end def test_load_encrypted_private_RSA_key_should_prompt_for_password_and_return_key prompt = MockPrompt.new File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password")) prompt.expects(:_ask).with("Enter passphrase for #{@key_file}:", has_entries(type: 'private_key', filename: @key_file), false).returns("password") assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file, nil, true, prompt).to_der end def test_load_encrypted_private_RSA_key_with_password_should_not_prompt_and_return_key File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password")) assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file, "password").to_der end def test_load_encrypted_private_DSA_key_should_prompt_for_password_and_return_key prompt = MockPrompt.new data = encrypted(dsa_key, "password") File.expects(:read).with(@key_file).returns(data) sha = Digest::SHA256.digest(data) prompt.expects(:_ask).with("Enter passphrase for #{@key_file}:", { type: 'private_key', filename: @key_file, sha: sha }, false).returns("password") assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file, nil, true, prompt).to_der end def test_load_encrypted_private_DSA_key_with_password_should_not_prompt_and_return_key File.expects(:read).with(@key_file).returns(encrypted(dsa_key, "password")) assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file, "password").to_der end def test_load_encrypted_private_key_should_give_three_tries_for_the_password_and_then_raise_exception prompt = MockPrompt.new File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password")) prompt.expects(:_ask).times(3).with("Enter passphrase for #{@key_file}:", has_entries(type: 'private_key', filename: @key_file), false).returns("passwod", "passphrase", "passwd") if OpenSSL::PKey.respond_to?(:read) error_class = [ArgumentError, OpenSSL::PKey::PKeyError] else error_class = [OpenSSL::PKey::RSAError] end assert_raises(*error_class) { Net::SSH::KeyFactory.load_private_key(@key_file, nil, true, prompt) } end def test_load_encrypted_private_key_should_raise_exception_without_asking_passphrase File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password")) Net::SSH::KeyFactory.expects(:prompt).never if OpenSSL::PKey.respond_to?(:read) error_class = [ArgumentError, OpenSSL::PKey::PKeyError] else error_class = [OpenSSL::PKey::RSAError] end assert_raises(*error_class) { Net::SSH::KeyFactory.load_private_key(@key_file, nil, false) } end def test_load_public_rsa_key_should_return_key File.expects(:read).with(@key_file).returns(public(rsa_key)) assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob end def test_load_public_rsa_key_with_comment_should_return_key File.expects(:read).with(@key_file).returns(public(rsa_key) + " key_comment") assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob end def test_load_public_rsa_key_with_options_should_return_key File.expects(:read).with(@key_file).returns(public(rsa_key, 'environment="FOO=bar"')) assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob end def test_load_public_rsa_key_with_options_and_comment_should_return_key File.expects(:read).with(@key_file).returns(public(rsa_key, 'environment="FOO=bar"') + " key_comment") assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob end def test_load_unencrypted_private_ecdsa_sha2_nistp256_key_should_return_key File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp256_key.to_pem) assert_equal ecdsa_sha2_nistp256_key.to_der, Net::SSH::KeyFactory.load_private_key('/key-file').to_der end def test_load_unencrypted_private_ecdsa_sha2_nistp384_key_should_return_key File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp384_key.to_pem) assert_equal ecdsa_sha2_nistp384_key.to_der, Net::SSH::KeyFactory.load_private_key('/key-file').to_der end def test_load_unencrypted_private_ecdsa_sha2_nistp521_key_should_return_key File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp521_key.to_pem) assert_equal ecdsa_sha2_nistp521_key.to_der, Net::SSH::KeyFactory.load_private_key('/key-file').to_der end def test_load_unencrypted_private_ecdsa_sha2_nistp256_key_should_have_fp_md5 File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp256_key.to_pem) assert_equal ecdsa_sha2_nistp256_key_fingerprint_md5, Net::SSH::KeyFactory.load_private_key('/key-file').fingerprint end def test_load_unencrypted_private_ecdsa_sha2_nistp256_key_should_have_fp_sha256 File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp256_key.to_pem) assert_equal ecdsa_sha2_nistp256_key_fingerprint_sha256, Net::SSH::KeyFactory.load_private_key('/key-file').fingerprint('sha256') end def test_load_should_parse_openssh_format_private_ecdsa_sha2_nistp256_key return if ENV['NET_SSH_NO_ED25519'] File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp256_key_openssh) assert_equal ecdsa_sha2_nistp256_key.to_blob, Net::SSH::KeyFactory.load_private_key('/key-file').to_blob end def test_load_unencrypted_private_ecdsa_sha2_nistp384_key_should_have_fp_md5 File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp384_key.to_pem) assert_equal ecdsa_sha2_nistp384_key_fingerprint_md5, Net::SSH::KeyFactory.load_private_key('/key-file').fingerprint end def test_load_unencrypted_private_ecdsa_sha2_nistp384_key_should_have_fp_sha256 File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp384_key.to_pem) assert_equal ecdsa_sha2_nistp384_key_fingerprint_sha256, Net::SSH::KeyFactory.load_private_key('/key-file').fingerprint('sha256') end def test_load_should_parse_openssh_format_private_ecdsa_sha2_nistp384_key return if ENV['NET_SSH_NO_ED25519'] File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp384_key_openssh) assert_equal ecdsa_sha2_nistp384_key.to_blob, Net::SSH::KeyFactory.load_private_key('/key-file').to_blob end def test_load_unencrypted_private_ecdsa_sha2_nistp521_key_should_have_fp_md5 File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp521_key.to_pem) assert_equal ecdsa_sha2_nistp521_key_fingerprint_md5, Net::SSH::KeyFactory.load_private_key('/key-file').fingerprint end def test_load_unencrypted_private_ecdsa_sha2_nistp521_key_should_have_fp_sha256 File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp521_key.to_pem) assert_equal ecdsa_sha2_nistp521_key_fingerprint_sha256, Net::SSH::KeyFactory.load_private_key('/key-file').fingerprint('sha256') end def test_load_should_parse_openssh_format_private_ecdsa_sha2_nistp521_key return if ENV['NET_SSH_NO_ED25519'] File.expects(:read).with(@key_file).returns(ecdsa_sha2_nistp521_key_openssh) assert_equal ecdsa_sha2_nistp521_key.to_blob, Net::SSH::KeyFactory.load_private_key('/key-file').to_blob end def test_load_public_ecdsa_sha2_nistp256_key_should_return_key File.expects(:read).with(@key_file).returns(public(ecdsa_sha2_nistp256_key)) assert_equal ecdsa_sha2_nistp256_key.to_blob, Net::SSH::KeyFactory.load_public_key('/key-file').to_blob end def test_load_public_ecdsa_sha2_nistp384_key_should_return_key File.expects(:read).with(@key_file).returns(public(ecdsa_sha2_nistp384_key)) assert_equal ecdsa_sha2_nistp384_key.to_blob, Net::SSH::KeyFactory.load_public_key('/key-file').to_blob end def test_load_public_ecdsa_sha2_nistp521_key_should_return_key File.expects(:read).with(@key_file).returns(public(ecdsa_sha2_nistp521_key)) assert_equal ecdsa_sha2_nistp521_key.to_blob, Net::SSH::KeyFactory.load_public_key('/key-file').to_blob end def test_load_anonymous_private_key_should_return_key_or_raise_exception File.expects(:read).with(@key_file).returns(anonymous_private_key) if OpenSSL::PKey.respond_to?(:read) assert_equal OpenSSL::PKey::RSA.new(anonymous_private_key).to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der else assert_raises(OpenSSL::PKey::PKeyError) { Net::SSH::KeyFactory.load_private_key(@key_file) } end end private def rsa_key_fingerprint_md5 '32:00:44:78:bf:91:02:c1:00:25:0f:f9:0a:f9:aa:c7' end def rsa_key_fingerprint_sha256 'SHA256:1XFnG2UY/fBunFk1vviHPVV5ruqbL6ZBfGVVOf9mRMk' end def rsa_key # 512 bits @rsa_key ||= OpenSSL::PKey::RSA.new("0\202\001;\002\001\000\002A\000\235\236\374N\e@2E\321\3757\003\354c\276N\f\003\3479Ko\005\317\0027\a\255=\345!\306\220\340\211;\027u\331\260\362\2063x\332\301y4\353\v%\032\214v\312\304\212\271GJ\353\2701\031\002\003\001\000\001\002@\022Y\306*\031\306\031\224Cde\231QV3{\306\256U\2477\377\017\000\020\323\363R\332\027\351\034\224OU\020\227H|pUS\n\263+%\304\341\321\273/\271\e\004L\250\273\020&,\t\304By\002!\000\311c\246%a\002\305\277\262R\266\244\250\025V_\351]\264\016\265\341\355\305\223\347Z$8\205#\023\002!\000\310\\\367|\243I\363\350\020\307\246\302\365\ed\212L\273\2158M\223w\a\367 C\t\224A4\243\002!\000\262]+}\327\231\331\002\2331^\312\036\204'g\363\f&\271\020\245\365-\024}\306\374e\202\2459\002 }\231\341\276\3551\277\307{5\\\361\233\353G\024wS\237\fk}\004\302&\205\277\340rb\211\327\002!\000\223\307\025I:\215_\260\370\252\3757\256Y&X\364\354\342\215\350\203E8\227|\f\237M\375D|") end def dsa_key # 512 bits @dsa_key ||= OpenSSL::PKey::DSA.new("0\201\367\002\001\000\002A\000\203\316/\037u\272&J\265\003l3\315d\324h\372{\t8\252#\331_\026\006\035\270\266\255\343\353Z\302\276\335\336\306\220\375\202L\244\244J\206>\346\b\315\211\302L\246x\247u\a\376\366\345\302\016#\002\025\000\244\274\302\221Og\275/\302+\356\346\360\024\373wI\2573\361\002@\027\215\270r*\f\213\350C\245\021:\350 \006\\\376\345\022`\210b\262\3643\023XLKS\320\370\002\276\347A\nU\204\276\324\256`=\026\240\330\306J\316V\213\024\e\030\215\355\006\037q\337\356ln\002@\017\257\034\f\260\333'S\271#\237\230E\321\312\027\021\226\331\251Vj\220\305\316\036\v\266+\000\230\270\177B\003?t\a\305]e\344\261\334\023\253\323\251\223M\2175)a(\004\"lI8\312\303\307\a\002\024_\aznW\345\343\203V\326\246ua\203\376\201o\350\302\002") end def dsa_key_fingerprint_md5 '8c:3a:e7:ea:34:cd:75:7a:fd:c9:b8:48:ce:4a:2f:97' end def dsa_key_fingerprint_sha256 'SHA256:9+7rXHxjuAmxm3UjuZ3T1qTF/UZUrmZQMJC8kNMr7J8' end def ecdsa_sha2_nistp256_key @ecdsa_sha2_nistp256_key ||= OpenSSL::PKey::EC.new("-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEINv6pPVLlkqvT1v5MJlWgaSWGwqupISG4U79bUXQDNCaoAoGCCqGSM49\nAwEHoUQDQgAElqubvi/GkSme+bwtncU1NiE0dWQ0EO07VufUQg8lUJ5+Fi6f96qa\n95T1zwOMQhY1h8PP9rQIZr4S48vN/ZnQLw==\n-----END EC PRIVATE KEY-----\n") end def ecdsa_sha2_nistp256_key_openssh @ecdsa_sha2_nistp256_key_openssh ||= <<~EOF -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSWq5u+L8aRKZ75vC2dxTU2ITR1ZDQQ 7TtW59RCDyVQnn4WLp/3qpr3lPXPA4xCFjWHw8/2tAhmvhLjy839mdAvAAAAoN5nLLHeZy yxAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJarm74vxpEpnvm8 LZ3FNTYhNHVkNBDtO1bn1EIPJVCefhYun/eqmveU9c8DjEIWNYfDz/a0CGa+EuPLzf2Z0C 8AAAAhANv6pPVLlkqvT1v5MJlWgaSWGwqupISG4U79bUXQDNCaAAAAAAECAwQFBgc= -----END OPENSSH PRIVATE KEY----- EOF end def ecdsa_sha2_nistp256_key_fingerprint_md5 'ed:9e:cd:74:41:a4:37:ae:99:9e:9a:c3:de:04:c9:e1' end def ecdsa_sha2_nistp256_key_fingerprint_sha256 'SHA256:yGdFZAf5Mbg5+EPA802cn4lo+uoBEj3RBK4DLG9WK1Y' end def ecdsa_sha2_nistp384_key @ecdsa_sha2_nistp384_key ||= OpenSSL::PKey::EC.new("-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDBxwkmydCn4mP4KMhlMpeBvIroQolWKVNoRPXpG7brFgK+Yiikqw8wd\nIZW5OlL4y3mgBwYFK4EEACKhZANiAARkoIR1oABi+aQJbKcmvzeYSKURQOyXM0HU\nR4T68v4hd/lJE4fFQRczj3wAaECe9u3CWI/oDlow4Vr0vab82ZGjIoblxblKQWYl\nyzENgzl226waGg1bLBo8Auilyf1B5yI=\n-----END EC PRIVATE KEY-----\n") end def ecdsa_sha2_nistp384_key_openssh @ecdsa_sha2_nistp384_key_openssh ||= <<~EOF -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS 1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRkoIR1oABi+aQJbKcmvzeYSKURQOyX M0HUR4T68v4hd/lJE4fFQRczj3wAaECe9u3CWI/oDlow4Vr0vab82ZGjIoblxblKQWYlyz ENgzl226waGg1bLBo8Auilyf1B5yIAAADI+tMSfPrTEnwAAAATZWNkc2Etc2hhMi1uaXN0 cDM4NAAAAAhuaXN0cDM4NAAAAGEEZKCEdaAAYvmkCWynJr83mEilEUDslzNB1EeE+vL+IX f5SROHxUEXM498AGhAnvbtwliP6A5aMOFa9L2m/NmRoyKG5cW5SkFmJcsxDYM5dtusGhoN WywaPALopcn9QeciAAAAMHHCSbJ0KfiY/goyGUyl4G8iuhCiVYpU2hE9ekbtusWAr5iKKS rDzB0hlbk6UvjLeQAAAAA= -----END OPENSSH PRIVATE KEY----- EOF end def ecdsa_sha2_nistp384_key_fingerprint_md5 '87:5a:c0:a0:23:55:22:05:ca:16:4d:cc:0c:e5:e7:74' end def ecdsa_sha2_nistp384_key_fingerprint_sha256 'SHA256:l8ZS7aKnquF8VUXAbHj9wPEEenUjyKIiuUSgOWbWqUw' end def ecdsa_sha2_nistp521_key @ecdsa_sha2_nistp521_key ||= OpenSSL::PKey::EC.new("-----BEGIN EC PRIVATE KEY-----\nMIHbAgEBBEHQ2i7kjEGQHQB4pUQW9a2eCLWR2S5Go8U3CDyfbRCrYEp/pTSgI8uu\nMXyR3bf3SjqFQgZ6MZk5lkyrissJuwmvZKAHBgUrgQQAI6GBiQOBhgAEAN14FACK\nbs/KTqw4rxijeozGTVJTh1hNzBl2XaIhM4Fv8o3fE/pvogymyFu53GCng6gC4dmx\n/hycF41iIM29xVKPAeBnRNl6MdFBjuthOmE8eCRezgk1Bak8aBDUrzNT8OQssscw\npvQK4nc6ga/wTDaQGy5kV8tCOHNs2wKH+p2LpWTJ\n-----END EC PRIVATE KEY-----\n") end def ecdsa_sha2_nistp521_key_openssh @ecdsa_sha2_nistp521_key_openssh ||= <<~EOF -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS 1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA3XgUAIpuz8pOrDivGKN6jMZNUlOH WE3MGXZdoiEzgW/yjd8T+m+iDKbIW7ncYKeDqALh2bH+HJwXjWIgzb3FUo8B4GdE2Xox0U GO62E6YTx4JF7OCTUFqTxoENSvM1Pw5CyyxzCm9AridzqBr/BMNpAbLmRXy0I4c2zbAof6 nYulZMkAAAEA7yORv+8jkb8AAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ AAAIUEAN14FACKbs/KTqw4rxijeozGTVJTh1hNzBl2XaIhM4Fv8o3fE/pvogymyFu53GCn g6gC4dmx/hycF41iIM29xVKPAeBnRNl6MdFBjuthOmE8eCRezgk1Bak8aBDUrzNT8OQsss cwpvQK4nc6ga/wTDaQGy5kV8tCOHNs2wKH+p2LpWTJAAAAQgDQ2i7kjEGQHQB4pUQW9a2e CLWR2S5Go8U3CDyfbRCrYEp/pTSgI8uuMXyR3bf3SjqFQgZ6MZk5lkyrissJuwmvZAAAAA ABAg== -----END OPENSSH PRIVATE KEY----- EOF end def ecdsa_sha2_nistp521_key_fingerprint_md5 '6d:5f:10:80:18:4a:69:f3:e3:70:a3:87:90:81:9a:11' end def ecdsa_sha2_nistp521_key_fingerprint_sha256 'SHA256:gxtS/gn7iVn6rGgH3EZCInzxN2/hiONcaRSyBJ/Nr4U' end def anonymous_private_key @anonymous_key = <<~EOF -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3id5gZ6bglJth yli8JNaRxhsqKwwPlReEI/mplzz5IP6gWQ92LogXbdBXtHf9ZpA53BeLmtcNBEY0 Ygd7sPBhlHABS5D5///zltSSX2+L5GCEiC6dpfGsySjqymWF+SZ2PaqfZbkWLmCD 9u4ysueaHf7xbF6txGprNp69efttWxdy+vU5tno7HVxemMZQUalpShFrdAYKKXEo cV7MtbkQjzubS14gaWGpWCXIl9uNKQeHpLKtre1Qn5Ft/zVpCHmhLQcYDuB1LAj9 7eoev4rIiOE2sfdkvKDlmFxvzq3myYH4o27WwAg9OZ5SBusn2zesKkRCBBEZ55rl uVknOGHXAgMBAAECggEAZE0U2OxsNxkfXS6+lXswQ5PW7pF90towcsdSPgrniGIu pKRnHbfKKbuaewOl+zZcpTIRL/rbgUKPtzrHSiJlC36aQyrvvJ/ZWV5ZJvC+vd19 nY/qob65NyrrkHwxRSjmiwGiR9/IaUXI+vUsMUqx5Ph1hawqhZ3sZlEAKR4LeDO8 M+OguG77jLaqj5/SNfi+GwyUDe85de4VfEG4S9HrMQk2Cp66rx0BqDnCLacyFQaI R0VczMXTU52q0uETmgUr8G9A1SaRc5ZWKAfZwxJTvqdIImWC9E+CY7wm+mZD4FE6 iVzVC0ngcdEd596kTDdU2BPVMluWzLkfqIrTt/5CeQKBgQDzgRzCPNxFtai6RAIi ekBSHqrDnrbeTaw32GVq5ACk1Zfk2I0svctz1iQ9qJ2SRINpygQhcyJKQ4r/LXi1 7Av9H/d6QV4T2AZzS4WcqBkxxRXFUfARtnKChzuCzNt9tNz4EZiv75RyQmztGZjV i94+ZvCyqup5be4Svf4MBxin9QKBgQDA9P4nHzFWZakTMei78LGb/4Auc+r0rZp7 8xg8Z92tvrDeJjMdesdhiFrPP1qiSYHnQ81MSWpn6BycBsHZqitejQmYnYput/s4 qG+m7SrkN8WL6rijYsbB+U14VDjMlBlOgcEgjlSNU2oeS+68u+uVI/fgyXcXn4Jq 33TSWSgfGwKBgA2tRdE/G9wqfOShZ0FKfoxePpcoNfs8f5zPYbrkPYkEmjh3VU6b Bm9mKrjv3JHXmU3608qRLe7f5lG42xvUu0OnZP4P59nTe2FEb6fB5VBfUn63wHUu OzZLpDMPkJB59SNV0a6oFT1pr7aNhoEQDxaQL5rJcMwLOaEB3OAOEft1AoGASz7+ 4Zi7b7rDPVYIMUpCqNfxT6wqovIUPWPmPqAuhXPIm0kAQ+2+VN2MtCc7m+/Ydawu IiK7GPweNAY6kDxZH00WweolstmSYVzl9Y2lXUwWgGKvUB/T7I7g1Bzb7YOPftsA ykZW2Kn/xwLLfdQ2oXleT82g4Jh2jmDHuMPF7qMCgYEA6QF45PvOgnrJessgmwO/ dEmkLl07PQYJPGZLaZteuWrvfMrn+AiW5aAdHzhzNaOtNy5B3T7zGUHtgxXegqgd /QdCVCJgnZUO/zdAxkr22dDn+WEXkL4wgBVStQvvnQp9C2NJcoOExvex5PLzKWQg WEKt5v3QsUEgVrzkM4K9UbI= -----END PRIVATE KEY----- EOF end def encrypted(key, password) cipher = OpenSSL::Cipher.new("des-ede3-cbc") key.export(cipher, password) end def public(key, args = nil) result = String.new if !args.nil? result << "#{args} " end result << "#{key.ssh_type} " result << [Net::SSH::Buffer.from(:key, key).to_s].pack("m*").strip.tr("\n\r\t ", "") result << " joe@host.test" end end net-ssh-7.2.1/test/test_known_hosts.rb000066400000000000000000000147001454036133000200220ustar00rootroot00000000000000require_relative './common' class TestKnownHosts < NetSSHTest def perform_test(source) kh = Net::SSH::KnownHosts.new(source) keys = kh.keys_for("github.com") assert_equal(1, keys.count) assert_equal("ssh-rsa", keys[0].ssh_type) end def test_key_for_when_all_hosts_are_recognized perform_test(path("known_hosts/github")) end def test_key_for_when_an_host_hash_is_recognized perform_test(path("known_hosts/github_hash")) end def test_parsing_known_hosts_empty_lines_and_comments perform_test(path("known_hosts/known_hosts_ignore")) end def test_missing_then_add Tempfile.open('github') do |f| f.write(File.read(path("known_hosts/github"))) kh = Net::SSH::KnownHosts.new(f.path) keys = kh.keys_for("github2.com") assert_equal(0, keys.count) assert_equal([], keys.to_a) kh.add('github2.com', rsa_key) keys2 = kh.keys_for("github2.com") assert_equal([rsa_key.to_blob], keys2.to_a.map(&:to_blob)) end end def test_search_for options = { user_known_hosts_file: path("known_hosts/github"), global_known_hosts_file: [] } keys = Net::SSH::KnownHosts.search_for('github.com', options) assert_equal(["ssh-rsa"], keys.map(&:ssh_type)) end def test_search_for_with_hostname_and_right_ip_with_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: true } keys = Net::SSH::KnownHosts.search_for('gitlab.com,35.231.145.151', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_and_right_ip_without_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: false } keys = Net::SSH::KnownHosts.search_for('gitlab.com,35.231.145.151', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_and_wrong_ip_with_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: true } keys = Net::SSH::KnownHosts.search_for('gitlab.com,192.0.2.1', options) assert_equal(0, keys.count) end def test_search_for_with_hostname_and_wrong_ip_without_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: false } keys = Net::SSH::KnownHosts.search_for('gitlab.com,192.0.2.2', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_only_with_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: true } keys = Net::SSH::KnownHosts.search_for('gitlab.com', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_only_without_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: false } keys = Net::SSH::KnownHosts.search_for('gitlab.com', options) assert_equal(1, keys.count) end def test_search_for_with_ip_only_with_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: true } keys = Net::SSH::KnownHosts.search_for('35.231.145.151', options) assert_equal(1, keys.count) end def test_search_for_with_ip_only_without_check_host_ip options = { user_known_hosts_file: path("known_hosts/gitlab"), check_host_ip: false } keys = Net::SSH::KnownHosts.search_for('35.231.145.151', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_matching_pattern options = { user_known_hosts_file: path("known_hosts/misc") } keys = Net::SSH::KnownHosts.search_for('subdomain.gitfoo.com', options) assert_equal(1, keys.count) end def test_search_for_with_hostname_not_matching_pattern_1 options = { user_known_hosts_file: path("known_hosts/misc") } keys = Net::SSH::KnownHosts.search_for('gitfoo.com', options) assert_equal(0, keys.count) end def test_search_for_with_hostname_not_matching_pattern_2 options = { user_known_hosts_file: path("known_hosts/misc") } keys = Net::SSH::KnownHosts.search_for('subdomain.gitmisc.com', options) assert_equal(0, keys.count) end def test_search_for_with_hostname_not_matching_pattern_3 options = { user_known_hosts_file: path("known_hosts/misc") } keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo.com', options) assert_equal(1, keys.count) end def test_asterisk_matches_multiple_dots with_config_file(lines: ["*.git???.com #{sample_key}"]) do |path| options = { user_known_hosts_file: path } keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo.com', options) assert_equal(1, keys.count) keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo2.com', options) assert_equal(0, keys.count) end end def test_asterisk_matches_everything with_config_file(lines: ["* #{sample_key}"]) do |path| options = { user_known_hosts_file: path } keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo.com', options) assert_equal(1, keys.count) keys = Net::SSH::KnownHosts.search_for('subsubdomain.subdomain.gitfoo2.com', options) assert_equal(1, keys.count) end end def test_search_for_then_add Tempfile.open('github') do |f| f.write(File.read(path("known_hosts/github"))) options = { user_known_hosts_file: f.path } keys = Net::SSH::KnownHosts.search_for('github2.com', options) assert_equal(0, keys.count) keys.add_host_key(rsa_key) assert_equal([rsa_key.to_blob], keys.map(&:to_blob)) keys = Net::SSH::KnownHosts.search_for('github2.com', options) assert_equal([rsa_key.to_blob], keys.map(&:to_blob)) end end def path(relative_path) 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 n = 0x7766554433221100 e = 0xffeeddccbbaa9988 asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Integer(n), OpenSSL::ASN1::Integer(e) ] ) OpenSSL::PKey::RSA.new(asn1.to_der) end end net-ssh-7.2.1/test/test_proxy_jump.rb000066400000000000000000000033171454036133000176640ustar00rootroot00000000000000require_relative 'common' require 'net/ssh/proxy/jump' class TestProxyJump < NetSSHTest def test_is_a_proxy_command proxy = Net::SSH::Proxy::Jump.new("user@jumphost") assert proxy.is_a?(Net::SSH::Proxy::Command) end def test_host proxy = Net::SSH::Proxy::Jump.new("jumphost") proxy.build_proxy_command_equivalent assert_equal "ssh -W %h:%p jumphost", proxy.command_line_template end def test_user_host proxy = Net::SSH::Proxy::Jump.new("sally@proxy") proxy.build_proxy_command_equivalent assert_equal "ssh -l sally -W %h:%p proxy", proxy.command_line_template end def test_user_host_port proxy = Net::SSH::Proxy::Jump.new("bob@jump:2222") proxy.build_proxy_command_equivalent assert_equal "ssh -l bob -p 2222 -W %h:%p jump", proxy.command_line_template end def test_multiple_jump_proxies proxy = Net::SSH::Proxy::Jump.new("user1@proxy1,user2@proxy2,user3@proxy3") proxy.build_proxy_command_equivalent assert_equal "ssh -l user1 -J user2@proxy2,user3@proxy3 -W %h:%p proxy1", proxy.command_line_template end def test_config_override proxy = Net::SSH::Proxy::Jump.new("proxy") proxy.build_proxy_command_equivalent(config: "/home/user/.ssh/config2") assert_equal "ssh -F /home/user/.ssh/config2 -W %h:%p proxy", proxy.command_line_template end def test_config_false proxy = Net::SSH::Proxy::Jump.new("proxy") proxy.build_proxy_command_equivalent(config: false) assert_equal "ssh -W %h:%p proxy", proxy.command_line_template end def test_config_true proxy = Net::SSH::Proxy::Jump.new("proxy") proxy.build_proxy_command_equivalent(config: true) assert_equal "ssh -W %h:%p proxy", proxy.command_line_template end end net-ssh-7.2.1/test/transport/000077500000000000000000000000001454036133000161145ustar00rootroot00000000000000net-ssh-7.2.1/test/transport/hmac/000077500000000000000000000000001454036133000170245ustar00rootroot00000000000000net-ssh-7.2.1/test/transport/hmac/test_md5.rb000066400000000000000000000020331454036133000210730ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' require 'net/ssh/transport/hmac/md5' 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 net-ssh-7.2.1/test/transport/hmac/test_md5_96.rb000066400000000000000000000011161454036133000214120ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' require 'transport/hmac/test_md5' require 'net/ssh/transport/hmac/md5_96' 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 net-ssh-7.2.1/test/transport/hmac/test_none.rb000066400000000000000000000013751454036133000213550ustar00rootroot00000000000000require 'common' require 'net/ssh/transport/hmac/none' 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 net-ssh-7.2.1/test/transport/hmac/test_ripemd160.rb000066400000000000000000000016401454036133000221200ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' require 'net/ssh/transport/hmac/ripemd160' 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 net-ssh-7.2.1/test/transport/hmac/test_sha1.rb000066400000000000000000000016111454036133000212430ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' require 'net/ssh/transport/hmac/sha1' 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 net-ssh-7.2.1/test/transport/hmac/test_sha1_96.rb000066400000000000000000000011311454036133000215560ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' require 'transport/hmac/test_sha1' require 'net/ssh/transport/hmac/sha1_96' 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 net-ssh-7.2.1/test/transport/hmac/test_sha2_256.rb000066400000000000000000000020531454036133000216410ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' 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 assert_equal OpenSSL::Digest::SHA256, subject.new.digest_class end def test_expected_key_length assert_equal 32, subject.key_length assert_equal 32, subject.new.key_length end def test_expected_mac_length assert_equal 32, subject.mac_length assert_equal 32, subject.new.mac_length end def test_expected_etm assert_equal false, subject.etm assert_equal false, subject.new.etm end def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "\x16^>\x9FhO}\xB1>(\xBAF\xFBW\xB8\xF2\xFA\x824+\xC0\x94\x95\xC2\r\xE6\x88/\xEF\t\xF5%", hmac.digest("hello world") end private def subject Net::SSH::Transport::HMAC::SHA2_256 end end end end net-ssh-7.2.1/test/transport/hmac/test_sha2_256_96.rb000066400000000000000000000011411454036133000221540ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' require 'transport/hmac/test_sha2_256' require 'net/ssh/transport/hmac/sha2_256_96' 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 net-ssh-7.2.1/test/transport/hmac/test_sha2_256_etm.rb000066400000000000000000000020651454036133000225110ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' 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 assert_equal OpenSSL::Digest::SHA256, subject.new.digest_class end def test_expected_key_length assert_equal 32, subject.key_length assert_equal 32, subject.new.key_length end def test_expected_mac_length assert_equal 32, subject.mac_length assert_equal 32, subject.new.mac_length end def test_expected_etm assert_equal true, subject.etm assert_equal true, subject.new.etm end def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "\x16^>\x9FhO}\xB1>(\xBAF\xFBW\xB8\xF2\xFA\x824+\xC0\x94\x95\xC2\r\xE6\x88/\xEF\t\xF5%", hmac.digest("hello world") end private def subject Net::SSH::Transport::HMAC::SHA2_256_Etm end end end end net-ssh-7.2.1/test/transport/hmac/test_sha2_512.rb000066400000000000000000000022021454036133000216300ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' 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 assert_equal OpenSSL::Digest::SHA512, subject.new.digest_class end def test_expected_key_length assert_equal 64, subject.key_length assert_equal 64, subject.new.key_length end def test_expected_mac_length assert_equal 64, subject.mac_length assert_equal 64, subject.new.mac_length end def test_expected_etm assert_equal false, subject.etm assert_equal false, subject.new.etm end def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "^\xB6\"\xED\x8B\xC4\xDE\xD4\xCF\xD0\r\x18\xA0<\xF4\xB5\x01Efz\xA80i\xFC\x18\xC1\x9A+\xDD\xFE<\xA2\xFDE1Ac\xF4\xADU\r\xFB^0\x90= \x837z\xCC\xD5p4a4\x83\xC6\x04m\xAA\xC1\xC0m", hmac.digest("hello world") end private def subject Net::SSH::Transport::HMAC::SHA2_512 end end end end net-ssh-7.2.1/test/transport/hmac/test_sha2_512_96.rb000066400000000000000000000011621454036133000221520ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' require 'transport/hmac/test_sha2_512' require 'net/ssh/transport/hmac/sha2_512_96' 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 net-ssh-7.2.1/test/transport/hmac/test_sha2_512_etm.rb000066400000000000000000000022141454036133000225000ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' 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 assert_equal OpenSSL::Digest::SHA512, subject.new.digest_class end def test_expected_key_length assert_equal 64, subject.key_length assert_equal 64, subject.new.key_length end def test_expected_mac_length assert_equal 64, subject.mac_length assert_equal 64, subject.new.mac_length end def test_expected_etm assert_equal true, subject.etm assert_equal true, subject.new.etm end def test_expected_digest hmac = subject.new("1234567890123456") assert_equal "^\xB6\"\xED\x8B\xC4\xDE\xD4\xCF\xD0\r\x18\xA0<\xF4\xB5\x01Efz\xA80i\xFC\x18\xC1\x9A+\xDD\xFE<\xA2\xFDE1Ac\xF4\xADU\r\xFB^0\x90= \x837z\xCC\xD5p4a4\x83\xC6\x04m\xAA\xC1\xC0m", hmac.digest("hello world") end private def subject Net::SSH::Transport::HMAC::SHA2_512_Etm end end end end net-ssh-7.2.1/test/transport/kex/000077500000000000000000000000001454036133000167035ustar00rootroot00000000000000net-ssh-7.2.1/test/transport/kex/test_curve25519_sha256.rb000066400000000000000000000127461454036133000231230ustar00rootroot00000000000000unless ENV['NET_SSH_NO_ED25519'] require_relative '../../common' require 'transport/kex/test_diffie_hellman_group1_sha1' require 'net/ssh/transport/kex/curve25519_sha256_loader' require 'ostruct' module Transport module Kex class TestCurve25519Sha256 < NetSSHTest include Net::SSH::Transport::Constants def setup 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 end def test_exchange_keys_should_return_expected_results_when_successful result = exchange! assert_equal session_id, result[:session_id] assert_equal server_host_key.to_blob, result[:server_key].to_blob assert_equal shared_secret, result[:shared_secret] assert_equal digester, result[:hashing_algorithm] end def test_exchange_keys_with_unverifiable_host_should_raise_exception connection.verifier { false } assert_raises(Net::SSH::Exception) { exchange! } end def test_exchange_keys_with_signature_key_type_mismatch_should_raise_exception assert_raises(Net::SSH::Exception) { exchange! key_type: 'ssh-dss' } end def test_exchange_keys_with_host_key_type_mismatch_should_raise_exception algorithms host_key: 'ssh-dss' assert_raises(Net::SSH::Exception) { exchange! key_type: 'ssh-dss' } end def test_exchange_keys_when_server_signature_could_not_be_verified_should_raise_exception @signature = '1234567890' assert_raises(Net::SSH::Exception) { exchange! } end def test_exchange_keys_should_pass_expected_parameters_to_host_key_verifier verified = false connection.verifier do |data| verified = true assert_equal server_host_key.to_blob, data[:key].to_blob blob = b(:key, data[:key]).to_s fingerprint = "SHA256:#{Base64.encode64(OpenSSL::Digest.digest('SHA256', blob)).chomp.gsub(/=+\z/, '')}" assert_equal blob, data[:key_blob] assert_equal fingerprint, data[:fingerprint] assert_equal connection, data[:session] true end assert_nothing_raised { exchange! } assert verified end private def digester OpenSSL::Digest::SHA256 end def subject Net::SSH::Transport::Kex::Curve25519Sha256 end def key_type 'ecdsa-sha2-nistp256' end def exchange!(options = {}) connection.expect do |t, buffer| assert_equal KEXECDH_INIT, buffer.type assert_equal ecdh.ecdh.public_key.to_bytes, buffer.read_string t.return(KEXECDH_REPLY, :string, b(:key, server_host_key), :string, server_ecdh_pubkey.to_bytes, :string, b(:string, options[:key_type] || key_type, :string, signature)) connection.expect do |t2, buffer2| assert_equal NEWKEYS, buffer2.type t2.return(NEWKEYS) end end ecdh.exchange_keys end def ecdh @ecdh ||= subject.new(algorithms, connection, packet_data) end def algorithms(options = {}) @algorithms ||= OpenStruct.new(host_key: options[:server_host_key] || 'ecdsa-sha2-nistp256', host_key_format: options[:server_host_key] || 'ecdsa-sha2-nistp256') end def connection @connection ||= MockTransport.new end def server_key @server_key ||= ::X25519::Scalar.generate end def server_host_key @server_host_key ||= OpenSSL::PKey::EC.generate('prime256v1') end def packet_data @packet_data ||= { client_version_string: 'client version string', server_version_string: 'server version string', server_algorithm_packet: 'server algorithm packet', client_algorithm_packet: 'client algorithm packet' } end def server_ecdh_pubkey @server_ecdh_pubkey ||= server_key.public_key end def shared_secret @shared_secret ||= OpenSSL::BN.new(ecdh.ecdh.diffie_hellman(server_ecdh_pubkey).to_bytes, 2) end def session_id @session_id ||= begin buffer = Net::SSH::Buffer.from(:string, packet_data[:client_version_string], :string, packet_data[:server_version_string], :string, packet_data[:client_algorithm_packet], :string, packet_data[:server_algorithm_packet], :string, Net::SSH::Buffer.from(:key, server_host_key), :string, ecdh.ecdh.public_key.to_bytes, :string, server_ecdh_pubkey.to_bytes, :bignum, shared_secret) digester.digest(buffer.to_s) end end def signature @signature ||= server_host_key.ssh_do_sign(session_id) end def b(*args) Net::SSH::Buffer.from(*args) end end end end end net-ssh-7.2.1/test/transport/kex/test_diffie_hellman_group14_sha1.rb000066400000000000000000000005601454036133000255130ustar00rootroot00000000000000require_relative '../../common' require 'net/ssh/transport/kex/diffie_hellman_group14_sha1' require_relative './test_diffie_hellman_group1_sha1' require 'ostruct' module Transport module Kex class TestDiffieHellmanGroup14SHA1 < TestDiffieHellmanGroup1SHA1 def subject Net::SSH::Transport::Kex::DiffieHellmanGroup14SHA1 end end end end net-ssh-7.2.1/test/transport/kex/test_diffie_hellman_group14_sha256.rb000066400000000000000000000005511454036133000256670ustar00rootroot00000000000000require_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 net-ssh-7.2.1/test/transport/kex/test_diffie_hellman_group1_sha1.rb000066400000000000000000000130521454036133000254270ustar00rootroot00000000000000require_relative '../../common' require 'net/ssh/transport/kex/diffie_hellman_group1_sha1' 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 end def digest_type OpenSSL::Digest::SHA1 end def test_exchange_keys_should_return_expected_results_when_successful result = exchange! assert_equal session_id, result[:session_id] assert_equal server_key.to_blob, result[:server_key].to_blob assert_equal shared_secret, result[:shared_secret] assert_equal digest_type, result[:hashing_algorithm] end def test_exchange_keys_with_unverifiable_host_should_raise_exception connection.verifier { false } assert_raises(Net::SSH::Exception) { exchange! } end def test_exchange_keys_with_signature_key_type_mismatch_should_raise_exception assert_raises(Net::SSH::Exception) { exchange! key_type: "ssh-dss" } end def test_exchange_keys_with_host_key_type_mismatch_should_raise_exception algorithms host_key: "ssh-dss" assert_raises(Net::SSH::Exception) { exchange! key_type: "ssh-dss" } end def test_exchange_keys_when_server_signature_could_not_be_verified_should_raise_exception @signature = "1234567890" assert_raises(Net::SSH::Exception) { exchange! } end def test_exchange_keys_should_pass_expected_parameters_to_host_key_verifier verified = false connection.verifier do |data| verified = true assert_equal server_key.to_blob, data[:key].to_blob blob = b(:key, data[:key]).to_s fingerprint = "SHA256:#{Base64.encode64(OpenSSL::Digest.digest('SHA256', blob)).chomp.gsub(/=+\z/, '')}" assert_equal blob, data[:key_blob] assert_equal fingerprint, data[:fingerprint] assert_equal connection, data[:session] true end assert_nothing_raised { exchange! } assert verified end def test_exchange_keys_can_use_md5_fingerprints verified = false connection.options[:fingerprint_hash] = 'MD5' connection.verifier do |data| verified = true assert_equal server_key.to_blob, data[:key].to_blob blob = b(:key, data[:key]).to_s fingerprint = OpenSSL::Digest.hexdigest('MD5', blob).scan(/../).join(":") assert_equal fingerprint, data[:fingerprint] true end assert_nothing_raised { exchange! } assert verified end private def exchange!(options = {}) connection.expect do |t, buffer| assert_equal KEXDH_INIT, buffer.type assert_equal dh.dh.pub_key, buffer.read_bignum t.return(KEXDH_REPLY, :string, b(:key, server_key), :bignum, server_dh_pubkey, :string, b(:string, options[:key_type] || "ssh-rsa", :string, signature)) connection.expect do |t2, buffer2| assert_equal NEWKEYS, buffer2.type t2.return(NEWKEYS) end end dh.exchange_keys end def dh_options(options = {}) @dh_options = options end def dh @dh ||= subject.new(algorithms, connection, packet_data.merge(need_bytes: 20).merge(@dh_options || {})) end def algorithms(options = {}) @algorithms ||= OpenStruct.new(host_key: options[:host_key] || "ssh-rsa", host_key_format: options[:host_key] || "ssh-rsa") end def connection @connection ||= MockTransport.new end def subject Net::SSH::Transport::Kex::DiffieHellmanGroup1SHA1 end # 512 bits is the smallest possible key that will work with this, so # we use it for speed reasons def server_key(bits = 512) @server_key ||= OpenSSL::PKey::RSA.new(bits) end def packet_data @packet_data ||= { client_version_string: "client version string", server_version_string: "server version string", server_algorithm_packet: "server algorithm packet", client_algorithm_packet: "client algorithm packet" } end def server_dh_pubkey @server_dh_pubkey ||= OpenSSL::BN.new(dh_512bits_bn, 10) end def shared_secret @shared_secret ||= OpenSSL::BN.new(dh.dh.compute_key(server_dh_pubkey), 2) end def session_id @session_id ||= begin buffer = Net::SSH::Buffer.from(:string, packet_data[:client_version_string], :string, packet_data[:server_version_string], :string, packet_data[:client_algorithm_packet], :string, packet_data[:server_algorithm_packet], :string, Net::SSH::Buffer.from(:key, server_key), :bignum, dh.dh.pub_key, :bignum, server_dh_pubkey, :bignum, shared_secret) digest_type.digest(buffer.to_s) end end def signature @signature ||= server_key.ssh_do_sign(session_id) end def bn(number, base = 10) OpenSSL::BN.new(number.to_s, base) end def b(*args) Net::SSH::Buffer.from(*args) end end end end net-ssh-7.2.1/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb000066400000000000000000000100001454036133000271760ustar00rootroot00000000000000require 'common' require 'transport/kex/test_diffie_hellman_group1_sha1' require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1' 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 assert_equal need_minimum, buffer.read_long assert_equal need_bits, buffer.read_long assert_equal 8192, buffer.read_long t.return(KEXDH_GEX_GROUP, :bignum, bn(options[:p] || default_p), :bignum, bn(options[:g] || 2)) t.expect do |t2, buffer2| assert_equal KEXDH_GEX_INIT, buffer2.type assert_equal dh.dh.pub_key, buffer2.read_bignum t2.return(KEXDH_GEX_REPLY, :string, b(:key, server_key), :bignum, server_dh_pubkey, :string, b(:string, options[:key_type] || "ssh-rsa", :string, signature)) t2.expect do |t3, buffer3| assert_equal NEWKEYS, buffer3.type t3.return(NEWKEYS) end end end dh.exchange_keys end def subject Net::SSH::Transport::Kex::DiffieHellmanGroupExchangeSHA1 end def digest_type OpenSSL::Digest::SHA1 end def session_id @session_id ||= begin buffer = Net::SSH::Buffer.from(:string, packet_data[:client_version_string], :string, packet_data[:server_version_string], :string, packet_data[:client_algorithm_packet], :string, packet_data[:server_algorithm_packet], :string, Net::SSH::Buffer.from(:key, server_key), :long, 1024, :long, need_bits, # need bits, figure this part out, :long, 8192, :bignum, dh.dh.p, :bignum, dh.dh.g, :bignum, dh.dh.pub_key, :bignum, server_dh_pubkey, :bignum, shared_secret) digest_type.digest(buffer.to_s) end end end end end net-ssh-7.2.1/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb000066400000000000000000000007101454036133000273610ustar00rootroot00000000000000require 'common' require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1' require 'transport/kex/test_diffie_hellman_group_exchange_sha1' 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 net-ssh-7.2.1/test/transport/kex/test_ecdh_sha2_nistp256.rb000066400000000000000000000122211454036133000235570ustar00rootroot00000000000000require 'openssl' require 'ostruct' require_relative '../../common' require_relative './test_diffie_hellman_group1_sha1' require 'net/ssh/transport/kex/ecdh_sha2_nistp256' module Transport module Kex class TestEcdhSHA2NistP256 < NetSSHTest include Net::SSH::Transport::Constants def setup @ecdh = @algorithms = @connection = @server_key = @packet_data = @shared_secret = nil end def test_exchange_keys_should_return_expected_results_when_successful result = exchange! assert_equal session_id, result[:session_id] assert_equal server_host_key.to_blob, result[:server_key].to_blob assert_equal shared_secret, result[:shared_secret] assert_equal digester, result[:hashing_algorithm] end def test_exchange_keys_with_unverifiable_host_should_raise_exception connection.verifier { false } assert_raises(Net::SSH::Exception) { exchange! } end def test_exchange_keys_with_signature_key_type_mismatch_should_raise_exception assert_raises(Net::SSH::Exception) { exchange! key_type: 'ssh-dss' } end def test_exchange_keys_with_host_key_type_mismatch_should_raise_exception algorithms host_key: 'ssh-dss' assert_raises(Net::SSH::Exception) { exchange! key_type: 'ssh-dss' } end def test_exchange_keys_when_server_signature_could_not_be_verified_should_raise_exception @signature = '1234567890' assert_raises(Net::SSH::Exception) { exchange! } end def test_exchange_keys_should_pass_expected_parameters_to_host_key_verifier verified = false connection.verifier do |data| verified = true assert_equal server_host_key.to_blob, data[:key].to_blob blob = b(:key, data[:key]).to_s fingerprint = "SHA256:#{Base64.encode64(OpenSSL::Digest.digest('SHA256', blob)).chomp.gsub(/=+\z/, '')}" assert_equal blob, data[:key_blob] assert_equal fingerprint, data[:fingerprint] assert_equal connection, data[:session] true end assert_nothing_raised { exchange! } assert verified end private def digester OpenSSL::Digest::SHA256 end def subject Net::SSH::Transport::Kex::EcdhSHA2NistP256 end def ecparam 'prime256v1' end def key_type 'ecdsa-sha2-nistp256' end def exchange!(options = {}) connection.expect do |t, buffer| assert_equal KEXECDH_INIT, buffer.type assert_equal ecdh.ecdh.public_key.to_bn.to_s(2), buffer.read_string t.return(KEXECDH_REPLY, :string, b(:key, server_host_key), :string, server_ecdh_pubkey.to_bn.to_s(2), :string, b(:string, options[:key_type] || key_type, :string, signature)) connection.expect do |t2, buffer2| assert_equal NEWKEYS, buffer2.type t2.return(NEWKEYS) end end ecdh.exchange_keys end def ecdh @ecdh ||= subject.new(algorithms, connection, packet_data) end def algorithms(options = {}) @algorithms ||= OpenStruct.new(host_key: options[:server_host_key] || 'ecdsa-sha2-nistp256', host_key_format: options[:server_host_key] || 'ecdsa-sha2-nistp256') end def connection @connection ||= MockTransport.new end def server_key @server_key ||= OpenSSL::PKey::EC.generate(ecparam) end def server_host_key @server_host_key ||= OpenSSL::PKey::EC.generate('prime256v1') end def packet_data @packet_data ||= { client_version_string: 'client version string', server_version_string: 'server version string', server_algorithm_packet: 'server algorithm packet', client_algorithm_packet: 'client algorithm packet' } end def server_ecdh_pubkey @server_ecdh_pubkey ||= server_key.public_key end def shared_secret @shared_secret ||= OpenSSL::BN.new(ecdh.ecdh.dh_compute_key(server_ecdh_pubkey), 2) end def session_id @session_id ||= begin buffer = Net::SSH::Buffer.from(:string, packet_data[:client_version_string], :string, packet_data[:server_version_string], :string, packet_data[:client_algorithm_packet], :string, packet_data[:server_algorithm_packet], :string, Net::SSH::Buffer.from(:key, server_host_key), :string, ecdh.ecdh.public_key.to_bn.to_s(2), :string, server_ecdh_pubkey.to_bn.to_s(2), :bignum, shared_secret) digester.digest(buffer.to_s) end end def signature @signature ||= server_host_key.ssh_do_sign(session_id) end def b(*args) Net::SSH::Buffer.from(*args) end end end end net-ssh-7.2.1/test/transport/kex/test_ecdh_sha2_nistp384.rb000066400000000000000000000015651454036133000235720ustar00rootroot00000000000000require 'openssl' require 'transport/kex/test_ecdh_sha2_nistp256' module Transport module Kex class TestEcdhSHA2NistP384 < TestEcdhSHA2NistP256 def setup @ecdh = @algorithms = @connection = @server_key = @packet_data = @shared_secret = nil end def test_exchange_keys_should_return_expected_results_when_successful result = exchange! assert_equal session_id, result[:session_id] assert_equal server_host_key.to_blob, result[:server_key].to_blob assert_equal shared_secret, result[:shared_secret] assert_equal digester, result[:hashing_algorithm] end private def digester OpenSSL::Digest::SHA384 end def subject Net::SSH::Transport::Kex::EcdhSHA2NistP384 end def ecparam 'secp384r1' end end end end net-ssh-7.2.1/test/transport/kex/test_ecdh_sha2_nistp521.rb000066400000000000000000000015651454036133000235630ustar00rootroot00000000000000require 'openssl' require 'transport/kex/test_ecdh_sha2_nistp256' module Transport module Kex class TestEcdhSHA2NistP521 < TestEcdhSHA2NistP256 def setup @ecdh = @algorithms = @connection = @server_key = @packet_data = @shared_secret = nil end def test_exchange_keys_should_return_expected_results_when_successful result = exchange! assert_equal session_id, result[:session_id] assert_equal server_host_key.to_blob, result[:server_key].to_blob assert_equal shared_secret, result[:shared_secret] assert_equal digester, result[:hashing_algorithm] end private def digester OpenSSL::Digest::SHA512 end def subject Net::SSH::Transport::Kex::EcdhSHA2NistP521 end def ecparam 'secp521r1' end end end end net-ssh-7.2.1/test/transport/test_algorithms.rb000066400000000000000000000561111454036133000216550ustar00rootroot00000000000000require_relative '../common' require 'logger' require 'net/ssh/transport/algorithms' module Transport class TestAlgorithms < NetSSHTest include Net::SSH::Transport::Constants def test_allowed_packets (0..255).each do |type| packet = stub("packet", type: type) case type when 1..4, 6..19, 21..49 then assert(Net::SSH::Transport::Algorithms.allowed_packet?(packet), "#{type} should be allowed during key exchange") else assert(!Net::SSH::Transport::Algorithms.allowed_packet?(packet), "#{type} should not be allowed during key exchange") end end end def test_constructor_should_build_default_list_of_preferred_algorithms assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512], algorithms[:host_key] assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1], algorithms[:kex] assert_equal chacha_poly_cipher + %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] assert_equal %w[], algorithms[:language] end def test_constructor_should_build_complete_list_of_algorithms_with_append_all_supported_algorithms assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss], algorithms(append_all_supported_algorithms: true)[:host_key] assert_equal x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-sha256 diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1], algorithms(append_all_supported_algorithms: true)[:kex] assert_equal chacha_poly_cipher + %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] assert_equal %w[], algorithms[:language] end def test_constructor_should_set_client_and_server_prefs_identically %w[encryption hmac compression language].each do |key| assert_equal algorithms[key.to_sym], algorithms[:"#{key}_client"], key assert_equal algorithms[key.to_sym], algorithms[:"#{key}_server"], key end end def test_constructor_with_preferred_host_key_type_should_put_preferred_host_key_type_first assert_equal %w[ssh-dss] + ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512], algorithms(host_key: "ssh-dss", append_all_supported_algorithms: true)[:host_key] end def test_constructor_with_known_hosts_reporting_known_host_key_should_use_that_host_key_type Net::SSH::KnownHosts.expects(:search_for).with( "net.ssh.test,127.0.0.1", { user_known_hosts_file: "/dev/null", global_known_hosts_file: "/dev/null" } ).returns([stub("key", ssh_type: "ssh-dss")]) assert_equal %w[ssh-dss] + ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512], algorithms[:host_key] end def ed_host_keys if Net::SSH::Authentication::ED25519Loader::LOADED %w[ssh-ed25519-cert-v01@openssh.com ssh-ed25519] else [] end end def ec_host_keys %w[ecdsa-sha2-nistp521-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp521 ecdsa-sha2-nistp384 ecdsa-sha2-nistp256] end def ed_ec_host_keys ed_host_keys + ec_host_keys end def test_constructor_with_unrecognized_host_key_type_should_return_whats_supported assert_equal ed_ec_host_keys + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512 ssh-dss], algorithms(host_key: "bogus ssh-rsa", append_all_supported_algorithms: true)[:host_key] end def ec_kex %w[ecdh-sha2-nistp521 ecdh-sha2-nistp384 ecdh-sha2-nistp256] end def x25519_kex if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED %w[curve25519-sha256 curve25519-sha256@libssh.org] else [] end end def chacha_poly_cipher if Net::SSH::Transport::ChaCha20Poly1305CipherLoader::LOADED %w[chacha20-poly1305@openssh.com] else [] end end def chacha_poly_cipher_str if chacha_poly_cipher.empty? "" else "#{chacha_poly_cipher.join(',')}," end 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-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-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-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 diffie-hellman-group14-sha256], algorithms(kex: %w[-diffie-hellman-group*-sha1 -diffie-hellman-group-exchange-sha1])[:kex] end def test_constructor_with_preferred_encryption_should_put_preferred_encryption_first assert_equal %w[aes256-cbc] + chacha_poly_cipher + %w[aes256-ctr aes192-ctr aes128-ctr 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(encryption: "aes256-cbc", append_all_supported_algorithms: true)[:encryption] end def test_constructor_with_multiple_preferred_encryption_should_put_all_preferred_encryption_first assert_equal %w[aes256-cbc 3des-cbc idea-cbc] + chacha_poly_cipher + %w[aes256-ctr aes192-ctr aes128-ctr aes192-cbc aes128-cbc rijndael-cbc@lysator.liu.se blowfish-ctr blowfish-cbc cast128-ctr cast128-cbc 3des-ctr none], algorithms(encryption: %w[aes256-cbc 3des-cbc idea-cbc], append_all_supported_algorithms: true)[:encryption] end def test_constructor_with_unrecognized_encryption_should_keep_whats_supported assert_equal %w[aes256-cbc] + chacha_poly_cipher + %w[aes256-ctr aes192-ctr aes128-ctr 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(encryption: %w[bogus aes256-cbc], append_all_supported_algorithms: true)[:encryption] end def test_constructor_with_preferred_encryption_supports_additions # There's nothing we can really append to the set since the default algos # are frozen so this is really just testing that it doesn't do anything # unexpected. assert_equal chacha_poly_cipher + %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(encryption: %w[+3des-cbc])[:encryption] end def test_constructor_with_preferred_encryption_supports_removals_with_wildcard assert_equal chacha_poly_cipher + %w[aes256-ctr aes192-ctr aes128-ctr cast128-ctr], algorithms(encryption: %w[-rijndael-cbc@lysator.liu.se -blowfish-* -3des-* -*-cbc -none])[:encryption] end def test_constructor_with_preferred_hmac_should_put_preferred_hmac_first assert_equal %w[hmac-md5-96 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 none], algorithms(hmac: "hmac-md5-96", append_all_supported_algorithms: true)[:hmac] end def test_constructor_with_multiple_preferred_hmac_should_put_all_preferred_hmac_first assert_equal %w[hmac-md5-96 hmac-sha1-96 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-ripemd160 hmac-ripemd160@openssh.com hmac-md5 none], algorithms(hmac: %w[hmac-md5-96 hmac-sha1-96], append_all_supported_algorithms: true)[:hmac] end def test_constructor_with_unrecognized_hmac_should_ignore_those assert_equal %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512 hmac-sha2-256 hmac-sha1 hmac-sha2-512-96 hmac-sha2-256-96 hmac-sha1-96 hmac-ripemd160 hmac-ripemd160@openssh.com hmac-md5 hmac-md5-96 none], algorithms(hmac: "unknown hmac-md5-96", append_all_supported_algorithms: true)[:hmac] end def test_constructor_with_preferred_hmac_supports_additions 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], algorithms(hmac: %w[+hmac-md5-96 -none])[:hmac] end def test_constructor_with_preferred_hmac_supports_removals_with_wildcard assert_equal %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512 hmac-sha2-256 hmac-sha2-512-96 hmac-sha2-256-96 hmac-ripemd160 hmac-ripemd160@openssh.com], algorithms(hmac: %w[-hmac-sha1* -hmac-md5* -none])[:hmac] end def test_constructor_with_preferred_compression_should_put_preferred_compression_first assert_equal %w[zlib none zlib@openssh.com], algorithms(compression: "zlib", append_all_supported_algorithms: true)[:compression] end def test_constructor_with_multiple_preferred_compression_should_put_all_preferred_compression_first assert_equal %w[zlib@openssh.com zlib none], algorithms(compression: %w[zlib@openssh.com zlib], append_all_supported_algorithms: true)[:compression] end def test_constructor_with_general_preferred_compression_should_put_none_last assert_equal %w[zlib@openssh.com zlib none], algorithms( compression: true, append_all_supported_algorithms: true )[:compression] end def test_constructor_with_unrecognized_compression_should_return_whats_supported assert_equal %w[none zlib zlib@openssh.com], algorithms(compression: %w[bogus none zlib], append_all_supported_algorithms: true)[:compression] end def test_constructor_with_host_key_append_to_default default_host_keys = Net::SSH::Transport::Algorithms::ALGORITHMS[:host_key] assert_equal default_host_keys, algorithms(host_key: '+ssh-dss')[:host_key] end def test_constructor_with_host_key_removals_with_wildcard assert_equal ed_host_keys + %w[ecdsa-sha2-nistp521-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp521 ecdsa-sha2-nistp384 ecdsa-sha2-nistp256], algorithms(host_key: %w[-ssh-rsa* -ssh-dss -rsa-sha*])[:host_key] end def test_initial_state_should_be_neither_pending_nor_initialized assert !algorithms.pending? assert !algorithms.initialized? end def test_key_exchange_when_initiated_by_server transport.expect do |_t, buffer| assert_kexinit(buffer) install_mock_key_exchange(buffer) end install_mock_algorithm_lookups algorithms.accept_kexinit(kexinit) assert_exchange_results end def test_key_exchange_when_initiated_by_client state = nil transport.expect do |_t, buffer| assert_kexinit(buffer) state = :sent_kexinit install_mock_key_exchange(buffer) end algorithms.rekey! assert_equal state, :sent_kexinit assert algorithms.pending? install_mock_algorithm_lookups algorithms.accept_kexinit(kexinit) assert_exchange_results end 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| assert_kexinit(buffer) install_mock_key_exchange(buffer, kex: Net::SSH::Transport::Kex::DiffieHellmanGroup1SHA1) end algorithms.accept_kexinit(kexinit) end 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) } assert_raises(Net::SSH::Exception) { algorithms.accept_kexinit(kexinit) } end def test_allow_when_not_pending_should_be_true_for_all_packets (0..255).each do |type| packet = stub("packet", type: type) assert algorithms.allow?(packet), type.to_s end end def test_allow_when_pending_should_be_true_only_for_packets_valid_during_key_exchange transport.expect! algorithms.rekey! assert algorithms.pending? (0..255).each do |type| packet = stub("packet", type: type) case type when 1..4, 6..19, 21..49 then assert(algorithms.allow?(packet), "#{type} should be allowed during key exchange") else assert(!algorithms.allow?(packet), "#{type} should not be allowed during key exchange") end end end def test_exchange_with_zlib_compression_enabled_sets_compression_to_standard algorithms compression: 'zlib' transport.expect do |_t, buffer| assert_kexinit(buffer, compression_client: 'zlib', compression_server: 'zlib') install_mock_key_exchange(buffer) end install_mock_algorithm_lookups algorithms.accept_kexinit(kexinit) assert_equal :standard, transport.client_options[:compression] assert_equal :standard, transport.server_options[:compression] end 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| assert_kexinit(buffer, compression_client: 'zlib@openssh.com', compression_server: 'zlib@openssh.com') install_mock_key_exchange(buffer) end install_mock_algorithm_lookups algorithms.accept_kexinit(kexinit) assert_equal :delayed, transport.client_options[:compression] assert_equal :delayed, transport.server_options[:compression] end # 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.new) debug_logger = Logger.new(string_io) debug_logger.level = Logger::DEBUG # Create our algorithm instance, with our logger sent to the underlying transport instance alg = algorithms( {}, logger: debug_logger ) # Here are our two lists - "ours" and "theirs" # # [a,b] overlap # [d] "unsupported" values ours = %w[a b c] theirs = %w[a b d] ## Hit the method directly alg.send( :compose_algorithm_list, ours, theirs ) assert string_io.string.include?(%(unsupported algorithm: `["d"]')) end def test_host_key_format algorithm_types = %w[ ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ] algorithm_types .map { |t| [t, [0, 1].map { |n| "#{t}-cert-v0#{n}@openssh.com" }.push(t)] } .each do |type, host_keys| host_keys.each do |hk| algorithms(host_key: hk).instance_eval { @host_key = hk } assert_equal type, algorithms.host_key_format end end end private def install_mock_key_exchange(buffer, options = {}) kex = options[:kex] || Net::SSH::Transport::Kex::DiffieHellmanGroupExchangeSHA256 Net::SSH::Transport::Kex::MAP.each do |_name, klass| next if klass == kex klass.expects(:new).never end kex.expects(:new) .with(algorithms, transport, client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION, server_version_string: transport.server_version.version, server_algorithm_packet: kexinit.to_s, client_algorithm_packet: buffer.to_s, need_bytes: 32, minimum_dh_bits: nil, logger: nil) .returns(stub("kex", exchange_keys: { shared_secret: shared_secret, session_id: session_id, hashing_algorithm: hashing_algorithm })) end def install_mock_algorithm_lookups(options = {}) params = { shared: shared_secret.to_ssh, hash: session_id, digester: hashing_algorithm } key = key("C") cipher_client = OpenStruct.new(implicit_map?: false, id: :client_cipher) # rubocop:disable Style/OpenStructUse server_cipher = OpenStruct.new(implicit_map?: false, id: :server_cipher) # rubocop:disable Style/OpenStructUse Net::SSH::Transport::CipherFactory.expects(:get) .with(options[:client_cipher] || "aes256-ctr", params.merge(iv: key("A"), key: key, encrypt: true)) .returns(cipher_client) key = key("D") Net::SSH::Transport::CipherFactory.expects(:get) .with(options[:server_cipher] || "aes256-ctr", params.merge(iv: key("B"), key: key, decrypt: true)) .returns(server_cipher) Net::SSH::Transport::HMAC.expects(:get).with(options[:client_hmac] || "hmac-sha2-256", key("E"), params).returns(:client_hmac) Net::SSH::Transport::HMAC.expects(:get).with(options[:server_hmac] || "hmac-sha2-256", key("F"), params).returns(:server_hmac) end def shared_secret @shared_secret ||= dh_512bits_bn end def session_id @session_id ||= "this is the session id" end def hashing_algorithm OpenSSL::Digest::SHA1 end def key(salt) hashing_algorithm.digest(shared_secret.to_ssh + session_id + salt + session_id) end def cipher(type, options = {}) Net::SSH::Transport::CipherFactory.get(type, options) end 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-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1", :string, options[:host_key] || "ssh-rsa,ssh-dss", :string, options[:encryption_client] || "aes256-ctr,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc", :string, options[:encryption_server] || "aes256-ctr,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,idea-cbc", :string, options[:hmac_client] || "hmac-sha2-256,hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96", :string, options[:hmac_server] || "hmac-sha2-256,hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96", :string, options[:compression_client] || "none,zlib@openssh.com,zlib", :string, options[:compression_server] || "none,zlib@openssh.com,zlib", :string, options[:language_client] || "", :string, options[:language_server] || "", :bool, options[:first_kex_follows]) end def assert_kexinit(buffer, options = {}) assert_equal KEXINIT, buffer.type assert_equal 16, buffer.read(16).length assert_equal options[:kex] || (x25519_kex + ec_kex + %w[diffie-hellman-group-exchange-sha256 diffie-hellman-group14-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] || "#{chacha_poly_cipher_str}aes256-ctr,aes192-ctr,aes128-ctr", buffer.read_string assert_equal options[:encryption_server] || "#{chacha_poly_cipher_str}aes256-ctr,aes192-ctr,aes128-ctr", buffer.read_string assert_equal options[:hmac_client] || 'hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-sha1', buffer.read_string assert_equal options[:hmac_server] || 'hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-sha1', buffer.read_string assert_equal options[:compression_client] || 'none,zlib@openssh.com,zlib', buffer.read_string assert_equal options[:compression_server] || 'none,zlib@openssh.com,zlib', buffer.read_string assert_equal options[:language_client] || '', buffer.read_string assert_equal options[:language_server] || '', buffer.read_string assert_equal options[:first_kex_follows] || false, buffer.read_bool end def assert_exchange_results assert algorithms.initialized? assert !algorithms.pending? assert !transport.client_options[:compression] assert !transport.server_options[:compression] assert_equal :client_cipher, transport.client_options[:cipher].id assert_equal :server_cipher, transport.server_options[:cipher].id assert_equal :client_hmac, transport.client_options[:hmac] assert_equal :server_hmac, transport.server_options[:hmac] end def algorithms(algorithms_options = {}, transport_options = {}) @algorithms ||= Net::SSH::Transport::Algorithms.new( transport(transport_options), algorithms_options ) end def transport(transport_options = {}) @transport ||= MockTransport.new( { user_known_hosts_file: '/dev/null', global_known_hosts_file: '/dev/null' }.merge(transport_options) ) end end end net-ssh-7.2.1/test/transport/test_cipher_factory.rb000066400000000000000000000340341454036133000225050ustar00rootroot00000000000000# encoding: ASCII-8BIT require_relative '../common' require 'net/ssh/transport/cipher_factory' module Transport class TestCipherFactory < NetSSHTest def self.if_supported?(name) yield if Net::SSH::Transport::CipherFactory.supported?(name) end def test_lengths_for_none assert_equal [0, 0], factory.get_lengths("none") assert_equal [0, 0], factory.get_lengths("bogus") end def test_lengths_for_blowfish_cbc assert_equal [16, 8], factory.get_lengths("blowfish-cbc") end if_supported?("idea-cbc") do def test_lengths_for_idea_cbc assert_equal [16, 8], factory.get_lengths("idea-cbc") end end def test_lengths_for_rijndael_cbc assert_equal [32, 16], factory.get_lengths("rijndael-cbc@lysator.liu.se") end def test_lengths_for_cast128_cbc assert_equal [16, 8], factory.get_lengths("cast128-cbc") end def test_lengths_for_3des_cbc assert_equal [24, 8], factory.get_lengths("3des-cbc") end def test_lengths_for_aes128_cbc assert_equal [16, 16], factory.get_lengths("aes128-cbc") end def test_lengths_for_aes192_cbc assert_equal [24, 16], factory.get_lengths("aes192-cbc") end def test_lengths_for_aes256_cbc assert_equal [32, 16], factory.get_lengths("aes256-cbc") end def test_lengths_for_3des_ctr assert_equal [24, 8], factory.get_lengths("3des-ctr") end def test_lengths_for_aes128_ctr assert_equal [16, 16], factory.get_lengths("aes128-ctr") end def test_lengths_for_aes192_ctr assert_equal [24, 16], factory.get_lengths("aes192-ctr") end def test_lengths_for_aes256_ctr assert_equal [32, 16], factory.get_lengths("aes256-ctr") end def test_lengths_for_blowfish_ctr assert_equal [16, 8], factory.get_lengths("blowfish-ctr") end def test_lengths_for_cast128_ctr assert_equal [16, 8], factory.get_lengths("cast128-ctr") end BLOWFISH_CBC = "\210\021\200\315\240_\026$\352\204g\233\244\242x\332e\370\001\327\224Nv@9_\323\037\252kb\037\036\237\375]\343/y\037\237\312Q\f7]\347Y\005\275%\377\0010$G\272\250B\265Nd\375\342\372\025r6}+Y\213y\n\237\267\\\374^\346BdJ$\353\220Ik\023<\236&H\277=\225" def test_blowfish_cbc_for_encryption assert_equal BLOWFISH_CBC, encrypt("blowfish-cbc") end def test_blowfish_cbc_for_decryption assert_equal TEXT, decrypt("blowfish-cbc", BLOWFISH_CBC) end if_supported?("idea-cbc") do IDEA_CBC = "W\234\017G\231\b\357\370H\b\256U]\343M\031k\233]~\023C\363\263\177\262-\261\341$\022\376mv\217\322\b\2763\270H\306\035\343z\313\312\3531\351\t\201\302U\022\360\300\354ul7$z\320O]\360g\024\305\005`V\005\335A\351\312\270c\320D\232\eQH1\340\265\2118\031g*\303v" def test_idea_cbc_for_encryption assert_equal IDEA_CBC, encrypt("idea-cbc") end def test_idea_cbc_for_decryption assert_equal TEXT, decrypt("idea-cbc", IDEA_CBC) end end RIJNDAEL = "$\253\271\255\005Z\354\336&\312\324\221\233\307Mj\315\360\310Fk\241EfN\037\231\213\361{'\310\204\347I\343\271\005\240`\325;\034\346uM>#\241\231C`\374\261\vo\226;Z\302:\b\250\366T\330\\#V\330\340\226\363\374!\bm\266\232\207!\232\347\340\t\307\370\356z\236\343=v\210\206y" def test_rijndael_cbc_for_encryption assert_equal RIJNDAEL, encrypt("rijndael-cbc@lysator.liu.se") end def test_rijndael_cbc_for_decryption assert_equal TEXT, decrypt("rijndael-cbc@lysator.liu.se", RIJNDAEL) end CAST128_CBC = "qW\302\331\333P\223t[9 ~(sg\322\271\227\272\022I\223\373p\255>k\326\314\260\2003\236C_W\211\227\373\205>\351\334\322\227\223\e\236\202Ii\032!P\214\035:\017\360h7D\371v\210\264\317\236a\262w1\2772\023\036\331\227\240:\f/X\351\324I\t[x\350\323E\2301\016m" def test_cast128_cbc_for_encryption assert_equal CAST128_CBC, encrypt("cast128-cbc") end def test_cast128_cbc_for_decryption assert_equal TEXT, decrypt("cast128-cbc", CAST128_CBC) end TRIPLE_DES_CBC = "\322\252\216D\303Q\375gg\367A{\177\313\3436\272\353%\223K?\257\206|\r&\353/%\340\336 \203E8rY\206\234\004\274\267\031\233T/{\"\227/B!i?[qGaw\306T\206\223\213n \212\032\244%]@\355\250\334\312\265E\251\017\361\270\357\230\274KP&^\031r+r%\370" def test_3des_cbc_for_encryption assert_equal TRIPLE_DES_CBC, encrypt("3des-cbc") end def test_3des_cbc_for_decryption assert_equal TEXT, decrypt("3des-cbc", TRIPLE_DES_CBC) end AES128_CBC = "k\026\350B\366-k\224\313\3277}B\035\004\200\035\r\233\024$\205\261\231Q\2214r\245\250\360\315\237\266hg\262C&+\321\346Pf\267v\376I\215P\327\345-\232&HK\375\326_\030<\a\276\212\303g\342C\242O\233\260\006\001a&V\345`\\T\e\236.\207\223l\233ri^\v\252\363\245" def test_aes128_cbc_for_encryption assert_equal AES128_CBC, encrypt("aes128-cbc") end def test_aes128_cbc_for_decryption assert_equal TEXT, decrypt("aes128-cbc", AES128_CBC) end AES192_CBC = "\256\017)x\270\213\336\303L\003f\235'jQ\3231k9\225\267\242\364C4\370\224\201\302~\217I\202\374\2167='\272\037\225\223\177Y\r\212\376(\275\n\3553\377\177\252C\254\236\016MA\274Z@H\331<\rL\317\205\323[\305X8\376\237=\374\352bH9\244\0231\353\204\352p\226\326~J\242" def test_aes192_cbc_for_encryption assert_equal AES192_CBC, encrypt("aes192-cbc") end def test_aes192_cbc_for_decryption assert_equal TEXT, decrypt("aes192-cbc", AES192_CBC) end AES256_CBC = "$\253\271\255\005Z\354\336&\312\324\221\233\307Mj\315\360\310Fk\241EfN\037\231\213\361{'\310\204\347I\343\271\005\240`\325;\034\346uM>#\241\231C`\374\261\vo\226;Z\302:\b\250\366T\330\\#V\330\340\226\363\374!\bm\266\232\207!\232\347\340\t\307\370\356z\236\343=v\210\206y" def test_aes256_cbc_for_encryption assert_equal AES256_CBC, encrypt("aes256-cbc") end def test_aes256_cbc_for_decryption assert_equal TEXT, decrypt("aes256-cbc", AES256_CBC) end BLOWFISH_CTR = "\xF5\xA6\x1E{\x8F(\x85G\xFAh\xDB\x19\xDC\xDF\xA2\x9A\x99\xDD5\xFF\xEE\x8BE\xE6\xB5\x92\x82\xE80\x91\x11`\xEF\x10\xED\xE9\xD3\vG\x0E\xAF\xB2K\t\xA4\xA6\x05\xD1\x17\x0Fl\r@E\x8DJ\e\xE63\x04\xB5\x05\x99Y\xCC\xFBb\x8FK+\x8C1v\xE4N\b?B\x06Rz\xA6\xB6N/b\xCE}\x83\x8DY\xD7\x92qU\x0F" def test_blowfish_ctr_for_encryption assert_equal BLOWFISH_CTR, encrypt("blowfish-ctr") end def test_blowfish_ctr_for_decryption assert_equal TEXT, decrypt("blowfish-ctr", BLOWFISH_CTR) end CAST128_CTR = "\xB5\xBB\xC3h\x80\x90`{\xD7I\x03\xE9\x80\xC4\xC4U\xE3@\xF1\xE9\xEFX\xDB6\xEE,\x8E\xC2\xE8\x89\x17\xBArf\x81\r\x96\xDC\xB1_'\x83hs\t7\xB8@\x17\xAA\xD9;\xE8\x8E\x94\xBD\xFF\xA4K\xA4\xFA\x8F-\xCD\bO\xD9I`\xE5\xC9H\x99\x14\xC5K\xC8\xEF\xEA#\x1D\xE5\x13O\xE1^P\xDC\x1C^qm\v|c@" def test_cast128_ctr_for_encryption assert_equal CAST128_CTR, encrypt("cast128-ctr") end def test_cast128_ctr_for_decryption assert_equal TEXT, decrypt("cast128-ctr", CAST128_CTR) end TRIPLE_DES_CTR = "\x90\xCD\b\xD2\xF1\x15:\x98\xF4sJ\xF0\xC9\xAA\xC5\xE3\xB4\xCFq\x93\xBAB\xF9v\xE1\xE7\x8B<\xBC\x97R\xDF?kK~Nw\xF3\x92`\x90]\xD9\xEF\x16\xC85V\x03C\xE9\x14\xF0\x86\xEB\x19\x85\x82\xF6\x16gz\x9B`\xB1\xCE\x80&?\xC8\xBD\xBC+\x91/)\xA5x\xBB\xCF\x06\x15#\e\xB3\xBD\x9B\x1F\xA7\xE2\xC7\xA3\xFC\x06\xC8" def test_3des_ctr_for_encryption if defined?(JRUBY_VERSION) # on JRuby, this test fails due to JRUBY-6558 puts "Skipping 3des-ctr tests for JRuby" else assert_equal TRIPLE_DES_CTR, encrypt("3des-ctr") end end def test_3des_ctr_for_decryption if defined?(JRUBY_VERSION) # on JRuby, this test fails due to JRUBY-6558 puts "Skipping 3des-ctr tests for JRuby" else assert_equal TEXT, decrypt("3des-ctr", TRIPLE_DES_CTR) end end AES128_CTR = "\x9D\xC7]R\x89\x01\xC4\x14\x00\xE7\xCEc`\x80\v\xC7\xF7\xBD\xD5#d\f\xC9\xB0\xDE\xA6\x8Aq\x10p\x8F\xBC\xFF\x8B\xB4\xC5\xB3\xF7,\xF7eO\x06Q]\x0F\x05\x86\xEC\xA6\xC8\x12\xE9\xC4\x9D0\xD3\x9AL\x192\xAA\xDFu\x0E\xECz\x7F~g\xCA\xEA\xBA\x80,\x83V\x10\xF6/\x04\xD2\x8A\x94\x94\xA9T>~\xD2\r\xE6\x0E\xA0q\xEF" AES128_CTR2 = "\xA5\xAA\xE3\xEC\xA7\xCCc\xFA~\x01\r\xD87\xE6\"\n6\x05\xD1\x9B\xC8_o\xD1i\xF6t\xD7[\xE5\x8B%>]\xD6\xC4<\x1DBd\xA9\x02\x9C\xEB\x89#\x955\xD6\x0F\xD0\x03\xF9\xC6\xD7\xB0@\e\\\xAB\xC0\xA9\xFB\x91\#{w\xADL\xF6'(\xCC\x14\xA2A\x16\xC1\x9C'\xD1\xBA'i\x88\x80\xF1\xA7E\x82\xA8\xC7@\xBA\a\xEA" def test_aes128_ctr_for_encryption assert_equal AES128_CTR, encrypt("aes128-ctr") end def test_aes128_ctr_for_encryption2 assert_equal [AES128_CTR, AES128_CTR2], encrypt2("aes128-ctr") end def test_aes128_ctr_for_decryption2 assert_equal [TEXT, TEXT2], decrypt2("aes128-ctr", [AES128_CTR, AES128_CTR2]) end def test_aes128_ctr_for_decryption assert_equal TEXT, decrypt("aes128-ctr", AES128_CTR) end AES192_CTR = "\xE2\xE7\x1FJ\xE5\xB09\xE1\xB7/\xB3\x95\xF2S\xCE\x8C\x93\x14mFY\x88*\xCE\b\xA6\x87W\xD7\xEC/\xC9\xB6\x9Ba\a\x8E\x89-\xD7\xB2j\a\xB3\a\x92f\"\x96\x8D\xBF\x01\t\xB8Y\xF3\x92\x01\xCC7\xB6w\xF9\"=u:\xA1\xD5*\n\x9E\xC7p\xDC\x11\a\x1C\x88y\xE8\x87`\xA6[fF\x9B\xACv\xA6\xDA1|#F" def test_aes192_ctr_for_encryption assert_equal AES192_CTR, encrypt("aes192-ctr") end def test_aes192_ctr_for_decryption assert_equal TEXT, decrypt("aes192-ctr", AES192_CTR) end AES256_CTR = "2\xB8\xE6\xC9\x95\xB4\x05\xD2\xC7+\x7F\x88\xEB\xD4\xA0\b\"\xBF\x9E\x85t\x19,\e\x90\x11\x04b\xC7\xEE$\xDE\xE6\xC5@G\xFEm\xE1u\x9B\au\xAF\xB5\xB8\x857\x87\x139u\xAC\x1A\xAB\fh\x8FiW~\xB8:\xA4\xA0#~\xC4\x89\xBA5#:\xFC\xC8\xE3\x9B\xF0A2\x87\x980\xD1\xE3\xBC'\xBE\x1E\n\x1A*B\x06\xF3\xCC" AES256_CTR2 = "\x13\xBF}\x93\xC3\xFCkw[\\\x8A\xDA\x9F\x85e3AH!\x19\xD9S(+x]B\x1A\x85):\x1Ce\xB1\xD1\x9F^\x8D\\\xFA\xFE\xC6\x9FDkm=?>.\x93\xA6O\x80\xB5o\xBE\xB5\\82\xEEWi\xFC<\xA7\xB6g\xBD\xF1\xA6\xAA\xE7\xD3_&N\xC9[K8\xE61L\xD1\xC0\xC8\x02\b\xE7\xF1!\xA5\x04\xCA" def test_aes256_ctr_for_encryption assert_equal AES256_CTR, encrypt("aes256-ctr") end def test_aes256_ctr_for_encryption2 assert_equal [AES256_CTR, AES256_CTR2], encrypt2("aes256-ctr") end def test_aes256_ctr_for_decryption assert_equal TEXT, decrypt("aes256-ctr", AES256_CTR) end def test_none_for_encryption assert_equal TEXT, encrypt("none").strip end def test_none_for_decryption assert_equal TEXT, decrypt("none", TEXT) end CHACHA20POLY1305 = ["73cbe4dd0d6495d2048bb1ba5f01f055f6271efaa5e56b9de3586bd116ededab481dc7833d5b6aaa7f7827d49c82185a02f62262d9efae8e6973a4e98251dcbbf2beebfc29c40a75192604729b6f7d412add8fe1a730e4b21c8aa1c73786090bd46bb66121c888b3e628c3f5a9a6a0738f8fcc2a611ef97bd0a665eb565ba0247d"].pack('H*') OPTIONS_CHACHAPOLY = { iv: "ABC", key: "abcd" * 16, digester: OpenSSL::Digest::MD5, shared: "1234567890123456780", hash: '!@#$%#$^%$&^&%#$@$' } def encrypt_cha_cha_poly(type) cipher = factory.get(type, OPTIONS_CHACHAPOLY.merge(encrypt: true)) sequence_number = 1 result = cipher.update_cipher_mac(TEXT.dup[0...64], sequence_number) result << cipher.update_cipher_mac(TEXT.dup[64...], sequence_number + 1) result end def decrypt_chacha_poly(type, data) cipher = factory.get(type, OPTIONS_CHACHAPOLY.merge(decrypt: true)) result = "" sequence_number = 1 pos = 0 2.times do encrypted_len = data[pos...pos + 4] len = cipher.read_length(encrypted_len, sequence_number) encrypted_data = data[pos + 4...pos + 4 + len] mac_data = data[pos + 4 + len...pos + (4 + len + cipher.mac_length)] result << cipher.read_and_mac(encrypted_len + encrypted_data, mac_data.dup, sequence_number) sequence_number += 1 pos += 4 + len + cipher.mac_length end return result.strip end def test_chacha20_poly1305_for_encryption skip "TODO: chacha20-poly1305 not loaded" unless Net::SSH::Transport::ChaCha20Poly1305CipherLoader::LOADED ret = encrypt_cha_cha_poly("chacha20-poly1305@openssh.com") assert_equal CHACHA20POLY1305, ret end def test_chacha20_poly1305_for_decryption skip "TODO: chacha20-poly1305 not loaded" unless Net::SSH::Transport::ChaCha20Poly1305CipherLoader::LOADED assert_equal TEXT, decrypt_chacha_poly("chacha20-poly1305@openssh.com", CHACHA20POLY1305) end private TEXT = "But soft! What light through yonder window breaks? It is the east, and Juliet is the sun!" TEXT2 = "2But soft! What light through yonder window breaks? It is the east, and Juliet is the sun!" OPTIONS = { iv: "ABC", key: "abc", digester: OpenSSL::Digest::MD5, shared: "1234567890123456780", hash: '!@#$%#$^%$&^&%#$@$' } def factory Net::SSH::Transport::CipherFactory end def encrypt(type) cipher = factory.get(type, OPTIONS.merge(encrypt: true)) padding = TEXT.length % cipher.block_size result = cipher.update(TEXT.dup) result << cipher.update(" " * (cipher.block_size - padding)) if padding > 0 result << cipher.final end def encrypt2(type) cipher = factory.get(type, OPTIONS.merge(encrypt: true)) padding = TEXT.length % cipher.block_size result = cipher.update(TEXT.dup) result << cipher.update(" " * (cipher.block_size - padding)) if padding > 0 result << cipher.final cipher.reset cipher.iv = "0123456789123456" padding = TEXT2.length % cipher.block_size result2 = cipher.update(TEXT2.dup) result2 << cipher.update(" " * (cipher.block_size - padding)) if padding > 0 result2 << cipher.final [result, result2] end def decrypt(type, data) cipher = factory.get(type, OPTIONS.merge(decrypt: true)) result = cipher.update(data.dup) result << cipher.final result.strip end def decrypt2(type, datas) cipher = factory.get(type, OPTIONS.merge(decrypt: true)) result = cipher.update(datas[0].dup) result << cipher.final first = result.strip cipher.reset result = cipher.update(datas[1].dup) result << cipher.final second = result.strip [first, second] end end end net-ssh-7.2.1/test/transport/test_hmac.rb000066400000000000000000000022611454036133000204110ustar00rootroot00000000000000require 'common' require 'net/ssh/transport/hmac' module Transport class TestHMAC < NetSSHTest 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] hmac = Net::SSH::Transport::HMAC.get(name, key, { shared: "123", hash: "^&*", digester: OpenSSL::Digest::SHA1 }) assert_instance_of Net::SSH::Transport::HMAC::MAP[name], hmac assert_equal key, hmac.key end define_method("test_key_length_with_#{method}_returns_correct_key_length") do assert_equal Net::SSH::Transport::HMAC::MAP[name].key_length, Net::SSH::Transport::HMAC.key_length(name) end end def test_get_with_unrecognized_hmac_raises_argument_error assert_raises(ArgumentError) do Net::SSH::Transport::HMAC.get("bogus") end end def test_key_length_with_unrecognized_hmac_raises_argument_error assert_raises(ArgumentError) do Net::SSH::Transport::HMAC.get("bogus") end end end end net-ssh-7.2.1/test/transport/test_identity_cipher.rb000066400000000000000000000014031454036133000226610ustar00rootroot00000000000000require '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 end def test_encrypt_should_return_self assert_equal cipher, cipher.encrypt end def test_decrypt_should_return_self assert_equal cipher, cipher.decrypt end def test_update_should_return_argument assert_equal "hello, world", cipher.update("hello, world") end def test_final_should_return_empty_string assert_equal "", cipher.final end def test_name_should_be_identity assert_equal "identity", cipher.name end private def cipher Net::SSH::Transport::IdentityCipher end end end net-ssh-7.2.1/test/transport/test_packet_stream.rb000066400000000000000000003025131454036133000223260ustar00rootroot00000000000000# encoding: ASCII-8BIT 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 def test_client_name_when_getnameinfo_works stream.expects(:getsockname).returns(:sockaddr) Socket.expects(:getnameinfo).with(:sockaddr, Socket::NI_NAMEREQD).returns(["net.ssh.test"]) assert_equal "net.ssh.test", stream.client_name end def test_client_name_when_getnameinfo_fails_first_and_then_works stream.expects(:getsockname).returns(:sockaddr) Socket.expects(:getnameinfo).with(:sockaddr, Socket::NI_NAMEREQD).raises(SocketError) Socket.expects(:getnameinfo).with(:sockaddr).returns(["1.2.3.4"]) assert_equal "1.2.3.4", stream.client_name end def test_client_name_when_getnameinfo_fails_but_gethostbyname_works stream.expects(:getsockname).returns(:sockaddr) Socket.expects(:getnameinfo).with(:sockaddr, Socket::NI_NAMEREQD).raises(SocketError) Socket.expects(:getnameinfo).with(:sockaddr).raises(SocketError) Socket.expects(:gethostname).returns(:hostname) Socket.expects(:gethostbyname).with(:hostname).returns(["net.ssh.test"]) assert_equal "net.ssh.test", stream.client_name end def test_client_name_when_getnameinfo_and_gethostbyname_all_fail stream.expects(:getsockname).returns(:sockaddr) Socket.expects(:getnameinfo).with(:sockaddr, Socket::NI_NAMEREQD).raises(SocketError) Socket.expects(:getnameinfo).with(:sockaddr).raises(SocketError) Socket.expects(:gethostname).returns(:hostname) Socket.expects(:gethostbyname).with(:hostname).raises(SocketError) assert_equal "unknown", stream.client_name end def test_peer_ip_should_query_socket_for_info_about_peer stream.expects(:getpeername).returns(:sockaddr) Socket.expects(:getnameinfo).with(:sockaddr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).returns(["1.2.3.4"]) assert_equal "1.2.3.4", stream.peer_ip end def test_peer_ip_should_return_no_hostip_when_socket_has_no_peername assert_equal false, stream.respond_to?(:getpeername) assert_equal Net::SSH::Transport::PacketStream::PROXY_COMMAND_HOST_IP, stream.peer_ip assert_equal '', stream.peer_ip end def test_available_for_read_should_return_nontrue_when_select_fails IO.expects(:select).returns(nil) assert !stream.available_for_read? end def test_available_for_read_should_return_nontrue_when_self_is_not_ready IO.expects(:select).with([stream], nil, nil, 0).returns([[], [], []]) assert !stream.available_for_read? end def test_available_for_read_should_return_true_when_self_is_ready IO.expects(:select).with([stream], nil, nil, 0).returns([[self], [], []]) assert stream.available_for_read? end def test_cleanup_should_delegate_cleanup_to_client_and_server_states stream.client.expects(:cleanup) stream.server.expects(:cleanup) stream.cleanup end def test_if_needs_rekey_should_not_yield_if_neither_client_nor_server_states_need_rekey stream.if_needs_rekey? { flunk "shouldn't need rekey" } assert(true) end def test_if_needs_rekey_should_yield_and_cleanup_if_client_needs_rekey stream.client.stubs(:needs_rekey?).returns(true) stream.client.expects(:reset!) stream.server.expects(:reset!).never rekeyed = false stream.if_needs_rekey? { rekeyed = true } assert(rekeyed) end def test_if_needs_rekey_should_yield_and_cleanup_if_server_needs_rekey stream.server.stubs(:needs_rekey?).returns(true) stream.server.expects(:reset!) stream.client.expects(:reset!).never rekeyed = false stream.if_needs_rekey? { rekeyed = true } assert(rekeyed) end def test_if_needs_rekey_should_yield_and_cleanup_if_both_need_rekey stream.server.stubs(:needs_rekey?).returns(true) stream.client.stubs(:needs_rekey?).returns(true) stream.server.expects(:reset!) stream.client.expects(:reset!) rekeyed = false stream.if_needs_rekey? { rekeyed = true } assert(rekeyed) end def test_next_packet_should_not_block_by_default IO.expects(:select).returns(nil) assert_nothing_raised do Timeout.timeout(1) { stream.next_packet } end end def test_next_packet_should_return_nil_when_non_blocking_and_not_ready IO.expects(:select).returns(nil) assert_nil stream.next_packet(:nonblock) end def test_next_packet_should_return_nil_when_non_blocking_and_partial_read IO.expects(:select).returns([[stream]]) stream.expects(:recv).returns([8].pack("N")) assert_nil stream.next_packet(:nonblock) assert !stream.read_buffer.empty? end def test_next_packet_should_return_packet_when_non_blocking_and_full_read IO.expects(:select).returns([[stream]]) stream.expects(:recv).returns(packet) packet = stream.next_packet(:nonblock) assert_not_nil packet assert_equal DEBUG, packet.type end def test_next_packet_should_eventually_return_packet_when_non_blocking_and_partial_read IO.stubs(:select).returns([[stream]]) stream.stubs(:recv).returns(packet[0, 10], packet[10..-1]) assert_nil stream.next_packet(:nonblock) packet = stream.next_packet(:nonblock) assert_not_nil packet assert_equal DEBUG, packet.type end def test_nonblocking_next_packet_should_raise IO.stubs(:select).returns([[stream]]) stream.stubs(:recv).returns("") assert_raises(Net::SSH::Disconnect) { stream.next_packet(:nonblock) } end def test_nonblocking_next_packet_should_return_packet_before_raise IO.stubs(:select).returns([[stream]]) stream.send(:input).append(packet) stream.stubs(:recv).returns("") packet = stream.next_packet(:nonblock) assert_not_nil packet assert_equal DEBUG, packet.type assert_raises(Net::SSH::Disconnect) { stream.next_packet(:nonblock) } end def test_next_packet_should_block_when_requested_until_entire_packet_is_available IO.stubs(:select).returns([[stream]]) stream.stubs(:recv).returns(packet[0, 10], packet[10, 20], packet[20..-1]) packet = stream.next_packet(:block) assert_not_nil packet assert_equal DEBUG, packet.type end def test_next_packet_when_blocking_should_fail_when_fill_could_not_read_any_data IO.stubs(:select).returns([[stream]]) stream.stubs(:recv).returns("") assert_raises(Net::SSH::Disconnect) { stream.next_packet(:block) } end def test_next_packet_when_blocking_times_out IO.expects(:select).with([stream], nil, nil, 7).returns(nil) assert_raises(Net::SSH::ConnectionTimeout) { stream.next_packet(:block, 7) } end def test_next_packet_fails_with_invalid_argument assert_raises(ArgumentError) { stream.next_packet("invalid") } end def test_send_packet_should_enqueue_and_send_data_immediately stream.expects(:send).times(3).with { |a, b| a == stream.write_buffer && b == 0 }.returns(15) IO.expects(:select).times(2).returns([[], [stream]]) stream.send_packet(ssh_packet) assert !stream.pending_write? end def test_enqueue_short_packet_should_ensure_packet_is_at_least_16_bytes_long packet = Net::SSH::Buffer.from(:byte, 0) stream.enqueue_packet(packet) # 12 originally, plus the block-size (8), plus the 4-byte length field assert_equal 24, stream.write_buffer.length end def test_enqueue_utf_8_packet_should_ensure_packet_length_is_in_bytes_and_multiple_of_block_length packet = Net::SSH::Buffer.from(:string, "\u2603") # Snowman is 3 bytes stream.enqueue_packet(packet) # When bytesize is measured wrong using length, the result is off by 2. # With length instead of bytesize, you get 26 length buffer. assert_equal 0, stream.write_buffer.length % 8 end PACKETS = { 'chacha20-poly1305@openssh.com' => { 'implicit' => { false => ["5aa01d1e3eff7c277d19f111a384b229fec8652db616d9350d6ef9f51f2011637b60d406673fffad5a647eba"].pack('H*'), :standard => ["5aa01d263f83e1451c7d981526aa8e03b3ec44857a5dde471d76ba92fd92c9a77911c43ca96a13e37a5b1a346508016793f4a57a"].pack('H*') } }, "3des-cbc" => { "hmac-md5" => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, \000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200\354=g\361\271[E\265\217\316\314\b\202\235\226\334" }, "hmac-md5-96" => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, \000\032w\312\t\306\374\271\345p\215\224", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200\354=g\361\271[E\265\217\316\314\b" }, "hmac-sha1" => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, \004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200\2117U\266\3444(\235\034\023\377\376\335\301\253rI\215W\311" }, "hmac-sha1-96" => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, \004\a\200\n\004\202z\270\236\261\330m", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200\2117U\266\3444(\235\034\023\377\376" }, "hmac-ripemd160" => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "hmac-ripemd160@openssh.com" => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "none" => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, ", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200" } }, "aes128-cbc" => { "hmac-md5" => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY\000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251-\345\b\025\242#\336P8\343\361\263\\\241\326\311" }, "hmac-md5-96" => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY\000\032w\312\t\306\374\271\345p\215\224", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251-\345\b\025\242#\336P8\343\361\263" }, "hmac-sha1" => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY\004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251yC\272\314@\301\n\346$\223\367\r\026\366\375(i'\212\351" }, "hmac-sha1-96" => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY\004\a\200\n\004\202z\270\236\261\330m", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251yC\272\314@\301\n\346$\223\367\r" }, "hmac-ripemd160" => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NYF\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251\3044\024\343q\356\023\032\262\201\e9\213d\265>^{\300\320" }, "hmac-ripemd160@openssh.com" => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NYF\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251\3044\024\343q\356\023\032\262\201\e9\213d\265>^{\300\320" }, "none" => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251" } }, "aes192-cbc" => { "hmac-md5" => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003\000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320D-\345\b\025\242#\336P8\343\361\263\\\241\326\311" }, "hmac-md5-96" => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003\000\032w\312\t\306\374\271\345p\215\224", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320D-\345\b\025\242#\336P8\343\361\263" }, "hmac-sha1" => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003\004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320DyC\272\314@\301\n\346$\223\367\r\026\366\375(i'\212\351" }, "hmac-sha1-96" => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003\004\a\200\n\004\202z\270\236\261\330m", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320DyC\272\314@\301\n\346$\223\367\r" }, "hmac-ripemd160" => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320D\3044\024\343q\356\023\032\262\201\e9\213d\265>^{\300\320" }, "hmac-ripemd160@openssh.com" => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320D\3044\024\343q\356\023\032\262\201\e9\213d\265>^{\300\320" }, "none" => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320D" } }, "aes256-cbc" => { "hmac-md5" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340\000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365-\345\b\025\242#\336P8\343\361\263\\\241\326\311" }, "hmac-md5-96" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340\000\032w\312\t\306\374\271\345p\215\224", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365-\345\b\025\242#\336P8\343\361\263" }, "hmac-sha1" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340\004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365yC\272\314@\301\n\346$\223\367\r\026\366\375(i'\212\351" }, "hmac-sha1-96" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340\004\a\200\n\004\202z\270\236\261\330m", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365yC\272\314@\301\n\346$\223\367\r" }, "hmac-ripemd160" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365\3044\024\343q\356\023\032\262\201\e9\213d\265>^{\300\320" }, "hmac-ripemd160@openssh.com" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365\3044\024\343q\356\023\032\262\201\e9\213d\265>^{\300\320" }, "none" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365" } }, "blowfish-cbc" => { "hmac-md5" => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)\000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006\354=g\361\271[E\265\217\316\314\b\202\235\226\334" }, "hmac-md5-96" => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)\000\032w\312\t\306\374\271\345p\215\224", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006\354=g\361\271[E\265\217\316\314\b" }, "hmac-sha1" => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)\004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006\2117U\266\3444(\235\034\023\377\376\335\301\253rI\215W\311" }, "hmac-sha1-96" => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)\004\a\200\n\004\202z\270\236\261\330m", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006\2117U\266\3444(\235\034\023\377\376" }, "hmac-ripemd160" => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "hmac-ripemd160@openssh.com" => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "none" => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006" } }, "cast128-cbc" => { "hmac-md5" => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376\000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325\354=g\361\271[E\265\217\316\314\b\202\235\226\334" }, "hmac-md5-96" => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376\000\032w\312\t\306\374\271\345p\215\224", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325\354=g\361\271[E\265\217\316\314\b" }, "hmac-sha1" => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376\004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325\2117U\266\3444(\235\034\023\377\376\335\301\253rI\215W\311" }, "hmac-sha1-96" => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376\004\a\200\n\004\202z\270\236\261\330m", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325\2117U\266\3444(\235\034\023\377\376" }, "hmac-ripemd160" => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "hmac-ripemd160@openssh.com" => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "none" => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325" } }, "idea-cbc" => { "hmac-md5" => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H\000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317\354=g\361\271[E\265\217\316\314\b\202\235\226\334" }, "hmac-md5-96" => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H\000\032w\312\t\306\374\271\345p\215\224", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317\354=g\361\271[E\265\217\316\314\b" }, "hmac-sha1" => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H\004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317\2117U\266\3444(\235\034\023\377\376\335\301\253rI\215W\311" }, "hmac-sha1-96" => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H\004\a\200\n\004\202z\270\236\261\330m", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317\2117U\266\3444(\235\034\023\377\376" }, "hmac-ripemd160" => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 HF\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "hmac-ripemd160@openssh.com" => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 HF\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "none" => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317" } }, "3des-ctr" => { "hmac-md5" => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94\xFB\xF3\v\xB1", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B\xEC=g\xF1\xB9[E\xB5\x8F\xCE\xCC\b\x82\x9D\x96\xDC" }, "hmac-md5-96" => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B\xEC=g\xF1\xB9[E\xB5\x8F\xCE\xCC\b" }, "hmac-sha1" => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m\xBD\x05\f\x82g\xB0g\xFE", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B\x897U\xB6\xE44(\x9D\x1C\x13\xFF\xFE\xDD\xC1\xABrI\x8DW\xC9" }, "hmac-sha1-96" => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B\x897U\xB6\xE44(\x9D\x1C\x13\xFF\xFE" }, "hmac-ripemd160" => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1F\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B)U\xBD\x03U\xDB\x95\x91Y)\xCF\xAE\xA0\xA6\x000\xE9\x1A\xF3Y" }, "hmac-ripemd160@openssh.com" => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1F\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B)U\xBD\x03U\xDB\x95\x91Y)\xCF\xAE\xA0\xA6\x000\xE9\x1A\xF3Y" }, "none" => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B" } }, "blowfish-ctr" => { "hmac-md5" => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94\xFB\xF3\v\xB1", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9\xEC=g\xF1\xB9[E\xB5\x8F\xCE\xCC\b\x82\x9D\x96\xDC" }, "hmac-md5-96" => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9\xEC=g\xF1\xB9[E\xB5\x8F\xCE\xCC\b" }, "hmac-sha1" => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m\xBD\x05\f\x82g\xB0g\xFE", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9\x897U\xB6\xE44(\x9D\x1C\x13\xFF\xFE\xDD\xC1\xABrI\x8DW\xC9" }, "hmac-sha1-96" => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9\x897U\xB6\xE44(\x9D\x1C\x13\xFF\xFE" }, "hmac-ripemd160" => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0F\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9)U\xBD\x03U\xDB\x95\x91Y)\xCF\xAE\xA0\xA6\x000\xE9\x1A\xF3Y" }, "hmac-ripemd160@openssh.com" => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0F\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9)U\xBD\x03U\xDB\x95\x91Y)\xCF\xAE\xA0\xA6\x000\xE9\x1A\xF3Y" }, "none" => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9" } }, "aes128-ctr" => { "hmac-md5" => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94\xFB\xF3\v\xB1", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15-\xE5\b\x15\xA2#\xDEP8\xE3\xF1\xB3\\\xA1\xD6\xC9" }, "hmac-md5-96" => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15-\xE5\b\x15\xA2#\xDEP8\xE3\xF1\xB3" }, "hmac-sha1" => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m\xBD\x05\f\x82g\xB0g\xFE", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15yC\xBA\xCC@\xC1\n\xE6$\x93\xF7\r\x16\xF6\xFD(i'\x8A\xE9" }, "hmac-sha1-96" => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15yC\xBA\xCC@\xC1\n\xE6$\x93\xF7\r" }, "hmac-ripemd160" => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFDF\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15\xC44\x14\xE3q\xEE\x13\x1A\xB2\x81\e9\x8Bd\xB5>^{\xC0\xD0" }, "hmac-ripemd160@openssh.com" => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFDF\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15\xC44\x14\xE3q\xEE\x13\x1A\xB2\x81\e9\x8Bd\xB5>^{\xC0\xD0" }, "none" => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15" } }, "aes192-ctr" => { "hmac-md5" => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94\xFB\xF3\v\xB1", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1-\xE5\b\x15\xA2#\xDEP8\xE3\xF1\xB3\\\xA1\xD6\xC9" }, "hmac-md5-96" => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1-\xE5\b\x15\xA2#\xDEP8\xE3\xF1\xB3" }, "hmac-sha1" => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m\xBD\x05\f\x82g\xB0g\xFE", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1yC\xBA\xCC@\xC1\n\xE6$\x93\xF7\r\x16\xF6\xFD(i'\x8A\xE9" }, "hmac-sha1-96" => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1yC\xBA\xCC@\xC1\n\xE6$\x93\xF7\r" }, "hmac-ripemd160" => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91F\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1\xC44\x14\xE3q\xEE\x13\x1A\xB2\x81\e9\x8Bd\xB5>^{\xC0\xD0" }, "hmac-ripemd160@openssh.com" => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91F\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1\xC44\x14\xE3q\xEE\x13\x1A\xB2\x81\e9\x8Bd\xB5>^{\xC0\xD0" }, "none" => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1" } }, "aes256-ctr" => { "hmac-md5" => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94\xFB\xF3\v\xB1", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\n-\xE5\b\x15\xA2#\xDEP8\xE3\xF1\xB3\\\xA1\xD6\xC9" }, "hmac-md5-96" => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\n-\xE5\b\x15\xA2#\xDEP8\xE3\xF1\xB3" }, "hmac-sha1" => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m\xBD\x05\f\x82g\xB0g\xFE", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\nyC\xBA\xCC@\xC1\n\xE6$\x93\xF7\r\x16\xF6\xFD(i'\x8A\xE9" }, "hmac-sha1-96" => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\nyC\xBA\xCC@\xC1\n\xE6$\x93\xF7\r" }, "hmac-ripemd160" => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\rF\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\n\xC44\x14\xE3q\xEE\x13\x1A\xB2\x81\e9\x8Bd\xB5>^{\xC0\xD0" }, "hmac-ripemd160@openssh.com" => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\rF\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\n\xC44\x14\xE3q\xEE\x13\x1A\xB2\x81\e9\x8Bd\xB5>^{\xC0\xD0" }, "none" => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\n" } }, "cast128-ctr" => { "hmac-md5" => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94\xFB\xF3\v\xB1", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA\xEC=g\xF1\xB9[E\xB5\x8F\xCE\xCC\b\x82\x9D\x96\xDC" }, "hmac-md5-96" => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7\x00\x1Aw\xCA\t\xC6\xFC\xB9\xE5p\x8D\x94", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA\xEC=g\xF1\xB9[E\xB5\x8F\xCE\xCC\b" }, "hmac-sha1" => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m\xBD\x05\f\x82g\xB0g\xFE", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA\x897U\xB6\xE44(\x9D\x1C\x13\xFF\xFE\xDD\xC1\xABrI\x8DW\xC9" }, "hmac-sha1-96" => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7\x04\a\x80\n\x04\x82z\xB8\x9E\xB1\xD8m", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA\x897U\xB6\xE44(\x9D\x1C\x13\xFF\xFE" }, "hmac-ripemd160" => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7F\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA)U\xBD\x03U\xDB\x95\x91Y)\xCF\xAE\xA0\xA6\x000\xE9\x1A\xF3Y" }, "hmac-ripemd160@openssh.com" => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7F\xC3\xC7\x87\xA5\x86\xD5~\xCD(\xF8\xD9\xCB\xC5\vHI\xCAL\x8E", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA)U\xBD\x03U\xDB\x95\x91Y)\xCF\xAE\xA0\xA6\x000\xE9\x1A\xF3Y" }, "none" => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA" } }, "none" => { "hmac-md5" => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212\000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^\354=g\361\271[E\265\217\316\314\b\202\235\226\334" }, "hmac-md5-96" => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212\000\032w\312\t\306\374\271\345p\215\224", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^\354=g\361\271[E\265\217\316\314\b" }, "hmac-sha1-96" => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212\004\a\200\n\004\202z\270\236\261\330m", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^\2117U\266\3444(\235\034\023\377\376" }, "hmac-sha1" => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212\004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^\2117U\266\3444(\235\034\023\377\376\335\301\253rI\215W\311" }, "hmac-ripemd160" => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "hmac-ripemd160@openssh.com" => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^)U\275\003U\333\225\221Y)\317\256\240\246\0000\351\032\363Y" }, "none" => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^" } }, "rijndael-cbc@lysator.liu.se" => { "hmac-md5" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340\000\032w\312\t\306\374\271\345p\215\224\373\363\v\261", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365-\345\b\025\242#\336P8\343\361\263\\\241\326\311" }, "hmac-md5-96" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340\000\032w\312\t\306\374\271\345p\215\224", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365-\345\b\025\242#\336P8\343\361\263" }, "hmac-sha1" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340\004\a\200\n\004\202z\270\236\261\330m\275\005\f\202g\260g\376", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365yC\272\314@\301\n\346$\223\367\r\026\366\375(i'\212\351" }, "hmac-sha1-96" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340\004\a\200\n\004\202z\270\236\261\330m", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365yC\272\314@\301\n\346$\223\367\r" }, "hmac-ripemd160" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365\3044\024\343q\356\023\032\262\201\e9\213d\265>^{\300\320" }, "hmac-ripemd160@openssh.com" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340F\303\307\207\245\206\325~\315(\370\331\313\305\vHI\312L\216", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365\3044\024\343q\356\023\032\262\201\e9\213d\265>^{\300\320" }, "none" => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365" } } } sha2_packets = { '3des-cbc' => { 'hmac-sha2-256' => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, 7{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200\367F\231v\265o\f9$\224\201\e\364+\226H\374\377=\ts\202`\026\e,\347\t\217\206t\307" }, 'hmac-sha2-256-96' => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, 7{\320\316\365Wy\"c\036y\260", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200\367F\231v\265o\f9$\224\201\e" }, 'hmac-sha2-512' => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, #/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200Q\3112O\223\361\216\235\022\216\0162\256\343\214\320\v\321\366/$\017]2\302\3435\217\324\245\037\301\225p\270\221c\307\302u\213b 4#\202PFI\371\267l\374\311\001\262z(\335|\334\2446\226" }, 'hmac-sha2-512-96' => { false => "\003\352\031\261k\243\200\204\301\203]!\a\306\217\201\a[^\304\317\322\264\265~\361\017\n\205\272, #/\317\000\340I\274\363_\225U*", :standard => "\317\222v\316\234<\310\377\310\034\346\351\020:\025{\372PDS\246\344\312J\364\301\n\262\r<\037\231Mu\031\240\255\026\362\200Q\3112O\223\361\216\235\022\216\0162" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00\x18n\v\xF0\xD3\xCE,\xD5)\xEC\xE4\xA0\xCC\x9DK\x7F\x99\x03\xCE\x9E\x19\xBD\xFA\xED\x93|\xC0Y\x86\xE4\xA7\x91\x9B^\x97\x91\xBD\xEA+\xA2\x1FE\x7FK\xA2\f\xD2\x8A\x14\xD5\xB7\xD1\xF3\xE8\x95\xE7C", :standard => "\x00\x00\x00 \xF51]F\xB2\x1E\xF8CM=\x85\xDC\x86w\xE0\x19s\x81\xF8\xBBT\x11\xC4\x81\x9A\xC5-tc\xE7\n\xC8\xA8l\xE5Y\t4\xFB\xD5\xCC\xF1\xF8\e\xE8\xC54\xDC\x84\xFC\e8pl\xD6\xF7\xF5_\xFA\xE9Cp\xC0P" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00\x18n\v\xF0\xD3\xCE,\xD5)\xEC\xE4\xA0\xCC\x9DK\x7F\x99\x03\xCE\x9E\x19\xBD\xFA\xED\x93[\x90|\xD8\e\xE1u\x9D1t\x91\e\xB6K\\WH\x97\xE4\x8F0\xED\xF6y\xA5(\x15a\xCB\xDA\xA0\x05\x8A)V\x8E\x9CLN\xA3\x95g7v($\x86l\xE691\xEB\xA5\xFC\x1EG\x91\xCA*\xD7\x01\xBE\xAA\"", :standard => "\x00\x00\x00 \xF51]F\xB2\x1E\xF8CM=\x85\xDC\x86w\xE0\x19s\x81\xF8\xBBT\x11\xC4\x81\x9A\xC5-tc\xE7\n\xC8~;\x97\t\x83N|\xA8h8\xBD\x8F\xA9v<\"\xE1\xC5\xE0\x81)\xEC^\xD1\xC2\n&\xC4r\xA6\xCFPr\xC8VB\r\x01a\x98?\x97\xDB<\xCD{\xC2@\xA8%\xE1\xD9\xEE^{9\xACwL\e\x8D\x96s\xB7" } }, 'aes128-cbc' => { 'hmac-sha2-256' => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY7{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251\373\035\334\340M\032B\307\324\232\211m'\347k\253\371\341\326\254\356\263[\2412\302R\320\274\365\255\003" }, 'hmac-sha2-256-96' => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY7{\320\316\365Wy\"c\036y\260", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251\373\035\334\340M\032B\307\324\232\211m" }, 'hmac-sha2-512' => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY#/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251N\005f\275u\230\344xF\354+RSTS\360\235\004\311$cW\357o\"fy\031\321yX\tYK\347\363kd\a\022\307r\177[ \274\0164\222\300 \037\330<\264\001^\246\337\004\365\233\202\310" }, 'hmac-sha2-512-96' => { false => "\240\016\243k]0\330\253\030\320\334\261(\034E\211\230#\326\374\267\311O\211E(\234\325n\306NY#/\317\000\340I\274\363_\225U*", :standard => "\273\367\324\032\3762\334\026\r\246\342\022\016\325\024\270.\273\005\314\036\312\211\261\037A\361\362:W\316\352K\204\216b\2124>A\265g\331\177\233dK\251N\005f\275u\230\344xF\354+R" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00 \xA8\xD1\xBB\xFDLW\xC57k\x8B{\xE0^'\x8B\xB6s\x87\x87\xAE\x9D\xC0\x8B\x18H\xF2qe\xA4\xF06\xBF\xE7A\xEF\x8CJ\x14\xBB\xECC.\xF3\x98Rn\x9A$\xF9W\x16\xBC\xEE\xDDY{>\x1F\xE0$\xFCao<", :standard => "\x00\x00\x00 )\x10,\x8C\x85\r\a\x02I\xE3\xAE\xF6\xA7+\xEC\x11\xF9\x8C\xB9\xAE\xAAe6\x9F\x9Cn\xF2\x7F\xA6\xE8\xE6\xDB`\xB3\xCD\xF6\"\x95\xEA\x9A\xAA9\xB8\x91&\xA0\xCCV\x87\x97\xE6cU\x03\xC0(\xEC6\x9F\t\xE9\xAB\x9Az" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00 \xA8\xD1\xBB\xFDLW\xC57k\x8B{\xE0^'\x8B\xB6s\x87\x87\xAE\x9D\xC0\x8B\x18H\xF2qe\xA4\xF06\xBFu\xCBV\xAE\xE41\xD2Z\xC3\x14)\xF3\x9E\x84R\xC5\x11\xB8zr\xF5\xDD\x87h\xD1\xA8>\xA1F*%W#\xAC|~\bQ\xA6\xF1\xAE\xD7\x807\xBE\xFCq\x1D\xA7\xDEw#\xC28w\x8E\xE3Q\x83}\xE5l\x1F\xD1", :standard => "\x00\x00\x00 )\x10,\x8C\x85\r\a\x02I\xE3\xAE\xF6\xA7+\xEC\x11\xF9\x8C\xB9\xAE\xAAe6\x9F\x9Cn\xF2\x7F\xA6\xE8\xE6\xDB\xA7\xBB.\x93I5\xDF&\xD0\x98e\x8C\x87\xC7?\xD6|\x9C[\xFB\xE6\xE1T\t:\xC2w7\x16U\xD4V\x93N\xEDE\x03kGZ\xA4\xE9\xE42\xE5\x8E\x96\xDD\x9B*\xE0\x92L\x1A\xCE\x8D\xE9\xF7\xA1\xBC\xF0\xC9\xF2z" } }, 'aes192-cbc' => { 'hmac-sha2-256' => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\0037{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320D\373\035\334\340M\032B\307\324\232\211m'\347k\253\371\341\326\254\356\263[\2412\302R\320\274\365\255\003" }, 'hmac-sha2-512' => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003#/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320DN\005f\275u\230\344xF\354+RSTS\360\235\004\311$cW\357o\"fy\031\321yX\tYK\347\363kd\a\022\307r\177[ \274\0164\222\300 \037\330<\264\001^\246\337\004\365\233\202\310" }, 'hmac-sha2-256-96' => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\0037{\320\316\365Wy\"c\036y\260", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320D\373\035\334\340M\032B\307\324\232\211m" }, 'hmac-sha2-512-96' => { false => "P$\377\302\326\262\276\215\206\343&\257#\315>Mp\232P\345o\215\330\213\t\027\300\360\300\037\267\003#/\317\000\340I\274\363_\225U*", :standard => "se\347\230\026\311\212\250yH\241\302n\364:\276\270M=H1\317\222^\362\237D\225N\354:\343\205M\006[\313$U/yZ\330\235\032\307\320DN\005f\275u\230\344xF\354+R" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00 \x9F\xB2\v\xB0\xEDq\xE0V\x04\xBAJ\xE3\f\x19EFRs\xB2r\xB3'>\xF0\x96\xC8\a\np\xDED~\xA1=mQ\xDF\xF4L\x9A\xF1\xF3%S\xB1\xE4\x03\x9D\x04^\x022O'W\x9E\xCFD\xFF\xF5u\xFF\x16\a", :standard => "\x00\x00\x00 \xA5\x89\xA2\xF4\x85\xDD\xED\xC3=\x87\xAD\x81\x83~tV|\x90IH-\xA8\xF5$9\xE8Q\x88i\x9FL|u\xA1\x9F>[\xAA^\xF7L\xE9\x84\x94E\xC3\xD3\x9C\xB9\xB7.VsE\xD4\xA9\xB5\x924\xF4\xE4`\x00U" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00 \x9F\xB2\v\xB0\xEDq\xE0V\x04\xBAJ\xE3\f\x19EFRs\xB2r\xB3'>\xF0\x96\xC8\a\np\xDED~pp\b\x84F\x1A\xBE;\xE2\xAB4\vP$\xB6\xB5\xBAHDX\xE8\x81&G\xB5\xA3\xDA\xDC\v\x17\xC3\x99\xC5\xAD\xE6IR\xDC\x023\r\e)\xFDV\xB4\xCCV\xB5\x98\xB0$\xAB\xE0\x148\x8Ea\xF2x\x85\r\xDAv", :standard => "\x00\x00\x00 \xA5\x89\xA2\xF4\x85\xDD\xED\xC3=\x87\xAD\x81\x83~tV|\x90IH-\xA8\xF5$9\xE8Q\x88i\x9FL|\xF7\xFE\x8C#HX\xD11\x81\xF8\x1Du\xD7;\xE0\a\xDB?\x002\xA9\xC2\x80\xC7m$[\x90vD\xB6\xCA\xD1\b\x11\xE0\xFE\xC7O\x9F\xCB\xB0\x97$\x92\xF2\x90\x8B$\xF3BM\x06B\xA4\xB1\x10L!\xD6*ud\x10" } }, 'aes256-cbc' => { 'hmac-sha2-256' => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\3407{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365\373\035\334\340M\032B\307\324\232\211m'\347k\253\371\341\326\254\356\263[\2412\302R\320\274\365\255\003" }, 'hmac-sha2-256-96' => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\3407{\320\316\365Wy\"c\036y\260", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365\373\035\334\340M\032B\307\324\232\211m" }, 'hmac-sha2-512' => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340#/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365N\005f\275u\230\344xF\354+RSTS\360\235\004\311$cW\357o\"fy\031\321yX\tYK\347\363kd\a\022\307r\177[ \274\0164\222\300 \037\330<\264\001^\246\337\004\365\233\202\310" }, 'hmac-sha2-512-96' => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340#/\317\000\340I\274\363_\225U*", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365N\005f\275u\230\344xF\354+R" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00 \xDF'\xE2\xE7\xF6.\x92\x9F\xBA)N\xFE\xA0\xCC\x9A\x9Ai{\xB5\r\f\x18\xFA\xA9\x89\x9B\xD3\xF0zXK^\xFF\xB9\x14\xF1?\x0Ez\xF5\x8A\t~x\xCC\xFFj\x15\xE8\"\xA1dUd\xA4\xA5?\xF0\x1E\xE9\x82\xE2R\a", :standard => "\x00\x00\x00 1\xFC\xCAD\x0E\x97\x7F\xC51\xB0\b\xE1\xE7\xAC\x90\x9E\xCD\xB2\x88\x84d*\xD5E\xE1\x15\xC9\xBE\xCB\x8D\x14^\x9A\xC4v\xAA\xA7\x19L\x0E\xCBX;\xEDh\xDC\xCD\xD0\xB4#>\x8B\x7F\xB6\x97U\x84\x9CB\x84\xB1]\x1Co" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00 \xDF'\xE2\xE7\xF6.\x92\x9F\xBA)N\xFE\xA0\xCC\x9A\x9Ai{\xB5\r\f\x18\xFA\xA9\x89\x9B\xD3\xF0zXK^>\xE2\xDE\x95]\xD4\f%gBo3\x95\xD7\xFBF\xD8\xC5\x18R\x10\xB0\xA6i\xB8\ej\xAA}?\xF8\xA7\xB2K\x9E\xE3\b\xA5.D\x94\x04,\xB1\xFA\x92\xAA\xA9`\x95\x19\xC7P\x92r<\xCB\x93\xD9\xD8Nu\x89\b", :standard => "\x00\x00\x00 1\xFC\xCAD\x0E\x97\x7F\xC51\xB0\b\xE1\xE7\xAC\x90\x9E\xCD\xB2\x88\x84d*\xD5E\xE1\x15\xC9\xBE\xCB\x8D\x14^\xF8\xD2x\x817\x7F#b\xEC\x96\xB9\xE2pG\x9BI\bQ\xC0\xA8\xB6$\xA8]\x05?e\xE5\x86S\x0Fw\xA4Q\xAFW\xFE>\x9B7:\eF\n\xDF\xB1\x85M\xA5N\xCC^\xC9\xA6\xCDp\xBA\x13>\xB9\xEB~\x87\xEB" } }, 'blowfish-cbc' => { 'hmac-sha2-256' => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)7{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006\367F\231v\265o\f9$\224\201\e\364+\226H\374\377=\ts\202`\026\e,\347\t\217\206t\307" }, 'hmac-sha2-256-96' => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)7{\320\316\365Wy\"c\036y\260", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006\367F\231v\265o\f9$\224\201\e" }, 'hmac-sha2-512' => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)#/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006Q\3112O\223\361\216\235\022\216\0162\256\343\214\320\v\321\366/$\017]2\302\3435\217\324\245\037\301\225p\270\221c\307\302u\213b 4#\202PFI\371\267l\374\311\001\262z(\335|\334\2446\226" }, 'hmac-sha2-512-96' => { false => "vT\353\203\247\206L\255e\371\001 6B/\234g\332\371\224l\227\257\346\373E\237C2\212u)#/\317\000\340I\274\363_\225U*", :standard => "U\257\231e\347\274\bh\016X\232h\334\v\005\316e1G$-\367##\256$rW\000\210\335_\360\f\000\205#\370\201\006Q\3112O\223\361\216\235\022\216\0162" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00\x18\x86\xB4\xFC\xB0\x1F\x93\xFB\xFF\xF6P\xF9ro\xF7\xB8\x87~l)q\x04dK&I\xFC\xEBQ\xC8\xFCO\"\x8D\x87\x98i\x92\xA7\xD8\xC9L\xE4Q\x91\xC6u\xA8\x06\xBEJK\xAEc&C\xFD", :standard => "\x00\x00\x00 \xCA@\xF0\xE1\xADdf|\v\x0E\xEEt\xE7\xCD!\x90c\xA5\xCDE\x81\xD0\xBC\xDC7\xF8Y\xA0\xE7^\x1E\xDA.\x9F=\x8A\xB7\xC5%u\xEF\n\xB6F\xCBw\xA30s>cDl\x1AP\x18I[\xFC<=\xCBm\xAF" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00\x18\x86\xB4\xFC\xB0\x1F\x93\xFB\xFF\xF6P\xF9ro\xF7\xB8\x87~l)q\x04dK&\x84\xC75!\xB9\x04gt\xD8\xF7]\x82\xC9\x93EvP4\xF1*\x19C3\xFBD_!\x1F\f\xE3#9\xA2\xA2\xB6P\xE4\x89\xAB\x89|kK$\xCE\x18\\F\x90\xB3\x88\x83\xA9\\\x9C\x86\x87E\xA3\x8BX\xEB%7", :standard => "\x00\x00\x00 \xCA@\xF0\xE1\xADdf|\v\x0E\xEEt\xE7\xCD!\x90c\xA5\xCDE\x81\xD0\xBC\xDC7\xF8Y\xA0\xE7^\x1E\xDA$\xE3\xEA|\x7F\"zF\x92\e%\x0EpYI\t\xA8R\xA1\x15\xB9\xA8\xA4\x91\xAA\x9CD>\x8B\xE8\xA2\xC9\x85\x00\x94\xDE\xBD\x9C\x8E+\x98\xEE\x91\x9Eb\xFE\x15\xD0X\xD8\xD0=\xF7j\x9C@\xAC_\x94[\x8D,7\f" } }, 'cast128-cbc' => { 'hmac-sha2-256' => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\3767{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325\367F\231v\265o\f9$\224\201\e\364+\226H\374\377=\ts\202`\026\e,\347\t\217\206t\307" }, 'hmac-sha2-256-96' => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\3767{\320\316\365Wy\"c\036y\260", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325\367F\231v\265o\f9$\224\201\e" }, 'hmac-sha2-512' => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376#/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325Q\3112O\223\361\216\235\022\216\0162\256\343\214\320\v\321\366/$\017]2\302\3435\217\324\245\037\301\225p\270\221c\307\302u\213b 4#\202PFI\371\267l\374\311\001\262z(\335|\334\2446\226" }, 'hmac-sha2-512-96' => { false => "\361\026\313!\31235|w~\n\261\257\277\e\277b\246b\342\333\eE\021N\345\343m\314\272\315\376#/\317\000\340I\274\363_\225U*", :standard => "\375i\253\004\311E\2011)\220$\251A\245\f(\371\263\314\242\353\260\272\367\276\"\031\224$\244\311W\307Oe\224\0017\336\325Q\3112O\223\361\216\235\022\216\0162" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00\x18\xC1`!\xB4q\x13\xBD\x8B\xA7x\xD4\x9A\x85\xE9;#\x7FX\xA2`\x939\xE8\x8B{s\xB7Kwo\x19\"\xB8\xAD\xA1\xB1k\xB6\x00\xFAc\xF9\xC4\x0E\xC0\xAF*m\bOW\xE9=\xD4\xF3\xB4", :standard => "\x00\x00\x00 \x98\x80\xFA\xB7'\x90\x9D'\xAE\x95s[\xDA,}\xACdpu[\xB1$\x8C\x8Cu<}k`\x84\xEE\xA6A\xD2qGow\xCE\x1F\x16c\xF1qx\xA4\x97\x03K\x93\xC86\xD6?\"k\xAD\xCD\x8D\xA9\x8E\xBFAZ" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00\x18\xC1`!\xB4q\x13\xBD\x8B\xA7x\xD4\x9A\x85\xE9;#\x7FX\xA2`\x939\xE8\x8B\xDF\xF7\xF4\xD3\x9E\xC3\"\xAD\xB2\xD6&*\x03.#\x1A\xB1s\x8E\x18I\x0F\x83*\xE8.#\xDE\x19\xFF \xCF\x9C\xFE&\xDDQ\xEA\xFD\x12\x8A\f\xE9\xD6\xF1\xDA}\x16\xE5\xEF\x10\xD4B\f\v@1\x04\xA5&r\xB03\xC0", :standard => "\x00\x00\x00 \x98\x80\xFA\xB7'\x90\x9D'\xAE\x95s[\xDA,}\xACdpu[\xB1$\x8C\x8Cu<}k`\x84\xEE\xA6\xF8P`\x00m\x93\x1Dz\xDD\xA3\xAE\xD4>\xC3y\xD6\x86\x00Osv\x02z\xFE\xE6\xD5\x85\x80\x02<\x8F_8B\xD0\x89\xA1[\xFE\xA8qX\xFE)\xDAf\xA7\x8A\xE5\xF7\xECCY$!s\xD9!\xA7\xDB\x84\x8Fq\x8F" } }, 'idea-cbc' => { 'hmac-sha2-256' => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H7{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317\367F\231v\265o\f9$\224\201\e\364+\226H\374\377=\ts\202`\026\e,\347\t\217\206t\307" }, 'hmac-sha2-512' => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H#/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317Q\3112O\223\361\216\235\022\216\0162\256\343\214\320\v\321\366/$\017]2\302\3435\217\324\245\037\301\225p\270\221c\307\302u\213b 4#\202PFI\371\267l\374\311\001\262z(\335|\334\2446\226" }, 'hmac-sha2-256-96' => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H7{\320\316\365Wy\"c\036y\260", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317\367F\231v\265o\f9$\224\201\e" }, 'hmac-sha2-512-96' => { false => "\342\255\202$\273\201\025#\245\2341F\263\005@{\000<\266&s\016\251NH=J\322/\220 H#/\317\000\340I\274\363_\225U*", :standard => "F\3048\360\357\265\215I\021)\a\254/\315%\354M\004\330\006\356\vFr\250K\225\223x\277+Q)\022\327\311K\025\322\317Q\3112O\223\361\216\235\022\216\0162" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00\x18\xD3~\x94JDl\x94\xBBN6\x922^YN{j\x04r\xE6\x96UCzn\xD6\x0E\x80\xA9\xB9\x06\xB8\xCD\x18\x1A\x17\xAA\xB5\xDFV_\x96\x981\xD4 \x97\x043|\xFB6\x84Yz\xC3", :standard => "\x00\x00\x00 \xBATT\xEC\xA5k\xF2\xCA\x8Fp\xE3\xF2\xB8Qtm\x82\x86Z\xFB\x95V\x1CWo\x89\x1D\b\xB0\xCA\x8FS\xBE\xFA\\\x01\x7F\x82\xFE\xBC\xD3G\x88J@\f\xF6\xC2\xA0\a\xCA\xACuQ\x8A \x87c\xABMO\xF5\xD0\"" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00\x18\xD3~\x94JDl\x94\xBBN6\x922^YN{j\x04r\xE6\x96UCz^\xED\x8E\x949=\xD7\xB4\x8C\n]\x1DY\xF9He\x8C\xC4$\xB2\xE2\xBB\x8F\xDA\xC6\x14\\\xCCe\xB9\xA3\x10\xB6n\x9Cl\xC5/e\xEBP#_\bAP\xD3\xA5Y{#\xCC0g\x96J\x87)\x17\xBB\xBC\x8B=\f", :standard => "\x00\x00\x00 \xBATT\xEC\xA5k\xF2\xCA\x8Fp\xE3\xF2\xB8Qtm\x82\x86Z\xFB\x95V\x1CWo\x89\x1D\b\xB0\xCA\x8FS\x8Bi\xE5\xDB\x16\x9F[N\f\xEB\x96k\x1C\xA5{\x9F\xFB\xB1$\xEA\xF0\x7F\xA5\xA7\x99(3\xFC+\xD2%\xB7\a\xEEu\x97\xF9\x98x\xB5\xFF\"I\xBEo\t\xCE?\xF5f^\x87\"?\x18\x89$\xF8\xB0j\xEBnO\x15" } }, '3des-ctr' => { 'hmac-sha2-256' => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB17{\xD0\xCE\xF5Wy\"c\x1Ey\xB0-\xBD\xCA~\x8F\x10U\xED\x01\xFF\x95F\xE5\x86\xAD\xC7\x13N\xE8J", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B\xF7F\x99v\xB5o\f9$\x94\x81\e\xF4+\x96H\xFC\xFF=\ts\x82`\x16\e,\xE7\t\x8F\x86t\xC7" }, 'hmac-sha2-256-96' => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB17{\xD0\xCE\xF5Wy\"c\x1Ey\xB0", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91B\xF7F\x99v\xB5o\f9$\x94\x81\e" }, 'hmac-sha2-512' => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1#/\xCF\x00\xE0I\xBC\xF3_\x95U*\xD7z\x81\xCEc\xC3\xBDA\xF2\xD8^J\xBF\xC05oI\xBA\xF2\xEA\x86\xF8h\x8B\xB2\xC89\xC8v\x1F\x04\x12\x80]&\xF5\xC8\xC0\x90D[\xE8\x1E\x95\x89\xEB\xF1\xF6\x9F\xB7\x84\xD5", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91BQ\xC92O\x93\xF1\x8E\x9D\x12\x8E\x0E2\xAE\xE3\x8C\xD0\v\xD1\xF6/$\x0F]2\xC2\xE35\x8F\xD4\xA5\x1F\xC1\x95p\xB8\x91c\xC7\xC2u\x8Bb 4#\x82PFI\xF9\xB7l\xFC\xC9\x01\xB2z(\xDD|\xDC\xA46\x96" }, 'hmac-sha2-512-96' => { false => "\xED#\x86\xD5\xE1mP\v\f\xB9\xC1\xE6\xFD\xA0~,\xD3\x13\x12\x8Cp\xD4F\x92\xCB\xB6R>\xFA]\x9B\xB1#/\xCF\x00\xE0I\xBC\xF3_\x95U*", :standard => "\xED#\x86\xED\xE0\x11\xCDim\xDD\xA8\xE2x\x8EB\x06\x9E73$\xBC\x9FA\xE0\xDB\xAE\x11Y\xAD\xED\xD43\x86N\x89\xFE\x14V\x91BQ\xC92O\x93\xF1\x8E\x9D\x12\x8E\x0E2" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00\x18\xE9'\x87\xC9\xE9iXoi\xDB\xBD\xE5\xFF\xABe,\xB4z|\xEBx\xCC\x05\xF5\xF9\xB7t\x87\x9Frz\x81\x91\x91\xB4\x96j\x92NA1\x85\xDATA\xB4\xA0W\xBDI\xE4$\xCDh&\t", :standard => "\x00\x00\x00 \xE8[\x1A\xAB\x88\r1k\xEC\xF5\x81\xCF\xB2\x8FD\x84x1{\x99h\xD4F\x92\xC3Q\xEEQ\xB5QLl\xFBU\xEE\xC3s\xBEoG\xBC\xC9\xF4\xA2\x86\xE7G\xAF\x18n5\xB1\xBD.\xC3\x82\xA3QC\xBC99%." }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00\x18\xE9'\x87\xC9\xE9iXoi\xDB\xBD\xE5\xFF\xABe,\xB4z|\xEBx\xCC\x05\xF5\x80\xA28\x81L\xDE0\xF9\xB6+'\x1D\xAD\xA7i\xE3\xB9\xDD\x04f\x05\xB5~\xBD\xF0\xDD\xF0\xDB2:_-\xB2\xCC\xA8}O\x0Ey\xF7\x93\x00\xBE\xF2\xA0\xA3^`\xE4\xEAd\xF9;\x99\aq\xBD\xBB\xA5\xA4'\xF4\x8E\x1A", :standard => "\x00\x00\x00 \xE8[\x1A\xAB\x88\r1k\xEC\xF5\x81\xCF\xB2\x8FD\x84x1{\x99h\xD4F\x92\xC3Q\xEEQ\xB5QLl\x99\xD1{#`wQDr\x91\xB4\xCD>9\x8C~\xB9\x02=g\xC3\x04\x00\xF4\e\"\x8C\x9EM\xEA\xB3\x91 N\x87bN\xAA\xE9W\n\xA4Y\x8AU\x9D\xB9\x0F\xF0\xB6\x197\xDF5!\x92W\x9B\xAC\xA2k\xD5\xAFk" } }, 'blowfish-ctr' => { 'hmac-sha2-256' => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC07{\xD0\xCE\xF5Wy\"c\x1Ey\xB0-\xBD\xCA~\x8F\x10U\xED\x01\xFF\x95F\xE5\x86\xAD\xC7\x13N\xE8J", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9\xF7F\x99v\xB5o\f9$\x94\x81\e\xF4+\x96H\xFC\xFF=\ts\x82`\x16\e,\xE7\t\x8F\x86t\xC7" }, 'hmac-sha2-256-96' => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC07{\xD0\xCE\xF5Wy\"c\x1Ey\xB0", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9\xF7F\x99v\xB5o\f9$\x94\x81\e" }, 'hmac-sha2-512' => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0#/\xCF\x00\xE0I\xBC\xF3_\x95U*\xD7z\x81\xCEc\xC3\xBDA\xF2\xD8^J\xBF\xC05oI\xBA\xF2\xEA\x86\xF8h\x8B\xB2\xC89\xC8v\x1F\x04\x12\x80]&\xF5\xC8\xC0\x90D[\xE8\x1E\x95\x89\xEB\xF1\xF6\x9F\xB7\x84\xD5", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9Q\xC92O\x93\xF1\x8E\x9D\x12\x8E\x0E2\xAE\xE3\x8C\xD0\v\xD1\xF6/$\x0F]2\xC2\xE35\x8F\xD4\xA5\x1F\xC1\x95p\xB8\x91c\xC7\xC2u\x8Bb 4#\x82PFI\xF9\xB7l\xFC\xC9\x01\xB2z(\xDD|\xDC\xA46\x96" }, 'hmac-sha2-512-96' => { false => "\xF7gk6\xB8\xACK\x1D\xC4Ls\xB0{\x0F\xC7\xC4M\xC5>\xF6G8\xD4\xBCu\x152FoJ\xB0\xC0#/\xCF\x00\xE0I\xBC\xF3_\x95U*", :standard => "\xF7gk\x0E\xB9\xD0\xD6\x7F\xA5(\x1A\xB4\xFE!\xFB\xEE\x00\xE1\x1F^\x8Bs\xD3\xCEe\rq!8\xFA\xFFB\r\xE9\xFC\xF6\xCA\xBC\x03\xA9Q\xC92O\x93\xF1\x8E\x9D\x12\x8E\x0E2" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00\x18\xF3cj*\xB0\xA8Cy\xA1.\x0F\xB3y\x04\xDC\xC4*\xACP\x91O \x97\xDB\xEA\xD57~3;\xEB\xCD\xB6\xCDV\xEEp\x93\xF5\v\xB1T\xA6\xD2L=71L]\xE2\xDA\xE0G2\x05", :standard => "\x00\x00\x00 \xF2\x1F\xF7H\xD1\xCC*}$\x003\x994 \xFDl\xE6\xE7W\xE3_8\xD4\xBC}\xF2\x8E) Fg\x1D\xAAZ^[\xEF\x17\x1A2\xAAY\xEB\xB0F\x8F Z1Ung\xB7\xBC.\xA2\xE2\x8A\xC1\x17h\xC1\x17\xF2" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00\x18\xF3cj*\xB0\xA8Cy\xA1.\x0F\xB3y\x04\xDC\xC4*\xACP\x91O \x97\xDBI\xF9\xEC\xD5\x92\xA4([\xA3\x12:\x1E\x8B_q\x81\x89\xB1\xE1\x13B\xB1\xD1A,\xCA\xFF\xC1\x99\f\xEC\xA0T\xC4\xAE\x1Ed\x0F{B\x85\xD4\xD8\xAA-0\xF6{\xC1\x9C\x95qo+\xA1\x90 `;\xADL\xC5]@", :standard => "\x00\x00\x00 \xF2\x1F\xF7H\xD1\xCC*}$\x003\x994 \xFDl\xE6\xE7W\xE3_8\xD4\xBC}\xF2\x8E) Fg\x1D\xF4\xCEUk\a\xF8\xA2V\xFD]\xA98W)\x86\xC6\xAF\xEB\xBB\xBF\xB2\xF8\xF7u\xAD\x90\xB5P\xAA\x195\x93:\x85\xAB1\xD5d9\x99\xD6\xE8\\V\xCC*q\xDB\xD5\x7F|`\x10\xE9yR\x82-\xDA\xE4\xE0K%\x87" } }, 'aes128-ctr' => { 'hmac-sha2-256' => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD7{\xD0\xCE\xF5Wy\"c\x1Ey\xB0-\xBD\xCA~\x8F\x10U\xED\x01\xFF\x95F\xE5\x86\xAD\xC7\x13N\xE8J", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15\xFB\x1D\xDC\xE0M\x1AB\xC7\xD4\x9A\x89m'\xE7k\xAB\xF9\xE1\xD6\xAC\xEE\xB3[\xA12\xC2R\xD0\xBC\xF5\xAD\x03" }, 'hmac-sha2-256-96' => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD7{\xD0\xCE\xF5Wy\"c\x1Ey\xB0", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15\xFB\x1D\xDC\xE0M\x1AB\xC7\xD4\x9A\x89m" }, 'hmac-sha2-512' => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD#/\xCF\x00\xE0I\xBC\xF3_\x95U*\xD7z\x81\xCEc\xC3\xBDA\xF2\xD8^J\xBF\xC05oI\xBA\xF2\xEA\x86\xF8h\x8B\xB2\xC89\xC8v\x1F\x04\x12\x80]&\xF5\xC8\xC0\x90D[\xE8\x1E\x95\x89\xEB\xF1\xF6\x9F\xB7\x84\xD5", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15N\x05f\xBDu\x98\xE4xF\xEC+RSTS\xF0\x9D\x04\xC9$cW\xEFo\"fy\x19\xD1yX\tYK\xE7\xF3kd\a\x12\xC7r\x7F[ \xBC\x0E4\x92\xC0 \x1F\xD8<\xB4\x01^\xA6\xDF\x04\xF5\x9B\x82\xC8" }, 'hmac-sha2-512-96' => { false => "\xD6\x98\xC1n+6\xCA`s2\x06\xAA\x80\xFA\xF3\xF6\xCA\xF9\xC8[BB\xDC\x9F\xDC$\x88*\xA7\x00\x8E\xFD#/\xCF\x00\xE0I\xBC\xF3_\x95U*", :standard => "\xD6\x98\xC1^2JW\x02\x12Vo\xAE\x05\xD4\xCF\xDC\x87\xDD\xE9\xF3\x8E\t\xDB\xED\xCC<\xCBM\xF0\xB0\xC1\x7F\xD7\x17\x931\xBC~\r\xF2\x87\xB89\x9B\x8B\xB3\x8E\x15N\x05f\xBDu\x98\xE4xF\xEC+R" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00 \xDA\x9C\xC0r#2\xC2\x04\x16Pz\xA9\x82\xF1\xE8\xF6\xAD\x90\xA6 "\x00\x00\x00 \xD3\xE0]\x10BV\xAB\x00\x93~F\x83\xCF\xD5\xC9^a\xDB\xA1NZB\xDC\x9F\xD4\xC34E\xE8\fY \x91\x80\xE49I`\xE6\xED\x96\xA2C\x01\xF5\xB7{\t<\x88d\xDC/#O\x9B\xC2v\x955\x1F\x1D\x8A\x0F" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00 \xDA\x9C\xC0r#2\xC2\x04\x16Pz\xA9\x82\xF1\xE8\xF6\xAD\x90\xA6 "\x00\x00\x00 \xD3\xE0]\x10BV\xAB\x00\x93~F\x83\xCF\xD5\xC9^a\xDB\xA1NZB\xDC\x9F\xD4\xC34E\xE8\fY \xCEl\xF3\x95u\xF9\xD2\x17\x83\x98\xC6Q\x91S?\e^\xF6q\xA0M\x92S'\xD7\x1D\x84\x0E]\x82\x8F!U\x85S\xD2*\xB7\xC5\xBD\x00j\xC2@W\x82\xC9\x14\x95c\a\x99\xC6\xA9\xA0q\xD9\xCA\x87\x01zg\x18\v" } }, 'aes192-ctr' => { 'hmac-sha2-256' => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x917{\xD0\xCE\xF5Wy\"c\x1Ey\xB0-\xBD\xCA~\x8F\x10U\xED\x01\xFF\x95F\xE5\x86\xAD\xC7\x13N\xE8J", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1\xFB\x1D\xDC\xE0M\x1AB\xC7\xD4\x9A\x89m'\xE7k\xAB\xF9\xE1\xD6\xAC\xEE\xB3[\xA12\xC2R\xD0\xBC\xF5\xAD\x03" }, 'hmac-sha2-256-96' => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x917{\xD0\xCE\xF5Wy\"c\x1Ey\xB0", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1\xFB\x1D\xDC\xE0M\x1AB\xC7\xD4\x9A\x89m" }, 'hmac-sha2-512' => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91#/\xCF\x00\xE0I\xBC\xF3_\x95U*\xD7z\x81\xCEc\xC3\xBDA\xF2\xD8^J\xBF\xC05oI\xBA\xF2\xEA\x86\xF8h\x8B\xB2\xC89\xC8v\x1F\x04\x12\x80]&\xF5\xC8\xC0\x90D[\xE8\x1E\x95\x89\xEB\xF1\xF6\x9F\xB7\x84\xD5", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1N\x05f\xBDu\x98\xE4xF\xEC+RSTS\xF0\x9D\x04\xC9$cW\xEFo\"fy\x19\xD1yX\tYK\xE7\xF3kd\a\x12\xC7r\x7F[ \xBC\x0E4\x92\xC0 \x1F\xD8<\xB4\x01^\xA6\xDF\x04\xF5\x9B\x82\xC8" }, 'hmac-sha2-512-96' => { false => "\xA8\x02\xB4-\xFBYo4F\"\xCF\xB8\x92\xF08\xAC\xE8\xECk\xECO\xE7\xF8\x01\xF8\xB0\x9E\x05\xFB\xA7\xA7\x91#/\xCF\x00\xE0I\xBC\xF3_\x95U*", :standard => "\xA8\x02\xB4\x1D\xE2%\xF2V'F\xA6\xBC\x17\xDE\x04\x86\xA5\xC8JD\x83\xAC\xFFs\xE8\xA8\xDDb\xAC\x17\xE8\x13\x92V\x9E\x00!\x1F\xD4\x00\x92T\x15\xDE\xA4\xCA\xE9\xC1N\x05f\xBDu\x98\xE4xF\xEC+R" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00 \xA4\x06\xB51\xF3]gP\#@\xB3\xBB\x90\xFB#\xAC\x8F\x85\x05\x8BG\xFF\xBBf\xA7\xE7m\xE8\xF2\\u.l0\x91c\x82\xD9}7\x13\xE1\xAF \xB5\xE8 \xA5\xA5\x1E\x7Fe\x13\x8A\xCEdo\x1A\x10)\xA0\x9DO\xBB", :standard => "\x00\x00\x00 \xADz(S\x929\x0ET\xA6n\x8F\x91\xDD\xDF\x02\x04C\xCE\x02\xF9W\xE7\xF8\x01\xF0W\"j\xB4\xABpL\xFB\xEFi?y^&\xBF\xF0#\xDD}\xBFU\xE7\xAA\x83y\xA7M\xAFMJm\xD2\x81\x1C\x9C;\xC0]\x89" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00 \xA4\x06\xB51\xF3]gP\#@\xB3\xBB\x90\xFB#\xAC\x8F\x85\x05\x8BG\xFF\xBBf\xA7\xE7m\xE8\xF2\\u.@\xBDZq\xDDG\xF3\xEC\x9A,~9\xC9m\x19\xAE7\xBB\xAA\\\n\xAE\xCFn)0\xC6n\xA2\xC6\xB2\xD5\xD0N\n\xDDl\xE5\xA0\xE2A\x89\x1F#'\r\xA5\x81t\x81Z\eF\x8E\xEASO\xFE/\xA3\x9A4{\xDF", :standard => "\x00\x00\x00 \xADz(S\x929\x0ET\xA6n\x8F\x91\xDD\xDF\x02\x04C\xCE\x02\xF9W\xE7\xF8\x01\xF0W\"j\xB4\xABpLk;\x02(UdO\xBE`\x1F\x9D\xFD=\xE7\xD2\xEF\x80\xD3FC\xDF\xCA\xDD>r\x0Ev'\xFE9AK\xA3(\x0FT\n\x19M\\\xD8\xA1\x88\x87+^\x92\xC2\xF1\x94\xBB\xFD:\x04dC\xAE\x1E\xD5'jP\xE6\x17" } }, 'aes256-ctr' => { 'hmac-sha2-256' => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r7{\xD0\xCE\xF5Wy\"c\x1Ey\xB0-\xBD\xCA~\x8F\x10U\xED\x01\xFF\x95F\xE5\x86\xAD\xC7\x13N\xE8J", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\n\xFB\x1D\xDC\xE0M\x1AB\xC7\xD4\x9A\x89m'\xE7k\xAB\xF9\xE1\xD6\xAC\xEE\xB3[\xA12\xC2R\xD0\xBC\xF5\xAD\x03" }, 'hmac-sha2-256-96' => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r7{\xD0\xCE\xF5Wy\"c\x1Ey\xB0", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\n\xFB\x1D\xDC\xE0M\x1AB\xC7\xD4\x9A\x89m" }, 'hmac-sha2-512' => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r#/\xCF\x00\xE0I\xBC\xF3_\x95U*\xD7z\x81\xCEc\xC3\xBDA\xF2\xD8^J\xBF\xC05oI\xBA\xF2\xEA\x86\xF8h\x8B\xB2\xC89\xC8v\x1F\x04\x12\x80]&\xF5\xC8\xC0\x90D[\xE8\x1E\x95\x89\xEB\xF1\xF6\x9F\xB7\x84\xD5", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\nN\x05f\xBDu\x98\xE4xF\xEC+RSTS\xF0\x9D\x04\xC9$cW\xEFo\"fy\x19\xD1yX\tYK\xE7\xF3kd\a\x12\xC7r\x7F[ \xBC\x0E4\x92\xC0 \x1F\xD8<\xB4\x01^\xA6\xDF\x04\xF5\x9B\x82\xC8" }, 'hmac-sha2-512-96' => { false => "M\x1DcA\r]\\\x95?&\xE3D[\xCC1\x9B\xE0\xAF\x96\xA8\x86Y\xBD\x16\xE5xR%u\xC9(\r#/\xCF\x00\xE0I\xBC\xF3_\x95U*", :standard => "M\x1Dcq\x14!\xC1\xF7^B\x8A@\xDE\xE2\r\xB1\xAD\x8B\xB7\x00J\x12\xBAd\xF5`\x11B\"yg\x8F\x9F\xAB\xC8 d\xB4\xE7^w\xC4\x89\a\x17\x15\x82\nN\x05f\xBDu\x98\xE4xF\xEC+R" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00 A\x19b]\x05YT\xF1ZD\x9FGY\xC7*\x9B\x87\xC6\xF8\xCF\x8EA\xFEq\xBA/\xA1\xC8|2\xFA\xB2\xB1W\xD4\xA8\xEF\xC8~/>?\xF7f!(\x1C\xB7\x1C\x9C\xA9\xC2\xE4\xEF\x88k\e\x8A\xC4/QM\x84E", :standard => "\x00\x00\x00 He\xFF?d==\xF5\xDFj\xA3m\x14\xE3\v3K\x8D\xFF\xBD\x9EY\xBD\x16\xED\x9F\xEEJ:\xC5\xFF\xD0\x81\xFC\xA4\x87\xF3\x06x\xFE\xCDV*%\x13\xAA|\xA8\xE3\xB8^`vl\xFF\x02\xF9\xEC\x02\x8A\xFFt\xFC\x03" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00 A\x19b]\x05YT\xF1ZD\x9FGY\xC7*\x9B\x87\xC6\xF8\xCF\x8EA\xFEq\xBA/\xA1\xC8|2\xFA\xB2\x86x\r\x88Q-B\x03\xD5\x14\x8D\x98\xA3}&\x98va\xD9UZs\x82|\xDA{\xAD\x96\x88\x05%s\xB4*_\xBFWR\a\x90\xD9P\x81IY\xAE\x88|\x88\xC1k\x16\xDF\xDFPA\xBB\x13Dk\x84\xBBe1", :standard => "\x00\x00\x00 He\xFF?d==\xF5\xDFj\xA3m\x14\xE3\v3K\x8D\xFF\xBD\x9EY\xBD\x16\xED\x9F\xEEJ:\xC5\xFF\xD0\xB4\xAE\x8A\x86\xE6\xAE\xF3\xD8\xD8\xB2\x1Dazu$\x01nc\aYh\xBA\x15\x83\xE20\x900\\mX\x96\xF22\xEBJa\x96>\xED\x0E\x17`m\e\x97@\xF7Y+\xC8\x98\v\x17I\xC4\x86s\xCF\xB4j\xDEV0" } }, 'cast128-ctr' => { 'hmac-sha2-256' => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC77{\xD0\xCE\xF5Wy\"c\x1Ey\xB0-\xBD\xCA~\x8F\x10U\xED\x01\xFF\x95F\xE5\x86\xAD\xC7\x13N\xE8J", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA\xF7F\x99v\xB5o\f9$\x94\x81\e\xF4+\x96H\xFC\xFF=\ts\x82`\x16\e,\xE7\t\x8F\x86t\xC7" }, 'hmac-sha2-256-96' => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC77{\xD0\xCE\xF5Wy\"c\x1Ey\xB0", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEA\xF7F\x99v\xB5o\f9$\x94\x81\e" }, 'hmac-sha2-512' => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7#/\xCF\x00\xE0I\xBC\xF3_\x95U*\xD7z\x81\xCEc\xC3\xBDA\xF2\xD8^J\xBF\xC05oI\xBA\xF2\xEA\x86\xF8h\x8B\xB2\xC89\xC8v\x1F\x04\x12\x80]&\xF5\xC8\xC0\x90D[\xE8\x1E\x95\x89\xEB\xF1\xF6\x9F\xB7\x84\xD5", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEAQ\xC92O\x93\xF1\x8E\x9D\x12\x8E\x0E2\xAE\xE3\x8C\xD0\v\xD1\xF6/$\x0F]2\xC2\xE35\x8F\xD4\xA5\x1F\xC1\x95p\xB8\x91c\xC7\xC2u\x8Bb 4#\x82PFI\xF9\xB7l\xFC\xC9\x01\xB2z(\xDD|\xDC\xA46\x96" }, 'hmac-sha2-512-96' => { false => "\x10\xA0cJ6W\xC9\xC7\x02\xF8\xCD\xE31\xF9\xE7n\x0Fj\x7F\x99\x8A\f\x84\x80\x80\xE8p\x9C\x14\x83\x1C\xC7#/\xCF\x00\xE0I\xBC\xF3_\x95U*", :standard => "\x10\xA0cr7+T\xA5c\x9C\xA4\xE7\xB4\xD7\xDBDBN^1FG\x83\xF2\x90\xF03\xFBC3SE\xF7x;q\x89\xA80\xEAQ\xC92O\x93\xF1\x8E\x9D\x12\x8E\x0E2" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00\x18\x14\xA4bV>S\xC1\xA3g\x9A\xB1\xE03\xF2\xFCnh\x03\x11\xFE\x82\x14\xC7\xE7\xF3E\x16\e\xF9\x9FH;\x83\xF5G\xAA\xCE\x94\x1C\x19\xB9\x12\xC0\xB4\xBB\xC6\xA1A\xE8\xFA\x03[\x8D<\xEB\xBE", :standard => "\x00\x00\x00 \x15\xD8\xFF4_7\xA8\xA7\xE2\xB4\x8D\xCA~\xD6\xDD\xC6\xA4H\x16\x8C\x92\f\x84\x80\x88\x0F\xCC\xF3[\x8F\xCB\x1A\xD2Q\xA8\xA0\x990tw\x89*\xB4\xA1\xB9\x9A\xF2\xBD9v\"st\xB7:S5o\xD0\xA1\xD5\xA5\xC3\x83" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00\x18\x14\xA4bV>S\xC1\xA3g\x9A\xB1\xE03\xF2\xFCnh\x03\x11\xFE\x82\x14\xC7\xE7U\f\xDF\xD4(\xEE\xEE\xEE\x1D%\xDC\xE9n\xA6C\xB7\xB8!K\x90\xD2%\r\xB7\xAA\t\xE1\xA5\x12#\x93\x95m\xD8GY\xF4\xC9\xCC\xF8\x19(6\xD5\xE3\x8F.\xC9\xBFE\xAF\x8C\xF2\xA9\xD96v[zf\x02CI`", :standard => "\x00\x00\x00 \x15\xD8\xFF4_7\xA8\xA7\xE2\xB4\x8D\xCA~\xD6\xDD\xC6\xA4H\x16\x8C\x92\f\x84\x80\x88\x0F\xCC\xF3[\x8F\xCB\x1A\xE9\xC8\xAC\x85n\xDE\xF6H\xF0\xFAE_\xE43I\xCE\xC0\x8A\xC2\xD0+o\x1E\xFB\xCF\xFC\x8E\tI\xFFLlV\xEAP\xE8h\xEF\xF0p!p\x83B'\xFA\xF7wk\xA0\xD4\xB9x\xC6h\xAC\xD9\x94\xC1\x0E\xD44.\xFB" } }, 'none' => { 'hmac-sha2-256' => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\2127{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^\367F\231v\265o\f9$\224\201\e\364+\226H\374\377=\ts\202`\026\e,\347\t\217\206t\307" }, 'hmac-sha2-256-96' => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\2127{\320\316\365Wy\"c\036y\260", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^\367F\231v\265o\f9$\224\201\e" }, 'hmac-sha2-512' => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212#/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^Q\3112O\223\361\216\235\022\216\0162\256\343\214\320\v\321\366/$\017]2\302\3435\217\324\245\037\301\225p\270\221c\307\302u\213b 4#\202PFI\371\267l\374\311\001\262z(\335|\334\2446\226" }, 'hmac-sha2-512-96' => { false => "\000\000\000\034\b\004\001\000\000\000\tdebugging\000\000\000\000\b\030CgWO\260\212#/\317\000\340I\274\363_\225U*", :standard => "\000\000\000$\tx\234bad``\340LIM*MO\317\314K\ar\030\000\000\000\000\377\377\b\030CgWO\260\212^Q\3112O\223\361\216\235\022\216\0162" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00\x18\x04\x04\x01\x00\x00\x00\tdebugging\x00\x00\x00\x00\b\x18Cg:\xCA\xDDb\x1D\xA9'?\xBB\xB1\x86\xBB\x98\xD3\x1E4\xDA;\x93\xDF\xBFz\x8D\x98\xDF\xFB PZ\xD9o\xF8", :standard => "\x00\x00\x00 \x05x\x9Cbad``\xE0LIM*MO\xCF\xCCK\ar\x18\x00\x00\x00\x00\xFF\xFF\b\x18CgW\x80-\xA99B\x81\xFE\xF2\v*\x00\xF7\xB3o\xBCQ\xAEWj\xC9\x14\x197-8tk/\x9E\xAF\x00\xAB" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00\x18\x04\x04\x01\x00\x00\x00\tdebugging\x00\x00\x00\x00\b\x18Cg\x8D\x98\x9D\xCA`\xC7\x13| \xBE\xC4@N\xCE\x15\xD8\x0E\x03\xE3\xEC\x1AB\xD7\xC0\xA2j=\x8C\x17\xFA,\xAEY\xDD\xF7\xDC\bNTX\xEF\xF1\x80\x1A\x81h\xC7:\xDD\x9B\xC7R\xB4\x93\xA8#\x967f\xF7\xE0\x84\xCF\x9F", :standard => "\x00\x00\x00 \x05x\x9Cbad``\xE0LIM*MO\xCF\xCCK\ar\x18\x00\x00\x00\x00\xFF\xFF\b\x18CgW\xBADS\xA9\xF0I\x91\xFF< /\x1E\e\xC4v\x9B\x11\x9E\xEFiW[*d\xA26\xEA\xFB\xD5*\xC8\x9B\x9A\xAC.h:\xED\xA8R\xAD\xFC\x85@\xC3\x89\x8E,\x01*O\xF2\xFE\r\xCF\xA4\xA5\xAB\x03k\xC8\x9E])" } }, 'rijndael-cbc@lysator.liu.se' => { 'hmac-sha2-256' => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\3407{\320\316\365Wy\"c\036y\260-\275\312~\217\020U\355\001\377\225F\345\206\255\307\023N\350J", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365\373\035\334\340M\032B\307\324\232\211m'\347k\253\371\341\326\254\356\263[\2412\302R\320\274\365\255\003" }, 'hmac-sha2-256-96' => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\3407{\320\316\365Wy\"c\036y\260", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365\373\035\334\340M\032B\307\324\232\211m" }, 'hmac-sha2-512' => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340#/\317\000\340I\274\363_\225U*\327z\201\316c\303\275A\362\330^J\277\3005oI\272\362\352\206\370h\213\262\3109\310v\037\004\022\200]&\365\310\300\220D[\350\036\225\211\353\361\366\237\267\204\325", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365N\005f\275u\230\344xF\354+RSTS\360\235\004\311$cW\357o\"fy\031\321yX\tYK\347\363kd\a\022\307r\177[ \274\0164\222\300 \037\330<\264\001^\246\337\004\365\233\202\310" }, 'hmac-sha2-512-96' => { false => "\266\001oG(\201s\255[\202j\031-\354\353]\022\374\367j2\257\b#\273r\275\341\232\264\255\340#/\317\000\340I\274\363_\225U*", :standard => "\251!O/_\253\321\217e\225\202\202W\261p\r\357\357\375\231\264Y,nZ/\366\225G\256\3000\036\223\237\353\265vG\231\215cvY\236%\315\365N\005f\275u\230\344xF\354+R" }, 'hmac-sha2-256-etm@openssh.com' => { false => "\x00\x00\x00 \xDF'\xE2\xE7\xF6.\x92\x9F\xBA)N\xFE\xA0\xCC\x9A\x9Ai{\xB5\r\f\x18\xFA\xA9\x89\x9B\xD3\xF0zXK^\xFF\xB9\x14\xF1?\x0Ez\xF5\x8A\t~x\xCC\xFFj\x15\xE8\"\xA1dUd\xA4\xA5?\xF0\x1E\xE9\x82\xE2R\a", :standard => "\x00\x00\x00 1\xFC\xCAD\x0E\x97\x7F\xC51\xB0\b\xE1\xE7\xAC\x90\x9E\xCD\xB2\x88\x84d*\xD5E\xE1\x15\xC9\xBE\xCB\x8D\x14^\x9A\xC4v\xAA\xA7\x19L\x0E\xCBX;\xEDh\xDC\xCD\xD0\xB4#>\x8B\x7F\xB6\x97U\x84\x9CB\x84\xB1]\x1Co" }, 'hmac-sha2-512-etm@openssh.com' => { false => "\x00\x00\x00 \xDF'\xE2\xE7\xF6.\x92\x9F\xBA)N\xFE\xA0\xCC\x9A\x9Ai{\xB5\r\f\x18\xFA\xA9\x89\x9B\xD3\xF0zXK^>\xE2\xDE\x95]\xD4\f%gBo3\x95\xD7\xFBF\xD8\xC5\x18R\x10\xB0\xA6i\xB8\ej\xAA}?\xF8\xA7\xB2K\x9E\xE3\b\xA5.D\x94\x04,\xB1\xFA\x92\xAA\xA9`\x95\x19\xC7P\x92r<\xCB\x93\xD9\xD8Nu\x89\b", :standard => "\x00\x00\x00 1\xFC\xCAD\x0E\x97\x7F\xC51\xB0\b\xE1\xE7\xAC\x90\x9E\xCD\xB2\x88\x84d*\xD5E\xE1\x15\xC9\xBE\xCB\x8D\x14^\xF8\xD2x\x817\x7F#b\xEC\x96\xB9\xE2pG\x9BI\bQ\xC0\xA8\xB6$\xA8]\x05?e\xE5\x86S\x0Fw\xA4Q\xAFW\xFE>\x9B7:\eF\n\xDF\xB1\x85M\xA5N\xCC^\xC9\xA6\xCDp\xBA\x13>\xB9\xEB~\x87\xEB" } } } sha2_packets.each do |key, val| PACKETS[key].merge!(val) end ciphers = Net::SSH::Transport::CipherFactory::SSH_TO_OSSL.keys + Net::SSH::Transport::CipherFactory::SSH_TO_CLASS.keys hmacs = Net::SSH::Transport::HMAC::MAP.keys + ["implicit"] implicit_ciphers = ["chacha20-poly1305@openssh.com"] ciphers.each do |cipher_name| unless Net::SSH::Transport::CipherFactory.supported?(cipher_name) && PACKETS.key?(cipher_name) puts "Skipping packet stream test for #{cipher_name}" next end # JRuby Zlib implementation (1.4 & 1.5) does not have byte-to-byte compatibility with MRI's. # skip these 80 or more tests under JRuby. if defined?(JRUBY_VERSION) puts "Skipping zlib tests for JRuby" next end hmacs.each do |hmac_name| [false, :standard].each do |compress| next if (hmac_name != "implicit" && implicit_ciphers.include?(cipher_name)) || (hmac_name == "implicit" && !implicit_ciphers.include?(cipher_name)) cipher_method_name = cipher_name.gsub(/\W/, "_") hmac_method_name = hmac_name.gsub(/\W/, "_") define_method("test_next_packet_with_#{cipher_method_name}_and_#{hmac_method_name}_and_#{compress}_compression") do opts = { shared: "123", hash: "^&*", digester: OpenSSL::Digest::SHA1 } key = "ABC" cipher = Net::SSH::Transport::CipherFactory.get(cipher_name, opts.merge(key: key, decrypt: true, iv: "abc")) hmac = if cipher.implicit_mac? cipher.implicit_mac else Net::SSH::Transport::HMAC.get(hmac_name, "{}|", opts) end stream.server.set cipher: cipher, hmac: hmac, compression: compress stream.stubs(:recv).returns(PACKETS[cipher_name][hmac_name][compress]) IO.stubs(:select).returns([[stream]]) packet = stream.next_packet(:nonblock) assert_not_nil packet assert_equal DEBUG, packet.type assert packet[:always_display] assert_equal "debugging", packet[:message] assert_equal "", packet[:language] stream.cleanup end define_method("test_enqueue_packet_with_#{cipher_method_name}_and_#{hmac_method_name}_and_#{compress}_compression") do opts = { shared: "123", digester: OpenSSL::Digest::SHA1, hash: "^&*" } key = "ABC" cipher = Net::SSH::Transport::CipherFactory.get(cipher_name, opts.merge(key: key, iv: "abc", encrypt: true)) hmac = if cipher.implicit_mac? cipher.implicit_mac else Net::SSH::Transport::HMAC.get(hmac_name, "{}|", opts) end srand(100) stream.client.set cipher: cipher, hmac: hmac, compression: compress stream.enqueue_packet(ssh_packet) assert_equal PACKETS[cipher_name][hmac_name][compress], stream.write_buffer stream.cleanup end end end end private def stream @stream ||= begin stream = mock("packet_stream") stream.extend(Net::SSH::Transport::PacketStream) stream end end def ssh_packet Net::SSH::Buffer.from(:byte, DEBUG, :bool, true, :string, "debugging", :string, "") end def packet @packet ||= begin data = ssh_packet length = data.length + 4 + 1 # length + padding length padding = stream.server.cipher.block_size - (length % stream.server.cipher.block_size) padding += stream.server.cipher.block_size if padding < 4 Net::SSH::Buffer.from(:long, length + padding - 4, :byte, padding, :raw, data, :raw, "\0" * padding).to_s end end end end net-ssh-7.2.1/test/transport/test_server_version.rb000066400000000000000000000045611454036133000225610ustar00rootroot00000000000000require 'common' require 'net/ssh/transport/server_version' module Transport class TestServerVersion < NetSSHTest def test_1_99_server_version_should_be_acceptible s = subject(socket(true, "SSH-1.99-Testing_1.0\r\n")) assert s.header.empty? assert_equal "SSH-1.99-Testing_1.0", s.version end def test_2_0_server_version_should_be_acceptible s = subject(socket(true, "SSH-2.0-Testing_1.0\r\n")) assert s.header.empty? assert_equal "SSH-2.0-Testing_1.0", s.version end def test_trailing_whitespace_should_be_preserved # some servers, like Mocana, send a version string with trailing # spaces, which are significant when exchanging keys later. s = subject(socket(true, "SSH-2.0-Testing_1.0 \r\n")) assert_equal "SSH-2.0-Testing_1.0 ", s.version end def test_unacceptible_server_version_should_raise_exception assert_raises(Net::SSH::Exception) { subject(socket(false, "SSH-1.4-Testing_1.0\r\n")) } end def test_unexpected_server_close_should_raise_exception assert_raises(Net::SSH::Disconnect) { subject(socket(false, "\r\nDestination server does not have Ssh activated.\r\nContact Cisco Systems, Inc to purchase a\r\nlicense key to activate Ssh.\r\n", true)) } end def test_header_lines_should_be_accumulated s = subject(socket(true, "Welcome\r\nAnother line\r\nSSH-2.0-Testing_1.0\r\n")) assert_equal "Welcome\r\nAnother line\r\n", s.header assert_equal "SSH-2.0-Testing_1.0", s.version end def test_server_disconnect_should_raise_exception assert_raises(Net::SSH::Disconnect) { subject(socket(false, "SSH-2.0-Aborting")) } end private def socket(good, version_header, raise_eot = false) socket = mock("socket") socket.expects(:write).with("#{Net::SSH::Transport::ServerVersion::PROTO_VERSION}\r\n") socket.expects(:flush) data = version_header.split('') recv_times = data.length recv_times += 1 if data[-1] != "\n" if raise_eot socket.expects(:readpartial).with(1).times(recv_times + 1).returns(*data).then.raises(EOFError, 'end of file reached') else socket.expects(:readpartial).with(1).times(recv_times).returns(*data).then.returns(nil) end socket end def subject(socket) Net::SSH::Transport::ServerVersion.new(socket, nil) end end end net-ssh-7.2.1/test/transport/test_session.rb000066400000000000000000000343351454036133000211730ustar00rootroot00000000000000require_relative '../common' require 'net/ssh/transport/session' require 'net/ssh/proxy/http' require 'logger' # mocha adds #verify to Object, which throws off the host-key-verifier part of # these tests. # can't use .include? because ruby18 uses strings and ruby19 uses symbols :/ 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 TEST_HOST = "net.ssh.test" TEST_PORT = 22 def test_constructor_defaults assert_equal TEST_HOST, session.host assert_equal TEST_PORT, session.port assert_instance_of( Net::SSH::Verifiers::AcceptNewOrLocalTunnel, session.host_key_verifier ) end def test_verify_host_key_true_uses_accept_new_or_local_tunnel_verifier Kernel.expects(:warn).with( 'verify_host_key: true is deprecated, use :accept_new_or_local_tunnel' ) assert_instance_of( Net::SSH::Verifiers::AcceptNewOrLocalTunnel, session(verify_host_key: true).host_key_verifier ) end def test_verify_host_key_accept_new_or_local_tunnel_uses_accept_new_or_local_tunnel_verifier assert_instance_of( Net::SSH::Verifiers::AcceptNewOrLocalTunnel, session(verify_host_key: :accept_new_or_local_tunnel).host_key_verifier ) end def test_verify_host_key_nil_uses_accept_new_or_local_tunnel_verifier assert_instance_of( Net::SSH::Verifiers::AcceptNewOrLocalTunnel, session(verify_host_key: nil).host_key_verifier ) end def test_verify_host_key_very_uses_accept_new_verifier Kernel.expects(:warn).with('verify_host_key: :very is deprecated, use :accept_new') assert_instance_of( Net::SSH::Verifiers::AcceptNew, session(verify_host_key: :very).host_key_verifier ) end def test_verify_host_key_accept_new_uses_accept_new_verifier assert_instance_of( Net::SSH::Verifiers::AcceptNew, session(verify_host_key: :accept_new).host_key_verifier ) end def test_verify_host_key_secure_uses_always_verifier Kernel.expects(:warn).with('verify_host_key: :secure is deprecated, use :always') assert_instance_of( Net::SSH::Verifiers::Always, session(verify_host_key: :secure).host_key_verifier ) end def test_verify_host_key_false_uses_never_verifier Kernel.expects(:warn).with('verify_host_key: false is deprecated, use :never') assert_instance_of( Net::SSH::Verifiers::Never, session(verify_host_key: false).host_key_verifier ) end def test_verify_host_key_null_uses_never_verifier assert_instance_of( Net::SSH::Verifiers::Never, session(verify_host_key: :never).host_key_verifier ) end def test_unknown_verify_host_key_value_raises_exception_if_value_does_not_respond_to_verify assert_raises(ArgumentError) { session(verify_host_key: :bogus).host_key_verifier } end def test_verify_host_key_value_responding_to_verify_should_pass_muster object = stub("thingy", verify: true, verify_signature: true) assert_equal object, session(verify_host_key: object).host_key_verifier end def test_deprecated_host_key_verifier Kernel.expects(:warn).with('Warning: verifier without :verify_signature is deprecated') object = stub("thingy", verify: true) assert_not_nil session(verify_host_key: object).host_key_verifier end def test_host_as_string_should_return_host_and_ip_when_port_is_default session! socket.stubs(:peer_ip).returns("1.2.3.4") assert_equal "#{TEST_HOST},1.2.3.4", session.host_as_string end def test_host_as_string_should_return_host_and_ip_with_port_when_port_is_not_default session(port: 1234) # force session to be instantiated socket.stubs(:peer_ip).returns("1.2.3.4") assert_equal "[#{TEST_HOST}]:1234,[1.2.3.4]:1234", session.host_as_string end def test_host_as_string_should_return_only_host_when_host_is_ip session!(host: "1.2.3.4") socket.stubs(:peer_ip).returns("1.2.3.4") assert_equal "1.2.3.4", session.host_as_string end def test_host_as_string_should_return_only_host_and_port_when_host_is_ip_and_port_is_not_default session!(host: "1.2.3.4", port: 1234) socket.stubs(:peer_ip).returns("1.2.3.4") assert_equal "[1.2.3.4]:1234", session.host_as_string end def test_host_as_string_should_return_only_host_when_proxy_command_is_set session!(host: "1.2.3.4") socket.stubs(:peer_ip).returns(Net::SSH::Transport::PacketStream::PROXY_COMMAND_HOST_IP) assert_equal "1.2.3.4", session.host_as_string end def test_host_as_string_should_return_only_host_and_port_when_host_is_ip_and_port_is_not_default_and_proxy_command_is_set session!(host: "1.2.3.4", port: 1234) socket.stubs(:peer_ip).returns(Net::SSH::Transport::PacketStream::PROXY_COMMAND_HOST_IP) assert_equal "[1.2.3.4]:1234", session.host_as_string end def test_close_should_cleanup_and_close_socket session! socket.expects(:cleanup) socket.expects(:close) session.close end def test_service_request_should_return_buffer assert_equal "\005\000\000\000\004sftp", session.service_request('sftp').to_s end def test_rekey_when_kex_is_pending_should_do_nothing algorithms.stubs(pending?: true) algorithms.expects(:rekey!).never session.rekey! end def test_rekey_when_no_kex_is_pending_should_initiate_rekey_and_block_until_it_completes algorithms.stubs(pending?: false) algorithms.expects(:rekey!) session.expects(:wait).yields algorithms.expects(:initialized?).returns(true) session.rekey! end def test_rekey_as_needed_when_kex_is_pending_should_do_nothing session! algorithms.stubs(pending?: true) socket.expects(:if_needs_rekey?).never session.rekey_as_needed end def test_rekey_as_needed_when_no_kex_is_pending_and_no_rekey_is_needed_should_do_nothing session! algorithms.stubs(pending?: false) socket.stubs(if_needs_rekey?: false) session.expects(:rekey!).never session.rekey_as_needed end def test_rekey_as_needed_when_no_kex_is_pending_and_rekey_is_needed_should_initiate_rekey_and_block session! algorithms.stubs(pending?: false) socket.expects(:if_needs_rekey?).yields session.expects(:rekey!) session.rekey_as_needed end def test_peer_should_return_hash_of_info_about_peer session! socket.stubs(peer_ip: "1.2.3.4") assert_equal({ ip: "1.2.3.4", port: TEST_PORT, host: TEST_HOST, canonized: "net.ssh.test,1.2.3.4" }, session.peer) end def test_next_message_should_block_until_next_message_is_available session.expects(:poll_message).with(:block) session.next_message end def test_poll_message_should_query_next_packet_using_the_given_blocking_parameter session! socket.expects(:next_packet).with(:blocking_parameter, nil).returns(nil) session.poll_message(:blocking_parameter) end def test_poll_message_should_query_next_packet_using_the_timeout_option session!(timeout: 7) socket.expects(:next_packet).with(:nonblock, 7).returns(nil) session.poll_message end def test_poll_message_should_default_to_non_blocking session! socket.expects(:next_packet).with(:nonblock, nil).returns(nil) session.poll_message end def test_poll_message_should_silently_handle_disconnect_packets session! socket.expects(:next_packet).returns(P(:byte, DISCONNECT, :long, 1, :string, "testing", :string, "")) assert_raises(Net::SSH::Disconnect) { session.poll_message } end def test_poll_message_should_silently_handle_ignore_packets session! socket.expects(:next_packet).times(2).returns(P(:byte, IGNORE, :string, "test"), nil) assert_nil session.poll_message end def test_poll_message_should_silently_handle_unimplemented_packets session! socket.expects(:next_packet).times(2).returns(P(:byte, UNIMPLEMENTED, :long, 15), nil) assert_nil session.poll_message end def test_poll_message_should_silently_handle_debug_packets_with_always_display session! socket.expects(:next_packet).times(2).returns(P(:byte, DEBUG, :bool, true, :string, "testing", :string, ""), nil) assert_nil session.poll_message end def test_poll_message_should_silently_handle_debug_packets_without_always_display session! socket.expects(:next_packet).times(2).returns(P(:byte, DEBUG, :bool, false, :string, "testing", :string, ""), nil) assert_nil session.poll_message end def test_poll_message_should_silently_handle_kexinit_packets session! packet = P(:byte, KEXINIT, :raw, "lasdfalksdjfa;slkdfja;slkfjsdfaklsjdfa;df") socket.expects(:next_packet).times(2).returns(packet, nil) algorithms.expects(:accept_kexinit).with(packet) assert_nil session.poll_message end def test_poll_message_should_return_other_packets session! packet = P(:byte, SERVICE_ACCEPT, :string, "test") socket.expects(:next_packet).returns(packet) assert_equal packet, session.poll_message end def test_poll_message_should_enqueue_packets_when_algorithm_disallows_packet session! packet = P(:byte, SERVICE_ACCEPT, :string, "test") algorithms.stubs(:allow?).with(packet).returns(false) socket.expects(:next_packet).times(2).returns(packet, nil) assert_nil session.poll_message assert_equal [packet], session.queue end def test_poll_message_should_read_from_queue_when_next_in_queue_is_allowed_and_consume_queue_is_true session! packet = P(:byte, SERVICE_ACCEPT, :string, "test") session.push(packet) socket.expects(:next_packet).never assert_equal packet, session.poll_message assert session.queue.empty? end def test_poll_message_should_not_read_from_queue_when_next_in_queue_is_not_allowed session! packet = P(:byte, SERVICE_ACCEPT, :string, "test") algorithms.stubs(:allow?).with(packet).returns(false) session.push(packet) socket.expects(:next_packet).returns(nil) assert_nil session.poll_message assert_equal [packet], session.queue end def test_poll_message_should_not_read_from_queue_when_consume_queue_is_false session! packet = P(:byte, SERVICE_ACCEPT, :string, "test") session.push(packet) socket.expects(:next_packet).returns(nil) assert_nil session.poll_message(:nonblock, false) assert_equal [packet], session.queue end def test_wait_with_block_should_return_immediately_if_block_returns_truth session.expects(:poll_message).never session.wait { true } end def test_wait_should_not_consume_queue_on_reads n = 0 session.expects(:poll_message).with(:nonblock, false).returns(nil) session.wait { (n += 1) > 1 } end def test_wait_without_block_should_return_after_first_read session.expects(:poll_message).returns(nil) session.wait end def test_wait_should_enqueue_packets session! p1 = P(:byte, SERVICE_REQUEST, :string, "test") p2 = P(:byte, SERVICE_ACCEPT, :string, "test") socket.expects(:next_packet).times(2).returns(p1, p2) n = 0 session.wait { (n += 1) > 2 } assert_equal [p1, p2], session.queue end def test_push_should_enqueue_packet packet = P(:byte, SERVICE_ACCEPT, :string, "test") session.push(packet) assert_equal [packet], session.queue end def test_send_message_should_delegate_to_socket session! packet = P(:byte, SERVICE_ACCEPT, :string, "test") socket.expects(:send_packet).with(packet) session.send_message(packet) end def test_enqueue_message_should_delegate_to_socket session! packet = P(:byte, SERVICE_ACCEPT, :string, "test") socket.expects(:enqueue_packet).with(packet) session.enqueue_message(packet) end def test_configure_client_should_pass_options_to_socket_client_state session.configure_client compression: :standard assert_equal :standard, socket.client.compression end def test_configure_server_should_pass_options_to_socket_server_state session.configure_server compression: :standard assert_equal :standard, socket.server.compression end def test_hint_should_set_hint_on_socket assert !socket.hints[:authenticated] session.hint :authenticated assert socket.hints[:authenticated] end class TestLogger < Logger def initialize @strio = StringIO.new super(@strio) end def messages @strio.string end end def test_log_correct_debug_with_proxy logger = TestLogger.new proxy = Net::SSH::Proxy::HTTP.new("") session!(logger: logger, proxy: proxy) assert_match "establishing connection to #{TEST_HOST}:#{TEST_PORT} through proxy", logger.messages end def test_log_correct_debug_without_proxy logger = TestLogger.new session!(logger: logger) assert_match "establishing connection to #{TEST_HOST}:#{TEST_PORT}", logger.messages end private def socket @socket ||= stub("socket", hints: {}) end def server_version @server_version ||= stub("server_version") end def algorithms @algorithms ||= stub("algorithms", initialized?: true, allow?: true, start: true) end def session(options = {}) @session ||= begin host = options.delete(:host) || TEST_HOST if (proxy = options[:proxy]) proxy.stubs("open").returns(socket) else Socket.stubs(:tcp).with(host, options[:port] || TEST_PORT, nil, nil, { connect_timeout: options[:timeout] }).returns(socket) end Net::SSH::Transport::ServerVersion.stubs(:new).returns(server_version) Net::SSH::Transport::Algorithms.stubs(:new).returns(algorithms) Net::SSH::Transport::Session.new(host, options) end end # a simple alias to make the tests more self-documenting. the bang # version makes it look more like the session is being instantiated alias session! session end end net-ssh-7.2.1/test/transport/test_state.rb000066400000000000000000000134221454036133000206220ustar00rootroot00000000000000# encoding: ASCII-8BIT require 'common' require 'net/ssh/transport/state' module Transport class TestState < NetSSHTest def setup @socket = @state = @deflater = @inflater = nil end def teardown if @deflater @deflater.finish if !@deflater.finished? @deflater.close end if @inflater @inflater.finish if !@inflater.finished? @inflater.close end state.cleanup end def test_constructor_should_initialize_all_values assert_equal 0, state.sequence_number assert_equal 0, state.packets assert_equal 0, state.blocks assert_nil state.compression assert_nil state.compression_level assert_nil state.max_packets assert_nil state.max_blocks assert_nil state.rekey_limit assert_equal "identity", state.cipher.name assert_instance_of Net::SSH::Transport::HMAC::None, state.hmac end def test_increment_should_increment_counters state.increment(24) assert_equal 1, state.sequence_number assert_equal 1, state.packets assert_equal 3, state.blocks end def test_reset_should_reset_counters_and_fix_defaults_for_maximums state.increment(24) state.reset! assert_equal 1, state.sequence_number assert_equal 0, state.packets assert_equal 0, state.blocks assert_equal 2147483648, state.max_packets assert_equal 134217728, state.max_blocks end def test_set_should_set_variables_and_reset_counters state.expects(:reset!) state.set cipher: :a, hmac: :b, compression: :c, compression_level: :d, max_packets: 500, max_blocks: 1000, rekey_limit: 1500 assert_equal :a, state.cipher assert_equal :b, state.hmac assert_equal :c, state.compression assert_equal :d, state.compression_level assert_equal 500, state.max_packets assert_equal 1000, state.max_blocks assert_equal 1500, state.rekey_limit end def test_set_with_max_packets_should_respect_max_packets_setting state.set max_packets: 500 assert_equal 500, state.max_packets end def test_set_with_max_blocks_should_respect_max_blocks_setting state.set max_blocks: 1000 assert_equal 1000, state.max_blocks end def test_set_with_rekey_limit_should_include_rekey_limit_in_computation_of_max_blocks state.set rekey_limit: 4000 assert_equal 500, state.max_blocks end def test_compressor_defaults_to_default_zlib_compression expect = deflater.deflate("hello world") assert_equal expect, state.compressor.deflate("hello world") end def test_compressor_uses_compression_level_when_given state.set compression_level: 1 expect = deflater(1).deflate("hello world") assert_equal expect, state.compressor.deflate("hello world") end def test_compress_when_no_compression_is_active_returns_text assert_equal "hello everybody", state.compress("hello everybody") end def test_decompress_when_no_compression_is_active_returns_text assert_equal "hello everybody", state.decompress("hello everybody") end def test_compress_when_compression_is_delayed_and_no_auth_hint_is_set_should_return_text state.set compression: :delayed assert_equal "hello everybody", state.compress("hello everybody") end def test_decompress_when_compression_is_delayed_and_no_auth_hint_is_set_should_return_text state.set compression: :delayed assert_equal "hello everybody", state.decompress("hello everybody") end def test_compress_when_compression_is_enabled_should_return_compressed_text 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 # 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 def test_compress_when_compression_is_delayed_and_auth_hint_is_set_should_return_compressed_text socket.hints[:authenticated] = true state.set compression: :delayed 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_delayed_and_auth_hint_is_set_should_return_decompressed_text socket.hints[:authenticated] = true state.set compression: :delayed assert_equal "hello everybody", state.decompress("x\234\312H\315\311\311WH-K-\252L\312O\251\004\000\000\000\377\377") end def test_needs_rekey_should_be_true_if_packets_exceeds_max_packets state.set max_packets: 2 state.increment(8) state.increment(8) assert !state.needs_rekey? state.increment(8) assert state.needs_rekey? end def test_needs_rekey_should_be_true_if_blocks_exceeds_max_blocks state.set max_blocks: 10 assert !state.needs_rekey? state.increment(88) assert state.needs_rekey? end private def deflater(level = Zlib::DEFAULT_COMPRESSION) @deflater ||= Zlib::Deflate.new(level) end def inflater @inflater ||= Zlib::Inflate.new(nil) end def socket @socket ||= stub("socket", hints: {}) end def state @state ||= Net::SSH::Transport::State.new(socket, :test) end end end net-ssh-7.2.1/test/verifiers/000077500000000000000000000000001454036133000160565ustar00rootroot00000000000000net-ssh-7.2.1/test/verifiers/test_always.rb000066400000000000000000000025061454036133000207450ustar00rootroot00000000000000require 'common' require 'net/ssh/verifiers/always' require 'ostruct' class TestAlways < NetSSHTest def test_raises_unknown_key_error_if_empty secure_verifier = Net::SSH::Verifiers::Always.new host_keys = [] def host_keys.host 'foo' end assert_raises(Net::SSH::HostKeyUnknown) { 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') host_keys = [key] def host_keys.host 'foo' end 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') 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) } end def test_verify_signature secure_verifier = Net::SSH::Verifiers::Always.new assert(true, secure_verifier.verify_signature { true }) end end net-ssh-7.2.1/test/win_integration/000077500000000000000000000000001454036133000172605ustar00rootroot00000000000000net-ssh-7.2.1/test/win_integration/FreeSSHDService.ini000066400000000000000000000022351454036133000226470ustar00rootroot00000000000000[Telnet server] TelnetListenAddress=0.0.0.0 TelnetListenPort=23 TelnetMaxConnections=0 TelnetTimeout=0 TelnetBanner= TelnetCMD=C:\Windows\system32\cmd.exe TelnetRun=0 TelnetNewConsole=1 [SSH server] SSHListenAddress=127.0.0.1 SSHListenPort=2223 SSHMaxConnections=0 SSHTimeout=0 SSHBanner=Hello from freesshd SSHCMD=C:\Windows\system32\cmd.exe SSHRun=1 SSHNewConsole=1 SSHCiphers=0 SSHMACs=65535 SSHPasswordAuth=0 SSHPublickeyAuth=0 SSHPublickeyPath=C:\Program Files\freeSSHd RSAKeyPath=C:\Program Files\freeSSHd\RSAKey.cfg DSAKeyPath=C:\Program Files\freeSSHd\DSAKey.cfg [SSH tunneling] SSHLocalTunnel=0 SSHLocalTunnelOnly=0 SSHRemoteTunnel=0 SSHRemoteTunnelOnly=0 [SFTP] SFTPHomePath=$HOME\ [Access filtering] HostRestrictions= HostRestrictionsAllow=0 [Logging] LogEvents=0 LogFilePath=C:\Program Files\freeSSHdfreesshd.log LogResolveIP=0 [Automatic updates] UpdateCheckOnStartup=1 UpdateDontPrompt=0 UpdateShowMessages=1 UpdateLastMessageID=1 [Users] UserCount=2 [User0] Name=foo Auth=1 Password=0BEEC7B5EA3F0FDBC95D0DD47F3C5BC275DA8A3373 Domain= Shell=1 SFTP=0 Tunnel=0 [User1] Name=bar Auth=1 Password=62CDB7020FF920E5AA642C3D4066950DD1F01F4DE1 Domain= Shell=1 SFTP=0 Tunnel=0 net-ssh-7.2.1/test/win_integration/FreeSSHDService32.ini000066400000000000000000000022651454036133000230170ustar00rootroot00000000000000[Telnet server] TelnetListenAddress=0.0.0.0 TelnetListenPort=23 TelnetMaxConnections=0 TelnetTimeout=0 TelnetBanner= TelnetCMD=C:\Windows\system32\cmd.exe TelnetRun=0 TelnetNewConsole=1 [SSH server] SSHListenAddress=127.0.0.1 SSHListenPort=2223 SSHMaxConnections=0 SSHTimeout=0 SSHBanner=Hello from freesshd SSHCMD=C:\Windows\system32\cmd.exe SSHRun=1 SSHNewConsole=1 SSHCiphers=0 SSHMACs=65535 SSHPasswordAuth=0 SSHPublickeyAuth=0 SSHPublickeyPath=C:\Program Files (x86)\freeSSHd RSAKeyPath=C:\Program Files (x86)\freeSSHd\RSAKey.cfg DSAKeyPath=C:\Program Files (x86)\freeSSHd\DSAKey.cfg [SSH tunneling] SSHLocalTunnel=0 SSHLocalTunnelOnly=0 SSHRemoteTunnel=0 SSHRemoteTunnelOnly=0 [SFTP] SFTPHomePath=$HOME\ [Access filtering] HostRestrictions= HostRestrictionsAllow=0 [Logging] LogEvents=0 LogFilePath=C:\Program Files (x86)\freeSSHdfreesshd.log LogResolveIP=0 [Automatic updates] UpdateCheckOnStartup=1 UpdateDontPrompt=0 UpdateShowMessages=1 UpdateLastMessageID=1 [Users] UserCount=2 [User0] Name=foo Auth=1 Password=0BEEC7B5EA3F0FDBC95D0DD47F3C5BC275DA8A3373 Domain= Shell=1 SFTP=0 Tunnel=0 [User1] Name=bar Auth=1 Password=62CDB7020FF920E5AA642C3D4066950DD1F01F4DE1 Domain= Shell=1 SFTP=0 Tunnel=0 net-ssh-7.2.1/test/win_integration/common.rb000066400000000000000000000002421454036133000210730ustar00rootroot00000000000000$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib" require_relative '../common' require 'minitest' require 'mocha/minitest' require 'pty' require 'expect' net-ssh-7.2.1/test/win_integration/test_freesshd.rb000066400000000000000000000004541454036133000224520ustar00rootroot00000000000000require_relative '../common' require 'net/ssh' class TestPageapnt < NetSSHTest def test_connect ret = Net::SSH.start("localhost", "foo", password: 'foo', port: '2223') do |ssh| ssh.exec! "cmd /c echo hello from windows!" end assert_equal "hello from windows!\r\n", ret end end net-ssh-7.2.1/test/win_integration/test_pageant.rb000066400000000000000000000020121454036133000222560ustar00rootroot00000000000000require_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 agent.connect! if auto == :connect agent end end end end end