├── .github └── workflows │ └── linux.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── demos ├── demo.py ├── demo_keygen.py ├── demo_server.py ├── demo_sftp.py ├── demo_simple.py ├── forward.py ├── interactive.py ├── rforward.py ├── test_rsa.key ├── user_rsa_key └── user_rsa_key.pub ├── dev-requirements.txt ├── doc-requirements.txt ├── paramiko ├── __init__.py ├── _version.py ├── _winapi.py ├── agent.py ├── auth_handler.py ├── ber.py ├── buffered_pipe.py ├── channel.py ├── client.py ├── common.py ├── compress.py ├── config.py ├── dsskey.py ├── ecdsakey.py ├── ed25519key.py ├── file.py ├── hostkeys.py ├── kex_curve25519.py ├── kex_ecdh_nist.py ├── kex_gex.py ├── kex_group1.py ├── kex_group14.py ├── kex_group16.py ├── kex_gss.py ├── message.py ├── packet.py ├── pipe.py ├── pkey.py ├── primes.py ├── proxy.py ├── py3compat.py ├── rsakey.py ├── server.py ├── sftp.py ├── sftp_attr.py ├── sftp_client.py ├── sftp_file.py ├── sftp_handle.py ├── sftp_server.py ├── sftp_si.py ├── ssh_exception.py ├── ssh_gss.py ├── transport.py ├── util.py ├── win_openssh.py └── win_pageant.py ├── setup.cfg ├── setup.py ├── sites └── docs │ ├── api │ ├── agent.rst │ ├── buffered_pipe.rst │ ├── channel.rst │ ├── client.rst │ ├── config.rst │ ├── file.rst │ ├── hostkeys.rst │ ├── kex_gss.rst │ ├── keys.rst │ ├── message.rst │ ├── packet.rst │ ├── pipe.rst │ ├── proxy.rst │ ├── server.rst │ ├── sftp.rst │ ├── ssh_exception.rst │ ├── ssh_gss.rst │ └── transport.rst │ ├── conf.py │ ├── faq.rst │ ├── index.rst │ ├── installing.rst │ └── old_changelog.rst └── tests ├── __init__.py ├── cert_support ├── test_dss.key ├── test_dss.key-cert.pub ├── test_ecdsa_256.key ├── test_ecdsa_256.key-cert.pub ├── test_ed25519.key ├── test_ed25519.key-cert.pub ├── test_rsa.key └── test_rsa.key-cert.pub ├── conftest.py ├── loop.py ├── stub_sftp.py ├── test_auth.py ├── test_buffered_pipe.py ├── test_channelfile.py ├── test_client.py ├── test_dss.key ├── test_dss_1k_o.key ├── test_dss_password.key ├── test_ecdsa_256.key ├── test_ecdsa_384.key ├── test_ecdsa_384_o.key ├── test_ecdsa_521.key ├── test_ecdsa_password_256.key ├── test_ecdsa_password_384.key ├── test_ecdsa_password_521.key ├── test_ed25519.key ├── test_ed25519_nopad.key ├── test_ed25519_password.key ├── test_file.py ├── test_gssapi.py ├── test_hostkeys.py ├── test_kex.py ├── test_kex_gss.py ├── test_message.py ├── test_packetizer.py ├── test_pkey.py ├── test_rsa.key ├── test_rsa.key.pub ├── test_rsa_2k_o.key ├── test_rsa_password.key ├── test_sftp.py ├── test_sftp_big.py ├── test_ssh_exception.py ├── test_ssh_gss.py ├── test_transport.py ├── test_util.py └── util.py /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux tests 2 | 3 | on: 4 | push: {branches: [master]} 5 | pull_request: {branches: [master]} 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-24.04 10 | timeout-minutes: 30 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - {imgtag: "python:2.7-buster", crypto_ver: "2.6.1"} 17 | - {imgtag: "python:2.7-buster", crypto_ver: "3.2.1"} 18 | - {imgtag: "pypy:2.7-7.3.16", crypto_ver: "3.3.2"} 19 | - {imgtag: "python:3.5-buster", crypto_ver: "2.6.1"} 20 | - {imgtag: "python:3.5-buster", crypto_ver: "3.2.1"} 21 | - {imgtag: "python:3.6-bullseye", crypto_ver: "2.9.2"} 22 | - {imgtag: "python:3.6-bullseye", crypto_ver: "3.4.8"} 23 | - {imgtag: "python:3.7-bullseye", crypto_ver: "3.2.1"} 24 | - {imgtag: "python:3.7-bullseye", crypto_ver: "3.4.8"} 25 | - {imgtag: "python:3.8-bookworm", crypto_ver: "3.3.2"} 26 | - {imgtag: "python:3.8-bookworm", crypto_ver: "3.4.8"} 27 | - {imgtag: "python:3.9-bookworm", crypto_ver: "2.9.2"} 28 | - {imgtag: "python:3.9-bookworm", crypto_ver: "36.0.2"} 29 | - {imgtag: "python:3.10-bookworm", crypto_ver: "36.0.2"} 30 | - {imgtag: "python:3.10-bookworm", crypto_ver: "41.0.7"} 31 | - {imgtag: "python:3.11-bookworm", crypto_ver: "38.0.4"} 32 | - {imgtag: "python:3.11-bookworm", crypto_ver: "42.0.8"} 33 | - {imgtag: "python:3.12-bookworm", crypto_ver: "38.0.4"} 34 | - {imgtag: "python:3.12-bookworm", crypto_ver: "44.0.2"} 35 | - {imgtag: "python:3.13-bookworm", crypto_ver: "38.0.4"} 36 | - {imgtag: "python:3.13-bookworm", crypto_ver: "44.0.2"} 37 | - {imgtag: "pypy:3.11-7.3.19", crypto_ver: "44.0.2"} 38 | 39 | container: "${{matrix.imgtag}}" 40 | steps: 41 | - uses: actions/checkout@v3 42 | 43 | - name: Python dependencies 44 | run: | 45 | pip install -r dev-requirements.txt 46 | pip install cryptography==${{matrix.crypto_ver}} PyNaCl 47 | if [ ${{matrix.imgtag}} = python:3.7-bullseye ]; then 48 | export DEBIAN_FRONTEND=noninteractive 49 | apt-get -q -y update 50 | apt-get -q -y install libkrb5-dev krb5-admin-server \ 51 | krb5-kdc krb5-user krb5-multidev openssh-server 52 | pip install gssapi==1.5.1 pyasn1==0.4.5 k5test==0.9.2 53 | fi 54 | pip install -e . 55 | pip freeze 56 | 57 | - name: Lint 58 | if: ${{ ! (contains(matrix.imgtag, ':2.7-') || contains(matrix.imgtag, ':3.5-')) }} 59 | run: | 60 | flake8 --version 61 | flake8 --show-source 62 | 63 | - name: Test 64 | run: | 65 | pytest -v 66 | 67 | docs: 68 | runs-on: ubuntu-24.04 69 | timeout-minutes: 30 70 | steps: 71 | - uses: actions/checkout@v3 72 | 73 | - uses: actions/setup-python@v4 74 | with: 75 | python-version: 3.9 76 | 77 | - name: Install dependencies 78 | run: | 79 | pip install -r doc-requirements.txt 80 | pip install -e . 81 | - name: Build docs 82 | run: | 83 | sphinx-build -v -W sites/docs tmpbuild 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /build/ 3 | /dist/ 4 | demos/*.log 5 | .cache 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | recursive-include tests *.py *.key *.pub 3 | recursive-include demos *.py *.key user_rsa_key user_rsa_key.pub 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Paramiko-NG 3 | =========== 4 | 5 | This is a fork of `paramiko `_ for more active maintenance. 6 | 7 | This is still imported under the name ``paramiko``, but you can 8 | install with the pip-package-name *paramiko-ng* (default) or *paramiko* 9 | (by setting the environment variable ``PARAMIKO_REPLACE``, see `Installation`_). 10 | 11 | Changelog: `releases `_ 12 | 13 | :Paramiko-NG: Python SSH module 14 | :Copyright: Copyright (c) 2003-2009 Robey Pointer 15 | :Copyright: Copyright (c) 2013-2019 Jeff Forcier 16 | :Copyright: Copyright (c) 2019-2022 Pierce Lopez 17 | :License: `LGPL `_ 18 | :API docs: https://ploxiln.github.io/paramiko-ng/ 19 | :Development: https://github.com/ploxiln/paramiko-ng/ 20 | 21 | 22 | What 23 | ---- 24 | 25 | "Paramiko" is a combination of the Esperanto words for "paranoid" and 26 | "friend". It's a module for Python 2.7/3.4+ that implements the SSH2 protocol 27 | for secure (encrypted and authenticated) connections to remote machines. Unlike 28 | SSL (aka TLS), SSH2 protocol does not require hierarchical certificates signed 29 | by a powerful central authority. You may know SSH2 as the protocol that 30 | replaced Telnet and rsh for secure access to remote shells, but the protocol 31 | also includes the ability to open arbitrary channels to remote services across 32 | the encrypted tunnel (this is how SFTP works, for example). 33 | 34 | It is written entirely in Python (though it depends on third-party C wrappers 35 | for low level crypto; these are often available precompiled) and is released 36 | under the GNU Lesser General Public License (`LGPL `_). 37 | 38 | 39 | Installation 40 | ------------ 41 | 42 | The import name is still just ``paramiko``. Make sure the original *paramiko* 43 | is not installed before installing *paramiko-ng* - otherwise pip may report 44 | success even though *paramiko-ng* was not correctly installed. 45 | (Because the import name is the same, installed files can conflict.) 46 | 47 | The most common way to install is simply:: 48 | 49 | pip install paramiko-ng 50 | 51 | You can also install under the original "paramiko" pip-package-name, in order to 52 | satisfy requirements for other packages (replace "2.8.10" with desired version):: 53 | 54 | PARAMIKO_REPLACE=1 pip install "https://github.com/ploxiln/paramiko-ng/archive/2.8.10.tar.gz#egg=paramiko" 55 | 56 | For dependencies and other details, see https://ploxiln.github.io/paramiko-ng/installing.html 57 | 58 | 59 | Portability Issues 60 | ------------------ 61 | 62 | Paramiko primarily supports POSIX platforms with standard OpenSSH 63 | implementations, and is most frequently tested on Linux and OS X. Windows is 64 | supported as well, though it may not be as straightforward. 65 | 66 | 67 | Bugs & Support 68 | -------------- 69 | 70 | :Bug Reports: `Github `_ 71 | :IRC: ``#paramiko`` on Freenode 72 | 73 | 74 | Demo 75 | ---- 76 | 77 | Several demo scripts come with Paramiko to demonstrate how to use it. 78 | Probably the simplest demo is this:: 79 | 80 | import base64 81 | import paramiko 82 | key = paramiko.RSAKey(data=base64.b64decode(b'AAA...')) 83 | client = paramiko.SSHClient() 84 | client.get_host_keys().add('ssh.example.com', 'ssh-rsa', key) 85 | client.connect('ssh.example.com', username='strongbad', password='thecheat') 86 | stdin, stdout, stderr = client.exec_command('ls') 87 | for line in stdout: 88 | print('... ' + line.strip('\n')) 89 | client.close() 90 | 91 | This prints out the results of executing ``ls`` on a remote server. The host 92 | key ``b'AAA...'`` should of course be replaced by the actual base64 encoding of the 93 | host key. If you skip host key verification, the connection is not secure! 94 | 95 | The following example scripts (in `demos/ `_) get progressively more detailed: 96 | 97 | :demo_simple.py: 98 | Calls invoke_shell() and emulates a terminal/TTY through which you can 99 | execute commands interactively on a remote server. Think of it as a 100 | poor man's SSH command-line client. 101 | 102 | :demo.py: 103 | Same as demo_simple.py, but allows you to authenticate using a private 104 | key, attempts to use an SSH agent if present, and uses the long form of 105 | some of the API calls. 106 | 107 | :forward.py: 108 | Command-line script to set up port-forwarding across an SSH transport. 109 | 110 | :demo_sftp.py: 111 | Opens an SFTP session and does a few simple file operations. 112 | 113 | :demo_server.py: 114 | An SSH server that listens on port 2200 and accepts a login for 115 | 'robey' (password 'foo'), and pretends to be a BBS. Meant to be a 116 | very simple demo of writing an SSH server. 117 | 118 | :demo_keygen.py: 119 | A key generator similar to OpenSSH ``ssh-keygen(1)`` program with 120 | Paramiko keys generation and progress functions. 121 | 122 | Use 123 | --- 124 | 125 | The demo scripts are probably the best example of how to use this package. 126 | 127 | A much easier alternative to using paramiko-ng directly is to use 128 | `fab-classic `_ or 129 | `Fabric `_ 2.x (or, for managed networking equipment, 130 | `netmiko `_). Whereas recent releases of 131 | *fab-classic* depend on *paramiko-ng* directly, to use this with *Fabric* or 132 | *netmiko* you need to install *paramiko-ng* with the pip-package-name 133 | "paramiko", using the ``PARAMIKO_REPLACE`` environment variable as described 134 | above in `Installation`_. 135 | 136 | 137 | There are also unit tests which will verify that most of the components are working correctly:: 138 | 139 | $ pip install -r dev-requirements.txt 140 | $ pytest -v 141 | 142 | -------------------------------------------------------------------------------- /demos/demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2003-2007 Robey Pointer 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | 22 | import getpass 23 | import os 24 | import socket 25 | import sys 26 | import traceback 27 | 28 | from paramiko.py3compat import input 29 | import paramiko 30 | try: 31 | import interactive 32 | except ImportError: 33 | from . import interactive 34 | 35 | 36 | def agent_auth(transport, username): 37 | """ 38 | Attempt to authenticate to the given transport using any of the private 39 | keys available from an SSH agent. 40 | """ 41 | 42 | agent = paramiko.Agent() 43 | agent_keys = agent.get_keys() 44 | if len(agent_keys) == 0: 45 | return 46 | 47 | for key in agent_keys: 48 | print('Trying ssh-agent key %s' % key.get_fingerprint_sha256_b64()) 49 | try: 50 | transport.auth_publickey(username, key) 51 | print('... success!') 52 | return 53 | except paramiko.SSHException: 54 | print('... nope.') 55 | 56 | 57 | def manual_auth(username, hostname): 58 | default_auth = 'p' 59 | auth = input('Auth by (p)assword or (k)ey file? [%s] ' % default_auth) 60 | if len(auth) == 0: 61 | auth = default_auth 62 | 63 | if auth == 'k': 64 | default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') 65 | path = input('Private key file [%s]: ' % default_path) 66 | if len(path) == 0: 67 | path = default_path 68 | try: 69 | key = paramiko.load_private_key_file(path) 70 | except paramiko.PasswordRequiredException: 71 | password = getpass.getpass('Private key password: ') 72 | key = paramiko.load_private_key_file(path, password) 73 | t.auth_publickey(username, key) 74 | else: 75 | pw = getpass.getpass('Password for %s@%s: ' % (username, hostname)) 76 | t.auth_password(username, pw) 77 | 78 | 79 | # setup logging 80 | paramiko.util.log_to_file('demo.log') 81 | 82 | username = '' 83 | if len(sys.argv) > 1: 84 | hostname = sys.argv[1] 85 | if hostname.find('@') >= 0: 86 | username, hostname = hostname.split('@') 87 | else: 88 | hostname = input('Hostname: ') 89 | if len(hostname) == 0: 90 | print('*** Hostname required.') 91 | sys.exit(1) 92 | port = 22 93 | if hostname.find(':') >= 0: 94 | hostname, portstr = hostname.split(':') 95 | port = int(portstr) 96 | 97 | # now connect 98 | try: 99 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 100 | sock.connect((hostname, port)) 101 | except Exception as e: 102 | print('*** Connect failed: ' + str(e)) 103 | traceback.print_exc() 104 | sys.exit(1) 105 | 106 | try: 107 | t = paramiko.Transport(sock) 108 | try: 109 | t.start_client() 110 | except paramiko.SSHException: 111 | print('*** SSH negotiation failed.') 112 | sys.exit(1) 113 | 114 | try: 115 | keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts')) 116 | except IOError: 117 | try: 118 | keys = paramiko.util.load_host_keys(os.path.expanduser('~/ssh/known_hosts')) 119 | except IOError: 120 | print('*** Unable to open host keys file') 121 | keys = {} 122 | 123 | # check server's host key -- this is important. 124 | key = t.get_remote_server_key() 125 | if hostname not in keys: 126 | print('*** WARNING: Unknown host key!') 127 | elif key.get_name() not in keys[hostname]: 128 | print('*** WARNING: Unknown host key!') 129 | elif keys[hostname][key.get_name()] != key: 130 | print('*** WARNING: Host key has changed!!!') 131 | sys.exit(1) 132 | else: 133 | print('*** Host key OK.') 134 | 135 | # get username 136 | if username == '': 137 | default_username = getpass.getuser() 138 | username = input('Username [%s]: ' % default_username) 139 | if len(username) == 0: 140 | username = default_username 141 | 142 | agent_auth(t, username) 143 | if not t.is_authenticated(): 144 | manual_auth(username, hostname) 145 | if not t.is_authenticated(): 146 | print('*** Authentication failed. :(') 147 | t.close() 148 | sys.exit(1) 149 | 150 | chan = t.open_session() 151 | chan.get_pty() 152 | chan.invoke_shell() 153 | print('*** Here we go!\n') 154 | interactive.interactive_shell(chan) 155 | chan.close() 156 | t.close() 157 | 158 | except Exception as e: 159 | print('*** Caught exception: ' + str(e.__class__) + ': ' + str(e)) 160 | traceback.print_exc() 161 | try: 162 | t.close() 163 | except: 164 | pass 165 | sys.exit(1) 166 | -------------------------------------------------------------------------------- /demos/demo_keygen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2010 Sofian Brabez 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | import sys 22 | from optparse import OptionParser 23 | 24 | from paramiko import DSSKey 25 | from paramiko import RSAKey 26 | from paramiko.ssh_exception import SSHException 27 | 28 | usage = """%prog [-v] [-b bits] -t type [-N new_passphrase] [-f output_keyfile]""" 29 | 30 | default_values = { 31 | "ktype": "dsa", 32 | "bits": 1024, 33 | "filename": "output", 34 | "comment": "" 35 | } 36 | 37 | key_dispatch_table = { 38 | 'dsa': DSSKey, 39 | 'rsa': RSAKey, 40 | } 41 | 42 | 43 | def progress(arg=None): 44 | if not arg: 45 | sys.stdout.write('0%\x08\x08\x08 ') 46 | sys.stdout.flush() 47 | elif arg[0] == 'p': 48 | sys.stdout.write('25%\x08\x08\x08\x08 ') 49 | sys.stdout.flush() 50 | elif arg[0] == 'h': 51 | sys.stdout.write('50%\x08\x08\x08\x08 ') 52 | sys.stdout.flush() 53 | elif arg[0] == 'x': 54 | sys.stdout.write('75%\x08\x08\x08\x08 ') 55 | sys.stdout.flush() 56 | 57 | 58 | if __name__ == '__main__': 59 | phrase = None 60 | pfunc = None 61 | 62 | parser = OptionParser(usage=usage) 63 | parser.add_option("-t", "--type", type="string", dest="ktype", 64 | help="Specify type of key to create (dsa or rsa)", 65 | metavar="ktype", default=default_values["ktype"]) 66 | parser.add_option("-b", "--bits", type="int", dest="bits", 67 | help="Number of bits in the key to create", metavar="bits", 68 | default=default_values["bits"]) 69 | parser.add_option("-N", "--new-passphrase", dest="newphrase", 70 | help="Provide new passphrase", metavar="phrase") 71 | parser.add_option("-P", "--old-passphrase", dest="oldphrase", 72 | help="Provide old passphrase", metavar="phrase") 73 | parser.add_option("-f", "--filename", type="string", dest="filename", 74 | help="Filename of the key file", metavar="filename", 75 | default=default_values["filename"]) 76 | parser.add_option("-q", "--quiet", default=False, action="store_false", 77 | help="Quiet") 78 | parser.add_option("-v", "--verbose", default=False, action="store_true", 79 | help="Verbose") 80 | parser.add_option("-C", "--comment", type="string", dest="comment", 81 | help="Provide a new comment", metavar="comment", 82 | default=default_values["comment"]) 83 | 84 | (options, args) = parser.parse_args() 85 | 86 | if len(sys.argv) == 1: 87 | parser.print_help() 88 | sys.exit(0) 89 | 90 | ktype = options.ktype 91 | bits = options.bits 92 | filename = options.filename 93 | comment = options.comment 94 | 95 | if options.newphrase: 96 | phrase = getattr(options, 'newphrase') 97 | 98 | if options.verbose: 99 | pfunc = progress 100 | sys.stdout.write("Generating priv/pub %s %d bits key pair (%s/%s.pub)..." 101 | % (ktype, bits, filename, filename)) 102 | sys.stdout.flush() 103 | 104 | if ktype == 'dsa' and bits > 1024: 105 | raise SSHException("DSA Keys must be 1024 bits") 106 | 107 | if ktype not in key_dispatch_table: 108 | raise SSHException("Unknown %s algorithm to generate keys pair" % ktype) 109 | 110 | # generating private key 111 | prv = key_dispatch_table[ktype].generate(bits=bits, progress_func=pfunc) 112 | prv.write_private_key_file(filename, password=phrase) 113 | 114 | # generating public key 115 | pub = key_dispatch_table[ktype](filename=filename, password=phrase) 116 | with open("%s.pub" % filename, 'w') as f: 117 | f.write("%s %s" % (pub.get_name(), pub.get_base64())) 118 | if options.comment: 119 | f.write(" %s" % comment) 120 | 121 | if options.verbose: 122 | print("done.") 123 | 124 | print("Fingerprint: %d %s %s.pub (%s)" % ( 125 | bits, pub.get_fingerprint_sha256_b64(), filename, ktype.upper() 126 | )) 127 | -------------------------------------------------------------------------------- /demos/demo_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2003-2007 Robey Pointer 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | import socket 22 | import sys 23 | import threading 24 | import traceback 25 | 26 | import paramiko 27 | from paramiko.py3compat import decodebytes 28 | 29 | 30 | # setup logging 31 | paramiko.util.log_to_file('demo_server.log') 32 | 33 | host_key = paramiko.load_private_key_file('test_rsa.key') 34 | 35 | print("Read key: " + host_key.get_fingerprint_sha256_b64()) 36 | 37 | 38 | class Server (paramiko.ServerInterface): 39 | # 'data' is the output of base64.b64encode(key) 40 | # (using the "user_rsa_key" files) 41 | data = (b'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hp' 42 | b'fAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMC' 43 | b'KDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iT' 44 | b'UWT10hcuO4Ks8=') 45 | good_pub_key = paramiko.RSAKey(data=decodebytes(data)) 46 | 47 | def __init__(self): 48 | self.event = threading.Event() 49 | 50 | def check_channel_request(self, kind, chanid): 51 | if kind == 'session': 52 | return paramiko.OPEN_SUCCEEDED 53 | return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED 54 | 55 | def check_auth_password(self, username, password): 56 | if (username == 'robey') and (password == 'foo'): 57 | return paramiko.AUTH_SUCCESSFUL 58 | return paramiko.AUTH_FAILED 59 | 60 | def check_auth_publickey(self, username, key): 61 | print("Auth attempt with key: " + key.get_fingerprint_sha256_b64()) 62 | if (username == 'robey') and (key == self.good_pub_key): 63 | return paramiko.AUTH_SUCCESSFUL 64 | return paramiko.AUTH_FAILED 65 | 66 | def check_auth_gssapi_with_mic(self, username, 67 | gss_authenticated=paramiko.AUTH_FAILED, 68 | cc_file=None): 69 | """ 70 | .. note:: 71 | We are just checking in `AuthHandler` that the given user is a 72 | valid krb5 principal! We don't check if the krb5 principal is 73 | allowed to log in on the server, because there is no way to do that 74 | in python. So if you develop your own SSH server with paramiko for 75 | a certain platform like Linux, you should call ``krb5_kuserok()`` in 76 | your local kerberos library to make sure that the krb5_principal 77 | has an account on the server and is allowed to log in as a user. 78 | 79 | .. seealso:: 80 | `krb5_kuserok() man page 81 | `_ 82 | """ 83 | if gss_authenticated == paramiko.AUTH_SUCCESSFUL: 84 | return paramiko.AUTH_SUCCESSFUL 85 | return paramiko.AUTH_FAILED 86 | 87 | def check_auth_gssapi_keyex(self, username, 88 | gss_authenticated=paramiko.AUTH_FAILED, 89 | cc_file=None): 90 | if gss_authenticated == paramiko.AUTH_SUCCESSFUL: 91 | return paramiko.AUTH_SUCCESSFUL 92 | return paramiko.AUTH_FAILED 93 | 94 | def enable_auth_gssapi(self): 95 | return True 96 | 97 | def get_allowed_auths(self, username): 98 | return 'gssapi-keyex,gssapi-with-mic,password,publickey' 99 | 100 | def check_channel_shell_request(self, channel): 101 | self.event.set() 102 | return True 103 | 104 | def check_channel_pty_request(self, channel, term, width, height, pixelwidth, 105 | pixelheight, modes): 106 | return True 107 | 108 | 109 | DoGSSAPIKeyExchange = True 110 | 111 | # now connect 112 | try: 113 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 114 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 115 | sock.bind(('', 2200)) 116 | except Exception as e: 117 | print('*** Bind failed: ' + str(e)) 118 | traceback.print_exc() 119 | sys.exit(1) 120 | 121 | try: 122 | sock.listen(100) 123 | print('Listening for connection ...') 124 | client, addr = sock.accept() 125 | except Exception as e: 126 | print('*** Listen/accept failed: ' + str(e)) 127 | traceback.print_exc() 128 | sys.exit(1) 129 | 130 | print('Got a connection!') 131 | 132 | try: 133 | t = paramiko.Transport(client, gss_kex=DoGSSAPIKeyExchange) 134 | t.set_gss_host(socket.getfqdn("")) 135 | try: 136 | t.load_server_moduli() 137 | except: 138 | print('(Failed to load moduli -- gex will be unsupported.)') 139 | raise 140 | t.add_server_key(host_key) 141 | server = Server() 142 | try: 143 | t.start_server(server=server) 144 | except paramiko.SSHException: 145 | print('*** SSH negotiation failed.') 146 | sys.exit(1) 147 | 148 | # wait for auth 149 | chan = t.accept(20) 150 | if chan is None: 151 | print('*** No channel.') 152 | sys.exit(1) 153 | print('Authenticated!') 154 | 155 | server.event.wait(10) 156 | if not server.event.is_set(): 157 | print('*** Client never asked for a shell.') 158 | sys.exit(1) 159 | 160 | chan.send('\r\n\r\nWelcome to my dorky little BBS!\r\n\r\n') 161 | chan.send('We are on fire all the time! Hooray! Candy corn for everyone!\r\n') 162 | chan.send('Happy birthday to Robot Dave!\r\n\r\n') 163 | chan.send('Username: ') 164 | f = chan.makefile('rU') 165 | username = f.readline().strip('\r\n') 166 | chan.send('\r\nI don\'t like you, ' + username + '.\r\n') 167 | chan.close() 168 | 169 | except Exception as e: 170 | print('*** Caught exception: ' + str(e.__class__) + ': ' + str(e)) 171 | traceback.print_exc() 172 | try: 173 | t.close() 174 | except: 175 | pass 176 | sys.exit(1) 177 | -------------------------------------------------------------------------------- /demos/demo_sftp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2003-2007 Robey Pointer 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | # based on code provided by raymond mosteller (thanks!) 22 | 23 | import getpass 24 | import os 25 | import socket 26 | import sys 27 | import traceback 28 | 29 | import paramiko 30 | from paramiko.py3compat import input 31 | 32 | 33 | # setup logging 34 | paramiko.util.log_to_file('demo_sftp.log') 35 | 36 | # Paramiko client configuration 37 | UseGSSAPI = True # enable GSS-API / SSPI authentication 38 | DoGSSAPIKeyExchange = True 39 | Port = 22 40 | 41 | # get hostname 42 | username = '' 43 | if len(sys.argv) > 1: 44 | hostname = sys.argv[1] 45 | if hostname.find('@') >= 0: 46 | username, hostname = hostname.split('@') 47 | else: 48 | hostname = input('Hostname: ') 49 | if len(hostname) == 0: 50 | print('*** Hostname required.') 51 | sys.exit(1) 52 | 53 | if hostname.find(':') >= 0: 54 | hostname, portstr = hostname.split(':') 55 | Port = int(portstr) 56 | 57 | 58 | # get username 59 | if username == '': 60 | default_username = getpass.getuser() 61 | username = input('Username [%s]: ' % default_username) 62 | if len(username) == 0: 63 | username = default_username 64 | if not UseGSSAPI: 65 | password = getpass.getpass('Password for %s@%s: ' % (username, hostname)) 66 | else: 67 | password = None 68 | 69 | 70 | # get host key, if we know one 71 | hostkeytype = None 72 | hostkey = None 73 | try: 74 | host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts')) 75 | except IOError: 76 | try: 77 | # try ~/ssh/ too, because windows can't have a folder named ~/.ssh/ 78 | host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/ssh/known_hosts')) 79 | except IOError: 80 | print('*** Unable to open host keys file') 81 | host_keys = {} 82 | 83 | if hostname in host_keys: 84 | hostkeytype = host_keys[hostname].keys()[0] 85 | hostkey = host_keys[hostname][hostkeytype] 86 | print('Using host key of type %s' % hostkeytype) 87 | 88 | 89 | # now, connect and use paramiko Transport to negotiate SSH2 across the connection 90 | try: 91 | t = paramiko.Transport((hostname, Port)) 92 | t.connect(hostkey, username, password, gss_host=socket.getfqdn(hostname), 93 | gss_auth=UseGSSAPI, gss_kex=DoGSSAPIKeyExchange) 94 | sftp = paramiko.SFTPClient.from_transport(t) 95 | 96 | # dirlist on remote host 97 | dirlist = sftp.listdir('.') 98 | print("Dirlist: %s" % dirlist) 99 | 100 | # copy this demo onto the server 101 | try: 102 | sftp.mkdir("demo_sftp_folder") 103 | except IOError: 104 | print('(assuming demo_sftp_folder/ already exists)') 105 | with sftp.open('demo_sftp_folder/README', 'w') as f: 106 | f.write('This was created by demo_sftp.py.\n') 107 | with open('demo_sftp.py', 'r') as f: 108 | data = f.read() 109 | sftp.open('demo_sftp_folder/demo_sftp.py', 'w').write(data) 110 | print('created demo_sftp_folder/ on the server') 111 | 112 | # copy the README back here 113 | with sftp.open('demo_sftp_folder/README', 'r') as f: 114 | data = f.read() 115 | with open('README_demo_sftp', 'w') as f: 116 | f.write(data) 117 | print('copied README back here') 118 | 119 | # BETTER: use the get() and put() methods 120 | sftp.put('demo_sftp.py', 'demo_sftp_folder/demo_sftp.py') 121 | sftp.get('demo_sftp_folder/README', 'README_demo_sftp') 122 | 123 | t.close() 124 | 125 | except Exception as e: 126 | print('*** Caught exception: %s: %s' % (e.__class__, e)) 127 | traceback.print_exc() 128 | try: 129 | t.close() 130 | except: 131 | pass 132 | sys.exit(1) 133 | -------------------------------------------------------------------------------- /demos/demo_simple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2003-2007 Robey Pointer 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | 22 | import getpass 23 | import sys 24 | import traceback 25 | from paramiko.py3compat import input 26 | 27 | import paramiko 28 | try: 29 | import interactive 30 | except ImportError: 31 | from . import interactive 32 | 33 | 34 | # setup logging 35 | paramiko.util.log_to_file('demo_simple.log') 36 | # Paramiko client configuration 37 | UseGSSAPI = paramiko.GSS_AUTH_AVAILABLE # enable "gssapi-with-mic" authentication, if supported 38 | DoGSSAPIKeyExchange = paramiko.GSS_AUTH_AVAILABLE # enable "gssapi-kex" key exchange, if supported 39 | # UseGSSAPI = False 40 | # DoGSSAPIKeyExchange = False 41 | port = 22 42 | 43 | # get hostname 44 | username = '' 45 | if len(sys.argv) > 1: 46 | hostname = sys.argv[1] 47 | if hostname.find('@') >= 0: 48 | username, hostname = hostname.split('@') 49 | else: 50 | hostname = input('Hostname: ') 51 | if len(hostname) == 0: 52 | print('*** Hostname required.') 53 | sys.exit(1) 54 | 55 | if hostname.find(':') >= 0: 56 | hostname, portstr = hostname.split(':') 57 | port = int(portstr) 58 | 59 | 60 | # get username 61 | if username == '': 62 | default_username = getpass.getuser() 63 | username = input('Username [%s]: ' % default_username) 64 | if len(username) == 0: 65 | username = default_username 66 | if not UseGSSAPI and not DoGSSAPIKeyExchange: 67 | password = getpass.getpass('Password for %s@%s: ' % (username, hostname)) 68 | 69 | 70 | # now, connect and use paramiko Client to negotiate SSH2 across the connection 71 | try: 72 | client = paramiko.SSHClient() 73 | client.load_system_host_keys() 74 | client.set_missing_host_key_policy(paramiko.WarningPolicy()) 75 | print('*** Connecting...') 76 | if not UseGSSAPI and not DoGSSAPIKeyExchange: 77 | client.connect(hostname, port, username, password) 78 | else: 79 | try: 80 | client.connect(hostname, port, username, gss_auth=UseGSSAPI, 81 | gss_kex=DoGSSAPIKeyExchange) 82 | except Exception: 83 | # traceback.print_exc() 84 | password = getpass.getpass('Password for %s@%s: ' % (username, hostname)) 85 | client.connect(hostname, port, username, password) 86 | 87 | chan = client.invoke_shell() 88 | print(repr(client.get_transport())) 89 | print('*** Here we go!\n') 90 | interactive.interactive_shell(chan) 91 | chan.close() 92 | client.close() 93 | 94 | except Exception as e: 95 | print('*** Caught exception: %s: %s' % (e.__class__, e)) 96 | traceback.print_exc() 97 | try: 98 | client.close() 99 | except: 100 | pass 101 | sys.exit(1) 102 | -------------------------------------------------------------------------------- /demos/interactive.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | 20 | import socket 21 | import sys 22 | from paramiko.py3compat import u 23 | 24 | # windows does not have termios... 25 | try: 26 | import termios 27 | import tty 28 | has_termios = True 29 | except ImportError: 30 | has_termios = False 31 | 32 | 33 | def interactive_shell(chan): 34 | if has_termios: 35 | posix_shell(chan) 36 | else: 37 | windows_shell(chan) 38 | 39 | 40 | def posix_shell(chan): 41 | import select 42 | 43 | oldtty = termios.tcgetattr(sys.stdin) 44 | try: 45 | tty.setraw(sys.stdin.fileno()) 46 | tty.setcbreak(sys.stdin.fileno()) 47 | chan.settimeout(0.0) 48 | 49 | while True: 50 | r, w, e = select.select([chan, sys.stdin], [], []) 51 | if chan in r: 52 | try: 53 | x = u(chan.recv(1024)) 54 | if len(x) == 0: 55 | sys.stdout.write('\r\n*** EOF\r\n') 56 | break 57 | sys.stdout.write(x) 58 | sys.stdout.flush() 59 | except socket.timeout: 60 | pass 61 | if sys.stdin in r: 62 | x = sys.stdin.read(1) 63 | if len(x) == 0: 64 | break 65 | chan.send(x) 66 | 67 | finally: 68 | termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) 69 | 70 | 71 | # thanks to Mike Looijmans for this code 72 | def windows_shell(chan): 73 | import threading 74 | 75 | sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n") 76 | 77 | def writeall(sock): 78 | while True: 79 | data = sock.recv(256) 80 | if not data: 81 | sys.stdout.write('\r\n*** EOF ***\r\n\r\n') 82 | sys.stdout.flush() 83 | break 84 | sys.stdout.write(data.decode('utf-8')) 85 | sys.stdout.flush() 86 | 87 | writer = threading.Thread(target=writeall, args=(chan,)) 88 | writer.start() 89 | 90 | try: 91 | while True: 92 | d = sys.stdin.read(1) 93 | if not d: 94 | break 95 | chan.send(d) 96 | except EOFError: 97 | # user hit ^Z or F6 98 | pass 99 | -------------------------------------------------------------------------------- /demos/rforward.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2008 Robey Pointer 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | """ 22 | Sample script showing how to do remote port forwarding over paramiko. 23 | 24 | This script connects to the requested SSH server and sets up remote port 25 | forwarding (the openssh -R option) from a remote port through a tunneled 26 | connection to a destination reachable from the local machine. 27 | """ 28 | 29 | import getpass 30 | import socket 31 | import select 32 | import sys 33 | import threading 34 | from optparse import OptionParser 35 | 36 | import paramiko 37 | 38 | SSH_PORT = 22 39 | DEFAULT_PORT = 4000 40 | 41 | g_verbose = True 42 | 43 | 44 | def handler(chan, host, port): 45 | sock = socket.socket() 46 | try: 47 | sock.connect((host, port)) 48 | except Exception as e: 49 | verbose('Forwarding request to %s:%d failed: %r' % (host, port, e)) 50 | return 51 | 52 | verbose('Connected! Tunnel open %r -> %r -> %r' % (chan.origin_addr, 53 | chan.getpeername(), (host, port))) 54 | while True: 55 | r, w, x = select.select([sock, chan], [], []) 56 | if sock in r: 57 | data = sock.recv(1024) 58 | if len(data) == 0: 59 | break 60 | chan.sendall(data) 61 | if chan in r: 62 | data = chan.recv(1024) 63 | if len(data) == 0: 64 | break 65 | sock.sendall(data) 66 | chan.close() 67 | sock.close() 68 | verbose('Tunnel closed from %r' % (chan.origin_addr,)) 69 | 70 | 71 | def reverse_forward_tunnel(server_port, remote_host, remote_port, transport): 72 | transport.request_port_forward('', server_port) 73 | while True: 74 | chan = transport.accept(1000) 75 | if chan is None: 76 | continue 77 | thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port)) 78 | thr.daemon = True 79 | thr.start() 80 | 81 | 82 | def verbose(s): 83 | if g_verbose: 84 | print(s) 85 | 86 | 87 | HELP = """\ 88 | Set up a reverse forwarding tunnel across an SSH server, using paramiko. A 89 | port on the SSH server (given with -p) is forwarded across an SSH session 90 | back to the local machine, and out to a remote site reachable from this 91 | network. This is similar to the openssh -R option. 92 | """ 93 | 94 | 95 | def get_host_port(spec, default_port): 96 | "parse 'hostname:22' into a host and port, with the port optional" 97 | args = (spec.split(':', 1) + [default_port])[:2] 98 | args[1] = int(args[1]) 99 | return args[0], args[1] 100 | 101 | 102 | def parse_options(): 103 | global g_verbose 104 | 105 | parser = OptionParser(usage='usage: %prog [options] [:]', 106 | version='%prog 1.0', description=HELP) 107 | parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True, 108 | help='squelch all informational output') 109 | parser.add_option('-p', '--remote-port', action='store', type='int', dest='port', 110 | default=DEFAULT_PORT, 111 | help='port on server to forward (default: %d)' % DEFAULT_PORT) 112 | parser.add_option('-u', '--user', action='store', type='string', dest='user', 113 | default=getpass.getuser(), 114 | help='username for SSH authentication (default: %s)' % getpass.getuser()) 115 | parser.add_option('-K', '--key', action='store', type='string', dest='keyfile', 116 | default=None, 117 | help='private key file to use for SSH authentication') 118 | parser.add_option('', '--no-key', action='store_false', dest='look_for_keys', default=True, 119 | help='don\'t look for or use a private key file') 120 | parser.add_option('-P', '--password', action='store_true', dest='readpass', default=False, 121 | help='read password (for key or password auth) from stdin') 122 | parser.add_option('-r', '--remote', action='store', type='string', dest='remote', default=None, 123 | help='remote host and port to forward to', metavar='host:port') 124 | options, args = parser.parse_args() 125 | 126 | if len(args) != 1: 127 | parser.error('Incorrect number of arguments.') 128 | if options.remote is None: 129 | parser.error('Remote address required (-r).') 130 | 131 | g_verbose = options.verbose 132 | server_host, server_port = get_host_port(args[0], SSH_PORT) 133 | remote_host, remote_port = get_host_port(options.remote, SSH_PORT) 134 | return options, (server_host, server_port), (remote_host, remote_port) 135 | 136 | 137 | def main(): 138 | options, server, remote = parse_options() 139 | 140 | password = None 141 | if options.readpass: 142 | password = getpass.getpass('Enter SSH password: ') 143 | 144 | client = paramiko.SSHClient() 145 | client.load_system_host_keys() 146 | client.set_missing_host_key_policy(paramiko.WarningPolicy()) 147 | 148 | verbose('Connecting to ssh host %s:%d ...' % (server[0], server[1])) 149 | try: 150 | client.connect(server[0], server[1], username=options.user, key_filename=options.keyfile, 151 | look_for_keys=options.look_for_keys, password=password) 152 | except Exception as e: 153 | print('*** Failed to connect to %s:%d: %r' % (server[0], server[1], e)) 154 | sys.exit(1) 155 | 156 | verbose('Now forwarding remote port %d to %s:%d ...' % (options.port, remote[0], remote[1])) 157 | 158 | try: 159 | reverse_forward_tunnel(options.port, remote[0], remote[1], client.get_transport()) 160 | except KeyboardInterrupt: 161 | print('C-c: Port forwarding stopped.') 162 | sys.exit(0) 163 | 164 | 165 | if __name__ == '__main__': 166 | main() 167 | -------------------------------------------------------------------------------- /demos/test_rsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz 3 | oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/ 4 | d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB 5 | gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0 6 | EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon 7 | soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H 8 | tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU 9 | avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA 10 | 4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g 11 | H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv 12 | qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV 13 | HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc 14 | nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /demos/user_rsa_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDI7iK3d8eWYZlYloat94c5VjtFY7c/0zuGl8C7uMnZ3t6i2G99 3 | 66hEW0nCFSZkOW5F0XKEVj+EUCHvo8koYC6wiohAqWQnEwIoOoh7GSAcB8gP/qaq 4 | +adIl/Rvlby/mHakj+y05LBND6nFWHAn1y1gOFFKUXSJNRZPXSFy47gqzwIBIwKB 5 | gQCbANjz7q/pCXZLp1Hz6tYHqOvlEmjK1iabB1oqafrMpJ0eibUX/u+FMHq6StR5 6 | M5413BaDWHokPdEJUnabfWXXR3SMlBUKrck0eAer1O8m78yxu3OEdpRk+znVo4DL 7 | guMeCdJB/qcF0kEsx+Q8HP42MZU1oCmk3PbfXNFwaHbWuwJBAOQ/ry/hLD7AqB8x 8 | DmCM82A9E59ICNNlHOhxpJoh6nrNTPCsBAEu/SmqrL8mS6gmbRKUaya5Lx1pkxj2 9 | s/kWOokCQQDhXCcYXjjWiIfxhl6Rlgkk1vmI0l6785XSJNv4P7pXjGmShXfIzroh 10 | S8uWK3tL0GELY7+UAKDTUEVjjQdGxYSXAkEA3bo1JzKCwJ3lJZ1ebGuqmADRO6UP 11 | 40xH977aadfN1mEI6cusHmgpISl0nG5YH7BMsvaT+bs1FUH8m+hXDzoqOwJBAK3Z 12 | X/za+KV/REya2z0b+GzgWhkXUGUa/owrEBdHGriQ47osclkUgPUdNqcLmaDilAF4 13 | 1Z4PHPrI5RJIONAx+JECQQC/fChqjBgFpk6iJ+BOdSexQpgfxH/u/457W10Y43HR 14 | soS+8btbHqjQkowQ/2NTlUfWvqIlfxs6ZbFsIp/HrhZL 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /demos/user_rsa_key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8= robey@ralph.lag.net 2 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | # flake8 strictly controls versions of pyflakes/pycodestyle 2 | flake8==5.0.4;python_version>="3.6" 3 | # for running tests 4 | pytest==4.6.11;python_version<"3.9" 5 | pytest==6.2.5;python_version>="3.9" 6 | mock==2.0.0;python_version<"3.3" 7 | -------------------------------------------------------------------------------- /doc-requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | Babel==2.9.1 3 | charset-normalizer==2.0.4 4 | docutils==0.16 5 | idna==3.2 6 | imagesize==1.2.0 7 | Jinja2==3.0.1 8 | MarkupSafe==2.0.1 9 | Pygments==2.10.0 10 | pyparsing==2.4.7 11 | releases==1.6.3 12 | six==1.16.0 13 | semantic_version==2.6.0 14 | snowballstemmer==2.1.0 15 | Sphinx==2.4.4 16 | sphinxcontrib-applehelp==1.0.2 17 | sphinxcontrib-devhelp==1.0.2 18 | sphinxcontrib-htmlhelp==2.0.0 19 | sphinxcontrib-jsmath==1.0.1 20 | sphinxcontrib-qthelp==1.0.3 21 | sphinxcontrib-serializinghtml==1.1.5 22 | -------------------------------------------------------------------------------- /paramiko/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2011 Robey Pointer 2 | # Copyright (C) 2013-2019 Jeff Forcier 3 | # Copyright (C) 2019-2020 Pierce Lopez 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | from paramiko.transport import SecurityOptions, Transport 22 | from paramiko.client import ( 23 | SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, 24 | WarningPolicy, 25 | ) 26 | from paramiko.auth_handler import AuthHandler 27 | from paramiko.ssh_gss import GSSAuth, GSS_AUTH_AVAILABLE, GSS_EXCEPTIONS 28 | from paramiko.channel import Channel, ChannelFile, ChannelStderrFile, ChannelStdinFile 29 | from paramiko.ssh_exception import ( 30 | SSHException, PasswordRequiredException, BadAuthenticationType, 31 | ChannelException, BadHostKeyException, AuthenticationException, 32 | ProxyCommandFailure, 33 | ) 34 | from paramiko.server import ServerInterface, SubsystemHandler, InteractiveQuery 35 | from paramiko.rsakey import RSAKey 36 | from paramiko.dsskey import DSSKey 37 | from paramiko.ecdsakey import ECDSAKey 38 | from paramiko.ed25519key import Ed25519Key 39 | from paramiko.sftp import SFTPError 40 | from paramiko.sftp_client import SFTP, SFTPClient 41 | from paramiko.sftp_server import SFTPServer 42 | from paramiko.sftp_attr import SFTPAttributes 43 | from paramiko.sftp_handle import SFTPHandle 44 | from paramiko.sftp_si import SFTPServerInterface 45 | from paramiko.sftp_file import SFTPFile 46 | from paramiko.message import Message 47 | from paramiko.packet import Packetizer 48 | from paramiko.file import BufferedFile 49 | from paramiko.agent import Agent, AgentKey 50 | from paramiko.pkey import ( 51 | PKey, 52 | PublicBlob, 53 | load_private_key, 54 | load_private_key_file, 55 | ) 56 | from paramiko.hostkeys import HostKeys 57 | from paramiko.config import SSHConfig 58 | from paramiko.proxy import ProxyCommand 59 | from paramiko.common import io_sleep 60 | from paramiko.common import ( 61 | AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, OPEN_SUCCEEDED, 62 | OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, 63 | OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, OPEN_FAILED_RESOURCE_SHORTAGE, 64 | ) 65 | from paramiko.sftp import ( 66 | SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, 67 | SFTP_BAD_MESSAGE, SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, 68 | SFTP_OP_UNSUPPORTED, 69 | ) 70 | from paramiko._version import __version__, __version_info__ # noqa: F401 71 | 72 | 73 | __author__ = "various" 74 | __license__ = "GNU Lesser General Public License (LGPL)" 75 | __pkgname__ = "paramiko-ng" 76 | 77 | __all__ = [ 78 | 'Transport', 79 | 'SSHClient', 80 | 'MissingHostKeyPolicy', 81 | 'AutoAddPolicy', 82 | 'RejectPolicy', 83 | 'WarningPolicy', 84 | 'SecurityOptions', 85 | 'AuthHandler', 86 | 'Channel', 87 | 'ChannelFile', 88 | 'ChannelStderrFile', 89 | 'ChannelStdinFile', 90 | 'PKey', 91 | 'RSAKey', 92 | 'DSSKey', 93 | 'ECDSAKey', 94 | 'Ed25519Key', 95 | 'PublicBlob', 96 | 'load_private_key', 97 | 'load_private_key_file', 98 | 'Message', 99 | 'Packetizer', 100 | 'SSHException', 101 | 'AuthenticationException', 102 | 'PasswordRequiredException', 103 | 'BadAuthenticationType', 104 | 'ChannelException', 105 | 'BadHostKeyException', 106 | 'ProxyCommand', 107 | 'ProxyCommandFailure', 108 | 'GSSAuth', 109 | 'GSS_AUTH_AVAILABLE', 110 | 'GSS_EXCEPTIONS', 111 | 'AUTH_SUCCESSFUL', 112 | 'AUTH_PARTIALLY_SUCCESSFUL', 113 | 'AUTH_FAILED', 114 | 'OPEN_SUCCEEDED', 115 | 'OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED', 116 | 'OPEN_FAILED_CONNECT_FAILED', 117 | 'OPEN_FAILED_UNKNOWN_CHANNEL_TYPE', 118 | 'OPEN_FAILED_RESOURCE_SHORTAGE', 119 | 'SFTP', 120 | 'SFTPFile', 121 | 'SFTPHandle', 122 | 'SFTPClient', 123 | 'SFTPServer', 124 | 'SFTPError', 125 | 'SFTPAttributes', 126 | 'SFTPServerInterface', 127 | 'SFTP_OK', 128 | 'SFTP_EOF', 129 | 'SFTP_NO_SUCH_FILE', 130 | 'SFTP_PERMISSION_DENIED', 131 | 'SFTP_FAILURE', 132 | 'SFTP_BAD_MESSAGE', 133 | 'SFTP_NO_CONNECTION', 134 | 'SFTP_CONNECTION_LOST', 135 | 'SFTP_OP_UNSUPPORTED', 136 | 'ServerInterface', 137 | 'SubsystemHandler', 138 | 'InteractiveQuery', 139 | 'BufferedFile', 140 | 'Agent', 141 | 'AgentKey', 142 | 'HostKeys', 143 | 'SSHConfig', 144 | 'io_sleep', 145 | ] 146 | -------------------------------------------------------------------------------- /paramiko/_version.py: -------------------------------------------------------------------------------- 1 | # last version component is odd for pre-release development, even for stable release 2 | __version_info__ = (2, 9, 0) 3 | __version__ = '.'.join(map(str, __version_info__)) 4 | -------------------------------------------------------------------------------- /paramiko/ber.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | from paramiko.common import max_byte, zero_byte 19 | from paramiko.py3compat import b, byte_ord, byte_chr, long 20 | 21 | import paramiko.util as util 22 | 23 | 24 | class BERException (Exception): 25 | pass 26 | 27 | 28 | class BER(object): 29 | def __init__(self, content=bytes()): 30 | self.content = b(content) 31 | self.idx = 0 32 | 33 | def asbytes(self): 34 | return self.content 35 | 36 | def __str__(self): 37 | return self.asbytes() 38 | 39 | def __repr__(self): 40 | return 'BER(\'' + repr(self.content) + '\')' 41 | 42 | def decode(self): 43 | return self.decode_next() 44 | 45 | def decode_next(self): 46 | if self.idx >= len(self.content): 47 | return None 48 | ident = byte_ord(self.content[self.idx]) 49 | self.idx += 1 50 | if (ident & 31) == 31: 51 | # identifier > 30 52 | ident = 0 53 | while self.idx < len(self.content): 54 | t = byte_ord(self.content[self.idx]) 55 | self.idx += 1 56 | ident = (ident << 7) | (t & 0x7f) 57 | if not (t & 0x80): 58 | break 59 | if self.idx >= len(self.content): 60 | return None 61 | # now fetch length 62 | size = byte_ord(self.content[self.idx]) 63 | self.idx += 1 64 | if size & 0x80: 65 | # more complimicated... 66 | # FIXME: theoretically should handle indefinite-length (0x80) 67 | t = size & 0x7f 68 | if self.idx + t > len(self.content): 69 | return None 70 | size = util.inflate_long( 71 | self.content[self.idx: self.idx + t], True) 72 | self.idx += t 73 | if self.idx + size > len(self.content): 74 | # can't fit 75 | return None 76 | data = self.content[self.idx: self.idx + size] 77 | self.idx += size 78 | # now switch on id 79 | if ident == 0x30: 80 | # sequence 81 | return self.decode_sequence(data) 82 | elif ident == 2: 83 | # int 84 | return util.inflate_long(data) 85 | else: 86 | # 1: boolean (00 false, otherwise true) 87 | msg = 'Unknown ber encoding type {:d} (robey is lazy)' 88 | raise BERException(msg.format(ident)) 89 | 90 | @staticmethod 91 | def decode_sequence(data): 92 | out = [] 93 | ber = BER(data) 94 | while True: 95 | x = ber.decode_next() 96 | if x is None: 97 | break 98 | out.append(x) 99 | return out 100 | 101 | def encode_tlv(self, ident, val): 102 | # no need to support ident > 31 here 103 | self.content += byte_chr(ident) 104 | if len(val) > 0x7f: 105 | lenstr = util.deflate_long(len(val)) 106 | self.content += byte_chr(0x80 + len(lenstr)) + lenstr 107 | else: 108 | self.content += byte_chr(len(val)) 109 | self.content += val 110 | 111 | def encode(self, x): 112 | if type(x) is bool: 113 | if x: 114 | self.encode_tlv(1, max_byte) 115 | else: 116 | self.encode_tlv(1, zero_byte) 117 | elif (type(x) is int) or (type(x) is long): 118 | self.encode_tlv(2, util.deflate_long(x)) 119 | elif type(x) is str: 120 | self.encode_tlv(4, x) 121 | elif (type(x) is list) or (type(x) is tuple): 122 | self.encode_tlv(0x30, self.encode_sequence(x)) 123 | else: 124 | raise BERException( 125 | 'Unknown type for encoding: {!r}'.format(type(x)) 126 | ) 127 | 128 | @staticmethod 129 | def encode_sequence(data): 130 | ber = BER() 131 | for item in data: 132 | ber.encode(item) 133 | return ber.asbytes() 134 | -------------------------------------------------------------------------------- /paramiko/compress.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Compression implementations for a Transport. 21 | """ 22 | 23 | import zlib 24 | 25 | 26 | class ZlibCompressor (object): 27 | def __init__(self): 28 | # Use the default level of zlib compression 29 | self.z = zlib.compressobj() 30 | 31 | def __call__(self, data): 32 | return self.z.compress(data) + self.z.flush(zlib.Z_FULL_FLUSH) 33 | 34 | 35 | class ZlibDecompressor (object): 36 | def __init__(self): 37 | self.z = zlib.decompressobj() 38 | 39 | def __call__(self, data): 40 | return self.z.decompress(data) 41 | -------------------------------------------------------------------------------- /paramiko/ed25519key.py: -------------------------------------------------------------------------------- 1 | # This file is part of paramiko. 2 | # 3 | # Paramiko is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU Lesser General Public License as published by the Free 5 | # Software Foundation; either version 2.1 of the License, or (at your option) 6 | # any later version. 7 | # 8 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 9 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 10 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | # details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public License 14 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 15 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 16 | 17 | from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm 18 | from cryptography.hazmat.primitives import serialization 19 | from cryptography.hazmat.primitives.asymmetric import ed25519 20 | 21 | from paramiko.message import Message 22 | from paramiko.pkey import PKey, register_pkey_type 23 | from paramiko.ssh_exception import SSHException 24 | 25 | 26 | @register_pkey_type 27 | class Ed25519Key(PKey): 28 | """ 29 | Representation of an `Ed25519 `_ key. 30 | 31 | .. note:: 32 | Ed25519 key support was added to OpenSSH in version 6.5. 33 | 34 | .. versionadded:: 2.2 35 | .. versionchanged:: 2.3 36 | Added a ``file_obj`` parameter to match other key classes. 37 | """ 38 | 39 | # Legacy file format does not support Ed25519 40 | LEGACY_TYPE = None 41 | OPENSSH_TYPE_PREFIX = 'ssh-ed25519' 42 | 43 | @staticmethod 44 | def is_supported(): 45 | """ 46 | Check if the openssl version pyca/cryptography is linked against 47 | supports Ed25519 keys. 48 | """ 49 | try: 50 | ed25519.Ed25519PublicKey.from_public_bytes(b"\x00" * 32) 51 | except UnsupportedAlgorithm: 52 | return False # openssl < 1.1.0 53 | return True 54 | 55 | def __init__(self, msg=None, data=None, filename=None, password=None, 56 | file_obj=None, _raw=None): 57 | self.public_blob = None 58 | verifying_key = None 59 | signing_key = None 60 | 61 | if msg is None and data is not None: 62 | msg = Message(data) 63 | if msg is not None: 64 | self._check_type_and_load_cert( 65 | msg=msg, 66 | key_type="ssh-ed25519", 67 | cert_type="ssh-ed25519-cert-v01@openssh.com", 68 | ) 69 | verifying_key = ed25519.Ed25519PublicKey.from_public_bytes(msg.get_binary()) 70 | elif filename is not None: 71 | _raw = self._from_private_key_file(filename, password) 72 | elif file_obj is not None: 73 | _raw = self._from_private_key(file_obj, password) 74 | if _raw is not None: 75 | signing_key = self._decode_key(_raw) 76 | 77 | if signing_key is None and verifying_key is None: 78 | raise ValueError("need a key") 79 | 80 | self._signing_key = signing_key 81 | self._verifying_key = verifying_key or signing_key.public_key() 82 | 83 | def _decode_key(self, _raw): 84 | pkformat, data = _raw 85 | if pkformat != self.FORMAT_OPENSSH: 86 | raise SSHException("Invalid key format") 87 | 88 | message = Message(data) 89 | public = message.get_binary() 90 | key_data = message.get_binary() 91 | comment = message.get_binary() # noqa: F841 92 | 93 | # The second half of the key data is yet another copy of the public key... 94 | signing_key = ed25519.Ed25519PrivateKey.from_private_bytes(key_data[:32]) 95 | 96 | # Verify that all the public keys are the same... 97 | derived_public = signing_key.public_key().public_bytes( 98 | serialization.Encoding.Raw, 99 | serialization.PublicFormat.Raw, 100 | ) 101 | if public != key_data[32:] or public != derived_public: 102 | raise SSHException("Invalid key public part mis-match") 103 | 104 | return signing_key 105 | 106 | def asbytes(self): 107 | public_bytes = self._verifying_key.public_bytes( 108 | serialization.Encoding.Raw, 109 | serialization.PublicFormat.Raw, 110 | ) 111 | m = Message() 112 | m.add_string("ssh-ed25519") 113 | m.add_string(public_bytes) 114 | return m.asbytes() 115 | 116 | def get_name(self): 117 | return "ssh-ed25519" 118 | 119 | def get_bits(self): 120 | return 256 121 | 122 | def can_sign(self): 123 | return self._signing_key is not None 124 | 125 | def sign_ssh_data(self, data): 126 | m = Message() 127 | m.add_string("ssh-ed25519") 128 | m.add_string(self._signing_key.sign(data)) 129 | return m 130 | 131 | def verify_ssh_sig(self, data, msg): 132 | if msg.get_text() != "ssh-ed25519": 133 | return False 134 | try: 135 | self._verifying_key.verify(msg.get_binary(), data) 136 | except InvalidSignature: 137 | return False 138 | else: 139 | return True 140 | -------------------------------------------------------------------------------- /paramiko/kex_curve25519.py: -------------------------------------------------------------------------------- 1 | # This file is part of paramiko. 2 | # 3 | # Paramiko is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU Lesser General Public License as published by the Free 5 | # Software Foundation; either version 2.1 of the License, or (at your option) 6 | # any later version. 7 | # 8 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 9 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 10 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 11 | # details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public License 14 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 15 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 16 | 17 | """ 18 | Key exchange using DJB's Curve25519. Originally introduced in OpenSSH 6.5 19 | """ 20 | 21 | # Author: Dan Fuhry 22 | 23 | from hashlib import sha256 24 | 25 | from paramiko.message import Message 26 | from paramiko.py3compat import byte_chr, long 27 | from paramiko.ssh_exception import SSHException 28 | from cryptography.hazmat.primitives.asymmetric import x25519 29 | from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat 30 | from cryptography.exceptions import UnsupportedAlgorithm 31 | from binascii import hexlify 32 | 33 | _MSG_KEXC25519_INIT, _MSG_KEXC25519_REPLY = range(30, 32) 34 | c_MSG_KEXC25519_INIT, c_MSG_KEXC25519_REPLY = [ 35 | byte_chr(c) for c in range(30, 32) 36 | ] 37 | 38 | 39 | class KexCurve25519(object): 40 | name = "curve25519-sha256@libssh.org" 41 | hash_algo = sha256 42 | K = None 43 | 44 | def __init__(self, transport): 45 | self.transport = transport 46 | 47 | self.P = long(0) 48 | # Client public key 49 | self.Q_C = None 50 | # Server public key 51 | self.Q_S = None 52 | 53 | def start_kex(self): 54 | self._generate_key_pair() 55 | if self.transport.server_mode: 56 | self.transport._expect_packet(_MSG_KEXC25519_INIT) 57 | return 58 | m = Message() 59 | m.add_byte(c_MSG_KEXC25519_INIT) 60 | Q_C_bytes = self.Q_C.public_bytes( 61 | encoding=Encoding.Raw, format=PublicFormat.Raw 62 | ) 63 | m.add_string(Q_C_bytes) 64 | self.transport._send_message(m) 65 | self.transport._expect_packet(_MSG_KEXC25519_REPLY) 66 | 67 | def parse_next(self, ptype, m): 68 | if self.transport.server_mode and (ptype == _MSG_KEXC25519_INIT): 69 | return self._parse_kexc25519_init(m) 70 | elif not self.transport.server_mode and ( 71 | ptype == _MSG_KEXC25519_REPLY 72 | ): 73 | 74 | return self._parse_kexc25519_reply(m) 75 | msg = "KexCurve25519 asked to handle packet type {:d}" 76 | raise SSHException(msg.format(ptype)) 77 | 78 | @staticmethod 79 | def is_supported(): 80 | """ 81 | Check if the openssl version pyca/cryptography is linked against 82 | supports curve25519 key agreement. 83 | """ 84 | try: 85 | x25519.X25519PublicKey.from_public_bytes(b"\x00" * 32) 86 | except UnsupportedAlgorithm: 87 | return False # openssl < 1.1.0 88 | 89 | return True 90 | 91 | is_available = is_supported # for compatibility with upstream's variant 92 | 93 | # ...internals... 94 | 95 | def _generate_key_pair(self): 96 | while True: 97 | self.P = x25519.X25519PrivateKey.generate() 98 | pub = self.P.public_key().public_bytes( 99 | encoding=Encoding.Raw, format=PublicFormat.Raw 100 | ) 101 | if len(pub) != 32: 102 | continue 103 | 104 | if self.transport.server_mode: 105 | self.Q_S = self.P.public_key() 106 | else: 107 | self.Q_C = self.P.public_key() 108 | break 109 | 110 | def _parse_kexc25519_reply(self, m): 111 | # client mode 112 | 113 | # 3 fields in response: 114 | # - KEX host key 115 | # - Ephemeral (Curve25519) key 116 | # - Signature 117 | K_S = m.get_string() 118 | self.Q_S = x25519.X25519PublicKey.from_public_bytes(m.get_string()) 119 | sig = m.get_binary() 120 | 121 | # Compute shared secret 122 | K = self.P.exchange(self.Q_S) 123 | K = long(hexlify(K), 16) 124 | 125 | hm = Message() 126 | hm.add( 127 | self.transport.local_version, 128 | self.transport.remote_version, 129 | self.transport.local_kex_init, 130 | self.transport.remote_kex_init, 131 | ) 132 | 133 | # "hm" is used as the initial transport key 134 | hm.add_string(K_S) 135 | hm.add_string( 136 | self.Q_C.public_bytes( 137 | encoding=Encoding.Raw, format=PublicFormat.Raw 138 | ) 139 | ) 140 | hm.add_string( 141 | self.Q_S.public_bytes( 142 | encoding=Encoding.Raw, format=PublicFormat.Raw 143 | ) 144 | ) 145 | hm.add_mpint(K) 146 | self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest()) 147 | # Verify that server signed kex message with its own pubkey 148 | self.transport._verify_key(K_S, sig) 149 | self.transport._activate_outbound() 150 | 151 | def _parse_kexc25519_init(self, m): 152 | # server mode 153 | 154 | # Only one field in the client's message, which is their public key 155 | Q_C_bytes = m.get_string() 156 | self.Q_C = x25519.X25519PublicKey.from_public_bytes(Q_C_bytes) 157 | 158 | # Compute shared secret 159 | K = self.P.exchange(self.Q_C) 160 | K = long(hexlify(K), 16) 161 | 162 | # Prepare hostkey 163 | K_S = self.transport.get_server_key().asbytes() 164 | 165 | # Compute initial transport key 166 | hm = Message() 167 | hm.add( 168 | self.transport.remote_version, 169 | self.transport.local_version, 170 | self.transport.remote_kex_init, 171 | self.transport.local_kex_init, 172 | ) 173 | 174 | hm.add_string(K_S) 175 | hm.add_string(Q_C_bytes) 176 | hm.add_string( 177 | self.Q_S.public_bytes( 178 | encoding=Encoding.Raw, format=PublicFormat.Raw 179 | ) 180 | ) 181 | hm.add_mpint(K) 182 | H = self.hash_algo(hm.asbytes()).digest() 183 | self.transport._set_K_H(K, H) 184 | 185 | # Compute signature 186 | sig = self.transport.get_server_key().sign_ssh_data(H) 187 | # construct reply 188 | m = Message() 189 | m.add_byte(c_MSG_KEXC25519_REPLY) 190 | m.add_string(K_S) 191 | m.add_string( 192 | self.Q_S.public_bytes( 193 | encoding=Encoding.Raw, format=PublicFormat.Raw 194 | ) 195 | ) 196 | m.add_string(sig) 197 | self.transport._send_message(m) 198 | self.transport._activate_outbound() 199 | -------------------------------------------------------------------------------- /paramiko/kex_ecdh_nist.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ephemeral Elliptic Curve Diffie-Hellman (ECDH) key exchange 3 | RFC 5656, Section 4 4 | """ 5 | 6 | from hashlib import sha256, sha384, sha512 7 | from paramiko.message import Message 8 | from paramiko.py3compat import byte_chr, long 9 | from paramiko.ssh_exception import SSHException 10 | from cryptography.hazmat.backends import default_backend 11 | from cryptography.hazmat.primitives.asymmetric import ec 12 | from cryptography.hazmat.primitives import serialization 13 | from binascii import hexlify 14 | 15 | _MSG_KEXECDH_INIT, _MSG_KEXECDH_REPLY = range(30, 32) 16 | c_MSG_KEXECDH_INIT, c_MSG_KEXECDH_REPLY = [byte_chr(c) for c in range(30, 32)] 17 | 18 | 19 | class KexNistp256(object): 20 | 21 | name = "ecdh-sha2-nistp256" 22 | hash_algo = sha256 23 | curve = ec.SECP256R1() 24 | 25 | def __init__(self, transport): 26 | self.transport = transport 27 | # private key, client public and server public keys 28 | self.P = long(0) 29 | self.Q_C = None 30 | self.Q_S = None 31 | 32 | def start_kex(self): 33 | self._generate_key_pair() 34 | if self.transport.server_mode: 35 | self.transport._expect_packet(_MSG_KEXECDH_INIT) 36 | return 37 | m = Message() 38 | m.add_byte(c_MSG_KEXECDH_INIT) 39 | # SEC1: V2.0 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion 40 | m.add_string(self.Q_C.public_bytes(serialization.Encoding.X962, 41 | serialization.PublicFormat.UncompressedPoint)) 42 | self.transport._send_message(m) 43 | self.transport._expect_packet(_MSG_KEXECDH_REPLY) 44 | 45 | def parse_next(self, ptype, m): 46 | if self.transport.server_mode and (ptype == _MSG_KEXECDH_INIT): 47 | return self._parse_kexecdh_init(m) 48 | elif not self.transport.server_mode and (ptype == _MSG_KEXECDH_REPLY): 49 | return self._parse_kexecdh_reply(m) 50 | raise SSHException( 51 | 'KexECDH asked to handle packet type {:d}'.format(ptype) 52 | ) 53 | 54 | def _generate_key_pair(self): 55 | self.P = ec.generate_private_key(self.curve, default_backend()) 56 | if self.transport.server_mode: 57 | self.Q_S = self.P.public_key() 58 | return 59 | self.Q_C = self.P.public_key() 60 | 61 | def _parse_kexecdh_init(self, m): 62 | Q_C_bytes = m.get_string() 63 | self.Q_C = ec.EllipticCurvePublicKey.from_encoded_point( 64 | self.curve, Q_C_bytes 65 | ) 66 | K_S = self.transport.get_server_key().asbytes() 67 | K = self.P.exchange(ec.ECDH(), self.Q_C) 68 | K = long(hexlify(K), 16) 69 | # compute exchange hash 70 | hm = Message() 71 | hm.add(self.transport.remote_version, self.transport.local_version, 72 | self.transport.remote_kex_init, self.transport.local_kex_init) 73 | hm.add_string(K_S) 74 | hm.add_string(Q_C_bytes) 75 | # SEC1: V2.0 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion 76 | Q_S_bytes = self.Q_S.public_bytes(serialization.Encoding.X962, 77 | serialization.PublicFormat.UncompressedPoint) 78 | hm.add_string(Q_S_bytes) 79 | hm.add_mpint(long(K)) 80 | H = self.hash_algo(hm.asbytes()).digest() 81 | self.transport._set_K_H(K, H) 82 | sig = self.transport.get_server_key().sign_ssh_data(H) 83 | # construct reply 84 | m = Message() 85 | m.add_byte(c_MSG_KEXECDH_REPLY) 86 | m.add_string(K_S) 87 | m.add_string(Q_S_bytes) 88 | m.add_string(sig) 89 | self.transport._send_message(m) 90 | self.transport._activate_outbound() 91 | 92 | def _parse_kexecdh_reply(self, m): 93 | K_S = m.get_string() 94 | Q_S_bytes = m.get_string() 95 | self.Q_S = ec.EllipticCurvePublicKey.from_encoded_point( 96 | self.curve, Q_S_bytes 97 | ) 98 | sig = m.get_binary() 99 | K = self.P.exchange(ec.ECDH(), self.Q_S) 100 | K = long(hexlify(K), 16) 101 | # compute exchange hash and verify signature 102 | hm = Message() 103 | hm.add(self.transport.local_version, self.transport.remote_version, 104 | self.transport.local_kex_init, self.transport.remote_kex_init) 105 | hm.add_string(K_S) 106 | # SEC1: V2.0 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion 107 | hm.add_string(self.Q_C.public_bytes(serialization.Encoding.X962, 108 | serialization.PublicFormat.UncompressedPoint)) 109 | hm.add_string(Q_S_bytes) 110 | hm.add_mpint(K) 111 | self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest()) 112 | self.transport._verify_key(K_S, sig) 113 | self.transport._activate_outbound() 114 | 115 | 116 | class KexNistp384(KexNistp256): 117 | name = "ecdh-sha2-nistp384" 118 | hash_algo = sha384 119 | curve = ec.SECP384R1() 120 | 121 | 122 | class KexNistp521(KexNistp256): 123 | name = "ecdh-sha2-nistp521" 124 | hash_algo = sha512 125 | curve = ec.SECP521R1() 126 | -------------------------------------------------------------------------------- /paramiko/kex_group1.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of 21 | 1024 bit key halves, using a known "p" prime and "g" generator. 22 | """ 23 | 24 | import os 25 | from hashlib import sha1 26 | 27 | from paramiko import util 28 | from paramiko.common import max_byte, zero_byte 29 | from paramiko.message import Message 30 | from paramiko.py3compat import byte_chr, long, byte_mask 31 | from paramiko.ssh_exception import SSHException 32 | 33 | 34 | _MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32) 35 | c_MSG_KEXDH_INIT, c_MSG_KEXDH_REPLY = [byte_chr(c) for c in range(30, 32)] 36 | 37 | b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7 38 | b0000000000000000 = zero_byte * 8 39 | 40 | 41 | class KexGroup1(object): 42 | 43 | # draft-ietf-secsh-transport-09.txt, page 17 44 | P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF # noqa 45 | G = 2 46 | 47 | name = 'diffie-hellman-group1-sha1' 48 | hash_algo = sha1 49 | 50 | def __init__(self, transport): 51 | self.transport = transport 52 | self.x = long(0) 53 | self.e = long(0) 54 | self.f = long(0) 55 | 56 | def start_kex(self): 57 | self._generate_x() 58 | if self.transport.server_mode: 59 | # compute f = g^x mod p, but don't send it yet 60 | self.f = pow(self.G, self.x, self.P) 61 | self.transport._expect_packet(_MSG_KEXDH_INIT) 62 | return 63 | # compute e = g^x mod p (where g=2), and send it 64 | self.e = pow(self.G, self.x, self.P) 65 | m = Message() 66 | m.add_byte(c_MSG_KEXDH_INIT) 67 | m.add_mpint(self.e) 68 | self.transport._send_message(m) 69 | self.transport._expect_packet(_MSG_KEXDH_REPLY) 70 | 71 | def parse_next(self, ptype, m): 72 | if self.transport.server_mode and (ptype == _MSG_KEXDH_INIT): 73 | return self._parse_kexdh_init(m) 74 | elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY): 75 | return self._parse_kexdh_reply(m) 76 | msg = "KexGroup1 asked to handle packet type {:d}" 77 | raise SSHException(msg.format(ptype)) 78 | 79 | # ...internals... 80 | 81 | def _generate_x(self): 82 | # generate an "x" (1 < x < q), where q is (p-1)/2. 83 | # p is a 128-byte (1024-bit) number, where the first 64 bits are 1. 84 | # therefore q can be approximated as a 2^1023. we drop the subset of 85 | # potential x where the first 63 bits are 1, because some of those 86 | # will be larger than q (but this is a tiny tiny subset of 87 | # potential x). 88 | while 1: 89 | x_bytes = os.urandom(128) 90 | x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:] 91 | if (x_bytes[:8] != b7fffffffffffffff and 92 | x_bytes[:8] != b0000000000000000): 93 | break 94 | self.x = util.inflate_long(x_bytes) 95 | 96 | def _parse_kexdh_reply(self, m): 97 | # client mode 98 | host_key = m.get_string() 99 | self.f = m.get_mpint() 100 | if (self.f < 1) or (self.f > self.P - 1): 101 | raise SSHException('Server kex "f" is out of range') 102 | sig = m.get_binary() 103 | K = pow(self.f, self.x, self.P) 104 | # okay, build up the hash H of 105 | # (V_C || V_S || I_C || I_S || K_S || e || f || K) 106 | hm = Message() 107 | hm.add(self.transport.local_version, self.transport.remote_version, 108 | self.transport.local_kex_init, self.transport.remote_kex_init) 109 | hm.add_string(host_key) 110 | hm.add_mpint(self.e) 111 | hm.add_mpint(self.f) 112 | hm.add_mpint(K) 113 | self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest()) 114 | self.transport._verify_key(host_key, sig) 115 | self.transport._activate_outbound() 116 | 117 | def _parse_kexdh_init(self, m): 118 | # server mode 119 | self.e = m.get_mpint() 120 | if (self.e < 1) or (self.e > self.P - 1): 121 | raise SSHException('Client kex "e" is out of range') 122 | K = pow(self.e, self.x, self.P) 123 | key = self.transport.get_server_key().asbytes() 124 | # okay, build up the hash H of 125 | # (V_C || V_S || I_C || I_S || K_S || e || f || K) 126 | hm = Message() 127 | hm.add(self.transport.remote_version, self.transport.local_version, 128 | self.transport.remote_kex_init, self.transport.local_kex_init) 129 | hm.add_string(key) 130 | hm.add_mpint(self.e) 131 | hm.add_mpint(self.f) 132 | hm.add_mpint(K) 133 | H = self.hash_algo(hm.asbytes()).digest() 134 | self.transport._set_K_H(K, H) 135 | # sign it 136 | sig = self.transport.get_server_key().sign_ssh_data(H) 137 | # send reply 138 | m = Message() 139 | m.add_byte(c_MSG_KEXDH_REPLY) 140 | m.add_string(key) 141 | m.add_mpint(self.f) 142 | m.add_string(sig) 143 | self.transport._send_message(m) 144 | self.transport._activate_outbound() 145 | -------------------------------------------------------------------------------- /paramiko/kex_group14.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013 Torsten Landschoff 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of 21 | 2048 bit key halves, using a known "p" prime and "g" generator. 22 | """ 23 | 24 | from paramiko.kex_group1 import KexGroup1 25 | from hashlib import sha1, sha256 26 | 27 | 28 | class KexGroup14(KexGroup1): 29 | 30 | # http://tools.ietf.org/html/rfc3526#section-3 31 | P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF # noqa 32 | G = 2 33 | 34 | name = 'diffie-hellman-group14-sha1' 35 | hash_algo = sha1 36 | 37 | 38 | class KexGroup14SHA256(KexGroup14): 39 | name = 'diffie-hellman-group14-sha256' 40 | hash_algo = sha256 41 | -------------------------------------------------------------------------------- /paramiko/kex_group16.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 Edgar Sousa 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of 21 | 4096 bit key halves, using a known "p" prime and "g" generator. 22 | """ 23 | 24 | from paramiko.kex_group1 import KexGroup1 25 | from hashlib import sha512 26 | 27 | 28 | class KexGroup16SHA512(KexGroup1): 29 | 30 | # http://tools.ietf.org/html/rfc3526#section-5 31 | P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF # noqa: E501 32 | G = 2 33 | 34 | name = "diffie-hellman-group16-sha512" 35 | hash_algo = sha512 36 | -------------------------------------------------------------------------------- /paramiko/pipe.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Abstraction of a one-way pipe where the read end can be used in 21 | `select.select`. Normally this is trivial, but Windows makes it nearly 22 | impossible. 23 | 24 | The pipe acts like an Event, which can be set or cleared. When set, the pipe 25 | will trigger as readable in `select `. 26 | """ 27 | 28 | import sys 29 | import os 30 | import socket 31 | import threading 32 | 33 | 34 | def make_pipe(): 35 | if sys.platform[:3] != 'win': 36 | p = PosixPipe() 37 | else: 38 | p = WindowsPipe() 39 | return p 40 | 41 | 42 | class PosixPipe (object): 43 | def __init__(self): 44 | self._rfd, self._wfd = os.pipe() 45 | self._set = False 46 | self._forever = False 47 | self._closed = False 48 | 49 | def close(self): 50 | os.close(self._rfd) 51 | os.close(self._wfd) 52 | # used for unit tests: 53 | self._closed = True 54 | 55 | def fileno(self): 56 | return self._rfd 57 | 58 | def clear(self): 59 | if not self._set or self._forever: 60 | return 61 | os.read(self._rfd, 1) 62 | self._set = False 63 | 64 | def set(self): 65 | if self._set or self._closed: 66 | return 67 | self._set = True 68 | os.write(self._wfd, b'*') 69 | 70 | def set_forever(self): 71 | self._forever = True 72 | self.set() 73 | 74 | 75 | class WindowsPipe (object): 76 | """ 77 | On Windows, only an OS-level "WinSock" may be used in select(), but reads 78 | and writes must be to the actual socket object. 79 | """ 80 | def __init__(self): 81 | serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 82 | serv.bind(('127.0.0.1', 0)) 83 | serv.listen(1) 84 | 85 | # need to save sockets in _rsock/_wsock so they don't get closed 86 | self._rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 87 | self._rsock.connect(('127.0.0.1', serv.getsockname()[1])) 88 | 89 | self._wsock, addr = serv.accept() 90 | serv.close() 91 | self._set = False 92 | self._forever = False 93 | self._closed = False 94 | 95 | def close(self): 96 | self._rsock.close() 97 | self._wsock.close() 98 | # used for unit tests: 99 | self._closed = True 100 | 101 | def fileno(self): 102 | return self._rsock.fileno() 103 | 104 | def clear(self): 105 | if not self._set or self._forever: 106 | return 107 | self._rsock.recv(1) 108 | self._set = False 109 | 110 | def set(self): 111 | if self._set or self._closed: 112 | return 113 | self._set = True 114 | self._wsock.send(b'*') 115 | 116 | def set_forever(self): 117 | self._forever = True 118 | self.set() 119 | 120 | 121 | class OrPipe (object): 122 | def __init__(self, pipe, lock): 123 | self._set = False 124 | self._partner = None 125 | self._pipe = pipe 126 | self._lock = lock 127 | 128 | def set(self): 129 | with self._lock: 130 | self._set = True 131 | if not self._partner._set: 132 | self._pipe.set() 133 | 134 | def clear(self): 135 | with self._lock: 136 | self._set = False 137 | if not self._partner._set: 138 | self._pipe.clear() 139 | 140 | 141 | def make_or_pipe(pipe): 142 | """ 143 | wraps a pipe into two pipe-like objects which are "or"d together to 144 | affect the real pipe. if either returned pipe is set, the wrapped pipe 145 | is set. when both are cleared, the wrapped pipe is cleared. 146 | """ 147 | lock = threading.Lock() 148 | p1 = OrPipe(pipe, lock) 149 | p2 = OrPipe(pipe, lock) 150 | p1._partner = p2 151 | p2._partner = p1 152 | return p1, p2 153 | -------------------------------------------------------------------------------- /paramiko/primes.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Utility functions for dealing with primes. 21 | """ 22 | 23 | import os 24 | 25 | from paramiko import util 26 | from paramiko.py3compat import byte_mask, long 27 | from paramiko.ssh_exception import SSHException 28 | 29 | 30 | def _roll_random(n): 31 | """returns a random # from 0 to N-1""" 32 | bits = util.bit_length(n - 1) 33 | byte_count = (bits + 7) // 8 34 | hbyte_mask = pow(2, bits % 8) - 1 35 | 36 | # so here's the plan: 37 | # we fetch as many random bits as we'd need to fit N-1, and if the 38 | # generated number is >= N, we try again. in the worst case (N-1 is a 39 | # power of 2), we have slightly better than 50% odds of getting one that 40 | # fits, so i can't guarantee that this loop will ever finish, but the odds 41 | # of it looping forever should be infinitesimal. 42 | while True: 43 | x = os.urandom(byte_count) 44 | if hbyte_mask > 0: 45 | x = byte_mask(x[0], hbyte_mask) + x[1:] 46 | num = util.inflate_long(x, 1) 47 | if num < n: 48 | break 49 | return num 50 | 51 | 52 | class ModulusPack (object): 53 | """ 54 | convenience object for holding the contents of the /etc/ssh/moduli file, 55 | on systems that have such a file. 56 | """ 57 | 58 | def __init__(self): 59 | # pack is a hash of: bits -> [ (generator, modulus) ... ] 60 | self.pack = {} 61 | self.discarded = [] 62 | 63 | def _parse_modulus(self, line): 64 | timestamp, mod_type, tests, tries, size, generator, modulus = \ 65 | line.split() 66 | mod_type = int(mod_type) 67 | tests = int(tests) 68 | tries = int(tries) 69 | size = int(size) 70 | generator = int(generator) 71 | modulus = long(modulus, 16) 72 | 73 | # weed out primes that aren't at least: 74 | # type 2 (meets basic structural requirements) 75 | # test 4 (more than just a small-prime sieve) 76 | # tries < 100 if test & 4 (at least 100 tries of miller-rabin) 77 | if ( 78 | mod_type < 2 or 79 | tests < 4 or 80 | (tests & 4 and tests < 8 and tries < 100) 81 | ): 82 | self.discarded.append( 83 | (modulus, 'does not meet basic requirements')) 84 | return 85 | if generator == 0: 86 | generator = 2 87 | 88 | # there's a bug in the ssh "moduli" file (yeah, i know: shock! dismay! 89 | # call cnn!) where it understates the bit lengths of these primes by 1. 90 | # this is okay. 91 | bl = util.bit_length(modulus) 92 | if (bl != size) and (bl != size + 1): 93 | self.discarded.append( 94 | (modulus, 'incorrectly reported bit length {}'.format(size))) 95 | return 96 | if bl not in self.pack: 97 | self.pack[bl] = [] 98 | self.pack[bl].append((generator, modulus)) 99 | 100 | def read_file(self, filename): 101 | """ 102 | :raises IOError: passed from any file operations that fail. 103 | """ 104 | self.pack = {} 105 | with open(filename, 'r') as f: 106 | for line in f: 107 | line = line.strip() 108 | if (len(line) == 0) or (line[0] == '#'): 109 | continue 110 | try: 111 | self._parse_modulus(line) 112 | except: 113 | continue 114 | 115 | def get_modulus(self, min, prefer, max): 116 | bitsizes = sorted(self.pack.keys()) 117 | if len(bitsizes) == 0: 118 | raise SSHException('no moduli available') 119 | good = -1 120 | # find nearest bitsize >= preferred 121 | for b in bitsizes: 122 | if (b >= prefer) and (b <= max) and (b < good or good == -1): 123 | good = b 124 | # if that failed, find greatest bitsize >= min 125 | if good == -1: 126 | for b in bitsizes: 127 | if (b >= min) and (b <= max) and (b > good): 128 | good = b 129 | if good == -1: 130 | # their entire (min, max) range has no intersection with our range. 131 | # if their range is below ours, pick the smallest. otherwise pick 132 | # the largest. it'll be out of their range requirement either way, 133 | # but we'll be sending them the closest one we have. 134 | good = bitsizes[0] 135 | if min > good: 136 | good = bitsizes[-1] 137 | # now pick a random modulus of this bitsize 138 | n = _roll_random(len(self.pack[good])) 139 | return self.pack[good][n] 140 | -------------------------------------------------------------------------------- /paramiko/proxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 Yipit, Inc 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | 20 | import os 21 | import sys 22 | import time 23 | import socket 24 | 25 | from paramiko.ssh_exception import ProxyCommandFailure 26 | from paramiko.util import ClosingContextManager, poll_read 27 | 28 | 29 | class ProxyCommand(ClosingContextManager): 30 | """ 31 | Wraps a subprocess running ProxyCommand-driven programs. 32 | 33 | This class implements a the socket-like interface needed by the 34 | `.Transport` and `.Packetizer` classes. Using this class instead of a 35 | regular socket makes it possible to talk with a Popen'd command that will 36 | proxy traffic between the client and a server hosted in another machine. 37 | 38 | Instances of this class may be used as context managers. 39 | """ 40 | def __init__(self, command_line): 41 | """ 42 | Create a new CommandProxy instance. The instance created by this 43 | class can be passed as an argument to the `.Transport` class. 44 | 45 | :param str command_line: 46 | the command that should be executed and used as the proxy. 47 | """ 48 | # NOTE: subprocess import done lazily so platforms without it (e.g. 49 | # GAE) can still import us during overall Paramiko load. 50 | from subprocess import Popen, PIPE 51 | self.cmd = command_line 52 | self.process = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, 53 | bufsize=0, shell=True) 54 | self.timeout = None 55 | 56 | def send(self, content): 57 | """ 58 | Write the content received from the SSH client to the standard 59 | input of the forked command. 60 | 61 | :param str content: string to be sent to the forked command 62 | """ 63 | try: 64 | self.process.stdin.write(content) 65 | except IOError as e: 66 | # There was a problem with the child process. It probably 67 | # died and we can't proceed. The best option here is to 68 | # raise an exception informing the user that the informed 69 | # ProxyCommand is not working. 70 | raise ProxyCommandFailure(self.cmd, e.strerror) 71 | return len(content) 72 | 73 | def recv(self, size): 74 | """ 75 | Read from the standard output of the forked program. 76 | 77 | :param int size: how many chars should be read 78 | 79 | :return: the string of bytes read, which may be shorter than requested 80 | """ 81 | buffer = b'' 82 | try: 83 | if sys.platform == 'win32': 84 | # windows does not support select() on pipes (only on sockets) 85 | return os.read(self.process.stdout.fileno(), size) 86 | 87 | start = time.time() 88 | while len(buffer) < size: 89 | if self.closed: 90 | if buffer: 91 | return buffer 92 | raise EOFError() 93 | 94 | select_timeout = None 95 | if self.timeout is not None: 96 | elapsed = (time.time() - start) 97 | if elapsed >= self.timeout: 98 | raise socket.timeout() 99 | select_timeout = self.timeout - elapsed 100 | 101 | r = poll_read([self.process.stdout], select_timeout) 102 | if r and r[0] == self.process.stdout: 103 | buffer += os.read(self.process.stdout.fileno(), size - len(buffer)) 104 | 105 | return buffer 106 | 107 | except socket.timeout: 108 | if buffer: 109 | # Don't raise socket.timeout, return partial result instead 110 | return buffer 111 | raise # socket.timeout is a subclass of IOError 112 | except IOError as e: 113 | raise ProxyCommandFailure(self.cmd, e.strerror) 114 | 115 | def close(self): 116 | if self.process.poll() is None: 117 | try: 118 | self.process.terminate() 119 | except OSError: 120 | pass 121 | 122 | @property 123 | def closed(self): 124 | return self.process.poll() is not None 125 | 126 | @property 127 | def _closed(self): 128 | # Concession to Python 3 socket-like API 129 | return self.closed 130 | 131 | def settimeout(self, timeout): 132 | self.timeout = timeout 133 | -------------------------------------------------------------------------------- /paramiko/py3compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import base64 3 | 4 | __all__ = [ 5 | "PY2", 6 | "StringIO", 7 | "b", 8 | "builtins", 9 | "byte_chr", 10 | "byte_mask", 11 | "byte_ord", 12 | "decodebytes", 13 | "encodebytes", 14 | "input", 15 | "integer_types", 16 | "long", 17 | "string_types", 18 | "text_type", 19 | "u", 20 | ] 21 | 22 | PY2 = sys.version_info[0] < 3 23 | 24 | if PY2: 25 | string_types = basestring # NOQA 26 | text_type = unicode # NOQA 27 | integer_types = (int, long) # NOQA 28 | long = long # NOQA 29 | input = raw_input # NOQA 30 | decodebytes = base64.decodestring 31 | encodebytes = base64.encodestring 32 | 33 | import __builtin__ as builtins 34 | 35 | byte_ord = ord # NOQA 36 | byte_chr = chr # NOQA 37 | 38 | def byte_mask(c, mask): 39 | return chr(ord(c) & mask) 40 | 41 | def b(s): 42 | """cast unicode or bytes to bytes""" 43 | if isinstance(s, (str, buffer)): # noqa: F821 44 | return s 45 | elif isinstance(s, unicode): # NOQA 46 | return s.encode('utf-8') 47 | else: 48 | raise TypeError("Expected unicode or bytes, got {!r}".format(s)) 49 | 50 | def u(s): 51 | """cast bytes or unicode to unicode""" 52 | if isinstance(s, unicode): # NOQA 53 | return s 54 | elif isinstance(s, (str, buffer)): # noqa: F821 55 | return s.decode('utf-8') 56 | else: 57 | raise TypeError("Expected unicode or bytes, got {!r}".format(s)) 58 | 59 | import cStringIO 60 | StringIO = cStringIO.StringIO 61 | 62 | 63 | else: # python 3+ 64 | import struct 65 | import builtins 66 | string_types = str 67 | text_type = str 68 | integer_types = int 69 | input = input 70 | decodebytes = base64.decodebytes 71 | encodebytes = base64.encodebytes 72 | 73 | class long(int): 74 | pass 75 | 76 | def byte_ord(c): 77 | # In case we're handed a string instead of an int. 78 | if not isinstance(c, int): 79 | c = ord(c) 80 | return c 81 | 82 | def byte_chr(c): 83 | assert isinstance(c, int) 84 | return struct.pack('B', c) 85 | 86 | def byte_mask(c, mask): 87 | assert isinstance(c, int) 88 | return struct.pack('B', c & mask) 89 | 90 | def b(s): 91 | """cast unicode or bytes to bytes""" 92 | if isinstance(s, bytes): 93 | return s 94 | elif isinstance(s, str): 95 | return s.encode('utf-8') 96 | else: 97 | raise TypeError("Expected unicode or bytes, got {!r}".format(s)) 98 | 99 | def u(s): 100 | """cast bytes or unicode to unicode""" 101 | if isinstance(s, bytes): 102 | return s.decode('utf-8') 103 | elif isinstance(s, str): 104 | return s 105 | else: 106 | raise TypeError("Expected unicode or bytes, got {!r}".format(s)) 107 | 108 | import io 109 | StringIO = io.StringIO 110 | -------------------------------------------------------------------------------- /paramiko/rsakey.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | RSA keys. 21 | """ 22 | 23 | from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm 24 | from cryptography.hazmat.backends import default_backend 25 | from cryptography.hazmat.primitives import hashes, serialization 26 | from cryptography.hazmat.primitives.asymmetric import rsa, padding 27 | 28 | from paramiko.message import Message 29 | from paramiko.pkey import PKey, register_pkey_type 30 | from paramiko.ssh_exception import SSHException 31 | 32 | 33 | @register_pkey_type 34 | class RSAKey(PKey): 35 | """ 36 | Representation of an RSA key which can be used to sign and verify SSH2 data. 37 | """ 38 | 39 | LEGACY_TYPE = "RSA" 40 | OPENSSH_TYPE_PREFIX = "ssh-rsa" 41 | 42 | def __init__(self, msg=None, data=None, filename=None, password=None, 43 | key=None, file_obj=None, _raw=None): 44 | self.key = None 45 | self.public_blob = None 46 | if file_obj is not None: 47 | _raw = self._from_private_key(file_obj, password) 48 | if filename is not None: 49 | _raw = self._from_private_key_file(filename, password) 50 | if _raw is not None: 51 | self._decode_key(_raw) 52 | return 53 | if (msg is None) and (data is not None): 54 | msg = Message(data) 55 | if key is not None: 56 | self.key = key 57 | else: 58 | self._check_type_and_load_cert( 59 | msg=msg, 60 | key_type='ssh-rsa', 61 | cert_type='ssh-rsa-cert-v01@openssh.com', 62 | ) 63 | self.key = rsa.RSAPublicNumbers( 64 | e=msg.get_mpint(), n=msg.get_mpint() 65 | ).public_key(default_backend()) 66 | 67 | @property 68 | def size(self): 69 | return self.key.key_size 70 | 71 | @property 72 | def public_numbers(self): 73 | if isinstance(self.key, rsa.RSAPrivateKey): 74 | return self.key.private_numbers().public_numbers 75 | else: 76 | return self.key.public_numbers() 77 | 78 | def asbytes(self): 79 | m = Message() 80 | m.add_string('ssh-rsa') 81 | m.add_mpint(self.public_numbers.e) 82 | m.add_mpint(self.public_numbers.n) 83 | return m.asbytes() 84 | 85 | def get_name(self): 86 | return 'ssh-rsa' 87 | 88 | def get_bits(self): 89 | return self.size 90 | 91 | def can_sign(self): 92 | return isinstance(self.key, rsa.RSAPrivateKey) 93 | 94 | def sign_ssh_data(self, data): 95 | sig = self.key.sign( 96 | data, 97 | padding=padding.PKCS1v15(), 98 | algorithm=hashes.SHA1(), 99 | ) 100 | 101 | m = Message() 102 | m.add_string('ssh-rsa') 103 | m.add_string(sig) 104 | return m 105 | 106 | def verify_ssh_sig(self, data, msg): 107 | if msg.get_text() != 'ssh-rsa': 108 | return False 109 | key = self.key 110 | if isinstance(key, rsa.RSAPrivateKey): 111 | key = key.public_key() 112 | 113 | # pad received signature with leading zeros, key.verify() expects 114 | # a signature of key_size bits (e.g. PuTTY doesn't pad) 115 | sign = msg.get_binary() 116 | diff = key.key_size - len(sign) * 8 117 | if diff > 0: 118 | sign = b"\x00" * ((diff + 7) // 8) + sign 119 | 120 | try: 121 | key.verify(sign, data, padding.PKCS1v15(), hashes.SHA1()) 122 | except InvalidSignature: 123 | return False 124 | else: 125 | return True 126 | 127 | def write_private_key_file(self, filename, password=None): 128 | self._write_private_key_file( 129 | filename, 130 | self.key, 131 | serialization.PrivateFormat.TraditionalOpenSSL, 132 | password=password 133 | ) 134 | 135 | def write_private_key(self, file_obj, password=None): 136 | self._write_private_key( 137 | file_obj, 138 | self.key, 139 | serialization.PrivateFormat.TraditionalOpenSSL, 140 | password=password 141 | ) 142 | 143 | @staticmethod 144 | def generate(bits, progress_func=None): 145 | """ 146 | Generate a new private RSA key. This factory function can be used to 147 | generate a new host key or authentication key. 148 | 149 | :param int bits: number of bits the generated key should be. 150 | :param progress_func: Unused 151 | :return: new `.RSAKey` private key 152 | """ 153 | key = rsa.generate_private_key( 154 | public_exponent=65537, key_size=bits, backend=default_backend() 155 | ) 156 | return RSAKey(key=key) 157 | 158 | # ...internals... 159 | def _decode_key(self, _raw): 160 | pkformat, data = _raw 161 | if pkformat == self.FORMAT_ORIGINAL: 162 | try: 163 | key = serialization.load_der_private_key( 164 | data, password=None, backend=default_backend() 165 | ) 166 | except (ValueError, TypeError, UnsupportedAlgorithm) as e: 167 | raise SSHException(str(e)) 168 | 169 | elif pkformat == self.FORMAT_OPENSSH: 170 | msg = Message(data) 171 | n = msg.get_mpint() 172 | e = msg.get_mpint() 173 | d = msg.get_mpint() 174 | iqmp = msg.get_mpint() 175 | p = msg.get_mpint() 176 | q = msg.get_mpint() 177 | 178 | public_numbers = rsa.RSAPublicNumbers(e=e, n=n) 179 | key = rsa.RSAPrivateNumbers( 180 | p=p, 181 | q=q, 182 | d=d, 183 | dmp1=d % (p - 1), 184 | dmq1=d % (q - 1), 185 | iqmp=iqmp, 186 | public_numbers=public_numbers, 187 | ).private_key(default_backend()) 188 | else: 189 | raise SSHException('unknown private key format.') 190 | 191 | if not isinstance(key, rsa.RSAPrivateKey): 192 | raise SSHException("Invalid key type") 193 | 194 | self.key = key 195 | -------------------------------------------------------------------------------- /paramiko/sftp.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | import socket 20 | import struct 21 | 22 | from paramiko import util 23 | from paramiko.common import DEBUG 24 | from paramiko.message import Message 25 | from paramiko.py3compat import byte_chr, byte_ord 26 | 27 | 28 | CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, \ 29 | CMD_FSTAT, CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, \ 30 | CMD_REMOVE, CMD_MKDIR, CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, \ 31 | CMD_READLINK, CMD_SYMLINK = range(1, 21) 32 | CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS = range(101, 106) 33 | CMD_EXTENDED, CMD_EXTENDED_REPLY = range(200, 202) 34 | 35 | SFTP_OK = 0 36 | SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, \ 37 | SFTP_BAD_MESSAGE, SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, \ 38 | SFTP_OP_UNSUPPORTED = range(1, 9) 39 | 40 | SFTP_DESC = ['Success', 41 | 'End of file', 42 | 'No such file', 43 | 'Permission denied', 44 | 'Failure', 45 | 'Bad message', 46 | 'No connection', 47 | 'Connection lost', 48 | 'Operation unsupported'] 49 | 50 | SFTP_FLAG_READ = 0x1 51 | SFTP_FLAG_WRITE = 0x2 52 | SFTP_FLAG_APPEND = 0x4 53 | SFTP_FLAG_CREATE = 0x8 54 | SFTP_FLAG_TRUNC = 0x10 55 | SFTP_FLAG_EXCL = 0x20 56 | 57 | _VERSION = 3 58 | 59 | 60 | # for debugging 61 | CMD_NAMES = { 62 | CMD_INIT: 'init', 63 | CMD_VERSION: 'version', 64 | CMD_OPEN: 'open', 65 | CMD_CLOSE: 'close', 66 | CMD_READ: 'read', 67 | CMD_WRITE: 'write', 68 | CMD_LSTAT: 'lstat', 69 | CMD_FSTAT: 'fstat', 70 | CMD_SETSTAT: 'setstat', 71 | CMD_FSETSTAT: 'fsetstat', 72 | CMD_OPENDIR: 'opendir', 73 | CMD_READDIR: 'readdir', 74 | CMD_REMOVE: 'remove', 75 | CMD_MKDIR: 'mkdir', 76 | CMD_RMDIR: 'rmdir', 77 | CMD_REALPATH: 'realpath', 78 | CMD_STAT: 'stat', 79 | CMD_RENAME: 'rename', 80 | CMD_READLINK: 'readlink', 81 | CMD_SYMLINK: 'symlink', 82 | CMD_STATUS: 'status', 83 | CMD_HANDLE: 'handle', 84 | CMD_DATA: 'data', 85 | CMD_NAME: 'name', 86 | CMD_ATTRS: 'attrs', 87 | CMD_EXTENDED: 'extended', 88 | CMD_EXTENDED_REPLY: 'extended_reply' 89 | } 90 | 91 | 92 | class SFTPError (Exception): 93 | pass 94 | 95 | 96 | class BaseSFTP (object): 97 | def __init__(self): 98 | self.logger = util.get_logger('paramiko.sftp') 99 | self.sock = None 100 | self.ultra_debug = False 101 | 102 | # ...internals... 103 | 104 | def _send_version(self): 105 | m = Message() 106 | m.add_int(_VERSION) 107 | self._send_packet(CMD_INIT, m) 108 | t, data = self._read_packet() 109 | if t != CMD_VERSION: 110 | raise SFTPError('Incompatible sftp protocol') 111 | version = struct.unpack('>I', data[:4])[0] 112 | # if version != _VERSION: 113 | # raise SFTPError('Incompatible sftp protocol') 114 | return version 115 | 116 | def _send_server_version(self): 117 | # winscp will freak out if the server sends version info before the 118 | # client finishes sending INIT. 119 | t, data = self._read_packet() 120 | if t != CMD_INIT: 121 | raise SFTPError('Incompatible sftp protocol') 122 | version = struct.unpack('>I', data[:4])[0] 123 | # advertise that we support "check-file" 124 | extension_pairs = ['check-file', 'md5,sha1'] 125 | msg = Message() 126 | msg.add_int(_VERSION) 127 | msg.add(*extension_pairs) 128 | self._send_packet(CMD_VERSION, msg) 129 | return version 130 | 131 | def _log(self, level, msg, *args, **kwargs): 132 | self.logger.log(level, msg, *args, **kwargs) 133 | 134 | def _loglist(self, level, msgs): 135 | for m in msgs: 136 | self._log(level, "%s", m) 137 | 138 | def _write_all(self, out): 139 | while len(out) > 0: 140 | n = self.sock.send(out) 141 | if n <= 0: 142 | raise EOFError() 143 | if n == len(out): 144 | return 145 | out = out[n:] 146 | return 147 | 148 | def _read_all(self, n): 149 | out = bytes() 150 | while n > 0: 151 | if isinstance(self.sock, socket.socket): 152 | # sometimes sftp is used directly over a socket instead of a paramiko channel 153 | # recv() may not return/raise an exception when socket closed ?? 154 | # so check periodically if socket is closed using select/poll - see a9c51b23cea3 155 | while True: 156 | read = util.poll_read([self.sock], 0.1) 157 | if len(read) > 0: 158 | x = self.sock.recv(n) 159 | break 160 | else: 161 | x = self.sock.recv(n) 162 | 163 | if len(x) == 0: 164 | raise EOFError() 165 | out += x 166 | n -= len(x) 167 | return out 168 | 169 | def _send_packet(self, t, packet): 170 | packet = packet.asbytes() 171 | out = struct.pack('>I', len(packet) + 1) + byte_chr(t) + packet 172 | if self.ultra_debug: 173 | self._loglist(DEBUG, util.format_binary(out, 'OUT: ')) 174 | self._write_all(out) 175 | 176 | def _read_packet(self): 177 | x = self._read_all(4) 178 | # most sftp servers won't accept packets larger than about 32k, so 179 | # anything with the high byte set (> 16MB) is just garbage. 180 | if byte_ord(x[0]): 181 | raise SFTPError('Garbage packet received') 182 | size = struct.unpack('>I', x)[0] 183 | data = self._read_all(size) 184 | if self.ultra_debug: 185 | self._loglist(DEBUG, util.format_binary(data, 'IN: ')) 186 | if size > 0: 187 | t = byte_ord(data[0]) 188 | return t, data[1:] 189 | return 0, bytes() 190 | -------------------------------------------------------------------------------- /paramiko/ssh_exception.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | 20 | class SSHException (Exception): 21 | """ 22 | Exception raised by failures in SSH2 protocol negotiation or logic errors. 23 | """ 24 | pass 25 | 26 | 27 | class AuthenticationException (SSHException): 28 | """ 29 | Exception raised when authentication failed for some reason. It may be 30 | possible to retry with different credentials. (Other classes specify more 31 | specific reasons.) 32 | 33 | .. versionadded:: 1.6 34 | """ 35 | pass 36 | 37 | 38 | class PasswordRequiredException (AuthenticationException): 39 | """ 40 | Exception raised when a password is needed to unlock a private key file. 41 | """ 42 | pass 43 | 44 | 45 | class BadAuthenticationType (AuthenticationException): 46 | """ 47 | Exception raised when an authentication type (like password) is used, but 48 | the server isn't allowing that type. (It may only allow public-key, for 49 | example.) 50 | 51 | .. versionadded:: 1.1 52 | """ 53 | allowed_types = [] 54 | 55 | def __init__(self, explanation, types): 56 | AuthenticationException.__init__(self, explanation, types) 57 | self.explanation = explanation 58 | self.allowed_types = types 59 | 60 | def __str__(self): 61 | return "%s; allowed types: %r" % self.args 62 | 63 | 64 | class PartialAuthentication (AuthenticationException): 65 | """ 66 | An internal exception thrown in the case of partial authentication. 67 | """ 68 | allowed_types = [] 69 | 70 | def __init__(self, types): 71 | AuthenticationException.__init__(self, types) 72 | self.allowed_types = types 73 | 74 | def __str__(self): 75 | return "Partial authentication; allowed types: %r" % self.allowed_types 76 | 77 | 78 | class ChannelException (SSHException): 79 | """ 80 | Exception raised when an attempt to open a new `.Channel` fails. 81 | 82 | :param int code: the error code returned by the server 83 | 84 | .. versionadded:: 1.6 85 | """ 86 | def __init__(self, code, text): 87 | SSHException.__init__(self, code, text) 88 | self.code = code 89 | self.text = text 90 | 91 | def __str__(self): 92 | return "ChannelException(%r, %r)" % self.args 93 | 94 | 95 | class BadHostKeyException (SSHException): 96 | """ 97 | The host key given by the SSH server did not match what we were expecting. 98 | 99 | :param str hostname: the hostname of the SSH server 100 | :param PKey got_key: the host key presented by the server 101 | :param PKey expected_key: the host key expected 102 | 103 | .. versionadded:: 1.6 104 | """ 105 | def __init__(self, hostname, got_key, expected_key): 106 | SSHException.__init__(self, hostname, got_key, expected_key) 107 | self.hostname = hostname 108 | self.key = got_key 109 | self.expected_key = expected_key 110 | 111 | def __str__(self): 112 | return "Host key for server %s does not match: got %s, expected %s" % ( 113 | self.hostname, self.key.get_base64(), self.expected_key.get_base64() 114 | ) 115 | 116 | 117 | class ProxyCommandFailure (SSHException): 118 | """ 119 | The "ProxyCommand" found in the .ssh/config file returned an error. 120 | 121 | :param str command: The command line that is generating this exception. 122 | :param str error: The error captured from the proxy command output. 123 | """ 124 | def __init__(self, command, error): 125 | SSHException.__init__(self, command, error) 126 | self.command = command 127 | self.error = error 128 | 129 | def __str__(self): 130 | return "ProxyCommand (%r) returned non-zero exit status: %r" % self.args 131 | -------------------------------------------------------------------------------- /paramiko/win_openssh.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Lew Gordon 2 | # Copyright (C) 2022 Patrick Spendrin 3 | # 4 | # This file is part of paramiko. 5 | # 6 | # Paramiko is free software; you can redistribute it and/or modify it under the 7 | # terms of the GNU Lesser General Public License as published by the Free 8 | # Software Foundation; either version 2.1 of the License, or (at your option) 9 | # any later version. 10 | # 11 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 12 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 14 | # details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 18 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 19 | import os.path 20 | import time 21 | 22 | PIPE_NAME = r"\\.\pipe\openssh-ssh-agent" 23 | 24 | 25 | def can_talk_to_agent(): 26 | # use os.listdir() instead of os.path.exists(), because os.path.exists() 27 | # uses CreateFileW() API and the pipe cannot be reopen unless the server 28 | # calls DisconnectNamedPipe(). 29 | dir_, name = os.path.split(PIPE_NAME) 30 | name = name.lower() 31 | return any(name == n.lower() for n in os.listdir(dir_)) 32 | 33 | 34 | class OpenSSHAgentConnection: 35 | def __init__(self): 36 | while True: 37 | try: 38 | self._pipe = os.open(PIPE_NAME, os.O_RDWR | os.O_BINARY) 39 | except OSError as e: 40 | # retry when errno 22 which means that the server has not 41 | # called DisconnectNamedPipe() yet. 42 | if e.errno != 22: 43 | raise 44 | else: 45 | break 46 | time.sleep(0.1) 47 | 48 | def send(self, data): 49 | return os.write(self._pipe, data) 50 | 51 | def recv(self, n): 52 | return os.read(self._pipe, n) 53 | 54 | def close(self): 55 | return os.close(self._pipe) 56 | -------------------------------------------------------------------------------- /paramiko/win_pageant.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2005 John Arbash-Meinel 2 | # Modified up by: Todd Whiteman 3 | # 4 | # This file is part of paramiko. 5 | # 6 | # Paramiko is free software; you can redistribute it and/or modify it under the 7 | # terms of the GNU Lesser General Public License as published by the Free 8 | # Software Foundation; either version 2.1 of the License, or (at your option) 9 | # any later version. 10 | # 11 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 12 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 14 | # details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 18 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 19 | 20 | """ 21 | Functions for communicating with Pageant, the basic windows ssh agent program. 22 | """ 23 | 24 | import array 25 | import ctypes.wintypes 26 | import platform 27 | import struct 28 | from paramiko.common import zero_byte 29 | from paramiko.py3compat import b 30 | 31 | try: 32 | import _thread as thread # Python 3.x 33 | except ImportError: 34 | import thread # Python 2.5-2.7 35 | 36 | from . import _winapi 37 | 38 | 39 | _AGENT_COPYDATA_ID = 0x804e50ba 40 | _AGENT_MAX_MSGLEN = 8192 41 | # Note: The WM_COPYDATA value is pulled from win32con, as a workaround 42 | # so we do not have to import this huge library just for this one variable. 43 | win32con_WM_COPYDATA = 74 44 | 45 | 46 | def _get_pageant_window_object(): 47 | return ctypes.windll.user32.FindWindowA(b'Pageant', b'Pageant') 48 | 49 | 50 | def can_talk_to_agent(): 51 | """ 52 | Check to see if there is a "Pageant" agent we can talk to. 53 | 54 | This checks both if we have the required libraries (win32all or ctypes) 55 | and if there is a Pageant currently running. 56 | """ 57 | return bool(_get_pageant_window_object()) 58 | 59 | 60 | if platform.architecture()[0] == '64bit': 61 | ULONG_PTR = ctypes.c_uint64 62 | else: 63 | ULONG_PTR = ctypes.c_uint32 64 | 65 | 66 | class COPYDATASTRUCT(ctypes.Structure): 67 | """ 68 | ctypes implementation of 69 | http://msdn.microsoft.com/en-us/library/windows/desktop/ms649010%28v=vs.85%29.aspx 70 | """ 71 | _fields_ = [ 72 | ('num_data', ULONG_PTR), 73 | ('data_size', ctypes.wintypes.DWORD), 74 | ('data_loc', ctypes.c_void_p), 75 | ] 76 | 77 | 78 | def _query_pageant(msg): 79 | """ 80 | Communication with the Pageant process is done through a shared 81 | memory-mapped file. 82 | """ 83 | hwnd = _get_pageant_window_object() 84 | if not hwnd: 85 | # Raise a failure to connect exception, pageant isn't running anymore! 86 | return None 87 | 88 | # create a name for the mmap 89 | map_name = 'PageantRequest%08x' % thread.get_ident() 90 | 91 | pymap = _winapi.MemoryMap(map_name, _AGENT_MAX_MSGLEN, 92 | _winapi.get_security_attributes_for_user()) 93 | with pymap: 94 | pymap.write(msg) 95 | # Create an array buffer containing the mapped filename 96 | char_buffer = array.array("b", b(map_name) + zero_byte) 97 | char_buffer_address, char_buffer_size = char_buffer.buffer_info() 98 | # Create a string to use for the SendMessage function call 99 | cds = COPYDATASTRUCT(_AGENT_COPYDATA_ID, char_buffer_size, 100 | char_buffer_address) 101 | 102 | response = ctypes.windll.user32.SendMessageA(hwnd, 103 | win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.byref(cds)) 104 | 105 | if response > 0: 106 | pymap.seek(0) 107 | datalen = pymap.read(4) 108 | retlen = struct.unpack('>I', datalen)[0] 109 | return datalen + pymap.read(retlen) 110 | return None 111 | 112 | 113 | class PageantConnection(object): 114 | """ 115 | Mock "connection" to an agent which roughly approximates the behavior of 116 | a unix local-domain socket (as used by Agent). Requests are sent to the 117 | pageant daemon via special Windows magick, and responses are buffered back 118 | for subsequent reads. 119 | """ 120 | 121 | def __init__(self): 122 | self._response = None 123 | 124 | def send(self, data): 125 | self._response = _query_pageant(data) 126 | 127 | def recv(self, n): 128 | if self._response is None: 129 | return '' 130 | ret = self._response[:n] 131 | self._response = self._response[n:] 132 | if self._response == '': 133 | self._response = None 134 | return ret 135 | 136 | def close(self): 137 | pass 138 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | license_file = LICENSE 6 | 7 | [flake8] 8 | exclude = .git,build,dist,sites 9 | max-line-length = 99 10 | ignore = 11 | # continuation line under-indented for visual indent 12 | E128, 13 | # multiple spaces after ':' 14 | E241, 15 | # at least two spaces before inline comment 16 | E261, 17 | # line break after binary operator 18 | W504, 19 | # do not use bare 'except' 20 | E722, 21 | # ambiguous variable name 22 | E741, 23 | 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2008 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA. 18 | 19 | import os 20 | from setuptools import setup 21 | 22 | longdesc = ''' 23 | *paramiko-ng* is a fork of `paramiko `_ 24 | 25 | For changes in releases of this fork, see https://github.com/ploxiln/paramiko-ng/releases 26 | 27 | This is a library for making SSH2 connections (client or server). 28 | Emphasis is on using SSH2 as an alternative to SSL for making secure 29 | connections between python scripts. All major ciphers and hash methods 30 | are supported. SFTP client and server mode are both supported too. 31 | 32 | Required packages: 33 | Cryptography 34 | 35 | The import name is still just ``paramiko``. Make sure the original *paramiko* 36 | is not installed before installing *paramiko-ng* - otherwise pip may report 37 | success even though *paramiko-ng* was not correctly installed. 38 | (Because the import name is the same, installed files can conflict.) 39 | 40 | You can also install under the original "paramiko" pip-package-name, 41 | in order to satisfy requirements for other packages:: 42 | 43 | PARAMIKO_REPLACE=1 pip install "https://github.com/ploxiln/paramiko-ng/archive/2.8.10.tar.gz#egg=paramiko" 44 | 45 | Replace "2.8.10" with the desired version. 46 | 47 | To install the latest development version:: 48 | 49 | pip install "git+https://github.com/ploxiln/paramiko-ng/#egg=paramiko-ng" 50 | 51 | ''' # noqa: E501 52 | 53 | name = "paramiko" if os.environ.get('PARAMIKO_REPLACE') else "paramiko-ng" 54 | 55 | # Version info -- read without importing 56 | _locals = {} 57 | with open('paramiko/_version.py') as fp: 58 | exec(fp.read(), None, _locals) 59 | version = _locals['__version__'] 60 | 61 | setup( 62 | name=name, 63 | version=version, 64 | packages=['paramiko'], 65 | description="SSH2 protocol library", 66 | long_description=longdesc, 67 | author="Jeff Forcier", 68 | author_email="jeff@bitprophet.org", 69 | maintainer='Pierce Lopez', 70 | maintainer_email='pierce.lopez@gmail.com', 71 | url="https://github.com/ploxiln/paramiko-ng/", 72 | license='LGPL', 73 | platforms='Posix; MacOS X; Windows', 74 | classifiers=[ 75 | 'Development Status :: 5 - Production/Stable', 76 | 'Intended Audience :: Developers', 77 | 'License :: OSI Approved :: ' 78 | 'GNU Library or Lesser General Public License (LGPL)', 79 | 'Operating System :: OS Independent', 80 | 'Topic :: Internet', 81 | 'Topic :: Security :: Cryptography', 82 | 'Programming Language :: Python', 83 | 'Programming Language :: Python :: 2', 84 | 'Programming Language :: Python :: 3', 85 | ], 86 | python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", 87 | install_requires=[ 88 | 'bcrypt>=3', 89 | 'cryptography>=2.6', 90 | ], 91 | extras_require={ 92 | 'Ed25519': [], # can be removed in 3.0 93 | 'gssapi': [ 94 | "pyasn1", 95 | 'gssapi;platform_system!="Windows"', 96 | 'pywin32;platform_system=="Windows"', 97 | ], 98 | }, 99 | ) 100 | -------------------------------------------------------------------------------- /sites/docs/api/agent.rst: -------------------------------------------------------------------------------- 1 | SSH agents 2 | ========== 3 | 4 | .. automodule:: paramiko.agent 5 | :inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /sites/docs/api/buffered_pipe.rst: -------------------------------------------------------------------------------- 1 | Buffered pipes 2 | ============== 3 | 4 | .. automodule:: paramiko.buffered_pipe 5 | -------------------------------------------------------------------------------- /sites/docs/api/channel.rst: -------------------------------------------------------------------------------- 1 | Channel 2 | ======= 3 | 4 | .. automodule:: paramiko.channel 5 | -------------------------------------------------------------------------------- /sites/docs/api/client.rst: -------------------------------------------------------------------------------- 1 | Client 2 | ====== 3 | 4 | .. automodule:: paramiko.client 5 | :member-order: bysource 6 | -------------------------------------------------------------------------------- /sites/docs/api/config.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | .. automodule:: paramiko.config 5 | :member-order: bysource 6 | -------------------------------------------------------------------------------- /sites/docs/api/file.rst: -------------------------------------------------------------------------------- 1 | Buffered files 2 | ============== 3 | 4 | .. automodule:: paramiko.file 5 | -------------------------------------------------------------------------------- /sites/docs/api/hostkeys.rst: -------------------------------------------------------------------------------- 1 | Host keys / ``known_hosts`` files 2 | ================================= 3 | 4 | .. automodule:: paramiko.hostkeys 5 | :member-order: bysource 6 | -------------------------------------------------------------------------------- /sites/docs/api/kex_gss.rst: -------------------------------------------------------------------------------- 1 | GSS-API key exchange 2 | ==================== 3 | 4 | .. automodule:: paramiko.kex_gss 5 | :member-order: bysource 6 | -------------------------------------------------------------------------------- /sites/docs/api/keys.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Key handling 3 | ============ 4 | 5 | Parent key class 6 | ================ 7 | 8 | .. automodule:: paramiko.pkey 9 | :member-order: bysource 10 | 11 | DSA (DSS) 12 | ========= 13 | 14 | .. automodule:: paramiko.dsskey 15 | 16 | RSA 17 | === 18 | 19 | .. automodule:: paramiko.rsakey 20 | 21 | ECDSA 22 | ===== 23 | 24 | .. automodule:: paramiko.ecdsakey 25 | 26 | Ed25519 27 | ======= 28 | 29 | .. automodule:: paramiko.ed25519key 30 | -------------------------------------------------------------------------------- /sites/docs/api/message.rst: -------------------------------------------------------------------------------- 1 | Message 2 | ======= 3 | 4 | .. automodule:: paramiko.message 5 | -------------------------------------------------------------------------------- /sites/docs/api/packet.rst: -------------------------------------------------------------------------------- 1 | Packetizer 2 | ========== 3 | 4 | .. automodule:: paramiko.packet 5 | -------------------------------------------------------------------------------- /sites/docs/api/pipe.rst: -------------------------------------------------------------------------------- 1 | Cross-platform pipe implementations 2 | =================================== 3 | 4 | .. automodule:: paramiko.pipe 5 | -------------------------------------------------------------------------------- /sites/docs/api/proxy.rst: -------------------------------------------------------------------------------- 1 | ``ProxyCommand`` support 2 | ======================== 3 | 4 | .. automodule:: paramiko.proxy 5 | -------------------------------------------------------------------------------- /sites/docs/api/server.rst: -------------------------------------------------------------------------------- 1 | Server implementation 2 | ===================== 3 | 4 | .. automodule:: paramiko.server 5 | :member-order: bysource 6 | -------------------------------------------------------------------------------- /sites/docs/api/sftp.rst: -------------------------------------------------------------------------------- 1 | SFTP 2 | ==== 3 | 4 | .. automodule:: paramiko.sftp 5 | .. automodule:: paramiko.sftp_client 6 | .. automodule:: paramiko.sftp_server 7 | .. automodule:: paramiko.sftp_attr 8 | .. automodule:: paramiko.sftp_file 9 | :inherited-members: 10 | :no-special-members: 11 | :show-inheritance: 12 | .. automodule:: paramiko.sftp_handle 13 | .. automodule:: paramiko.sftp_si 14 | -------------------------------------------------------------------------------- /sites/docs/api/ssh_exception.rst: -------------------------------------------------------------------------------- 1 | Exceptions 2 | ========== 3 | 4 | .. automodule:: paramiko.ssh_exception 5 | -------------------------------------------------------------------------------- /sites/docs/api/ssh_gss.rst: -------------------------------------------------------------------------------- 1 | GSS-API authentication 2 | ====================== 3 | 4 | .. automodule:: paramiko.ssh_gss 5 | :member-order: bysource 6 | 7 | .. autoclass:: _SSH_GSSAuth 8 | :member-order: bysource 9 | 10 | .. autoclass:: _SSH_GSSAPI 11 | :member-order: bysource 12 | 13 | .. autoclass:: _SSH_SSPI 14 | :member-order: bysource 15 | -------------------------------------------------------------------------------- /sites/docs/api/transport.rst: -------------------------------------------------------------------------------- 1 | Transport 2 | ========= 3 | 4 | .. automodule:: paramiko.transport 5 | :member-order: bysource 6 | -------------------------------------------------------------------------------- /sites/docs/conf.py: -------------------------------------------------------------------------------- 1 | import alabaster 2 | 3 | import sys 4 | from os.path import abspath, join, dirname 5 | sys.path.append(abspath(join(dirname(__file__), '..', '..'))) 6 | 7 | extensions = [ 8 | 'sphinx.ext.intersphinx', 9 | 'sphinx.ext.autodoc', 10 | 'alabaster', 11 | 'releases', 12 | ] 13 | 14 | # Alabaster theme + mini-extension 15 | html_theme_path = [alabaster.get_path()] 16 | html_theme = 'alabaster' 17 | html_theme_options = { 18 | 'description': "A Python implementation of SSHv2.", 19 | 'github_user': 'ploxiln', 20 | 'github_repo': 'paramiko-ng', 21 | 'github_type': 'star', 22 | } 23 | html_sidebars = { 24 | '**': [ 25 | 'about.html', 26 | 'navigation.html', 27 | 'searchbox.html', 28 | ] 29 | } 30 | 31 | # Regular settings 32 | project = 'Paramiko-NG' 33 | copyright = 'Paramiko contributors' 34 | master_doc = 'index' 35 | templates_path = ['_templates'] 36 | exclude_trees = ['_build'] 37 | source_suffix = '.rst' 38 | default_role = 'obj' 39 | 40 | # Autodoc settings 41 | autodoc_default_options = { 42 | 'members': True, 43 | 'special-members': "__init__, __eq__", 44 | } 45 | 46 | # Intersphinx connection to stdlib 47 | intersphinx_mapping = { 48 | 'python': ('https://docs.python.org/3.7', None), 49 | } 50 | 51 | # just used for old changelog 52 | releases_github_path = "paramiko/paramiko" 53 | releases_document_name = ["old_changelog"] 54 | -------------------------------------------------------------------------------- /sites/docs/faq.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Frequently Asked/Answered Questions 3 | =================================== 4 | 5 | Paramiko doesn't work with my Cisco, Windows or other non-Unix system! 6 | ====================================================================== 7 | 8 | In an ideal world, the developers would love to support every possible target 9 | system. Unfortunately, volunteer development time and access to non-mainstream 10 | platforms are limited, meaning that we can only fully support standard OpenSSH 11 | implementations such as those found on Linux, FreeBSD, OpenBSD, and macOS. 12 | 13 | Because of this, we typically close bug reports for nonstandard SSH implementations 14 | or host systems. However, closed does not imply locked - affected users can still 15 | post comments on such tickets, and we will consider actual patch submissions for 16 | these issues, provided they can get +1s from similarly affected users and are proven 17 | to not break existing functionality. 18 | 19 | For interacting with managed networking equipment from Cisco/Juniper/Arista/HP/etc 20 | over SSH, try using `netmiko `_. 21 | 22 | I'm having strange issues with my code hanging at shutdown! 23 | =========================================================== 24 | 25 | Make sure you explicitly ``.close()`` your connection objects (usually 26 | ``SSHClient``) if you're having any sort of hang/freeze at shutdown time! 27 | 28 | Doing so isn't strictly necessary 100% of the time, but it is almost always the 29 | right solution if you run into the various corner cases that cause race 30 | conditions, etc. 31 | -------------------------------------------------------------------------------- /sites/docs/index.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Welcome to Paramiko's documentation! 3 | ==================================== 4 | 5 | **Paramiko-NG** is a Python (2.7, 3.4+) implementation of the SSHv2 protocol [#]_, 6 | providing both client and server functionality. While it leverages a Python C 7 | extension for low level cryptography (`Cryptography `_), 8 | Paramiko-NG itself is a pure Python interface around SSH networking concepts. 9 | 10 | This site covers **Paramiko-NG**'s usage & API documentation. Paramiko-NG is still 11 | usually referred to as Paramiko in the documentation, and it is still imported 12 | as ``paramiko``. 13 | 14 | For information about installing Paramiko-NG: :doc:`installing` 15 | 16 | .. rubric:: Footnotes 17 | 18 | .. [#] 19 | SSH is defined in :rfc:`4251`, :rfc:`4252`, :rfc:`4253` and :rfc:`4254`. The 20 | primary working implementation of the protocol is the `OpenSSH project 21 | `_. Paramiko implements a large portion of the SSH 22 | feature set, but there are occasional gaps. 23 | 24 | 25 | API documentation 26 | ================= 27 | 28 | The high-level client API starts with creation of an `.SSHClient` object. For 29 | more direct control, pass a socket (or socket-like object) to a `.Transport`, 30 | and use `start_server <.Transport.start_server>` or `start_client 31 | <.Transport.start_client>` to negotiate with the remote host as either a server 32 | or client. 33 | 34 | As a client, you are responsible for authenticating using a password or private 35 | key, and checking the server's host key. (Key signature and verification is 36 | done by paramiko, but you will need to provide private keys and check that the 37 | content of a public key matches what you expected to see.) 38 | 39 | As a server, you are responsible for deciding which users, passwords, and keys 40 | to allow, and what kind of channels to allow. 41 | 42 | Once you have finished, either side may request flow-controlled `channels 43 | <.Channel>` to the other side, which are Python objects that act like sockets, 44 | but send and receive data over the encrypted session. 45 | 46 | For details, please see the following tables of contents (which are organized 47 | by area of interest.) 48 | 49 | 50 | Core SSH protocol classes 51 | ------------------------- 52 | 53 | .. toctree:: 54 | api/channel 55 | api/client 56 | api/message 57 | api/packet 58 | api/transport 59 | 60 | 61 | Authentication & keys 62 | --------------------- 63 | 64 | .. toctree:: 65 | api/agent 66 | api/hostkeys 67 | api/keys 68 | api/ssh_gss 69 | api/kex_gss 70 | 71 | 72 | Other primary functions 73 | ----------------------- 74 | 75 | .. toctree:: 76 | api/config 77 | api/proxy 78 | api/server 79 | api/sftp 80 | 81 | 82 | Miscellany 83 | ---------- 84 | 85 | .. toctree:: 86 | api/buffered_pipe 87 | api/file 88 | api/pipe 89 | api/ssh_exception 90 | 91 | 92 | Appendix 93 | ======== 94 | 95 | .. toctree:: 96 | :maxdepth: 1 97 | 98 | installing 99 | faq 100 | old_changelog 101 | -------------------------------------------------------------------------------- /sites/docs/installing.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Installing 3 | ========== 4 | 5 | Paramiko-NG itself 6 | ================== 7 | 8 | The recommended way to get Paramiko-NG is to install the latest stable release 9 | via `pip `_:: 10 | 11 | $ pip install paramiko-ng 12 | 13 | You can also install under the original "paramiko" pip-package-name, 14 | in order to satisfy requirements for other packages (replace "2.8.10" with desired version):: 15 | 16 | $ PARAMIKO_REPLACE=1 pip install "https://github.com/ploxiln/paramiko-ng/archive/2.8.10.tar.gz#egg=paramiko" 17 | 18 | Paramiko-NG currently supports Python 2.7, 3.4+, and PyPy. 19 | 20 | Paramiko-NG has only a few direct dependencies: 21 | 22 | - The big one is Cryptography; see :ref:`its specific note below ` for more details. 23 | - `bcrypt `_, for "new openssh format" private keys 24 | 25 | If you need GSS-API / SSPI support, see :ref:`the below subsection on it 26 | ` for details on its optional dependencies. 27 | 28 | 29 | .. _cryptography: 30 | 31 | Cryptography 32 | ============ 33 | 34 | `Cryptography `__ provides the low-level (C-based) 35 | encryption algorithms we need to implement the SSH protocol. It has detailed 36 | `installation instructions`_ (and an `FAQ `_) 37 | which you should read carefully. 38 | 39 | Cryptography provides statically built "wheels" for most common systems, 40 | which modern "pip" will preferentially install. These include all needed 41 | non-python components pre-built and should "just work". 42 | 43 | If you need or want to build cryptography from source, you will need a 44 | C build toolchain, development headers for Python, OpenSSL and 45 | ``libffi``, and starting with cryptography-3.4, also a Rust language 46 | toolchain installed. Again, see `Cryptography's install docs`_; 47 | these requirements may occasionally change. 48 | 49 | - Cryptography-3.4 dropped support for Python-2.7 50 | - Cryptography-3.3 dropped support for Python-3.5 51 | - Cryptography-3.2 dropped support for OpenSSL-1.0.2 52 | 53 | If you have a problem with these changing requirements, you can install 54 | the last patch release before the incompatible minor release like:: 55 | 56 | $ pip install 'cryptography<3.4' 57 | 58 | .. _installation instructions: 59 | .. _Cryptography's install docs: https://cryptography.io/en/latest/installation.html 60 | 61 | 62 | .. _gssapi: 63 | 64 | Optional dependencies for GSS-API / SSPI / Kerberos 65 | =================================================== 66 | 67 | In order to use GSS-API/Kerberos & related functionality, a couple of 68 | additional dependencies are required: 69 | 70 | * All platforms need **a working installation of GSS-API itself**, e.g. Heimdal 71 | * All platforms need `pyasn1 `__ 72 | * **Unix** needs `gssapi `__ 73 | * **Windows** needs `pywin32 `__ 74 | 75 | .. note:: 76 | If you use Microsoft SSPI for kerberos authentication and credential 77 | delegation, make sure that the target host is trusted for delegation in the 78 | active directory configuration. For details see: 79 | http://technet.microsoft.com/en-us/library/cc738491%28v=ws.10%29.aspx 80 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # This file's just here so test modules can use explicit-relative imports. 2 | -------------------------------------------------------------------------------- /tests/cert_support/test_dss.key: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIIBuwIBAAKBgQDngaYDZ30c6/7cJgEEbtl8FgKdwhba1Z7oOrOn4MI/6C42G1bY 3 | wMuqZf4dBCglsdq39SHrcjbE8Vq54gPSOh3g4+uV9Rcg5IOoPLbwp2jQfF6f1FIb 4 | sx7hrDCIqUcQccPSxetPBKmXI9RN8rZLaFuQeTnI65BKM98Ruwvq6SI2LwIVAPDP 5 | hSeawaJI27mKqOfe5PPBSmyHAoGBAJMXxXmPD9sGaQ419DIpmZecJKBUAy9uXD8x 6 | gbgeDpwfDaFJP8owByCKREocPFfi86LjCuQkyUKOfjYMN6iHIf1oEZjB8uJAatUr 7 | FzI0ArXtUqOhwTLwTyFuUojE5own2WYsOAGByvgfyWjsGhvckYNhI4ODpNdPlxQ8 8 | ZamaPGPsAoGARmR7CCPjodxASvRbIyzaVpZoJ/Z6x7dAumV+ysrV1BVYd0lYukmn 9 | jO1kKBWApqpH1ve9XDQYN8zgxM4b16L21kpoWQnZtXrY3GZ4/it9kUgyB7+NwacI 10 | BlXa8cMDL7Q/69o0d54U0X/NeX5QxuYR6OMJlrkQB7oiW/P/1mwjQgECFGI9QPSc 11 | h9pT9XHqn+1rZ4bK+QGA 12 | -----END DSA PRIVATE KEY----- 13 | -------------------------------------------------------------------------------- /tests/cert_support/test_dss.key-cert.pub: -------------------------------------------------------------------------------- 1 | ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgJA3GjLmg6JbIWxokW/c827lmPOSvSfPDIY586yICFqIAAACBAOeBpgNnfRzr/twmAQRu2XwWAp3CFtrVnug6s6fgwj/oLjYbVtjAy6pl/h0EKCWx2rf1IetyNsTxWrniA9I6HeDj65X1FyDkg6g8tvCnaNB8Xp/UUhuzHuGsMIipRxBxw9LF608EqZcj1E3ytktoW5B5OcjrkEoz3xG7C+rpIjYvAAAAFQDwz4UnmsGiSNu5iqjn3uTzwUpshwAAAIEAkxfFeY8P2wZpDjX0MimZl5wkoFQDL25cPzGBuB4OnB8NoUk/yjAHIIpEShw8V+LzouMK5CTJQo5+Ngw3qIch/WgRmMHy4kBq1SsXMjQCte1So6HBMvBPIW5SiMTmjCfZZiw4AYHK+B/JaOwaG9yRg2Ejg4Ok10+XFDxlqZo8Y+wAAACARmR7CCPjodxASvRbIyzaVpZoJ/Z6x7dAumV+ysrV1BVYd0lYukmnjO1kKBWApqpH1ve9XDQYN8zgxM4b16L21kpoWQnZtXrY3GZ4/it9kUgyB7+NwacIBlXa8cMDL7Q/69o0d54U0X/NeX5QxuYR6OMJlrkQB7oiW/P/1mwjQgEAAAAAAAAAAAAAAAEAAAAJdXNlcl90ZXN0AAAACAAAAAR0ZXN0AAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDskr46Umjxh3wo7PoPQsSVS3xt6+5PhwmXrnVtBBnkOo+zHRwQo8G8sY+Lc6oOOzA5GCSawKOwqE305GIDfB8/L9EKOkAjdN18imDjw/YuJFA4bl9yFhsXrCb1GZPJw0pJ0H0Eid9EldyMQAhGE49MWvnFMQl1TgO6YWq/g71xAFimge0LvVWijlbMy7O+nsGxSpinIprV5S9Viv8XC/ku89tadZfca1uxq751aGfAWGeYrVytpUl8UO0ggqH6BaUvkDU7rWh2n5RHUTvgzceKWnz5wqd8BngK37WmJjAgCtHCJS5ZRf6oJGj2QVcqc6cjvEFWsCuOKB4KAjktauWxAAABDwAAAAdzc2gtcnNhAAABAK6jweL231fRhFoybEGTOXJfj0lx55KpDsw9Q1rBvZhrSgwUr2dFr9HVcKe44mTC7CMtdW5VcyB67l1fnMil/D/e4zYxI0PvbW6RxLFNqvvtxBu5sGt5B7uzV4aAV31TpWR0l5RwwpZqc0NUlTx7oMutN1BDrPqW70QZ/iTEwalkn5fo1JWej0cf4BdC9VgYDLnprx0KN3IToukbszRQySnuR6MQUfj0m7lUloJfF3rq8G0kNxWqDGoJilMhO5Lqu9wAhlZWdouypI6bViO6+ToCVixLNUYs3EfS1zCxvXpiyMvh6rZofJ6WqzUuSd4Mzb2Ka4ocTKi7kynF+OG0Ivo= tests/test_dss.key.pub 2 | -------------------------------------------------------------------------------- /tests/cert_support/test_ecdsa_256.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49 3 | AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD 4 | ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/cert_support/test_ecdsa_256.key-cert.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgJ+ZkRXedIWPl9y6fvel60p47ys5WgwMSjiwzJ2Ho+4MAAAAIbmlzdHAyNTYAAABBBJSPZm3ZWkvk/Zx8WP+fZRZ5/NBBHnGQwR6uIC6XHGPDIHuWUzIjAwA0bzqkOUffEsbLe+uQgKl5kbc/L8KA/eoAAAAAAAAAAAAAAAEAAAAJdXNlcl90ZXN0AAAACAAAAAR0ZXN0AAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDskr46Umjxh3wo7PoPQsSVS3xt6+5PhwmXrnVtBBnkOo+zHRwQo8G8sY+Lc6oOOzA5GCSawKOwqE305GIDfB8/L9EKOkAjdN18imDjw/YuJFA4bl9yFhsXrCb1GZPJw0pJ0H0Eid9EldyMQAhGE49MWvnFMQl1TgO6YWq/g71xAFimge0LvVWijlbMy7O+nsGxSpinIprV5S9Viv8XC/ku89tadZfca1uxq751aGfAWGeYrVytpUl8UO0ggqH6BaUvkDU7rWh2n5RHUTvgzceKWnz5wqd8BngK37WmJjAgCtHCJS5ZRf6oJGj2QVcqc6cjvEFWsCuOKB4KAjktauWxAAABDwAAAAdzc2gtcnNhAAABALdnEil8XIFkcgLZgYwS2cIQPHetUzMNxYCqzk7mSfVpCaIYNTr27RG+f+sD0cerdAIUUvhCT7iA82/Y7wzwkO2RUBi61ATfw9DDPPRQTDfix1SSRwbmPB/nVI1HlPMCEs6y48PFaBZqXwJPS3qycgSxoTBhaLCLzT+r6HRaibY7kiRLDeL3/WHyasK2PRdcYJ6KrLd0ctQcUHZCLK3fJfMfuQRg8MZLVrmK3fHStCXHpRFueRxUhZjaiS9evA/NtzEQhf46JDClQ2rLYpSqSg7QUR/rKwqWWyMuQkOHmlJw797VVa+ZzpUFXP7ekWel3FaBj8IHiimIA7Jm6dOCLm4= tests/test_ecdsa_256.key.pub 2 | -------------------------------------------------------------------------------- /tests/cert_support/test_ed25519.key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACB69SvZKJh/9VgSL0G27b5xVYa8nethH3IERbi0YqJDXwAAAKhjwAdrY8AH 4 | awAAAAtzc2gtZWQyNTUxOQAAACB69SvZKJh/9VgSL0G27b5xVYa8nethH3IERbi0YqJDXw 5 | AAAEA9tGQi2IrprbOSbDCF+RmAHd6meNSXBUQ2ekKXm4/8xnr1K9komH/1WBIvQbbtvnFV 6 | hryd62EfcgRFuLRiokNfAAAAI2FsZXhfZ2F5bm9yQEFsZXhzLU1hY0Jvb2stQWlyLmxvY2 7 | FsAQI= 8 | -----END OPENSSH PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /tests/cert_support/test_ed25519.key-cert.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIjBkc8l1X887CLBHraU+d6/74Hxr9oa+3HC0iioecZ6AAAAIHr1K9komH/1WBIvQbbtvnFVhryd62EfcgRFuLRiokNfAAAAAAAAAAAAAAABAAAACXVzZXJfdGVzdAAAAAgAAAAEdGVzdAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEA7JK+OlJo8Yd8KOz6D0LElUt8bevuT4cJl651bQQZ5DqPsx0cEKPBvLGPi3OqDjswORgkmsCjsKhN9ORiA3wfPy/RCjpAI3TdfIpg48P2LiRQOG5fchYbF6wm9RmTycNKSdB9BInfRJXcjEAIRhOPTFr5xTEJdU4DumFqv4O9cQBYpoHtC71Voo5WzMuzvp7BsUqYpyKa1eUvVYr/Fwv5LvPbWnWX3Gtbsau+dWhnwFhnmK1craVJfFDtIIKh+gWlL5A1O61odp+UR1E74M3Hilp8+cKnfAZ4Ct+1piYwIArRwiUuWUX+qCRo9kFXKnOnI7xBVrArjigeCgI5LWrlsQAAAQ8AAAAHc3NoLXJzYQAAAQCNfYITv/GCW42fLI89x0pKpXIET/xHIBVan5S3fy5SZq9gLG1Db9g/FITDfOVA7OX8mU/91rucHGtuEi3isILdNFrCcoLEml289tyyluUbbFD5fjvBchMWBkYPwrOPfEzSs299Yk8ZgfV1pjWlndfV54s4c9pinkGu8c0Vzc6stEbWkdmoOHE8su3ogUPg/hOygDzJ+ZOgP5HIUJ6YgkgVpWgZm7zofwdZfa2HEb+WhZaKfMK1UCw1UiSBVk9dx6qzF9m243tHwSHraXvb9oJ1wT1S/MypTbP4RT4fHN8koYNrv2szEBN+lkRgk1D7xaxS/Md2TJsau9ho/UCXSR8L tests/test_ed25519.key.pub 2 | -------------------------------------------------------------------------------- /tests/cert_support/test_rsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz 3 | oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/ 4 | d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB 5 | gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0 6 | EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon 7 | soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H 8 | tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU 9 | avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA 10 | 4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g 11 | H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv 12 | qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV 13 | HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc 14 | nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/cert_support/test_rsa.key-cert.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgsZlXTd5NE4uzGAn6TyAqQj+IPbsTEFGap2x5pTRwQR8AAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4cAAAAAAAAE0gAAAAEAAAAmU2FtcGxlIHNlbGYtc2lnbmVkIE9wZW5TU0ggY2VydGlmaWNhdGUAAAASAAAABXVzZXIxAAAABXVzZXIyAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAACVAAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4cAAACPAAAAB3NzaC1yc2EAAACATFHFsARDgQevc6YLxNnDNjsFtZ08KPMyYVx0w5xm95IVZHVWSOc5w+ccjqN9HRwxV3kP7IvL91qx0Uc3MJdB9g/O6HkAP+rpxTVoTb2EAMekwp5+i8nQJW4CN2BSsbQY1M6r7OBZ5nmF4hOW/5Pu4l22lXe2ydy8kEXOEuRpUeQ= test_rsa.key.pub 2 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import shutil 4 | import threading 5 | 6 | import pytest 7 | from paramiko import SFTPServer, SFTP, Transport, load_private_key_file 8 | 9 | from .loop import LoopSocket 10 | from .stub_sftp import StubServer, StubSFTPServer 11 | from .util import _support 12 | 13 | 14 | # TODO: not a huge fan of conftest.py files, see if we can move these somewhere 15 | # 'nicer'. 16 | 17 | 18 | # Perform logging by default; pytest will capture and thus hide it normally, 19 | # presenting it on error/failure. (But also allow turning it off when doing 20 | # very pinpoint debugging - e.g. using breakpoints, so you don't want output 21 | # hiding enabled, but also don't want all the logging to gum up the terminal.) 22 | if not os.environ.get('DISABLE_LOGGING', False): 23 | logging.basicConfig( 24 | level=logging.DEBUG, 25 | # Also make sure to set up timestamping for more sanity when debugging. 26 | format="[%(relativeCreated)s]\t%(levelname)s:%(name)s:%(message)s", 27 | datefmt="%H:%M:%S", 28 | ) 29 | 30 | 31 | def make_sftp_folder(): 32 | """ 33 | Ensure expected target temp folder exists on the remote end. 34 | 35 | Will clean it out if it already exists. 36 | """ 37 | # TODO: go back to using the sftp functionality itself for folder setup so 38 | # we can test against live SFTP servers again someday. (Not clear if anyone 39 | # is/was using the old capability for such, though...) 40 | # TODO: something that would play nicer with concurrent testing (but 41 | # probably e.g. using thread ID or UUIDs or something; not the "count up 42 | # until you find one not used!" crap from before...) 43 | # TODO: if we want to lock ourselves even harder into localhost-only 44 | # testing (probably not?) could use tempdir modules for this for improved 45 | # safety. Then again...why would someone have such a folder??? 46 | path = os.environ.get('TEST_FOLDER', 'paramiko-test-target') 47 | # Forcibly nuke this directory locally, since at the moment, the below 48 | # fixtures only ever run with a locally scoped stub test server. 49 | shutil.rmtree(path, ignore_errors=True) 50 | # Then create it anew, again locally, for the same reason. 51 | os.mkdir(path) 52 | return path 53 | 54 | 55 | @pytest.fixture # (scope='session') 56 | def sftp_server(): 57 | """ 58 | Set up an in-memory SFTP server thread. Yields the client Transport/socket. 59 | 60 | The resulting client Transport (along with all the server components) will 61 | be the same object throughout the test session; the `sftp` fixture then 62 | creates new higher level client objects wrapped around the client 63 | Transport, as necessary. 64 | """ 65 | # Sockets & transports 66 | socks = LoopSocket() 67 | sockc = LoopSocket() 68 | sockc.link(socks) 69 | tc = Transport(sockc) 70 | ts = Transport(socks) 71 | # Auth 72 | host_key = load_private_key_file(_support('test_rsa.key')) 73 | ts.add_server_key(host_key) 74 | # Server setup 75 | event = threading.Event() 76 | server = StubServer() 77 | ts.set_subsystem_handler('sftp', SFTPServer, StubSFTPServer) 78 | ts.start_server(event, server) 79 | # Wait (so client has time to connect? Not sure. Old.) 80 | event.wait(1.0) 81 | # Make & yield connection. 82 | tc.connect(username='slowdive', password='pygmalion') 83 | yield tc 84 | # TODO: any need for shutdown? Why didn't old suite do so? Or was that the 85 | # point of the "join all threads from threading module" crap in test.py? 86 | 87 | 88 | @pytest.fixture 89 | def sftp(sftp_server): 90 | """ 91 | Yield an SFTP client connected to the global in-session SFTP server thread. 92 | """ 93 | # Client setup 94 | client = SFTP.from_transport(sftp_server) 95 | # Work in 'remote' folder setup (as it wants to use the client) 96 | # TODO: how cleanest to make this available to tests? Doing it this way is 97 | # marginally less bad than the previous 'global'-using setup, but not by 98 | # much? 99 | client.FOLDER = make_sftp_folder() 100 | # Yield client to caller 101 | yield client 102 | # Clean up - as in make_sftp_folder, we assume local-only exec for now. 103 | shutil.rmtree(client.FOLDER, ignore_errors=True) 104 | -------------------------------------------------------------------------------- /tests/loop.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2009 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | import socket 20 | import threading 21 | 22 | from paramiko.common import asbytes 23 | 24 | 25 | class LoopSocket (object): 26 | """ 27 | A LoopSocket looks like a normal socket, but all data written to it is 28 | delivered on the read-end of another LoopSocket, and vice versa. It's 29 | like a software "socketpair". 30 | """ 31 | 32 | def __init__(self): 33 | self.__in_buffer = bytes() 34 | self.__lock = threading.Lock() 35 | self.__cv = threading.Condition(self.__lock) 36 | self.__timeout = None 37 | self.__mate = None 38 | self._closed = False 39 | 40 | def close(self): 41 | self.__unlink() 42 | self._closed = True 43 | try: 44 | self.__lock.acquire() 45 | self.__in_buffer = bytes() 46 | finally: 47 | self.__lock.release() 48 | 49 | def send(self, data): 50 | data = asbytes(data) 51 | if self.__mate is None: 52 | # EOF 53 | raise EOFError() 54 | self.__mate.__feed(data) 55 | return len(data) 56 | 57 | def recv(self, n): 58 | self.__lock.acquire() 59 | try: 60 | if self.__mate is None: 61 | # EOF 62 | return bytes() 63 | if len(self.__in_buffer) == 0: 64 | self.__cv.wait(self.__timeout) 65 | if len(self.__in_buffer) == 0: 66 | raise socket.timeout 67 | out = self.__in_buffer[:n] 68 | self.__in_buffer = self.__in_buffer[n:] 69 | return out 70 | finally: 71 | self.__lock.release() 72 | 73 | def settimeout(self, n): 74 | self.__timeout = n 75 | 76 | def link(self, other): 77 | self.__mate = other 78 | self.__mate.__mate = self 79 | 80 | def __feed(self, data): 81 | self.__lock.acquire() 82 | try: 83 | self.__in_buffer += data 84 | self.__cv.notify_all() 85 | finally: 86 | self.__lock.release() 87 | 88 | def __unlink(self): 89 | m = None 90 | self.__lock.acquire() 91 | try: 92 | if self.__mate is not None: 93 | m = self.__mate 94 | self.__mate = None 95 | finally: 96 | self.__lock.release() 97 | if m is not None: 98 | m.__unlink() 99 | -------------------------------------------------------------------------------- /tests/test_buffered_pipe.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2006-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Some unit tests for BufferedPipe. 21 | """ 22 | 23 | import threading 24 | import time 25 | import unittest 26 | 27 | from paramiko.buffered_pipe import BufferedPipe, PipeTimeout 28 | from paramiko import pipe 29 | 30 | 31 | def delay_thread(p): 32 | p.feed('a') 33 | time.sleep(0.5) 34 | p.feed('b') 35 | p.close() 36 | 37 | 38 | def close_thread(p): 39 | time.sleep(0.2) 40 | p.close() 41 | 42 | 43 | class BufferedPipeTest(unittest.TestCase): 44 | def test_buffered_pipe(self): 45 | p = BufferedPipe() 46 | self.assertTrue(not p.read_ready()) 47 | p.feed('hello.') 48 | self.assertTrue(p.read_ready()) 49 | data = p.read(6) 50 | self.assertEqual(b'hello.', data) 51 | 52 | p.feed('plus/minus') 53 | self.assertEqual(b'plu', p.read(3)) 54 | self.assertEqual(b's/m', p.read(3)) 55 | self.assertEqual(b'inus', p.read(4)) 56 | 57 | p.close() 58 | self.assertTrue(not p.read_ready()) 59 | self.assertEqual(b'', p.read(1)) 60 | 61 | def test_delay(self): 62 | p = BufferedPipe() 63 | self.assertTrue(not p.read_ready()) 64 | threading.Thread(target=delay_thread, args=(p,)).start() 65 | self.assertEqual(b'a', p.read(1, 0.1)) 66 | try: 67 | p.read(1, 0.1) 68 | self.assertTrue(False) 69 | except PipeTimeout: 70 | pass 71 | self.assertEqual(b'b', p.read(1, 1.0)) 72 | self.assertEqual(b'', p.read(1)) 73 | 74 | def test_close_while_reading(self): 75 | p = BufferedPipe() 76 | threading.Thread(target=close_thread, args=(p,)).start() 77 | data = p.read(1, 1.0) 78 | self.assertEqual(b'', data) 79 | 80 | def test_or_pipe(self): 81 | p = pipe.make_pipe() 82 | p1, p2 = pipe.make_or_pipe(p) 83 | self.assertFalse(p._set) 84 | p1.set() 85 | self.assertTrue(p._set) 86 | p2.set() 87 | self.assertTrue(p._set) 88 | p1.clear() 89 | self.assertTrue(p._set) 90 | p2.clear() 91 | self.assertFalse(p._set) 92 | -------------------------------------------------------------------------------- /tests/test_channelfile.py: -------------------------------------------------------------------------------- 1 | try: 2 | from unittest.mock import patch, create_autospec 3 | except ImportError: 4 | from mock import patch, create_autospec 5 | 6 | from paramiko import Channel, ChannelFile, ChannelStderrFile, ChannelStdinFile 7 | 8 | 9 | class ChannelFileBase(object): 10 | @patch("paramiko.channel.ChannelFile._set_mode") 11 | def test_defaults_to_unbuffered_reading(self, setmode): 12 | self.klass(Channel(None)) 13 | setmode.assert_called_once_with("r", -1) 14 | 15 | @patch("paramiko.channel.ChannelFile._set_mode") 16 | def test_can_override_mode_and_bufsize(self, setmode): 17 | self.klass(Channel(None), mode="w", bufsize=25) 18 | setmode.assert_called_once_with("w", 25) 19 | 20 | def test_read_recvs_from_channel(self): 21 | chan = create_autospec(Channel, instance=True) 22 | cf = self.klass(chan) 23 | cf.read(100) 24 | chan.recv.assert_called_once_with(100) 25 | 26 | def test_write_calls_channel_sendall(self): 27 | chan = create_autospec(Channel, instance=True) 28 | cf = self.klass(chan, mode="w") 29 | cf.write("ohai") 30 | chan.sendall.assert_called_once_with(b"ohai") 31 | 32 | 33 | class TestChannelFile(ChannelFileBase): 34 | klass = ChannelFile 35 | 36 | 37 | class TestChannelStderrFile(object): 38 | def test_read_calls_channel_recv_stderr(self): 39 | chan = create_autospec(Channel, instance=True) 40 | cf = ChannelStderrFile(chan) 41 | cf.read(100) 42 | chan.recv_stderr.assert_called_once_with(100) 43 | 44 | def test_write_calls_channel_sendall(self): 45 | chan = create_autospec(Channel, instance=True) 46 | cf = ChannelStderrFile(chan, mode="w") 47 | cf.write("ohai") 48 | chan.sendall_stderr.assert_called_once_with(b"ohai") 49 | 50 | 51 | class TestChannelStdinFile(ChannelFileBase): 52 | klass = ChannelStdinFile 53 | 54 | def test_close_calls_channel_shutdown_write(self): 55 | chan = create_autospec(Channel, instance=True) 56 | cf = ChannelStdinFile(chan, mode="wb") 57 | cf.flush = create_autospec(lambda: None) 58 | cf.close() 59 | # Sanity check that we still call BufferedFile.close() 60 | cf.flush.assert_called_once_with() 61 | assert cf._closed is True 62 | # Actual point of test 63 | chan.shutdown_write.assert_called_once_with() 64 | -------------------------------------------------------------------------------- /tests/test_dss.key: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIIBuwIBAAKBgQDngaYDZ30c6/7cJgEEbtl8FgKdwhba1Z7oOrOn4MI/6C42G1bY 3 | wMuqZf4dBCglsdq39SHrcjbE8Vq54gPSOh3g4+uV9Rcg5IOoPLbwp2jQfF6f1FIb 4 | sx7hrDCIqUcQccPSxetPBKmXI9RN8rZLaFuQeTnI65BKM98Ruwvq6SI2LwIVAPDP 5 | hSeawaJI27mKqOfe5PPBSmyHAoGBAJMXxXmPD9sGaQ419DIpmZecJKBUAy9uXD8x 6 | gbgeDpwfDaFJP8owByCKREocPFfi86LjCuQkyUKOfjYMN6iHIf1oEZjB8uJAatUr 7 | FzI0ArXtUqOhwTLwTyFuUojE5own2WYsOAGByvgfyWjsGhvckYNhI4ODpNdPlxQ8 8 | ZamaPGPsAoGARmR7CCPjodxASvRbIyzaVpZoJ/Z6x7dAumV+ysrV1BVYd0lYukmn 9 | jO1kKBWApqpH1ve9XDQYN8zgxM4b16L21kpoWQnZtXrY3GZ4/it9kUgyB7+NwacI 10 | BlXa8cMDL7Q/69o0d54U0X/NeX5QxuYR6OMJlrkQB7oiW/P/1mwjQgECFGI9QPSc 11 | h9pT9XHqn+1rZ4bK+QGA 12 | -----END DSA PRIVATE KEY----- 13 | -------------------------------------------------------------------------------- /tests/test_dss_1k_o.key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAsyq4pxL 3 | R5sOprPDHGpvzxAAAAEAAAAAEAAAGxAAAAB3NzaC1kc3MAAACBAL8XEx7F9xuwBNles+vW 4 | pNF+YcofrBhjX1r5QhpBe0eoYWLHRcroN6lxwCdGYRfgOoRjTncBiixQX/uUxAY96zDh3i 5 | r492s2BcJt4ihvNn/AY0I0OTuX/2IwGk9CGzafjaeZNVYxMa8lcVt0hSOTjkPQ7gVuk6bJ 6 | zMInvie+VWKLAAAAFQDUgYdY+rhR0SkKbC09BS/SIHcB+wAAAIB44+4zpCNcd0CGvZlowH 7 | 99zyPX8uxQtmTLQFuR2O8O0FgVVuCdDgD0D9W8CLOp32oatpM0jyyN89EdvSWzjHzZJ+L6 8 | H1FtZps7uhpDFWHdva1R25vyGecLMUuXjo5t/D7oCDih+HwHoSAxoi0QvsPd8/qqHQVznN 9 | JKtR6thUpXEwAAAIAG4DCBjbgTTgpBw0egRkJwBSz0oTt+1IcapNU2jA6N8urMSk9YXHEQ 10 | HKN68BAF3YJ59q2Ujv3LOXmBqGd1T+kzwUszfMlgzq8MMu19Yfzse6AIK1Agn1Vj6F7YXL 11 | sXDN+T4KszX5+FJa7t/Zsp3nALWy6l0f4WKivEF5Y2QpEFcQAAAgCH6XUl1hYWB6kgCSHV 12 | a4C+vQHrgFNgNwEQnE074LXHXlAhxC+Dm8XTGqVPX1KRPWzadq9/+v6pqLFqiRueB86uRb 13 | J5WtAbUs3WwxAaC5Mi+mn42MBfL9PIwWPWCvstrAq9Nyj3EBMeX3XFLxN3RuGXIQnY/5rF 14 | f5hriUVxhWDQGIVbBKhkpn7Geqg6nLpn7iqQhzFmFGjPmAdrllgdVGJRLyIN6BRsaltDdy 15 | vxufkvGzKudvQ85QvsaoFJQ6K1d0S7907pexvxmWpcO7zchXb6i09BITWOAKIcHpVkbNQw 16 | +8pzSdpggsAwCRbfk/Jkezz8sXVUCfmmJ23NFUw04/0ZbilCADRsUaPfafgVPeDznBnuCm 17 | tfXa4JSrVUvPdwoex3SKZmYsFXwsuOEQnFkhUGHfWwTbmOmxzy6dtC24KYhnWG5OGFVJXh 18 | 3B8jQJGGs2ANfusI/Z0o15tAnQy5fqsLf9TT3RX7RG2ujIiDBsU+A1g//IXmSxxkUOQMZs 19 | v+cMI8KfODAXmQtB30+yAgoV03Zb/bdptv+HqPT4eeecstJUxzEGYADt1mDq3uV7fQbNmo 20 | 80bppU52JjztrJb7hBmXsXHPRRK6spQ1FCatqvu1ggZeXZpEifNsHeqCljt87ueXsQsORY 21 | pvhLzjTbTKZmjLDPuB+GxUNLEKh1ZNyAqKng== 22 | -----END OPENSSH PRIVATE KEY----- 23 | -------------------------------------------------------------------------------- /tests/test_dss_password.key: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,78DAEB836ED0A646 4 | 5 | ldWkq9OMlXqWmjIqppNnmNPIUj5uVT12LkBosTApTbibTme3kIJb1uDeG2BShVfY 6 | +vDOTUE9koGPDLsxW1t5At+EVyIDK8aIO0uHteXM5AbBX20LLUWRbRVqZhsMxqQh 7 | 3H3XlHiN+QhaWcb4fFuu18a8SkimTFpDnZuffoCDl/zh/B7XieARTLA805K/ZgVB 8 | BBwflkR2BE053XHrJAIx9BEUlLP76Fo18rvjLZOSeu3s+VnnhqUb5FCt5h50a46u 9 | YXQBbo2r9Zo1ilGMNEXJO0gk5hwGVmTySz53NkPA5HmWt8NIzv5jQHMDy7N+ZykF 10 | uwpP1R5M/ZIFY4Y5h/lvn6IJjQ7VySRPIbpN8o2YJv2OD1Ja80n3tU8Mg77o3o4d 11 | NwKm7cCjlq+FuIBdOsSgsB8FPQRUhW+jpFDxmWN64DM2cEg6RUdptby7WmMp0HwK 12 | 1qyEfxHjLMuDVlD7lASIDBrRlUjPtXEH1DzIYQuYaRZaixFoZ7EY+X73TwmrKFEU 13 | US9ZnQZtRtroRqGwR4fz4wQQsjTl/AmOijlBmi29taJccJsT/THrLQ5plOEd8OMv 14 | 9FsaPJXBU85gaRKo3JZtrw== 15 | -----END DSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/test_ecdsa_256.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49 3 | AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD 4 | ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/test_ecdsa_384.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIGkAgEBBDBDdO8IXvlLJgM7+sNtPl7tI7FM5kzuEUEEPRjXIPQM7mISciwJPBt+ 3 | y43EuG8nL4mgBwYFK4EEACKhZANiAAQWxom0C1vQAGYhjdoREMVmGKBWlisDdzyk 4 | mgyUjKpiJ9WfbIEVLsPGP8OdNjhr1y/8BZNIts+dJd6VmYw+4HzB+4F+U1Igs8K0 5 | JEvh59VNkvWheViadDXCM2MV8Nq+DNg= 6 | -----END EC PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /tests/test_ecdsa_384_o.key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDwIHkBEZ 3 | 75XuqQS6/7daAIAAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlz 4 | dHAzODQAAABhBIch5LXTq/L/TWsTGG6dIktxD8DIMh7EfvoRmWsks6CuNDTvFvbQNtY4QO 5 | 1mn5OXegHbS0M5DPIS++wpKGFP3suDEH08O35vZQasLNrL0tO2jyyEnzB2ZEx3PPYci811 6 | ygAAAOBKGxFl+JcMHjldOdTA9iwv88gxoelCwln/NATglUuyzHMLJwx53n8NLqrnHALvbz 7 | RHjyTmjU4dbSM9o9Vjhcvq+1aipjAQg2qx825f7T4BMoKyhLBS/qTg7RfyW/h0Sbequ1wl 8 | PhBfwhv0LUphRFsGdnOgrXWfZqWqxOP1WhJWIh1p+ja5va/Ii/+hD6RORQjvzbHTPJA53c 9 | OguISImkx0vdqPuFTLyclaC3eO4Px68Ki0b8cdyivExbAWLkNOtBdIAgeO7Egbruu4O5Sn 10 | I6bn1Kc+kZlWtO02IkwSA5DaKw== 11 | -----END OPENSSH PRIVATE KEY----- 12 | -------------------------------------------------------------------------------- /tests/test_ecdsa_521.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIHcAgEBBEIAprQtAS3OF6iVUkT8IowTHWicHzShGgk86EtuEXvfQnhZFKsWm6Jo 3 | iqAr1yEaiuI9LfB3Xs8cjuhgEEfbduYr/f6gBwYFK4EEACOhgYkDgYYABACaOaFL 4 | ZGuxa5AW16qj6VLypFbLrEWrt9AZUloCMefxO8bNLjK/O5g0rAVasar1TnyHE9qj 5 | 4NwzANZASWjQNbc4MAG8vzqezFwLIn/kNyNTsXNfqEko9OgHZknlj2Z79dwTJcRA 6 | L4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA== 7 | -----END EC PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /tests/test_ecdsa_password_256.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,EEB56BC745EDB2DE04FC3FE1F8DA387E 4 | 5 | wdt7QTCa6ahTJLaEPH7NhHyBcxhzrzf93d4UwQOuAhkM6//jKD4lF9fErHBW0f3B 6 | ExberCU3UxfEF3xX2thXiLw47JgeOCeQUlqRFx92p36k6YmfNGX6W8CsZ3d+XodF 7 | Z+pb6m285CiSX+W95NenFMexXFsIpntiCvTifTKJ8os= 8 | -----END EC PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /tests/test_ecdsa_password_384.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,7F7B5DBE4CE040D822441AFE7A023A1D 4 | 5 | y/d6tGonAXYgJniQoFCdto+CuT1y1s41qzwNLN9YdNq/+R/dtQvZAaOuGtHJRFE6 6 | wWabhY1bSjavVPT2z1Zw1jhDJX5HGrf9LDoyORKtUWtUJoUvGdYLHbcg8Q+//WRf 7 | R0A01YuSw1SJX0a225S1aRcsDAk1k5F8EMb8QzSSDgjAOI8ldQF35JI+ofNSGjgS 8 | BPOlorQXTJxDOGmokw/Wql6MbhajXKPO39H2Z53W88U= 9 | -----END EC PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /tests/test_ecdsa_password_521.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,AEB2DE62C65D1A88C4940A3476B2F10A 4 | 5 | 5kNk/FFPbHa0402QTrgpIT28uirJ4Amvb2/ryOEyOCe0NPbTLCqlQekj2RFYH2Un 6 | pgCLUDkelKQv4pyuK8qWS7R+cFjE/gHHCPUWkK3djZUC8DKuA9lUKeQIE+V1vBHc 7 | L5G+MpoYrPgaydcGx/Uqnc/kVuZx1DXLwrGGtgwNROVBtmjXC9EdfeXHLL1y0wvH 8 | paNgacJpUtgqJEmiehf7eL/eiReegG553rZK3jjfboGkREUaKR5XOgamiKUtgKoc 9 | sMpImVYCsRKd/9RI+VOqErZaEvy/9j0Ye3iH32wGOaA= 10 | -----END EC PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /tests/test_ed25519.key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACB69SvZKJh/9VgSL0G27b5xVYa8nethH3IERbi0YqJDXwAAAKhjwAdrY8AH 4 | awAAAAtzc2gtZWQyNTUxOQAAACB69SvZKJh/9VgSL0G27b5xVYa8nethH3IERbi0YqJDXw 5 | AAAEA9tGQi2IrprbOSbDCF+RmAHd6meNSXBUQ2ekKXm4/8xnr1K9komH/1WBIvQbbtvnFV 6 | hryd62EfcgRFuLRiokNfAAAAI2FsZXhfZ2F5bm9yQEFsZXhzLU1hY0Jvb2stQWlyLmxvY2 7 | FsAQI= 8 | -----END OPENSSH PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /tests/test_ed25519_nopad.key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACAHzPvYoDSkMVX52/CbA2M2aSBS7R0wt/9b2n5n+osNygAAAJAHZ1meB2dZ 4 | ngAAAAtzc2gtZWQyNTUxOQAAACAHzPvYoDSkMVX52/CbA2M2aSBS7R0wt/9b2n5n+osNyg 5 | AAAEAIyamvYUpzCovQuUtLhz+fwE4qYQo+rTuUVIX4fmTzMAfM+9igNKQxVfnb8JsDYzZp 6 | IFLtHTC3/1vafmf6iw3KAAAADW15IGNvbW1lbnQgaXM= 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /tests/test_ed25519_password.key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABDaKD4ac7 3 | kieb+UfXaLaw68AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIOQn7fjND5ozMSV3 4 | CvbEtIdT73hWCMRjzS/lRdUDw50xAAAAsE8kLGyYBnl9ihJNqv378y6mO3SkzrDbWXOnK6 5 | ij0vnuTAvcqvWHAnyu6qBbplu/W2m55ZFeAItgaEcV2/V76sh/sAKlERqrLFyXylN0xoOW 6 | NU5+zU08aTlbSKGmeNUU2xE/xfJq12U9XClIRuVUkUpYANxNPbmTRpVrbD3fgXMhK97Jrb 7 | DEn8ca1IqMPiYmd/hpe5+tq3OxyRljXjCUFWTnqkp9VvUdzSTdSGZHsW9i 8 | -----END OPENSSH PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /tests/test_gssapi.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013-2014 science + computing ag 2 | # Author: Sebastian Deiss 3 | # 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | """ 22 | Test the used APIs for GSS-API / SSPI authentication 23 | """ 24 | 25 | import socket 26 | 27 | from .util import KerberosTestCase, update_env 28 | 29 | 30 | class GSSAPITest(KerberosTestCase): 31 | def setUp(self): 32 | super(GSSAPITest, self).setUp() 33 | self.krb5_mech = "1.2.840.113554.1.2.2" 34 | self.targ_name = self.realm.hostname 35 | self.server_mode = False 36 | update_env(self, self.realm.env) 37 | 38 | def test_pyasn1(self): 39 | """ 40 | Test the used methods of pyasn1. 41 | """ 42 | from pyasn1.type.univ import ObjectIdentifier 43 | from pyasn1.codec.der import encoder, decoder 44 | oid = encoder.encode(ObjectIdentifier(self.krb5_mech)) 45 | mech, __ = decoder.decode(oid) 46 | self.assertEqual(self.krb5_mech, mech.__str__()) 47 | 48 | def _gssapi_sspi_test(self): 49 | try: 50 | import gssapi 51 | _API = "MIT-NEW" 52 | except ImportError: 53 | import sspicon 54 | import sspi 55 | _API = "SSPI" 56 | 57 | c_token = None 58 | gss_ctxt_status = False 59 | mic_msg = b"G'day Mate!" 60 | 61 | if _API == "MIT-NEW": 62 | if self.server_mode: 63 | gss_flags = (gssapi.RequirementFlag.protection_ready, 64 | gssapi.RequirementFlag.integrity, 65 | gssapi.RequirementFlag.mutual_authentication, 66 | gssapi.RequirementFlag.delegate_to_peer) 67 | else: 68 | gss_flags = (gssapi.RequirementFlag.protection_ready, 69 | gssapi.RequirementFlag.integrity, 70 | gssapi.RequirementFlag.delegate_to_peer) 71 | # Initialize a GSS-API context. 72 | krb5_oid = gssapi.MechType.kerberos 73 | target_name = gssapi.Name("host@" + self.targ_name, 74 | name_type=gssapi.NameType.hostbased_service) 75 | gss_ctxt = gssapi.SecurityContext(name=target_name, 76 | flags=gss_flags, 77 | mech=krb5_oid, 78 | usage='initiate') 79 | if self.server_mode: 80 | c_token = gss_ctxt.step(c_token) 81 | gss_ctxt_status = gss_ctxt.complete 82 | self.assertEqual(False, gss_ctxt_status) 83 | # Accept a GSS-API context. 84 | gss_srv_ctxt = gssapi.SecurityContext(usage='accept') 85 | s_token = gss_srv_ctxt.step(c_token) 86 | gss_ctxt_status = gss_srv_ctxt.complete 87 | self.assertNotEqual(None, s_token) 88 | self.assertEqual(True, gss_ctxt_status) 89 | # Establish the client context 90 | c_token = gss_ctxt.step(s_token) 91 | self.assertEqual(None, c_token) 92 | else: 93 | while not gss_ctxt.complete: 94 | c_token = gss_ctxt.step(c_token) 95 | self.assertNotEqual(None, c_token) 96 | # Build MIC 97 | mic_token = gss_ctxt.get_signature(mic_msg) 98 | 99 | if self.server_mode: 100 | # Check MIC 101 | status = gss_srv_ctxt.verify_signature(mic_msg, mic_token) 102 | self.assertEqual(0, status) 103 | 104 | else: 105 | gss_flags = ( 106 | sspicon.ISC_REQ_INTEGRITY | 107 | sspicon.ISC_REQ_MUTUAL_AUTH | 108 | sspicon.ISC_REQ_DELEGATE 109 | ) 110 | # Initialize a GSS-API context. 111 | target_name = "host/" + socket.getfqdn(self.targ_name) 112 | gss_ctxt = sspi.ClientAuth("Kerberos", 113 | scflags=gss_flags, 114 | targetspn=target_name) 115 | if self.server_mode: 116 | error, token = gss_ctxt.authorize(c_token) 117 | c_token = token[0].Buffer 118 | self.assertEqual(0, error) 119 | # Accept a GSS-API context. 120 | gss_srv_ctxt = sspi.ServerAuth("Kerberos", spn=target_name) 121 | error, token = gss_srv_ctxt.authorize(c_token) 122 | s_token = token[0].Buffer 123 | # Establish the context. 124 | error, token = gss_ctxt.authorize(s_token) 125 | c_token = token[0].Buffer 126 | self.assertEqual(None, c_token) 127 | self.assertEqual(0, error) 128 | # Build MIC 129 | mic_token = gss_ctxt.sign(mic_msg) 130 | # Check MIC 131 | gss_srv_ctxt.verify(mic_msg, mic_token) 132 | else: 133 | error, token = gss_ctxt.authorize(c_token) 134 | c_token = token[0].Buffer 135 | self.assertNotEqual(0, error) 136 | 137 | def test_gssapi_sspi_client(self): 138 | self._gssapi_sspi_test() 139 | 140 | def test_gssapi_sspi_server(self): 141 | self.server_mode = True 142 | self._gssapi_sspi_test() 143 | -------------------------------------------------------------------------------- /tests/test_hostkeys.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2006-2007 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Some unit tests for HostKeys. 21 | """ 22 | 23 | from binascii import hexlify 24 | import os 25 | import unittest 26 | 27 | import paramiko 28 | from paramiko.py3compat import decodebytes 29 | 30 | 31 | test_hosts_file = """\ 32 | secure.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1PD6U2/TVxET6lkpKhOk5r\ 33 | 9q/kAYG6sP9f5zuUYP8i7FOFp/6ncCEbbtg/lB+A3iidyxoSWl+9jtoyyDOOVX4UIDV9G11Ml8om3\ 34 | D+jrpI9cycZHqilK0HmxDeCuxbwyMuaCygU9gS2qoRvNLWZk70OpIKSSpBo0Wl3/XUmz9uhc= 35 | broken.example.com ssh-rsa AAAA 36 | happy.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA8bP1ZA7DCZDB9J0s50l31M\ 37 | BGQ3GQ/Fc7SX6gkpXkwcZryoi4kNFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW\ 38 | 5ymME3bQ4J/k1IKxCtz/bAlAqFgKoc+EolMziDYqWIATtW0rYTJvzGAzTmMj80/QpsFH+Pc2M= 39 | """ 40 | 41 | keyblob = b"""\ 42 | AAAAB3NzaC1yc2EAAAABIwAAAIEA8bP1ZA7DCZDB9J0s50l31MBGQ3GQ/Fc7SX6gkpXkwcZryoi4k\ 43 | NFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW5ymME3bQ4J/k1IKxCtz/bAlAqFgK\ 44 | oc+EolMziDYqWIATtW0rYTJvzGAzTmMj80/QpsFH+Pc2M=""" 45 | 46 | keyblob_dss = b"""\ 47 | AAAAB3NzaC1kc3MAAACBAOeBpgNnfRzr/twmAQRu2XwWAp3CFtrVnug6s6fgwj/oLjYbVtjAy6pl/\ 48 | h0EKCWx2rf1IetyNsTxWrniA9I6HeDj65X1FyDkg6g8tvCnaNB8Xp/UUhuzHuGsMIipRxBxw9LF60\ 49 | 8EqZcj1E3ytktoW5B5OcjrkEoz3xG7C+rpIjYvAAAAFQDwz4UnmsGiSNu5iqjn3uTzwUpshwAAAIE\ 50 | AkxfFeY8P2wZpDjX0MimZl5wkoFQDL25cPzGBuB4OnB8NoUk/yjAHIIpEShw8V+LzouMK5CTJQo5+\ 51 | Ngw3qIch/WgRmMHy4kBq1SsXMjQCte1So6HBMvBPIW5SiMTmjCfZZiw4AYHK+B/JaOwaG9yRg2Ejg\ 52 | 4Ok10+XFDxlqZo8Y+wAAACARmR7CCPjodxASvRbIyzaVpZoJ/Z6x7dAumV+ysrV1BVYd0lYukmnjO\ 53 | 1kKBWApqpH1ve9XDQYN8zgxM4b16L21kpoWQnZtXrY3GZ4/it9kUgyB7+NwacIBlXa8cMDL7Q/69o\ 54 | 0d54U0X/NeX5QxuYR6OMJlrkQB7oiW/P/1mwjQgE=""" 55 | 56 | 57 | class HostKeysTest (unittest.TestCase): 58 | 59 | def setUp(self): 60 | with open('hostfile.temp', 'w') as f: 61 | f.write(test_hosts_file) 62 | 63 | def tearDown(self): 64 | os.unlink('hostfile.temp') 65 | 66 | def test_load(self): 67 | hostdict = paramiko.HostKeys('hostfile.temp') 68 | self.assertEqual(2, len(hostdict)) 69 | self.assertEqual(1, len(list(hostdict.values())[0])) 70 | self.assertEqual(1, len(list(hostdict.values())[1])) 71 | fp = hexlify(hostdict['secure.example.com']['ssh-rsa'].get_fingerprint()).upper() 72 | self.assertEqual(b'E6684DB30E109B67B70FF1DC5C7F1363', fp) 73 | 74 | def test_add(self): 75 | hostdict = paramiko.HostKeys('hostfile.temp') 76 | hh = '|1|BMsIC6cUIP2zBuXR3t2LRcJYjzM=|hpkJMysjTk/+zzUUzxQEa2ieq6c=' 77 | key = paramiko.RSAKey(data=decodebytes(keyblob)) 78 | hostdict.add(hh, 'ssh-rsa', key) 79 | self.assertEqual(3, len(list(hostdict))) 80 | x = hostdict['foo.example.com'] 81 | fp = hexlify(x['ssh-rsa'].get_fingerprint()).upper() 82 | self.assertEqual(b'7EC91BB336CB6D810B124B1353C32396', fp) 83 | self.assertTrue(hostdict.check('foo.example.com', key)) 84 | 85 | def test_dict(self): 86 | hostdict = paramiko.HostKeys('hostfile.temp') 87 | self.assertTrue('secure.example.com' in hostdict) 88 | self.assertTrue('not.example.com' not in hostdict) 89 | self.assertTrue('secure.example.com' in hostdict) 90 | self.assertTrue('not.example.com' not in hostdict) 91 | x = hostdict.get('secure.example.com', None) 92 | self.assertTrue(x is not None) 93 | fp = hexlify(x['ssh-rsa'].get_fingerprint()).upper() 94 | self.assertEqual(b'E6684DB30E109B67B70FF1DC5C7F1363', fp) 95 | i = 0 96 | for key in hostdict: 97 | i += 1 98 | self.assertEqual(2, i) 99 | 100 | def test_dict_set(self): 101 | hostdict = paramiko.HostKeys('hostfile.temp') 102 | key = paramiko.RSAKey(data=decodebytes(keyblob)) 103 | key_dss = paramiko.DSSKey(data=decodebytes(keyblob_dss)) 104 | hostdict['secure.example.com'] = { 105 | 'ssh-rsa': key, 106 | 'ssh-dss': key_dss 107 | } 108 | hostdict['fake.example.com'] = {} 109 | hostdict['fake.example.com']['ssh-rsa'] = key 110 | 111 | self.assertEqual(3, len(hostdict)) 112 | self.assertEqual(2, len(list(hostdict.values())[0])) 113 | self.assertEqual(1, len(list(hostdict.values())[1])) 114 | self.assertEqual(1, len(list(hostdict.values())[2])) 115 | fp = hexlify(hostdict['secure.example.com']['ssh-rsa'].get_fingerprint()).upper() 116 | self.assertEqual(b'7EC91BB336CB6D810B124B1353C32396', fp) 117 | fp = hexlify(hostdict['secure.example.com']['ssh-dss'].get_fingerprint()).upper() 118 | self.assertEqual(b'4478F0B9A23CC5182009FF755BC1D26C', fp) 119 | 120 | def test_delitem(self): 121 | hostdict = paramiko.HostKeys('hostfile.temp') 122 | target = 'happy.example.com' 123 | _ = hostdict[target] # will KeyError if not present 124 | del hostdict[target] 125 | try: 126 | _ = hostdict[target] 127 | except KeyError: 128 | pass # Good 129 | else: 130 | assert False, "Entry was not deleted from HostKeys on delitem!" 131 | 132 | def test_entry_delitem(self): 133 | hostdict = paramiko.HostKeys('hostfile.temp') 134 | target = 'happy.example.com' 135 | entry = hostdict[target] 136 | key_type_list = [key_type for key_type in entry] 137 | for key_type in key_type_list: 138 | del entry[key_type] 139 | 140 | # will KeyError if not present 141 | for key_type in key_type_list: 142 | try: 143 | del entry[key_type] 144 | except KeyError: 145 | pass # Good 146 | else: 147 | assert False, "Key was not deleted from Entry on delitem!" 148 | -------------------------------------------------------------------------------- /tests/test_kex_gss.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # Copyright (C) 2013-2014 science + computing ag 3 | # Author: Sebastian Deiss 4 | # 5 | # 6 | # This file is part of paramiko. 7 | # 8 | # Paramiko is free software; you can redistribute it and/or modify it under the 9 | # terms of the GNU Lesser General Public License as published by the Free 10 | # Software Foundation; either version 2.1 of the License, or (at your option) 11 | # any later version. 12 | # 13 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 14 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 15 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 16 | # details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public License 19 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 20 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 21 | 22 | """ 23 | Unit Tests for the GSS-API / SSPI SSHv2 Diffie-Hellman Key Exchange and user 24 | authentication 25 | """ 26 | 27 | 28 | import socket 29 | import threading 30 | import unittest 31 | 32 | import paramiko 33 | 34 | from .util import KerberosTestCase, update_env 35 | 36 | 37 | class NullServer (paramiko.ServerInterface): 38 | 39 | def get_allowed_auths(self, username): 40 | return 'gssapi-keyex' 41 | 42 | def check_auth_gssapi_keyex(self, username, 43 | gss_authenticated=paramiko.AUTH_FAILED, 44 | cc_file=None): 45 | if gss_authenticated == paramiko.AUTH_SUCCESSFUL: 46 | return paramiko.AUTH_SUCCESSFUL 47 | return paramiko.AUTH_FAILED 48 | 49 | def enable_auth_gssapi(self): 50 | UseGSSAPI = True 51 | return UseGSSAPI 52 | 53 | def check_channel_request(self, kind, chanid): 54 | return paramiko.OPEN_SUCCEEDED 55 | 56 | def check_channel_exec_request(self, channel, command): 57 | if command != b"yes": 58 | return False 59 | return True 60 | 61 | 62 | class GSSKexTest(KerberosTestCase): 63 | def setUp(self): 64 | self.username = self.realm.user_princ 65 | self.hostname = socket.getfqdn(self.realm.hostname) 66 | self.sockl = socket.socket() 67 | self.sockl.bind((self.realm.hostname, 0)) 68 | self.sockl.listen(1) 69 | self.addr, self.port = self.sockl.getsockname() 70 | self.event = threading.Event() 71 | update_env(self, self.realm.env) 72 | thread = threading.Thread(target=self._run) 73 | thread.start() 74 | 75 | def tearDown(self): 76 | for attr in "tc ts socks sockl".split(): 77 | if hasattr(self, attr): 78 | getattr(self, attr).close() 79 | 80 | def _run(self): 81 | self.socks, addr = self.sockl.accept() 82 | self.ts = paramiko.Transport(self.socks, gss_kex=True) 83 | host_key = paramiko.load_private_key_file('tests/test_rsa.key') 84 | self.ts.add_server_key(host_key) 85 | self.ts.set_gss_host(self.realm.hostname) 86 | try: 87 | self.ts.load_server_moduli() 88 | except: 89 | print('(Failed to load moduli -- gex will be unsupported.)') 90 | server = NullServer() 91 | self.ts.start_server(self.event, server) 92 | 93 | def _test_gsskex_and_auth(self, gss_host, rekey=False): 94 | """ 95 | Verify that Paramiko can handle SSHv2 GSS-API / SSPI authenticated 96 | Diffie-Hellman Key Exchange and user authentication with the GSS-API 97 | context created during key exchange. 98 | """ 99 | host_key = paramiko.load_private_key_file('tests/test_rsa.key') 100 | public_host_key = paramiko.RSAKey(data=host_key.asbytes()) 101 | 102 | self.tc = paramiko.SSHClient() 103 | self.tc.get_host_keys().add('[%s]:%d' % (self.hostname, self.port), 104 | 'ssh-rsa', public_host_key) 105 | self.tc.connect(self.hostname, self.port, username=self.username, 106 | gss_auth=True, gss_kex=True, gss_host=gss_host) 107 | 108 | self.event.wait(1.0) 109 | self.assertTrue(self.event.is_set()) 110 | self.assertTrue(self.ts.is_active()) 111 | self.assertEqual(self.username, self.ts.get_username()) 112 | self.assertTrue(self.ts.is_authenticated()) 113 | self.assertTrue(self.tc.get_transport().gss_kex_used) 114 | 115 | stdin, stdout, stderr = self.tc.exec_command('yes') 116 | schan = self.ts.accept(1.0) 117 | if rekey: 118 | self.tc.get_transport().renegotiate_keys() 119 | 120 | schan.send('Hello there.\n') 121 | schan.send_stderr('This is on stderr.\n') 122 | schan.close() 123 | 124 | self.assertEqual('Hello there.\n', stdout.readline()) 125 | self.assertEqual('', stdout.readline()) 126 | self.assertEqual('This is on stderr.\n', stderr.readline()) 127 | self.assertEqual('', stderr.readline()) 128 | 129 | stdin.close() 130 | stdout.close() 131 | stderr.close() 132 | 133 | def test_gsskex_and_auth(self): 134 | """ 135 | Verify that Paramiko can handle SSHv2 GSS-API / SSPI authenticated 136 | Diffie-Hellman Key Exchange and user authentication with the GSS-API 137 | context created during key exchange. 138 | """ 139 | self._test_gsskex_and_auth(gss_host=None) 140 | 141 | @unittest.expectedFailure # https://github.com/paramiko/paramiko/issues/1312 142 | def test_gsskex_and_auth_rekey(self): 143 | """ 144 | Verify that Paramiko can rekey. 145 | """ 146 | self._test_gsskex_and_auth(gss_host=None, rekey=True) 147 | -------------------------------------------------------------------------------- /tests/test_message.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2009 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Some unit tests for ssh protocol message blocks. 21 | """ 22 | 23 | import unittest 24 | 25 | from paramiko.message import Message 26 | from paramiko.common import byte_chr, zero_byte 27 | 28 | 29 | class MessageTest (unittest.TestCase): 30 | 31 | __a = b'\x00\x00\x00\x17\x07\x60\xe0\x90\x00\x00\x00\x01\x71\x00\x00\x00\x05\x68\x65\x6c\x6c\x6f\x00\x00\x03\xe8' + b'x' * 1000 # noqa: E501 32 | __b = b'\x01\x00\xf3\x00\x3f\x00\x00\x00\x10\x68\x75\x65\x79\x2c\x64\x65\x77\x65\x79\x2c\x6c\x6f\x75\x69\x65' # noqa: E501 33 | __c = b'\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\xf5\xe4\xd3\xc2\xb1\x09\x00\x00\x00\x01\x11\x00\x00\x00\x07\x00\xf5\xe4\xd3\xc2\xb1\x09\x00\x00\x00\x06\x9a\x1b\x2c\x3d\x4e\xf7' # noqa: E501 34 | __d = b'\x00\x00\x00\x05\x01\x00\x00\x00\x03\x63\x61\x74\x00\x00\x00\x03\x61\x2c\x62' 35 | 36 | def test_encode(self): 37 | msg = Message() 38 | msg.add_int(23) 39 | msg.add_int(123789456) 40 | msg.add_string('q') 41 | msg.add_string('hello') 42 | msg.add_string('x' * 1000) 43 | self.assertEqual(msg.asbytes(), self.__a) 44 | 45 | msg = Message() 46 | msg.add_boolean(True) 47 | msg.add_boolean(False) 48 | msg.add_byte(byte_chr(0xf3)) 49 | 50 | msg.add_bytes(zero_byte + byte_chr(0x3f)) 51 | msg.add_list(['huey', 'dewey', 'louie']) 52 | self.assertEqual(msg.asbytes(), self.__b) 53 | 54 | msg = Message() 55 | msg.add_int64(5) 56 | msg.add_int64(0xf5e4d3c2b109) 57 | msg.add_mpint(17) 58 | msg.add_mpint(0xf5e4d3c2b109) 59 | msg.add_mpint(-0x65e4d3c2b109) 60 | self.assertEqual(msg.asbytes(), self.__c) 61 | 62 | def test_decode(self): 63 | msg = Message(self.__a) 64 | self.assertEqual(msg.get_int(), 23) 65 | self.assertEqual(msg.get_int(), 123789456) 66 | self.assertEqual(msg.get_text(), 'q') 67 | self.assertEqual(msg.get_text(), 'hello') 68 | self.assertEqual(msg.get_text(), 'x' * 1000) 69 | 70 | msg = Message(self.__b) 71 | self.assertEqual(msg.get_boolean(), True) 72 | self.assertEqual(msg.get_boolean(), False) 73 | self.assertEqual(msg.get_byte(), byte_chr(0xf3)) 74 | self.assertEqual(msg.get_bytes(2), zero_byte + byte_chr(0x3f)) 75 | self.assertEqual(msg.get_list(), ['huey', 'dewey', 'louie']) 76 | 77 | msg = Message(self.__c) 78 | self.assertEqual(msg.get_int64(), 5) 79 | self.assertEqual(msg.get_int64(), 0xf5e4d3c2b109) 80 | self.assertEqual(msg.get_mpint(), 17) 81 | self.assertEqual(msg.get_mpint(), 0xf5e4d3c2b109) 82 | self.assertEqual(msg.get_mpint(), -0x65e4d3c2b109) 83 | 84 | def test_add(self): 85 | msg = Message() 86 | msg.add(5) 87 | msg.add(True) 88 | msg.add('cat') 89 | msg.add(['a', 'b']) 90 | self.assertEqual(msg.asbytes(), self.__d) 91 | 92 | def test_misc(self): 93 | msg = Message(self.__d) 94 | self.assertEqual(msg.get_int(), 5) 95 | self.assertEqual(msg.get_boolean(), True) 96 | self.assertEqual(msg.get_text(), 'cat') 97 | self.assertEqual(msg.get_so_far(), self.__d[:12]) 98 | self.assertEqual(msg.get_remainder(), self.__d[12:]) 99 | -------------------------------------------------------------------------------- /tests/test_packetizer.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2009 Robey Pointer 2 | # 3 | # This file is part of paramiko. 4 | # 5 | # Paramiko is free software; you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation; either version 2.1 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 | 19 | """ 20 | Some unit tests for the ssh2 protocol in Transport. 21 | """ 22 | 23 | import sys 24 | import unittest 25 | from hashlib import sha1 26 | 27 | import pytest 28 | 29 | from cryptography.hazmat.backends import default_backend 30 | from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes 31 | 32 | from paramiko import Message, Packetizer, util 33 | from paramiko.common import byte_chr, zero_byte 34 | 35 | from .loop import LoopSocket 36 | 37 | 38 | x55 = byte_chr(0x55) 39 | x1f = byte_chr(0x1f) 40 | 41 | 42 | class PacketizerTest (unittest.TestCase): 43 | 44 | def test_write(self): 45 | rsock = LoopSocket() 46 | wsock = LoopSocket() 47 | rsock.link(wsock) 48 | p = Packetizer(wsock) 49 | p.set_log(util.get_logger('paramiko.transport')) 50 | p.set_hexdump(True) 51 | encryptor = Cipher( 52 | algorithms.AES(zero_byte * 16), 53 | modes.CBC(x55 * 16), 54 | backend=default_backend() 55 | ).encryptor() 56 | p.set_outbound_cipher(encryptor, 16, sha1, 12, x1f * 20) 57 | 58 | # message has to be at least 16 bytes long, so we'll have at least one 59 | # block of data encrypted that contains zero random padding bytes 60 | m = Message() 61 | m.add_byte(byte_chr(100)) 62 | m.add_int(100) 63 | m.add_int(1) 64 | m.add_int(900) 65 | p.send_message(m) 66 | data = rsock.recv(100) 67 | # 32 + 12 bytes of MAC = 44 68 | self.assertEqual(44, len(data)) 69 | self.assertEqual( 70 | b'\x43\x91\x97\xbd\x5b\x50\xac\x25\x87\xc2\xc4\x6b\xc7\xe9\x38\xc0', data[:16] 71 | ) 72 | 73 | def test_read(self): 74 | rsock = LoopSocket() 75 | wsock = LoopSocket() 76 | rsock.link(wsock) 77 | p = Packetizer(rsock) 78 | p.set_log(util.get_logger('paramiko.transport')) 79 | p.set_hexdump(True) 80 | decryptor = Cipher( 81 | algorithms.AES(zero_byte * 16), 82 | modes.CBC(x55 * 16), 83 | backend=default_backend() 84 | ).decryptor() 85 | p.set_inbound_cipher(decryptor, 16, sha1, 12, x1f * 20) 86 | wsock.send(b'\x43\x91\x97\xbd\x5b\x50\xac\x25\x87\xc2\xc4\x6b\xc7\xe9\x38\xc0\x90\xd2\x16\x56\x0d\x71\x73\x61\x38\x7c\x4c\x3d\xfb\x97\x7d\xe2\x6e\x03\xb1\xa0\xc2\x1c\xd6\x41\x41\x4c\xb4\x59') # noqa: E501 87 | cmd, m = p.read_message() 88 | self.assertEqual(100, cmd) 89 | self.assertEqual(100, m.get_int()) 90 | self.assertEqual(1, m.get_int()) 91 | self.assertEqual(900, m.get_int()) 92 | 93 | @pytest.mark.skipif(sys.platform.startswith('win'), reason="no SIGALRM on windows") 94 | def test_closed(self): 95 | rsock = LoopSocket() 96 | wsock = LoopSocket() 97 | rsock.link(wsock) 98 | p = Packetizer(wsock) 99 | p.set_log(util.get_logger('paramiko.transport')) 100 | p.set_hexdump(True) 101 | encryptor = Cipher( 102 | algorithms.AES(zero_byte * 16), 103 | modes.CBC(x55 * 16), 104 | backend=default_backend() 105 | ).encryptor() 106 | p.set_outbound_cipher(encryptor, 16, sha1, 12, x1f * 20) 107 | 108 | # message has to be at least 16 bytes long, so we'll have at least one 109 | # block of data encrypted that contains zero random padding bytes 110 | m = Message() 111 | m.add_byte(byte_chr(100)) 112 | m.add_int(100) 113 | m.add_int(1) 114 | m.add_int(900) 115 | wsock.send = lambda x: 0 116 | from functools import wraps 117 | import errno 118 | import os 119 | import signal 120 | 121 | class TimeoutError(Exception): 122 | def __init__(self, error_message): 123 | if hasattr(errno, 'ETIME'): 124 | self.message = os.sterror(errno.ETIME) 125 | else: 126 | self.messaage = error_message 127 | 128 | def timeout(seconds=1, error_message='Timer expired'): 129 | def decorator(func): 130 | def _handle_timeout(signum, frame): 131 | raise TimeoutError(error_message) 132 | 133 | def wrapper(*args, **kwargs): 134 | signal.signal(signal.SIGALRM, _handle_timeout) 135 | signal.alarm(seconds) 136 | try: 137 | result = func(*args, **kwargs) 138 | finally: 139 | signal.alarm(0) 140 | return result 141 | 142 | return wraps(func)(wrapper) 143 | 144 | return decorator 145 | send = timeout()(p.send_message) 146 | self.assertRaises(EOFError, send, m) 147 | -------------------------------------------------------------------------------- /tests/test_rsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz 3 | oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/ 4 | d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB 5 | gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0 6 | EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon 7 | soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H 8 | tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU 9 | avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA 10 | 4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g 11 | H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv 12 | qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV 13 | HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc 14 | nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/test_rsa.key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c= 2 | -------------------------------------------------------------------------------- /tests/test_rsa_2k_o.key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABD0R3hOFS 3 | FMb2SJeo5h8QPNAAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDF+Dpr54DX 4 | 0WdeTDpNAMdkCWEkl3OXtNgf58qlN1gX572OLBqLf0zT4bHstUEpU3piazph/rSWcUMuBo 5 | D46tZ6jiH7H9b9Pem2eYQWaELDDkM+v9BMbEy5rMbFRLol5OtEvPFqneyEAanPOgvd8t3y 6 | yhSev9QVusakzJ8j8LGgrA8huYZ+Srnw0shEWLG70KUKCh3rG0QIvA8nfhtUOisr2Gp+F0 7 | YxMGb5gwBlQYAYE5l6u1SjZ7hNjyNosjK+wRBFgFFBYVpkZKJgWoK9w4ijFyzMZTucnZMq 8 | KOKAjIJvHfKBf2/cEfYxSq1EndqTqjYsd9T7/s2vcn1OH5a0wkERAAAD0JnzCJYfDeiUQ6 9 | 9LOAb6/NnhKvFjCdBYal60MfLcLBHvzHLJvTneQ4f1Vknq8xEVmRba7SDSfwaEybP/1FsP 10 | SGH6FNKA5gKllemgmcaUVr3wtNPtjX4WgsyHcwCRgHmOiyNrUj0OZR5wbZabHIIyirl4wa 11 | LBz8Jb3GalKEagtyWsBKDCKHCFNzh8xmsT1SWhnC7baRyC8e3krQm9hGbNhpj6Q5AtN3ql 12 | wBVamUp0eKxkt70mKBKI4v3DR8KqrEndeK6d0cegVEkE67fqa99a5J3uSDC8mglKrHiKEs 13 | dU1dh/bOF/H3aFpINlRwvlZ95Opby7rG0BHgbZONq0+VUnABVzNTM5Xd5UKjjCF28CrQBf 14 | XS6WeHeUx2zHtOmL1xdePk+Bii+SSUl3pLa4SDwX4nV95cSPx8vMm8dJEruxad6+MPoSuy 15 | Oyho89jqUTSgC/RPejuTgrnB3WbzE5SJb+V3zMata0J1wxbNfYKG9U+VucUZhP4+jzfNqH 16 | B/v8JqtuxnqR8NjPsK2+8wJxebL2KVNjKOm//6P3KSDsavpscGpVWOM06zUlwWCB26W3pP 17 | X/+xO9aR5wiBteFKoJG1waziIjqhOJSmvq+I/texUKEUd/eEFNt10Ubc0zy0sRYVN8rIRJ 18 | masQzCYuUylDzCa4ar1s4qngBZzWL2PRkPuXuhoHuT0J5no174GR6+6EAYZZhnq0tkYrhZ 19 | Ar0tQ4CDlI235a3MPHzvABuwYuWys1tBuLAb+6Gc6CmCiQ+mhojfQUBYG5T65iRFA5UQsH 20 | O1RLEC3yasxGcBI6d0J/fwOP/YLktNu3AeUumr0N9Xgf02DlBNwd+4GOI0LcQvl/3J8ppo 21 | bamTppKPEZ2d32VNEO+Z6Zx5DlIVm5gDeMvIvdwap445VnhL3ZZH2NCkAcXM9+0WH+Quas 22 | JCAMgPYiP9FzF+8Onmj2OmhgIVj/9eanhS3/GLrRC4xCvER2V7PwgB0I5qY110BPEttDyo 23 | IvYE51kvtdW447SK7HZywJnkyw2RNm+29dvWJJwSQckUHuZkXEtmEPk0ePL3yf2NH5XYJc 24 | pXX6Zac0KemCPIHr8l7GogE4Rb2BBTqddkegb9piz6QTAPcQnn+GuMFG06IBhUrgcMEQ8x 25 | UOXYUUrT5HvSxWUcgaYH1nfC3bTWmDaodw8/HQKyF6c44rujO2s2NLFOCAyQMUNdhh3lfD 26 | yHYLO7xYkP6xzzkpk+2lwBoeYdQdAwlKN/XqC8ZhBfwTdem/1hh1BpQJFbbFftWxU8gxxi 27 | iuI+vmlsuIsxKoGCq8YXuophx62lo= 28 | -----END OPENSSH PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /tests/test_rsa_password.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,DAA422E8A5A8EFB7 4 | 5 | +nssHGmWl91IcmGiE6DdCIqGvAP04tuLh60wLjWBvdjtF9CjztPnF57xe+6pBk7o 6 | YgF/Ry3ik9ZV9rHNcRXifDKM9crxtYlpUlkM2C0SP89sXaO0P1Q1yCnrtZUwDIKO 7 | BNV8et5X7+AGMFsy/nmv0NFMrbpoG03Dppsloecd29NTRlIXwxHRFyHxy6BdEib/ 8 | Dn0mEVbwg3dTvKrd/sODWR9hRwpDGM9nkEbUNJCh7vMwFKkIZZF8yqFvmGckuO5C 9 | HZkDJ6RkEDYrSZJAavQaiOPF5bu3cHughRfnrIKVrQuTTDiWjwX9Ny8e4p4k7dy7 10 | rLpbPhtxUOUbpOF7T1QxljDi1Tcq3Ebk3kN/ZLPRFnDrJfyUx+m9BXmAa78Wxs/l 11 | KaS8DTkYykd3+EGOeJFjZg2bvgqil4V+5JIt/+MQ5pZ/ui7i4GcH2bvZyGAbrXzP 12 | 3LipSAdN5RG+fViLe3HUtfCx4ZAgtU78TWJrLk2FwKQGglFxKLnswp+IKZb09rZV 13 | uxmG4pPLUnH+mMYdiy5ugzj+5C8iZ0/IstpHVmO6GWROfedpJ82eMztTOtdhfMep 14 | 8Z3HwAwkDtksL7Gq9klb0Wq5+uRlBWetixddAvnmqXNzYhaANWcAF/2a2Hz06Rb0 15 | e6pe/g0Ek5KV+6YI+D+oEblG0Sr+d4NtxtDTmIJKNVkmzlhI2s53bHp6txCb5JWJ 16 | S8mKLPBBBzaNXYd3odDvGXguuxUntWSsD11KyR6B9DXMIfWQW5dT7hp5kTMGlXWJ 17 | lD2hYab13DCCuAkwVTdpzhHYLZyxLYoSu05W6z8SAOs= 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /tests/test_ssh_exception.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | import pytest 4 | 5 | from paramiko import RSAKey 6 | from paramiko.ssh_exception import ( 7 | BadAuthenticationType, 8 | PartialAuthentication, 9 | ChannelException, 10 | BadHostKeyException, 11 | ProxyCommandFailure, 12 | ) 13 | 14 | 15 | @pytest.mark.parametrize(['exc'], [ 16 | (BadAuthenticationType("Bad authentication type", ["ok", "also-ok"]),), 17 | (PartialAuthentication(["ok", "also-ok"]),), 18 | (BadHostKeyException("myhost", RSAKey.generate(2048), RSAKey.generate(2048)),), 19 | (ProxyCommandFailure("nc servername 22", 1),), 20 | (ChannelException(17, "whatever"),), 21 | ]) 22 | def test_ssh_exception_strings(exc): 23 | assert isinstance(str(exc), str) 24 | assert isinstance(repr(exc), str) 25 | if type(exc) != BadHostKeyException: 26 | ne = pickle.loads(pickle.dumps(exc)) 27 | assert type(ne) == type(exc) 28 | -------------------------------------------------------------------------------- /tests/test_ssh_gss.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2003-2007 Robey Pointer 2 | # Copyright (C) 2013-2014 science + computing ag 3 | # Author: Sebastian Deiss 4 | # 5 | # 6 | # This file is part of paramiko. 7 | # 8 | # Paramiko is free software; you can redistribute it and/or modify it under the 9 | # terms of the GNU Lesser General Public License as published by the Free 10 | # Software Foundation; either version 2.1 of the License, or (at your option) 11 | # any later version. 12 | # 13 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 14 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 15 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 16 | # details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public License 19 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 20 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 21 | 22 | """ 23 | Unit Tests for the GSS-API / SSPI SSHv2 Authentication (gssapi-with-mic) 24 | """ 25 | 26 | import socket 27 | import threading 28 | 29 | import paramiko 30 | 31 | from .util import _support, KerberosTestCase, update_env 32 | from .test_client import FINGERPRINTS 33 | 34 | 35 | class NullServer (paramiko.ServerInterface): 36 | def get_allowed_auths(self, username): 37 | return 'gssapi-with-mic,publickey' 38 | 39 | def check_auth_gssapi_with_mic( 40 | self, 41 | username, 42 | gss_authenticated=paramiko.AUTH_FAILED, 43 | cc_file=None, 44 | ): 45 | if gss_authenticated == paramiko.AUTH_SUCCESSFUL: 46 | return paramiko.AUTH_SUCCESSFUL 47 | return paramiko.AUTH_FAILED 48 | 49 | def enable_auth_gssapi(self): 50 | return True 51 | 52 | def check_auth_publickey(self, username, key): 53 | try: 54 | expected = FINGERPRINTS[key.get_name()] 55 | except KeyError: 56 | return paramiko.AUTH_FAILED 57 | else: 58 | if key.get_fingerprint() == expected: 59 | return paramiko.AUTH_SUCCESSFUL 60 | return paramiko.AUTH_FAILED 61 | 62 | def check_channel_request(self, kind, chanid): 63 | return paramiko.OPEN_SUCCEEDED 64 | 65 | def check_channel_exec_request(self, channel, command): 66 | if command != b"yes": 67 | return False 68 | return True 69 | 70 | 71 | class GSSAuthTest(KerberosTestCase): 72 | def setUp(self): 73 | # TODO: username and targ_name should come from os.environ or whatever 74 | # the approved pytest method is for runtime-configuring test data. 75 | self.username = self.realm.user_princ 76 | self.hostname = socket.getfqdn(self.realm.hostname) 77 | self.sockl = socket.socket() 78 | self.sockl.bind((self.realm.hostname, 0)) 79 | self.sockl.listen(1) 80 | self.addr, self.port = self.sockl.getsockname() 81 | self.event = threading.Event() 82 | update_env(self, self.realm.env) 83 | thread = threading.Thread(target=self._run) 84 | thread.start() 85 | 86 | def tearDown(self): 87 | for attr in "tc ts socks sockl".split(): 88 | if hasattr(self, attr): 89 | getattr(self, attr).close() 90 | 91 | def _run(self): 92 | self.socks, addr = self.sockl.accept() 93 | self.ts = paramiko.Transport(self.socks) 94 | host_key = paramiko.load_private_key_file('tests/test_rsa.key') 95 | self.ts.add_server_key(host_key) 96 | server = NullServer() 97 | self.ts.start_server(self.event, server) 98 | 99 | def _test_connection(self, **kwargs): 100 | """ 101 | (Most) kwargs get passed directly into SSHClient.connect(). 102 | 103 | The exception is ... no exception yet 104 | """ 105 | host_key = paramiko.load_private_key_file('tests/test_rsa.key') 106 | public_host_key = paramiko.RSAKey(data=host_key.asbytes()) 107 | 108 | self.tc = paramiko.SSHClient() 109 | self.tc.set_missing_host_key_policy(paramiko.WarningPolicy()) 110 | self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 111 | 'ssh-rsa', public_host_key) 112 | self.tc.connect(hostname=self.addr, port=self.port, username=self.username, 113 | gss_host=self.hostname, gss_auth=True, **kwargs) 114 | 115 | self.event.wait(1.0) 116 | self.assertTrue(self.event.is_set()) 117 | self.assertTrue(self.ts.is_active()) 118 | self.assertEqual(self.username, self.ts.get_username()) 119 | self.assertTrue(self.ts.is_authenticated()) 120 | 121 | stdin, stdout, stderr = self.tc.exec_command('yes') 122 | schan = self.ts.accept(1.0) 123 | 124 | schan.send('Hello there.\n') 125 | schan.send_stderr('This is on stderr.\n') 126 | schan.close() 127 | 128 | self.assertEqual('Hello there.\n', stdout.readline()) 129 | self.assertEqual('', stdout.readline()) 130 | self.assertEqual('This is on stderr.\n', stderr.readline()) 131 | self.assertEqual('', stderr.readline()) 132 | 133 | stdin.close() 134 | stdout.close() 135 | stderr.close() 136 | 137 | def test_gss_auth(self): 138 | """ 139 | Verify that Paramiko can handle SSHv2 GSS-API / SSPI authentication 140 | (gssapi-with-mic) in client and server mode. 141 | """ 142 | self._test_connection(allow_agent=False, 143 | look_for_keys=False) 144 | 145 | def test_auth_trickledown(self): 146 | """ 147 | Failed gssapi-with-mic auth doesn't prevent subsequent key auth from 148 | succeeding 149 | """ 150 | self.hostname = "this_host_does_not_exists_and_causes_a_GSSAPI-exception" 151 | self._test_connection(key_filename=[_support('test_rsa.key')], 152 | allow_agent=False, 153 | look_for_keys=False) 154 | -------------------------------------------------------------------------------- /tests/util.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname, realpath, join 2 | import os 3 | import sys 4 | import unittest 5 | 6 | import pytest 7 | 8 | from paramiko.py3compat import builtins 9 | from paramiko.ssh_gss import GSS_AUTH_AVAILABLE 10 | 11 | 12 | def _support(filename): 13 | return join(dirname(realpath(__file__)), filename) 14 | 15 | 16 | def needs_builtin(name): 17 | """ 18 | Skip decorated test if builtin name does not exist. 19 | """ 20 | reason = "Test requires a builtin '{}'".format(name) 21 | return pytest.mark.skipif(not hasattr(builtins, name), reason=reason) 22 | 23 | 24 | slow = pytest.mark.slow 25 | 26 | 27 | # GSSAPI / Kerberos related tests need a working Kerberos environment. 28 | # The class `KerberosTestCase` provides such an environment or skips all tests. 29 | # There are 3 distinct cases: 30 | # 31 | # - A Kerberos environment has already been created and the environment 32 | # contains the required information. 33 | # 34 | # - We can use the package 'k5test' to setup an working kerberos environment on 35 | # the fly. 36 | # 37 | # - We skip all tests. 38 | # 39 | # ToDo: add a Windows specific implementation? 40 | 41 | class SkipKerberosTestCase(unittest.TestCase): 42 | @classmethod 43 | def setUpClass(cls): 44 | raise unittest.SkipTest("Missing gssapi or k5test") 45 | 46 | 47 | if not GSS_AUTH_AVAILABLE: 48 | KerberosTestCase = SkipKerberosTestCase 49 | 50 | elif (os.environ.get("K5TEST_USER_PRINC") and 51 | os.environ.get("K5TEST_HOSTNAME") and 52 | os.environ.get("KRB5_KTNAME")): # add other vars as needed 53 | # The environment provides the required information 54 | 55 | class DummyK5Realm(object): 56 | def __init__(self): 57 | for k in os.environ: 58 | if not k.startswith("K5TEST_"): 59 | continue 60 | setattr(self, k[7:].lower(), os.environ[k]) 61 | self.env = {} 62 | 63 | class KerberosTestCase(unittest.TestCase): 64 | @classmethod 65 | def setUpClass(cls): 66 | cls.realm = DummyK5Realm() 67 | 68 | @classmethod 69 | def tearDownClass(cls): 70 | del cls.realm 71 | else: 72 | try: 73 | # Try to setup a kerberos environment 74 | from k5test import KerberosTestCase 75 | except Exception: 76 | KerberosTestCase = SkipKerberosTestCase 77 | 78 | 79 | def update_env(testcase, mapping, env=os.environ): 80 | """Modify os.environ during a test case and restore during cleanup.""" 81 | saved_env = env.copy() 82 | 83 | def replace(target, source): 84 | target.update(source) 85 | for k in list(target): 86 | if k not in source: 87 | target.pop(k, None) 88 | 89 | testcase.addCleanup(replace, env, saved_env) 90 | env.update(mapping) 91 | return testcase 92 | 93 | 94 | def k5shell(args=None): 95 | """Create a shell with an kerberos environment 96 | 97 | This can be used to debug paramiko or to test the old GSSAPI. 98 | To test a different GSSAPI, simply activate a suitable venv 99 | within the shell. 100 | """ 101 | import k5test 102 | import atexit 103 | import subprocess 104 | k5 = k5test.K5Realm() 105 | atexit.register(k5.stop) 106 | os.environ.update(k5.env) 107 | for n in ("realm", "user_princ", "hostname"): 108 | os.environ["K5TEST_" + n.upper()] = getattr(k5, n) 109 | 110 | if not args: 111 | args = sys.argv[1:] 112 | if not args: 113 | args = [os.environ.get("SHELL", "bash")] 114 | sys.exit(subprocess.call(args)) 115 | --------------------------------------------------------------------------------