def chdir(self, path=None): """ Change the "current directory" of this SFTP session. Since SFTP doesn't really have the concept of a current working directory, this is emulated by Paramiko. Once you use this method to set a working directory, all operations on this `.SFTPClient` object will be relative to that path. You can pass in ``None`` to stop using a current working directory. :param str path: new current working directory :raises: ``IOError`` -- if the requested path doesn't exist on the server .. versionadded:: 1.4 """ if path is None: self._cwd = None return if not stat.S_ISDIR(self.stat(path).st_mode): code = errno.ENOTDIR raise SFTPError(code, "{}: {}".format(os.strerror(code), path)) self._cwd = b(self.normalize(path))
def from_string(cls, string): """ Create a public blob from a ``-cert.pub``-style string. """ fields = string.split(None, 2) if len(fields) < 2: msg = "Not enough fields for public blob: {}" raise ValueError(msg.format(fields)) key_type = fields[0] key_blob = decodebytes(b(fields[1])) try: comment = fields[2].strip() except IndexError: comment = None # Verify that the blob message first (string) field matches the # key_type m = Message(key_blob) blob_type = m.get_text() if blob_type != key_type: msg = "Invalid PublicBlob contents: key type={!r}, but blob type={!r}" raise ValueError(msg.format(key_type, blob_type)) # All good? All good. return cls(type_=key_type, blob=key_blob, comment=comment)
def _query_pageant(msg): """ Communication with the Pageant process is done through a shared memory-mapped file. """ hwnd = _get_pageant_window_object() if not hwnd: # Raise a failure to connect exception, pageant isn't running anymore! return None # create a name for the mmap map_name = "PageantRequest%08x" % thread.get_ident() pymap = _winapi.MemoryMap( map_name, _AGENT_MAX_MSGLEN, _winapi.get_security_attributes_for_user() ) with pymap: pymap.write(msg) # Create an array buffer containing the mapped filename char_buffer = array.array("b", b(map_name) + zero_byte) # noqa char_buffer_address, char_buffer_size = char_buffer.buffer_info() # Create a string to use for the SendMessage function call cds = COPYDATASTRUCT( _AGENT_COPYDATA_ID, char_buffer_size, char_buffer_address ) response = ctypes.windll.user32.SendMessageA( hwnd, win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.byref(cds) ) if response > 0: pymap.seek(0) datalen = pymap.read(4) retlen = struct.unpack(">I", datalen)[0] return datalen + pymap.read(retlen) return None
def asbytes(self): return b(str(self))
def _sha256_fingerprint(key_obj): return base64.b64encode(b(sha256(key_obj.asbytes()).digest()))[:-1]
def _parse_signing_key_data(self, data, password): from paramiko.transport import Transport # We may eventually want this to be usable for other key types, as # OpenSSH moves to it, but for now this is just for Ed25519 keys. # This format is described here: # https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key # The description isn't totally complete, and I had to refer to the # source for a full implementation. message = Message(data) if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC: raise SSHException("Invalid key") ciphername = message.get_text() kdfname = message.get_text() kdfoptions = message.get_binary() num_keys = message.get_int() if kdfname == "none": # kdfname of "none" must have an empty kdfoptions, the ciphername # must be "none" if kdfoptions or ciphername != "none": raise SSHException("Invalid key") elif kdfname == "bcrypt": if not password: raise PasswordRequiredException( "Private key file is encrypted" ) kdf = Message(kdfoptions) bcrypt_salt = kdf.get_binary() bcrypt_rounds = kdf.get_int() else: raise SSHException("Invalid key") if ciphername != "none" and ciphername not in Transport._cipher_info: raise SSHException("Invalid key") public_keys = [] for _ in range(num_keys): pubkey = Message(message.get_binary()) if pubkey.get_text() != "ssh-ed25519": raise SSHException("Invalid key") public_keys.append(pubkey.get_binary()) private_ciphertext = message.get_binary() if ciphername == "none": private_data = private_ciphertext else: cipher = Transport._cipher_info[ciphername] key = bcrypt.kdf( password=b(password), salt=bcrypt_salt, desired_key_bytes=cipher["key-size"] + cipher["block-size"], rounds=bcrypt_rounds, # We can't control how many rounds are on disk, so no sense # warning about it. ignore_few_rounds=True, ) decryptor = Cipher( cipher["class"](key[: cipher["key-size"]]), cipher["mode"](key[cipher["key-size"] :]), backend=default_backend(), ).decryptor() private_data = ( decryptor.update(private_ciphertext) + decryptor.finalize() ) message = Message(unpad(private_data)) if message.get_int() != message.get_int(): raise SSHException("Invalid key") signing_keys = [] for i in range(num_keys): if message.get_text() != "ssh-ed25519": raise SSHException("Invalid key") # A copy of the public key, again, ignore. public = message.get_binary() key_data = message.get_binary() # The second half of the key data is yet another copy of the public # key... signing_key = nacl.signing.SigningKey(key_data[:32]) # Verify that all the public keys are the same... assert ( signing_key.verify_key.encode() == public == public_keys[i] == key_data[32:] ) signing_keys.append(signing_key) # Comment, ignore. message.get_binary() if len(signing_keys) != 1: raise SSHException("Invalid key") return signing_keys[0]
def transport_run(self): # (use the exposed "run" method, because if we specify a thread target # of a private method, threading.Thread will keep a reference to it # indefinitely, creating a GC cycle and not letting Transport ever be # GC'd. it's a bug in Thread.) # Hold reference to 'sys' so we can test sys.modules to detect # interpreter shutdown. self.sys = sys # active=True occurs before the thread is launched, to avoid a race _active_threads.append(self) tid = hex(long(id(self)) & xffffffff) if self.server_mode: self._log(DEBUG, "starting thread (server mode): {}".format(tid)) else: self._log(DEBUG, "starting thread (client mode): {}".format(tid)) try: try: self.packetizer.write_all(b(self.local_version + "\r\n")) self._log( DEBUG, "Local version/idstring: {}".format(self.local_version), ) # noqa self._check_banner() # The above is actually very much part of the handshake, but # sometimes the banner can be read but the machine is not # responding, for example when the remote ssh daemon is loaded # in to memory but we can not read from the disk/spawn a new # shell. # Make sure we can specify a timeout for the initial handshake. # Re-use the banner timeout for now. self.packetizer.start_handshake(self.handshake_timeout) self._send_kex_init() self._expect_packet(MSG_KEXINIT) while self.active: if self.packetizer.need_rekey() and not self.in_kex: self._send_kex_init() try: ptype, m = self.packetizer.read_message() except NeedRekeyException: continue if ptype == MSG_IGNORE: continue elif ptype == MSG_DISCONNECT: self._parse_disconnect(m) break elif ptype == MSG_DEBUG: self._parse_debug(m) continue if len(self._expected_packet) > 0: if ptype not in self._expected_packet: if ptype == 30: continue raise SSHException( "Expecting packet from {!r}, got {:d}".format( self._expected_packet, ptype ) ) # noqa self._expected_packet = tuple() if (ptype >= 30) and (ptype <= 41): self.kex_engine.parse_next(ptype, m) continue if ptype in self._handler_table: error_msg = self._ensure_authed(ptype, m) if error_msg: self._send_message(error_msg) else: self._handler_table[ptype](self, m) elif ptype in self._channel_handler_table: chanid = m.get_int() chan = self._channels.get(chanid) if chan is not None: self._channel_handler_table[ptype](chan, m) elif chanid in self.channels_seen: self._log( DEBUG, "Ignoring message for dead channel {:d}".format( # noqa chanid ), ) else: self._log( ERROR, "Channel request for unknown channel {:d}".format( # noqa chanid ), ) break elif ( self.auth_handler is not None and ptype in self.auth_handler._handler_table ): handler = self.auth_handler._handler_table[ptype] handler(self.auth_handler, m) if len(self._expected_packet) > 0: continue else: # Respond with "I don't implement this particular # message type" message (unless the message type was # itself literally MSG_UNIMPLEMENTED, in which case, we # just shut up to avoid causing a useless loop). name = MSG_NAMES[ptype] warning = "Oops, unhandled type {} ({!r})".format( ptype, name ) self._log(WARNING, warning) if ptype != MSG_UNIMPLEMENTED: msg = Message() msg.add_byte(cMSG_UNIMPLEMENTED) msg.add_int(m.seqno) self._send_message(msg) self.packetizer.complete_handshake() except SSHException as e: self._log(ERROR, "Exception: " + str(e)) self._log(ERROR, util.tb_strings()) self.saved_exception = e except EOFError as e: self._log(DEBUG, "EOF in transport thread") self.saved_exception = e except socket.error as e: if type(e.args) is tuple: if e.args: emsg = "{} ({:d})".format(e.args[1], e.args[0]) else: # empty tuple, e.g. socket.timeout emsg = str(e) or repr(e) else: emsg = e.args self._log(ERROR, "Socket exception: " + emsg) self.saved_exception = e except Exception as e: self._log(ERROR, "Unknown exception: " + str(e)) self._log(ERROR, util.tb_strings()) self.saved_exception = e _active_threads.remove(self) for chan in list(self._channels.values()): chan._unlink() if self.active: self.active = False self.packetizer.close() if self.completion_event is not None: self.completion_event.set() if self.auth_handler is not None: self.auth_handler.abort() for event in self.channel_events.values(): event.set() try: self.lock.acquire() self.server_accept_cv.notify() finally: self.lock.release() self.sock.close() except Exception: # Don't raise spurious 'NoneType has no attribute X' errors when we # wake up during interpreter shutdown. Or rather -- raise # everything *if* sys.modules (used as a convenient sentinel) # appears to still exist. if self.sys.modules is not None: raise
def _parse_service_accept(self, m): service = m.get_text() if service == 'ssh-userauth': self._log(DEBUG, 'userauth is OK') m = Message() m.add_byte(cMSG_USERAUTH_REQUEST) m.add_string(self.username) m.add_string('ssh-connection') m.add_string(self.auth_method) if self.auth_method == 'password': m.add_boolean(False) password = b(self.password) m.add_string(password) elif self.auth_method == 'publickey': m.add_boolean(True) # Use certificate contents, if available, plain pubkey # otherwise if self.private_key.public_blob: m.add_string(self.private_key.public_blob.key_type) m.add_string(self.private_key.public_blob.key_blob) else: m.add_string(self.private_key.get_name()) m.add_string(self.private_key) blob = self._get_session_blob( self.private_key, 'ssh-connection', self.username) sig = self.private_key.sign_ssh_data(blob) m.add_string(sig) elif self.auth_method == 'keyboard-interactive': m.add_string('') m.add_string(self.submethods) elif self.auth_method == "gssapi-with-mic": sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds) m.add_bytes(sshgss.ssh_gss_oids()) # send the supported GSSAPI OIDs to the server self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_BANNER: self._parse_userauth_banner(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_RESPONSE: # Read the mechanism selected by the server. We send just # the Kerberos V5 OID, so the server can only respond with # this OID. mech = m.get_string() m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) try: m.add_string(sshgss.ssh_init_sec_context( self.gss_host, mech, self.username,)) except GSS_EXCEPTIONS as e: return self._handle_local_gss_failure(e) self.transport._send_message(m) while True: ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: srv_token = m.get_string() try: next_token = sshgss.ssh_init_sec_context( self.gss_host, mech, self.username, srv_token) except GSS_EXCEPTIONS as e: return self._handle_local_gss_failure(e) # After this step the GSSAPI should not return any # token. If it does, we keep sending the token to # the server until no more token is returned. if next_token is None: break else: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(next_token) self.transport.send_message(m) else: raise SSHException( "Received Package: {}".format(MSG_NAMES[ptype]) ) m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_MIC) # send the MIC to the server m.add_string(sshgss.ssh_get_mic(self.transport.session_id)) elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK: # RFC 4462 says we are not required to implement GSS-API # error messages. # See RFC 4462 Section 3.8 in # http://www.ietf.org/rfc/rfc4462.txt raise SSHException("Server returned an error token") elif ptype == MSG_USERAUTH_GSSAPI_ERROR: maj_status = m.get_int() min_status = m.get_int() err_msg = m.get_string() m.get_string() # Lang tag - discarded raise SSHException("""GSS-API Error: Major Status: {} Minor Status: {} Error Message: {} """.format(maj_status, min_status, err_msg)) elif ptype == MSG_USERAUTH_FAILURE: self._parse_userauth_failure(m) return else: raise SSHException( "Received Package: {}".format(MSG_NAMES[ptype])) elif ( self.auth_method == 'gssapi-keyex' and self.transport.gss_kex_used ): kexgss = self.transport.kexgss_ctxt kexgss.set_username(self.username) mic_token = kexgss.ssh_get_mic(self.transport.session_id) m.add_string(mic_token) elif self.auth_method == 'none': pass else: raise SSHException( 'Unknown auth method "{}"'.format(self.auth_method)) self.transport._send_message(m) else: self._log( DEBUG, 'Service request "{}" accepted (?)'.format(service))
def _read_private_key_openssh(self, lines, password): """ Read the new OpenSSH SSH2 private key format available since OpenSSH version 6.5 Reference: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key """ try: data = decodebytes(b("".join(lines))) except base64.binascii.Error as e: raise SSHException("base64 decoding error: {}".format(e)) # read data struct auth_magic = data[:15] if auth_magic != OPENSSH_AUTH_MAGIC: raise SSHException("unexpected OpenSSH key header encountered") cstruct = self._uint32_cstruct_unpack(data[15:], "sssur") cipher, kdfname, kdf_options, num_pubkeys, remainder = cstruct # For now, just support 1 key. if num_pubkeys > 1: raise SSHException( "unsupported: private keyfile has multiple keys") pubkey, privkey_blob = self._uint32_cstruct_unpack(remainder, "ss") if kdfname == b("bcrypt"): if cipher == b("aes256-cbc"): mode = modes.CBC elif cipher == b("aes256-ctr"): mode = modes.CTR else: raise SSHException( "unknown cipher `{}` used in private key file".format( cipher.decode("utf-8"))) # Encrypted private key. # If no password was passed in, raise an exception pointing # out that we need one if password is None: raise PasswordRequiredException( "private key file is encrypted") # Unpack salt and rounds from kdfoptions salt, rounds = self._uint32_cstruct_unpack(kdf_options, "su") # run bcrypt kdf to derive key and iv/nonce (32 + 16 bytes) key_iv = bcrypt.kdf( b(password), b(salt), 48, rounds, # We can't control how many rounds are on disk, so no sense # warning about it. ignore_few_rounds=True, ) key = key_iv[:32] iv = key_iv[32:] # decrypt private key blob decryptor = Cipher(algorithms.AES(key), mode(iv), default_backend()).decryptor() decrypted_privkey = decryptor.update(privkey_blob) decrypted_privkey += decryptor.finalize() elif cipher == b("none") and kdfname == b("none"): # Unencrypted private key decrypted_privkey = privkey_blob else: raise SSHException( "unknown cipher or kdf used in private key file") # Unpack private key and verify checkints cstruct = self._uint32_cstruct_unpack(decrypted_privkey, "uusr") checkint1, checkint2, keytype, keydata = cstruct if checkint1 != checkint2: raise SSHException( "OpenSSH private key file checkints do not match") return _unpad_openssh(keydata)
def _manage_encoding(self, output): return u(output, self._decode) if self._decode else b(output)
def set(self): if self._set or self._closed: return self._set = True self._wsock.send(b('*'))
def parse_pubkey(line): data = line.strip().split(" ")[1] return RSAKey(data=decodebytes(b(data)))
def _read_private_key_new_format(data, password): """ Read the new OpenSSH SSH2 private key format available since OpenSSH version 6.5 Reference: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key https://coolaj86.com/articles/the-openssh-private-key-format/ """ message = Message(data) OPENSSH_AUTH_MAGIC = b"openssh-key-v1\x00" if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC: raise SSHException("unexpected OpenSSH key header encountered") cipher = message.get_text() kdfname = message.get_text() kdfoptions = message.get_binary() num_keys = message.get_int() if num_keys > 1: raise SSHException( "unsupported: private keyfile has multiple keys") public_data = message.get_binary() privkey_blob = message.get_binary() pub_keytype = Message(public_data).get_text() if kdfname == "none": if kdfoptions or cipher != "none": raise SSHException("Invalid key options for kdf 'none'") private_data = privkey_blob elif kdfname == "bcrypt": if not password: raise PasswordRequiredException( "Private key file is encrypted") if cipher == 'aes256-cbc': mode = modes.CBC elif cipher == 'aes256-ctr': mode = modes.CTR else: raise SSHException( "unknown cipher '%s' used in private key file" % cipher) kdf = Message(kdfoptions) salt = kdf.get_binary() rounds = kdf.get_int() # run bcrypt kdf to derive key and iv/nonce (desired_key_bytes = 32 + 16 bytes) key_iv = bcrypt.kdf(b(password), salt, 48, rounds, ignore_few_rounds=True) key = key_iv[:32] iv = key_iv[32:] # decrypt private key blob decryptor = Cipher(algorithms.AES(key), mode(iv), default_backend()).decryptor() private_data = decryptor.update( privkey_blob) + decryptor.finalize() else: raise SSHException( "unknown cipher or kdf used in private key file") # Unpack private key and verify checkints priv_msg = Message(private_data) checkint1 = priv_msg.get_int() checkint2 = priv_msg.get_int() if checkint1 != checkint2: raise SSHException( 'OpenSSH private key file checkints do not match') keytype = priv_msg.get_text() if pub_keytype != keytype: raise SSHException( "Inconsistent key types for public and private parts") keydata = priv_msg.get_remainder() return keytype, _unpad(keydata)
def __init__(self, content=bytes()): self.content = b(content) self.idx = 0
def set(self): if self._set or self._closed: return self._set = True os.write(self._wfd, b('*'))
def _get_pageant_window_object(): return ctypes.windll.user32.FindWindowA(b('Pageant'), b('Pageant'))
def _parse_service_accept(self, m): service = m.get_text() if service == "ssh-userauth": # TODO 3.0: this message sucks ass. change it to something more # obvious. it always appears to mean "we already authed" but no! it # just means "we are allowed to TRY authing!" self._log(DEBUG, "userauth is OK") m = Message() m.add_byte(cMSG_USERAUTH_REQUEST) m.add_string(self.username) m.add_string("ssh-connection") m.add_string(self.auth_method) if self.auth_method == "password": m.add_boolean(False) password = b(self.password) m.add_string(password) elif self.auth_method == "publickey": m.add_boolean(True) key_type, bits = self._get_key_type_and_bits(self.private_key) algorithm = self._finalize_pubkey_algorithm(key_type) m.add_string(algorithm) m.add_string(bits) blob = self._get_session_blob( self.private_key, "ssh-connection", self.username, algorithm, ) sig = self.private_key.sign_ssh_data(blob, algorithm) m.add_string(sig) elif self.auth_method == "keyboard-interactive": m.add_string("") m.add_string(self.submethods) elif self.auth_method == "gssapi-with-mic": sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds) m.add_bytes(sshgss.ssh_gss_oids()) # send the supported GSSAPI OIDs to the server self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_BANNER: self._parse_userauth_banner(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_RESPONSE: # Read the mechanism selected by the server. We send just # the Kerberos V5 OID, so the server can only respond with # this OID. mech = m.get_string() m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) try: m.add_string( sshgss.ssh_init_sec_context( self.gss_host, mech, self.username)) except GSS_EXCEPTIONS as e: return self._handle_local_gss_failure(e) self.transport._send_message(m) while True: ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: srv_token = m.get_string() try: next_token = sshgss.ssh_init_sec_context( self.gss_host, mech, self.username, srv_token, ) except GSS_EXCEPTIONS as e: return self._handle_local_gss_failure(e) # After this step the GSSAPI should not return any # token. If it does, we keep sending the token to # the server until no more token is returned. if next_token is None: break else: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(next_token) self.transport.send_message(m) else: raise SSHException("Received Package: {}".format( MSG_NAMES[ptype])) m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_MIC) # send the MIC to the server m.add_string(sshgss.ssh_get_mic(self.transport.session_id)) elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK: # RFC 4462 says we are not required to implement GSS-API # error messages. # See RFC 4462 Section 3.8 in # http://www.ietf.org/rfc/rfc4462.txt raise SSHException("Server returned an error token") elif ptype == MSG_USERAUTH_GSSAPI_ERROR: maj_status = m.get_int() min_status = m.get_int() err_msg = m.get_string() m.get_string() # Lang tag - discarded raise SSHException("""GSS-API Error: Major Status: {} Minor Status: {} Error Message: {} """.format(maj_status, min_status, err_msg)) elif ptype == MSG_USERAUTH_FAILURE: self._parse_userauth_failure(m) return else: raise SSHException("Received Package: {}".format( MSG_NAMES[ptype])) elif (self.auth_method == "gssapi-keyex" and self.transport.gss_kex_used): kexgss = self.transport.kexgss_ctxt kexgss.set_username(self.username) mic_token = kexgss.ssh_get_mic(self.transport.session_id) m.add_string(mic_token) elif self.auth_method == "none": pass else: raise SSHException('Unknown auth method "{}"'.format( self.auth_method)) self.transport._send_message(m) else: self._log(DEBUG, 'Service request "{}" accepted (?)'.format(service))