def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals c_class = self.signing_key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(c_class) else: if msg is None: raise SSHException('Key object may not be empty') self.ecdsa_curve = self._ECDSA_CURVES.get_by_key_format_identifier( msg.get_text()) if self.ecdsa_curve is None: raise SSHException('Invalid key') curvename = msg.get_text() if curvename != self.ecdsa_curve.nist_name: raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() try: numbers = ec.EllipticCurvePublicNumbers.from_encoded_point( self.ecdsa_curve.curve_class(), pointinfo ) except ValueError: raise SSHException("Invalid public key") self.verifying_key = numbers.public_key(backend=default_backend())
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.verifying_key, self.signing_key = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ecdsa-sha2-nistp256': raise SSHException('Invalid key') curvename = msg.get_text() if curvename != 'nistp256': raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() if pointinfo[0:1] != four_byte: raise SSHException('Point compression is being used: %s' % binascii.hexlify(pointinfo)) self.verifying_key = VerifyingKey.from_string( pointinfo[1:], curve=curves.NIST256p) self.size = 256
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.verifying_key, self.signing_key = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ecdsa-sha2-nistp256': raise SSHException('Invalid key') curvename = msg.get_text() if curvename != 'nistp256': raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() if pointinfo[0:1] != four_byte: raise SSHException('Point compression is being used: %s' % binascii.hexlify(pointinfo)) self.verifying_key = VerifyingKey.from_string(pointinfo[1:], curve=curves.NIST256p) self.size = 256
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ecdsa-sha2-nistp256': raise SSHException('Invalid key') curvename = msg.get_text() if curvename != 'nistp256': raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() if pointinfo[0:1] != four_byte: raise SSHException('Point compression is being used: %s' % binascii.hexlify(pointinfo)) curve = ec.SECP256R1() numbers = ec.EllipticCurvePublicNumbers( x=inflate_long(pointinfo[1:1 + curve.key_size // 8], always_positive=True), y=inflate_long(pointinfo[1 + curve.key_size // 8:], always_positive=True), curve=curve ) self.verifying_key = numbers.public_key(backend=default_backend()) self.size = 256
def __init__( self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True, ): self.verifying_key = None self.signing_key = None self.public_blob = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals c_class = self.signing_key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(c_class) else: # Must set ecdsa_curve first; subroutines called herein may need to # spit out our get_name(), which relies on this. key_type = msg.get_text() # But this also means we need to hand it a real key/curve # identifier, so strip out any cert business. (NOTE: could push # that into _ECDSACurveSet.get_by_key_format_identifier(), but it # feels more correct to do it here?) suffix = "*****@*****.**" if key_type.endswith(suffix): key_type = key_type[: -len(suffix)] self.ecdsa_curve = self._ECDSA_CURVES.get_by_key_format_identifier( key_type ) key_types = self._ECDSA_CURVES.get_key_format_identifier_list() cert_types = [ "{}[email protected]".format(x) for x in key_types ] self._check_type_and_load_cert( msg=msg, key_type=key_types, cert_type=cert_types ) curvename = msg.get_text() if curvename != self.ecdsa_curve.nist_name: raise SSHException( "Can't handle curve of type {}".format(curvename) ) pointinfo = msg.get_binary() try: numbers = ec.EllipticCurvePublicNumbers.from_encoded_point( self.ecdsa_curve.curve_class(), pointinfo ) except ValueError: raise SSHException("Invalid public key") self.verifying_key = numbers.public_key(backend=default_backend())
def _decode_key(self, _raw): pkformat, data = _raw if pkformat == self.FORMAT_ORIGINAL: try: key = serialization.load_der_private_key( data, password=None, backend=default_backend() ) except (ValueError, TypeError, AssertionError, UnsupportedAlgorithm) as e: raise SSHException(str(e)) elif pkformat == self.FORMAT_OPENSSH: msg = Message(data) curve_name = msg.get_text() verkey = msg.get_binary() # noqa: F841 sigkey = msg.get_mpint() curve = self._ECDSA_CURVES.get_by_key_format_identifier("ecdsa-sha2-" + curve_name) if not curve: raise SSHException("Invalid key curve identifier") try: key = ec.derive_private_key(sigkey, curve.curve_class(), default_backend()) except (AttributeError, TypeError) as e: raise SSHException(str(e)) else: raise SSHException('unknown private key format.') if not isinstance(key, ec.EllipticCurvePrivateKey): raise SSHException("Invalid key type") self.signing_key = key self.verifying_key = key.public_key() curve_class = key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(curve_class)
def _decode_key(self, data): pkformat, data = data if pkformat == self._PRIVATE_KEY_FORMAT_ORIGINAL: try: key = serialization.load_der_private_key( data, password=None, backend=default_backend()) except (ValueError, AssertionError) as e: raise SSHException(str(e)) elif pkformat == self._PRIVATE_KEY_FORMAT_OPENSSH: try: msg = Message(data) curve_name = msg.get_text() verkey = msg.get_binary() # noqa: F841 sigkey = msg.get_mpint() name = "ecdsa-sha2-" + curve_name curve = self._ECDSA_CURVES.get_by_key_format_identifier(name) if not curve: raise SSHException("Invalid key curve identifier") key = ec.derive_private_key(sigkey, curve.curve_class(), default_backend()) except Exception as e: # PKey._read_private_key_openssh() should check or return # keytype - parsing could fail for any reason due to wrong type raise SSHException(str(e)) else: self._got_bad_key_format_id(pkformat) self.signing_key = key self.verifying_key = key.public_key() curve_class = key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(curve_class)
def validate(self): """ Verify that the contents of the certificate have been signed by the signing key in the certificate. This only does the cryptographic verification. Validation that the signing key is an accepted CA key or verifying other information such as timestamps or principals is not done here and is the responsibility of the application using the certificate. :return bool: True if the certificate data was signed by the CA in the cert. False if not. """ if self.signature is None or self.signature_key is None: return False # The signature_key is just a string containing the binary data of # the key. This will create a message of that data, determine the key # type from the first field, and then pass the message to the # appropriate key class. ca_key_msg = Message(self.signature_key) ca_key_type = ca_key_msg.get_text() ca_key_msg.rewind() ca_key_class = Transport._key_info.get(ca_key_type) if ca_key_class == None: err = "Unknown signature key type" raise SSHException(err) ca_key = ca_key_class(msg=ca_key_msg) # Create the certificate body if it hasn't been done already. if self._body_bytes is None: self._body_bytes = self.generate_body(ca_key, self.nonce) return ca_key.verify_ssh_sig(self._body_bytes, Message(self.signature))
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: deets = "key type={!r}, but blob type={!r}".format( key_type, blob_type ) raise ValueError("Invalid PublicBlob contents: {}".format(deets)) # All good? All good. return cls(type_=key_type, blob=key_blob, comment=comment)
def test_misc(self): msg = Message(self.__d) self.assertEqual(msg.get_int(), 5) self.assertEqual(msg.get_boolean(), True) self.assertEqual(msg.get_text(), 'cat') self.assertEqual(msg.get_so_far(), self.__d[:12]) self.assertEqual(msg.get_remainder(), self.__d[12:])
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.n = None self.e = None self.d = None self.p = None self.q = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.e, self.n = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ssh-rsa': raise SSHException('Invalid key') self.e = msg.get_mpint() self.n = msg.get_mpint() self.size = util.bit_length(self.n)
def __init__(self, msg=None, data=None, filename=None, password=None, key=None, file_obj=None): self.key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if key is not None: self.key = key else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ssh-rsa': raise SSHException('Invalid key') self.key = rsa.RSAPublicNumbers(e=msg.get_mpint(), n=msg.get_mpint()).public_key( default_backend())
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.p = None self.q = None self.g = None self.y = None self.x = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.p, self.q, self.g, self.y = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ssh-dss': raise SSHException('Invalid key') self.p = msg.get_mpint() self.q = msg.get_mpint() self.g = msg.get_mpint() self.y = msg.get_mpint() self.size = util.bit_length(self.p)
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ecdsa-sha2-nistp256': raise SSHException('Invalid key') curvename = msg.get_text() if curvename != 'nistp256': raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() if pointinfo[0:1] != four_byte: raise SSHException('Point compression is being used: %s' % binascii.hexlify(pointinfo)) curve = ec.SECP256R1() numbers = ec.EllipticCurvePublicNumbers( x=inflate_long(pointinfo[1:1 + curve.key_size // 8], always_positive=True), y=inflate_long(pointinfo[1 + curve.key_size // 8:], always_positive=True), curve=curve) self.verifying_key = numbers.public_key(backend=default_backend()) self.size = 256
def ecdsa_sig_from_agent_signed_response(response): msg = Message(response) algo = msg.get_text() sig = msg.get_binary() sig_msg = Message(sig) r = sig_msg.get_mpint() s = sig_msg.get_mpint() signature = encode_dss_signature(r, s) return signature
def _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string("none") m.add_boolean(False) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self._log( DEBUG, "Auth request (type={}) service={}, username={}".format( method, service, username ),
def test_decode(self): msg = Message(self.__a) self.assertEqual(msg.get_int(), 23) self.assertEqual(msg.get_int(), 123789456) self.assertEqual(msg.get_text(), 'q') self.assertEqual(msg.get_text(), 'hello') self.assertEqual(msg.get_text(), 'x' * 1000) msg = Message(self.__b) self.assertEqual(msg.get_boolean(), True) self.assertEqual(msg.get_boolean(), False) self.assertEqual(msg.get_byte(), byte_chr(0xf3)) self.assertEqual(msg.get_bytes(2), zero_byte + byte_chr(0x3f)) self.assertEqual(msg.get_list(), ['huey', 'dewey', 'louie']) msg = Message(self.__c) self.assertEqual(msg.get_int64(), 5) self.assertEqual(msg.get_int64(), 0xf5e4d3c2b109) self.assertEqual(msg.get_mpint(), 17) self.assertEqual(msg.get_mpint(), 0xf5e4d3c2b109) self.assertEqual(msg.get_mpint(), -0x65e4d3c2b109)
def test_2_decode(self): msg = Message(self.__a) self.assertEqual(msg.get_int(), 23) self.assertEqual(msg.get_int(), 123789456) self.assertEqual(msg.get_text(), "q") self.assertEqual(msg.get_text(), "hello") self.assertEqual(msg.get_text(), "x" * 1000) msg = Message(self.__b) self.assertEqual(msg.get_boolean(), True) self.assertEqual(msg.get_boolean(), False) self.assertEqual(msg.get_byte(), byte_chr(0xf3)) self.assertEqual(msg.get_bytes(2), zero_byte + byte_chr(0x3f)) self.assertEqual(msg.get_list(), ["huey", "dewey", "louie"]) msg = Message(self.__c) self.assertEqual(msg.get_int64(), 5) self.assertEqual(msg.get_int64(), 0xf5e4d3c2b109) self.assertEqual(msg.get_mpint(), 17) self.assertEqual(msg.get_mpint(), 0xf5e4d3c2b109) self.assertEqual(msg.get_mpint(), -0x65e4d3c2b109)
def test_2_decode(self): msg = Message(self.__a) self.assertEqual(msg.get_int(), 23) self.assertEqual(msg.get_int(), 123789456) self.assertEqual(msg.get_text(), 'q') self.assertEqual(msg.get_text(), 'hello') self.assertEqual(msg.get_text(), 'x' * 1000) msg = Message(self.__b) self.assertEqual(msg.get_boolean(), True) self.assertEqual(msg.get_boolean(), False) self.assertEqual(msg.get_byte(), byte_chr(0xf3)) self.assertEqual(msg.get_bytes(2), zero_byte + byte_chr(0x3f)) self.assertEqual(msg.get_list(), ['huey', 'dewey', 'louie']) msg = Message(self.__c) self.assertEqual(msg.get_int64(), 5) self.assertEqual(msg.get_int64(), 0xf5e4d3c2b109) self.assertEqual(msg.get_mpint(), 17) self.assertEqual(msg.get_mpint(), 0xf5e4d3c2b109) self.assertEqual(msg.get_mpint(), -0x65e4d3c2b109)
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ecdsa-sha2-nistp256': raise SSHException('Invalid key') curvename = msg.get_text() if curvename != 'nistp256': raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() try: numbers = ec.EllipticCurvePublicNumbers.from_encoded_point( ec.SECP256R1(), pointinfo) except ValueError: raise SSHException("Invalid public key") self.verifying_key = numbers.public_key(backend=default_backend()) self.size = 256
def __init__(self, msg=None, data=None, filename=None, password=None): verifying_key = signing_key = None if msg is None and data is not None: msg = Message(data) if msg is not None: if msg.get_text() != "ssh-ed25519": raise SSHException("Invalid key") verifying_key = nacl.signing.VerifyKey(msg.get_binary()) elif filename is not None: with open(filename, "r") as f: data = self._read_private_key("OPENSSH", f) signing_key = self._parse_signing_key_data(data, password) if signing_key is None and verifying_key is None: raise ValueError("need a key") self._signing_key = signing_key self._verifying_key = verifying_key
def __init__(self, msg=None, data=None, filename=None, password=None, key=None, file_obj=None): self.key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if key is not None: self.key = key else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ssh-rsa': raise SSHException('Invalid key') self.key = rsa.RSAPublicNumbers( e=msg.get_mpint(), n=msg.get_mpint() ).public_key(default_backend())
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.n = None self.e = None self.d = None self.p = None self.q = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.e, self.n = vals else: if msg is None: raise SSHException("Key object may not be empty") if msg.get_text() != "ssh-rsa": raise SSHException("Invalid key") self.e = msg.get_mpint() self.n = msg.get_mpint() self.size = util.bit_length(self.n)
def _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string("none") m.add_boolean(False) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self._log( DEBUG, "Auth request (type={}) service={}, username={}".format( method, service, username ), ) if service != "ssh-connection": self._disconnect_service_not_available() return if (self.auth_username is not None) and ( self.auth_username != username ): self._log( WARNING, "Auth rejected because the client attempted to change username in mid-flight", # noqa ) self._disconnect_no_more_auth() return self.auth_username = username # check if GSS-API authentication is enabled gss_auth = self.transport.server_object.enable_auth_gssapi() if method == "none": result = self.transport.server_object.check_auth_none(username) elif method == "password": changereq = m.get_boolean() password = m.get_binary() try: password = password.decode("UTF-8") except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing # passwords, but collect the list of valid auth types from # the callback anyway self._log(DEBUG, "Auth request to change passwords (rejected)") newpassword = m.get_binary() try: newpassword = newpassword.decode("UTF-8", "replace") except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password( username, password ) elif method == "publickey": sig_attached = m.get_boolean() keytype = m.get_text() keyblob = m.get_binary() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException as e: self._log(INFO, "Auth rejected: public key: {}".format(str(e))) key = None except Exception as e: msg = ( "Auth rejected: unsupported or mangled public key ({}: {})" ) # noqa self._log(INFO, msg.format(e.__class__.__name__, e)) key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey( username, key ) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(cMSG_USERAUTH_PK_OK) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self._log(INFO, "Auth rejected: invalid signature") result = AUTH_FAILED elif method == "keyboard-interactive": submethods = m.get_string() result = self.transport.server_object.check_auth_interactive( username, submethods ) if isinstance(result, InteractiveQuery): # make interactive query instead of response self._interactive_query(result) return elif method == "gssapi-with-mic" and gss_auth: sshgss = GSSAuth(method) # Read the number of OID mechanisms supported by the client. # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's # the only OID we support. mechs = m.get_int() # We can't accept more than one OID, so if the SSH client sends # more than one, disconnect. if mechs > 1: self._log( INFO, "Disconnect: Received more than one GSS-API OID mechanism", ) self._disconnect_no_more_auth() desired_mech = m.get_string() mech_ok = sshgss.ssh_check_mech(desired_mech) # if we don't support the mechanism, disconnect. if not mech_ok: self._log( INFO, "Disconnect: Received an invalid GSS-API OID mechanism", ) self._disconnect_no_more_auth() # send the Kerberos V5 GSSAPI OID to the client supported_mech = sshgss.ssh_gss_oids("server") # RFC 4462 says we are not required to implement GSS-API error # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE) m.add_bytes(supported_mech) self.transport.auth_handler = GssapiWithMicAuthHandler( self, sshgss ) self.transport._expected_packet = ( MSG_USERAUTH_GSSAPI_TOKEN, MSG_USERAUTH_REQUEST, MSG_SERVICE_REQUEST, ) self.transport._send_message(m) return elif method == "gssapi-keyex" and gss_auth: mic_token = m.get_string() sshgss = self.transport.kexgss_ctxt if sshgss is None: # If there is no valid context, we reject the authentication result = AUTH_FAILED self._send_auth_result(username, method, result) try: sshgss.ssh_check_mic( mic_token, self.transport.session_id, self.auth_username ) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_keyex( username, result ) else: result = self.transport.server_object.check_auth_none(username) # okay, send result self._send_auth_result(username, method, result)
def __init__(self, msg=None, data=None, privkey_filename=None, cert_filename=None, password=None, privkey_file_obj=None, cert_file_obj=None): self.nonce = None self.key = None self.serial = None self.type = None self.key_id = None self.valid_principals = None self.valid_after = None self.valid_before = None self.critical_options = None self.extensions = None self.reserved = None self.signature_key = None self.signature = None self.d = None self.p = None self.q = None if cert_filename is not None: msg = self._load_cert_from_file(cert_filename) elif cert_file_obj is not None: msg = self._load_cert(cert_file_obj) elif cert_filename is None and cert_file_obj is None and data is None: raise SSHException('Either a data object or a certificate file must be given') if privkey_file_obj is not None: self._from_private_key(privkey_file_obj, password) elif privkey_filename is not None: self._from_private_key_file(privkey_filename, password) if (msg is None) and (data is not None): msg = Message(data) if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != '*****@*****.**': raise SSHException('Invalid key') self.nonce = msg.get_string() e = msg.get_mpint() n = msg.get_mpint() # Key might've been set by a private key file. If not, set it from the # cert if self.key is None: self.key = rsa.RSAPublicNumbers(e=e, n=n).public_key( default_backend()) self.serial = msg.get_int64() self.type = msg.get_int() self.key_id = msg.get_string() self.valid_principals = msg.get_string() self.valid_after = msg.get_int64() self.valid_before = msg.get_int64() self.critical_options = msg.get_string() self.extensions = msg.get_string() self.reserved = msg.get_string() self.signature_key = msg.get_string() self.signature = msg.get_string()
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=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 listdir_iter(self, path='.', read_aheads=50): """ Generator version of `.listdir_attr`. See the API docs for `.listdir_attr` for overall details. This function adds one more kwarg on top of `.listdir_attr`: ``read_aheads``, an integer controlling how many ``SSH_FXP_READDIR`` requests are made to the server. The default of 50 should suffice for most file listings as each request/response cycle may contain multiple files (dependant on server implementation.) .. versionadded:: 1.15 """ path = self._adjust_cwd(path) self._log(DEBUG, 'listdir(%r)' % path) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError('Expected handle') handle = msg.get_string() nums = list() while True: try: # Send out a bunch of readdir requests so that we can read the # responses later on Section 6.7 of the SSH file transfer RFC # explains this # http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt for i in range(read_aheads): num = self._async_request(type(None), CMD_READDIR, handle) nums.append(num) # For each of our sent requests # Read and parse the corresponding packets # If we're at the end of our queued requests, then fire off # some more requests # Exit the loop when we've reached the end of the directory # handle for num in nums: t, pkt_data = self._read_packet() msg = Message(pkt_data) new_num = msg.get_int() if num == new_num: if t == CMD_STATUS: self._convert_status(msg) count = msg.get_int() for i in range(count): filename = msg.get_text() longname = msg.get_text() attr = SFTPAttributes._from_msg( msg, filename, longname) if (filename != '.') and (filename != '..'): yield attr # If we've hit the end of our queued requests, reset nums. nums = list() except EOFError: self._request(CMD_CLOSE, handle) return
def _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string('none') m.add_boolean(False) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) if service != 'ssh-connection': self._disconnect_service_not_available() return if (self.auth_username is not None) and (self.auth_username != username): self.transport._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight') self._disconnect_no_more_auth() return self.auth_username = username # check if GSS-API authentication is enabled gss_auth = self.transport.server_object.enable_auth_gssapi() if method == 'none': result = self.transport.server_object.check_auth_none(username) elif method == 'password': changereq = m.get_boolean() password = m.get_binary() try: password = password.decode('UTF-8') except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing passwords, but collect # the list of valid auth types from the callback anyway self.transport._log(DEBUG, 'Auth request to change passwords (rejected)') newpassword = m.get_binary() try: newpassword = newpassword.decode('UTF-8', 'replace') except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password(username, password) elif method == 'publickey': sig_attached = m.get_boolean() keytype = m.get_text() keyblob = m.get_binary() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException as e: self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e)) key = None except: self.transport._log(INFO, 'Auth rejected: unsupported or mangled public key') key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey(username, key) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(cMSG_USERAUTH_PK_OK) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self.transport._log(INFO, 'Auth rejected: invalid signature') result = AUTH_FAILED elif method == 'keyboard-interactive': lang = m.get_string() submethods = m.get_string() result = self.transport.server_object.check_auth_interactive(username, submethods) if isinstance(result, InteractiveQuery): # make interactive query instead of response self._interactive_query(result) return elif method == "gssapi-with-mic" and gss_auth: sshgss = GSSAuth(method) # Read the number of OID mechanisms supported by the client. # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's # the only OID we support. mechs = m.get_int() # We can't accept more than one OID, so if the SSH client sends # more than one, disconnect. if mechs > 1: self.transport._log(INFO, 'Disconnect: Received more than one GSS-API OID mechanism') self._disconnect_no_more_auth() desired_mech = m.get_string() mech_ok = sshgss.ssh_check_mech(desired_mech) # if we don't support the mechanism, disconnect. if not mech_ok: self.transport._log(INFO, 'Disconnect: Received an invalid GSS-API OID mechanism') self._disconnect_no_more_auth() # send the Kerberos V5 GSSAPI OID to the client supported_mech = sshgss.ssh_gss_oids("server") # RFC 4462 says we are not required to implement GSS-API error # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt while True: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE) m.add_bytes(supported_mech) self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: client_token = m.get_string() # use the client token as input to establish a secure # context. try: token = sshgss.ssh_accept_sec_context(self.gss_host, client_token, username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise if token is not None: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(token) self.transport._send_message(m) else: raise SSHException("Client asked to handle paket %s" %MSG_NAMES[ptype]) # check MIC ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_MIC: break mic_token = m.get_string() try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise # TODO: Implement client credential saving. # The OpenSSH server is able to create a TGT with the delegated # client credentials, but this is not supported by GSS-API. result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_with_mic(username, result) elif method == "gssapi-keyex" and gss_auth: mic_token = m.get_string() sshgss = self.transport.kexgss_ctxt if sshgss is None: # If there is no valid context, we reject the authentication result = AUTH_FAILED self._send_auth_result(username, method, result) try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, self.auth_username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_keyex(username, result) else: result = self.transport.server_object.check_auth_none(username) # okay, send result self._send_auth_result(username, method, result)
def _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string('none') m.add_boolean(0) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) if service != 'ssh-connection': self._disconnect_service_not_available() return if (self.auth_username is not None) and (self.auth_username != username): self.transport._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight') self._disconnect_no_more_auth() return self.auth_username = username if method == 'none': result = self.transport.server_object.check_auth_none(username) elif method == 'password': changereq = m.get_boolean() password = m.get_binary() try: password = password.decode('UTF-8') except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing passwords, but collect # the list of valid auth types from the callback anyway self.transport._log(DEBUG, 'Auth request to change passwords (rejected)') newpassword = m.get_binary() try: newpassword = newpassword.decode('UTF-8', 'replace') except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password(username, password) elif method == 'publickey': sig_attached = m.get_boolean() keytype = m.get_text() keyblob = m.get_binary() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException: e = sys.exc_info()[1] self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e)) key = None except: self.transport._log(INFO, 'Auth rejected: unsupported or mangled public key') key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey(username, key) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(cMSG_USERAUTH_PK_OK) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self.transport._log(INFO, 'Auth rejected: invalid signature') result = AUTH_FAILED elif method == 'keyboard-interactive': lang = m.get_string() submethods = m.get_string() result = self.transport.server_object.check_auth_interactive(username, submethods) if isinstance(result, InteractiveQuery): # make interactive query instead of response self._interactive_query(result) return else: result = self.transport.server_object.check_auth_none(username) # okay, send result self._send_auth_result(username, method, result)
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 _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string('none') m.add_boolean(False) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self.transport._log( DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) if service != 'ssh-connection': self._disconnect_service_not_available() return if (self.auth_username is not None) and (self.auth_username != username): self.transport._log( WARNING, 'Auth rejected because the client attempted to change username in mid-flight' ) self._disconnect_no_more_auth() return self.auth_username = username # check if GSS-API authentication is enabled gss_auth = self.transport.server_object.enable_auth_gssapi() if method == 'none': result = self.transport.server_object.check_auth_none(username) elif method == 'password': changereq = m.get_boolean() password = m.get_binary() try: password = password.decode('UTF-8') except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing passwords, but collect # the list of valid auth types from the callback anyway self.transport._log( DEBUG, 'Auth request to change passwords (rejected)') newpassword = m.get_binary() try: newpassword = newpassword.decode('UTF-8', 'replace') except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password( username, password) elif method == 'publickey': sig_attached = m.get_boolean() keytype = m.get_text() keyblob = m.get_binary() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException as e: self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e)) key = None except: self.transport._log( INFO, 'Auth rejected: unsupported or mangled public key') key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey( username, key) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(cMSG_USERAUTH_PK_OK) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self.transport._log(INFO, 'Auth rejected: invalid signature') result = AUTH_FAILED elif method == 'keyboard-interactive': lang = m.get_string() submethods = m.get_string() result = self.transport.server_object.check_auth_interactive( username, submethods) if isinstance(result, InteractiveQuery): # make interactive query instead of response self._interactive_query(result) return elif method == "gssapi-with-mic" and gss_auth: sshgss = GSSAuth(method) # Read the number of OID mechanisms supported by the client. # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's # the only OID we support. mechs = m.get_int() # We can't accept more than one OID, so if the SSH client sends # more than one, disconnect. if mechs > 1: self.transport._log( INFO, 'Disconnect: Received more than one GSS-API OID mechanism') self._disconnect_no_more_auth() desired_mech = m.get_string() mech_ok = sshgss.ssh_check_mech(desired_mech) # if we don't support the mechanism, disconnect. if not mech_ok: self.transport._log( INFO, 'Disconnect: Received an invalid GSS-API OID mechanism') self._disconnect_no_more_auth() # send the Kerberos V5 GSSAPI OID to the client supported_mech = sshgss.ssh_gss_oids("server") # RFC 4462 says we are not required to implement GSS-API error # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt while True: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE) m.add_bytes(supported_mech) self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: client_token = m.get_string() # use the client token as input to establish a secure # context. try: token = sshgss.ssh_accept_sec_context( self.gss_host, client_token, username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise if token is not None: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(token) self.transport._send_message(m) else: result = AUTH_FAILED self._send_auth_result(username, method, result) return # check MIC ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_MIC: break mic_token = m.get_string() try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise # TODO: Implement client credential saving. # The OpenSSH server is able to create a TGT with the delegated # client credentials, but this is not supported by GSS-API. result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_with_mic( username, result) elif method == "gssapi-keyex" and gss_auth: mic_token = m.get_string() sshgss = self.transport.kexgss_ctxt if sshgss is None: # If there is no valid context, we reject the authentication result = AUTH_FAILED self._send_auth_result(username, method, result) try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, self.auth_username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_keyex( username, result) else: result = self.transport.server_object.check_auth_none(username) # okay, send result self._send_auth_result(username, method, result)
def _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string("none") m.add_boolean(False) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self._log( DEBUG, "Auth request (type={}) service={}, username={}".format( method, service, username), ) if service != "ssh-connection": self._disconnect_service_not_available() return if (self.auth_username is not None) and (self.auth_username != username): self._log( WARNING, "Auth rejected because the client attempted to change username in mid-flight", # noqa ) self._disconnect_no_more_auth() return self.auth_username = username # check if GSS-API authentication is enabled gss_auth = self.transport.server_object.enable_auth_gssapi() if method == "none": result = self.transport.server_object.check_auth_none(username) elif method == "password": changereq = m.get_boolean() password = m.get_binary() try: password = password.decode("UTF-8") except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing # passwords, but collect the list of valid auth types from # the callback anyway self._log(DEBUG, "Auth request to change passwords (rejected)") newpassword = m.get_binary() try: newpassword = newpassword.decode("UTF-8", "replace") except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password( username, password) elif method == "publickey": sig_attached = m.get_boolean() keytype = m.get_text() keyblob = m.get_binary() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException as e: self._log(INFO, "Auth rejected: public key: {}".format(str(e))) key = None except Exception as e: msg = ( "Auth rejected: unsupported or mangled public key ({}: {})" ) # noqa self._log(INFO, msg.format(e.__class__.__name__, e)) key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey( username, key) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(cMSG_USERAUTH_PK_OK) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self._log(INFO, "Auth rejected: invalid signature") result = AUTH_FAILED elif method == "keyboard-interactive": submethods = m.get_string() result = self.transport.server_object.check_auth_interactive( username, submethods) if isinstance(result, InteractiveQuery): # make interactive query instead of response self._interactive_query(result) return elif method == "gssapi-with-mic" and gss_auth: sshgss = GSSAuth(method) # Read the number of OID mechanisms supported by the client. # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's # the only OID we support. mechs = m.get_int() # We can't accept more than one OID, so if the SSH client sends # more than one, disconnect. if mechs > 1: self._log( INFO, "Disconnect: Received more than one GSS-API OID mechanism", ) self._disconnect_no_more_auth() desired_mech = m.get_string() mech_ok = sshgss.ssh_check_mech(desired_mech) # if we don't support the mechanism, disconnect. if not mech_ok: self._log( INFO, "Disconnect: Received an invalid GSS-API OID mechanism", ) self._disconnect_no_more_auth() # send the Kerberos V5 GSSAPI OID to the client supported_mech = sshgss.ssh_gss_oids("server") # RFC 4462 says we are not required to implement GSS-API error # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE) m.add_bytes(supported_mech) self.transport.auth_handler = GssapiWithMicAuthHandler( self, sshgss) self.transport._expected_packet = ( MSG_USERAUTH_GSSAPI_TOKEN, MSG_USERAUTH_REQUEST, MSG_SERVICE_REQUEST, ) self.transport._send_message(m) return elif method == "gssapi-keyex" and gss_auth: mic_token = m.get_string() sshgss = self.transport.kexgss_ctxt if sshgss is None: # If there is no valid context, we reject the authentication result = AUTH_FAILED self._send_auth_result(username, method, result) try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, self.auth_username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_keyex( username, result) else: result = self.transport.server_object.check_auth_none(username) # okay, send result self._send_auth_result(username, method, result)
def _from_message(self, msg): """ Internal fuction to parse the contents of a certificate. There are several pieces of information to be parsed specificed by PROTOCOL.certkeys in the OpenSSH source code. string cert_key_type (i.e. "*****@*****.**") string nonce various public key data : Fields and types dependent on key type uint64 serial uint32 type string key_id string principals : Message containing a list of principals uint64 valid_after uint64 valid_before string critical_options : Message containing various critical options string extensions : Mesage containing optional extensions string reserved string signature_key string signature :param .Message msg: An instance of a paramiko.message.Message containing the certificate data. """ cert_key_type = msg.get_text() self.nonce = msg.get_binary() # Use the _key_parsers dictionary to look up the appropriate key # parser to read the key. Then dispatch to that function to do the # actual reading of the key data. key_parser = self._key_parsers.get(cert_key_type) if key_parser is None: err = "Unknown cert key type {}".format(cert_key_type) raise SSHException(err) self.key = key_parser(msg) self.serial = msg.get_int64() self.type = msg.get_int() self.key_id = msg.get_text() principals_msg = Message(msg.get_binary()) while principals_msg.get_remainder(): self.principals.append(principals_msg.get_text()) self.valid_after = msg.get_int64() self.valid_before = msg.get_int64() copts_msg = Message(msg.get_binary()) while copts_msg.get_remainder(): opt = copts_msg.get_text() val_msg = Message(copts_msg.get_binary()) val = val_msg.get_text() self.set_critical_option(opt, val) ext_msg = Message(msg.get_binary()) while ext_msg.get_remainder(): ext = ext_msg.get_text() val = ext_msg.get_binary() self.extensions[ext] = val self.reserved = msg.get_string() self.signature_key = msg.get_binary() self._body_bytes = msg.get_so_far() self.signature = msg.get_binary() self._bytes = msg.get_so_far()
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 listdir_iter(self, path='.', read_aheads=50): """ Generator version of `.listdir_attr`. See the API docs for `.listdir_attr` for overall details. This function adds one more kwarg on top of `.listdir_attr`: ``read_aheads``, an integer controlling how many ``SSH_FXP_READDIR`` requests are made to the server. The default of 50 should suffice for most file listings as each request/response cycle may contain multiple files (dependent on server implementation.) .. versionadded:: 1.15 """ path = self._adjust_cwd(path) self._log(DEBUG, 'listdir(%r)' % path) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError('Expected handle') handle = msg.get_string() nums = list() while True: try: # Send out a bunch of readdir requests so that we can read the # responses later on Section 6.7 of the SSH file transfer RFC # explains this # http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt for i in range(read_aheads): num = self._async_request(type(None), CMD_READDIR, handle) nums.append(num) # For each of our sent requests # Read and parse the corresponding packets # If we're at the end of our queued requests, then fire off # some more requests # Exit the loop when we've reached the end of the directory # handle for num in nums: t, pkt_data = self._read_packet() msg = Message(pkt_data) new_num = msg.get_int() if num == new_num: if t == CMD_STATUS: self._convert_status(msg) count = msg.get_int() for i in range(count): filename = msg.get_text() longname = msg.get_text() attr = SFTPAttributes._from_msg( msg, filename, longname) if (filename != '.') and (filename != '..'): yield attr # If we've hit the end of our queued requests, reset nums. nums = list() except EOFError: self._request(CMD_CLOSE, handle) return
def _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string('none') m.add_boolean(False) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) if service != 'ssh-connection': self._disconnect_service_not_available() return if (self.auth_username is not None) and (self.auth_username != username): self.transport._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight') self._disconnect_no_more_auth() return self.auth_username = username if method == 'none': result = self.transport.server_object.check_auth_none(username) elif method == 'password': changereq = m.get_boolean() password = m.get_binary() try: password = password.decode('UTF-8') except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing passwords, but collect # the list of valid auth types from the callback anyway self.transport._log(DEBUG, 'Auth request to change passwords (rejected)') newpassword = m.get_binary() try: newpassword = newpassword.decode('UTF-8', 'replace') except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password(username, password) elif method == 'publickey': sig_attached = m.get_boolean() keytype = m.get_text() keyblob = m.get_binary() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException as e: self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e)) key = None except: self.transport._log(INFO, 'Auth rejected: unsupported or mangled public key') key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey(username, key) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(cMSG_USERAUTH_PK_OK) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self.transport._log(INFO, 'Auth rejected: invalid signature') result = AUTH_FAILED elif method == 'keyboard-interactive': lang = m.get_string() submethods = m.get_string() result = self.transport.server_object.check_auth_interactive(username, submethods) if isinstance(result, InteractiveQuery): # make interactive query instead of response self._interactive_query(result) return else: result = self.transport.server_object.check_auth_none(username) # okay, send result self._send_auth_result(username, method, result)