def msg_kexdh_init(self, packet): # mpint e msg, self.client_exchange_value = ssh_packet.unpack_payload( KEXDH_INIT_PAYLOAD, packet) # XXX make sure e is a valid number # This is y. self.server_random_value = ssh_random.get_random_number(512) # p is large safe prime (DH_PRIME) # g is a generator for a subgroup of GF(p) (DH_GENERATOR) # compute f=g**y mod p self.server_exchange_value = pow(DH_GENERATOR, self.server_random_value, DH_PRIME) self.shared_secret = pow(self.client_exchange_value, self.server_random_value, DH_PRIME) K_S = self.transport.server_key.get_public_key_blob() payload_inputs = (self.c2s_version_string, self.s2c_version_string, self.c2s_kexinit_packet, self.s2c_kexinit_packet, K_S, self.client_exchange_value, self.server_exchange_value, self.shared_secret) H = ssh_packet.pack_payload(KEXDH_HASH_PAYLOAD, payload_inputs) self.exchange_hash = hashlib.sha1(H).digest() if self.session_id is None: # The session id is the first exchange hash. self.session_id = self.exchange_hash H_sig = self.transport.server_key.sign(self.exchange_hash) packet = ssh_packet.pack_payload( KEXDH_REPLY_PAYLOAD, (SSH_MSG_KEXDH_REPLY, K_S, self.server_exchange_value, H_sig)) self.transport.send_packet(packet)
def send_channel_request(self, request_type, payload, data, want_reply=1, default_reply_handler=True): """send_channel_request(self, request_type, payload, data, want_reply=1, default_reply_handler=True) -> None This is a generic mechanism for sending a channel request packet. <request_type>: Request type to send. <payload>: The ssh_packet format definition. <data>: The data to go with the payload. <want_reply>: The want_reply flag. <default_reply_handler>: If true and want_reply is True, then the default reply handler will be used. The default reply handler is pretty simple. Will just return if CHANNEL_REQUEST_SUCCESS is received. Will raise Channel_Request_Failure if FAILURE is received. """ # XXX: There is a problem with the spec. It does not indicate how to # match requests with responses. IIRC, they talked about it on # the mailing list and updated the spec. Need to investigate if # they have clarified the spec on how to handle this. # XXX: Or maybe concurrent channel requests on the same channel are # not allowed? if self.remote_channel.closed: raise Channel_Closed_Error pkt = packet.pack_payload( SSH_MSG_CHANNEL_REQUEST_PAYLOAD, (SSH_MSG_CHANNEL_REQUEST, self.remote_channel.channel_id, request_type, int(want_reply)), ) pkt_data = packet.pack_payload(payload, data) self.transport.send_packet(pkt + pkt_data) if want_reply and default_reply_handler: # Wait for response. assert len(self.channel_request_cv) == 0, "Concurrent channel requests not supported!" if not self.channel_request_cv.wait(): raise Channel_Request_Failure
def _try_auth(self, packet, loaded_key, username, service_name): self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: Got OK for this key type.') msg, key_algorithm_name, key_blob = unpack_payload( PAYLOAD_MSG_USERAUTH_PK_OK, packet) assert (key_algorithm_name == loaded_key.name) # XXX: Check key_blob, too? # Send the actual request. # Compute signature. session_id = self.transport.key_exchange.session_id sig_data = pack_payload( PAYLOAD_USERAUTH_REQUEST_PK_SIGNATURE, (session_id, SSH_MSG_USERAUTH_REQUEST, username, service_name, 'publickey', 1, loaded_key.name, loaded_key.get_public_key_blob())) signature = loaded_key.sign(sig_data) self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: Sending userauth request.') packet = pack_payload( PAYLOAD_MSG_USERAUTH_REQUEST_PK, (SSH_MSG_USERAUTH_REQUEST, username, service_name, 'publickey', 1, loaded_key.name, loaded_key.get_public_key_blob(), signature)) self.transport.send_packet(packet) message_type, packet = self.transport.receive_message( (SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE)) if message_type == SSH_MSG_USERAUTH_SUCCESS: # Success. return elif message_type == SSH_MSG_USERAUTH_FAILURE: self.msg_userauth_failure(packet) raise Authentication_Error else: # Should never happen. raise ValueError(message_type)
def authenticate(self, username, service_name): password = self.get_password(username) packet = pack_payload(PAYLOAD_MSG_USERAUTH_REQUEST_PASSWORD, (SSH_MSG_USERAUTH_REQUEST, username, service_name, 'password', 0, password)) self.transport.send_packet(packet) # While loop in case we get a CHANGEREQ packet. while 1: try: message_type, packet = self.transport.receive_message( (SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_PASSWD_CHANGEREQ)) except EOFError: # In case of an expired user, an EOFError is raised # Expired accounts are also considered as authentication errors raise Authentication_Error if message_type == SSH_MSG_USERAUTH_SUCCESS: # Success! return elif message_type == SSH_MSG_USERAUTH_FAILURE: self.msg_userauth_failure(packet) # XXX: Could ask for user's password again? # XXX: Should handle partial_success flag for CHANGEREQ response. raise Authentication_Error elif message_type == SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: self.msg_userauth_passwd_changereq(packet, username, service_name) else: # Should never happen. raise ValueError(message_type)
def send_unimplemented(self, sequence_number): self.debug.write(ssh_debug.DEBUG_3, 'send_unimplemented(sequence_number=%i)', (sequence_number, )) self.send_packet( ssh_packet.pack_payload(ssh_packet.PAYLOAD_MSG_UNIMPLEMENTED, (SSH_MSG_UNIMPLEMENTED, sequence_number)))
def authenticate(self, authentication_method, service_name): """authenticate(self, authentication_method, service) -> None Authenticate with the remote side. <authentication_method>: <service_name>: The name of the service that you want to use after authenticating. Typically 'ssh-connection'. """ # Ask the remote side if it is OK to use this authentication service. self.debug.write(debug.DEBUG_3, 'authenticate: sending service request (%s)', (authentication_method.name,)) service_request_packet = ssh_packet.pack_payload(ssh_packet.PAYLOAD_MSG_SERVICE_REQUEST, (transport.SSH_MSG_SERVICE_REQUEST, authentication_method.name)) self.send_packet(service_request_packet) # Server will disconnect if it doesn't like our service request. self.debug.write(debug.DEBUG_3, 'authenticate: waiting for SERVICE_ACCEPT') message_type, packet = self.receive_message((transport.SSH_MSG_SERVICE_ACCEPT,)) msg, accepted_service_name = ssh_packet.unpack_payload(ssh_packet.PAYLOAD_MSG_SERVICE_ACCEPT, packet) self.debug.write(debug.DEBUG_3, 'authenticate: got SERVICE_ACCEPT') if accepted_service_name != authentication_method.name: self.send_disconnect(transport.SSH_DISCONNECT_PROTOCOL_ERROR, 'accepted service does not match requested service "%s"!="%s"' % (authentication_method.name, accepted_service_name)) # This authetnication service is OK, try to authenticate. authentication_method.authenticate(service_name)
def authenticate(self, authentication_method, service_name): """authenticate(self, authentication_method, service) -> None Authenticate with the remote side. <authentication_method>: <service_name>: The name of the service that you want to use after authenticating. Typically 'ssh-connection'. """ # Ask the remote side if it is OK to use this authentication service. self.debug.write(debug.DEBUG_3, "authenticate: sending service request (%s)", (authentication_method.name,)) service_request_packet = ssh_packet.pack_payload( ssh_packet.PAYLOAD_MSG_SERVICE_REQUEST, (transport.SSH_MSG_SERVICE_REQUEST, authentication_method.name) ) self.send_packet(service_request_packet) # Server will disconnect if it doesn't like our service request. self.debug.write(debug.DEBUG_3, "authenticate: waiting for SERVICE_ACCEPT") message_type, packet = self.receive_message((transport.SSH_MSG_SERVICE_ACCEPT,)) msg, accepted_service_name = ssh_packet.unpack_payload(ssh_packet.PAYLOAD_MSG_SERVICE_ACCEPT, packet) self.debug.write(debug.DEBUG_3, "authenticate: got SERVICE_ACCEPT") if accepted_service_name != authentication_method.name: self.send_disconnect( transport.SSH_DISCONNECT_PROTOCOL_ERROR, 'accepted service does not match requested service "%s"!="%s"' % (authentication_method.name, accepted_service_name), ) # This authetnication service is OK, try to authenticate. authentication_method.authenticate(service_name)
def open(self): """open(self) -> None Opens the channel to the remote side. """ assert self.closed self.connection_service.register_channel(self) self.transport.debug.write(debug.DEBUG_2, "sending channel open request channel ID %i", (self.channel_id,)) # Send the open request. additional_data = self.get_additional_open_data() packet_payload = SSH_MSG_CHANNEL_OPEN_PAYLOAD + self.additional_packet_data_types packet_data = ( SSH_MSG_CHANNEL_OPEN, self.name, self.channel_id, self.window_size, self.max_packet_size, ) + additional_data pkt = packet.pack_payload(packet_payload, packet_data) self.transport.send_packet(pkt) success, data = self.channel_open_cv.wait() if success: self.set_additional_open_data(data) else: reason_code, reason_text, language = data raise Channel_Open_Error(self.channel_id, reason_code, reason_text, language)
def _send_kexinit(self): """_send_kexinit(self) -> None Sets self2remote.kexinit_packet. Separate function to help with unittests. """ cookie = random.get_random_data(16) packet = ssh_packet.pack_payload(ssh_packet.PAYLOAD_MSG_KEXINIT, (SSH_MSG_KEXINIT, cookie, [x.name for x in self.self2remote.supported_key_exchanges], [x.name for x in self.self2remote.supported_server_keys], [x.name for x in self.c2s.supported_ciphers], [x.name for x in self.s2c.supported_ciphers], [x.name for x in self.c2s.supported_macs], [x.name for x in self.s2c.supported_macs], [x.name for x in self.c2s.supported_compressions], [x.name for x in self.s2c.supported_compressions], [x.name for x in self.c2s.supported_languages], [x.name for x in self.s2c.supported_languages], self.self2remote.proactive_kex, # first_kex_packet_follows 0 # reserved ) ) self.self2remote.kexinit_packet = packet return packet
def send(self, data): """send(self, data) -> None Send the given data string. """ data_start = 0 while data_start < len(data): if self.remote_channel.closed: raise Channel_Closed_Error while self.remote_channel.window_data_left == 0: # Currently waiting for window update. self.window_data_added_cv.wait() # check again inside loop since if we're closed, the window # might never update if self.remote_channel.closed: raise Channel_Closed_Error # Send what we can. max_size = min(self.remote_channel.window_data_left, self.remote_channel.max_packet_size) data_to_send = data[data_start:data_start + max_size] data_start += max_size pkt = packet.pack_payload( SSH_MSG_CHANNEL_DATA_PAYLOAD, (SSH_MSG_CHANNEL_DATA, self.remote_channel.channel_id, data_to_send)) self.transport.debug.write( debug.DEBUG_3, 'channel %i window lowered by %i to %i', (self.remote_channel.channel_id, len(data_to_send), self.remote_channel.window_data_left)) self.remote_channel.window_data_left -= len(data_to_send) self.transport.send_packet(pkt)
def get_encryption_key(self, letter, required_size): """get_encryption_key(self, letter, required_size) -> key Computes an encryption key with the given letter. <required_size> is the length of the key that you require (in bytes). """ shared_secret = ssh_packet.pack_payload((ssh_packet.MPINT,), (self.shared_secret,)) key = self.get_hash_object( shared_secret, self.exchange_hash, letter, self.session_id).digest() if len(key) > required_size: # Key is too big...return only what is needed. key = key[:required_size] elif len(key) < required_size: # Key is not big enough...compute additional hashes until big enough. # K1 = HASH(K || H || X || session_id) (X is e.g. "A") # K2 = HASH(K || H || K1) # K3 = HASH(K || H || K1 || K2) # ... # key = K1 || K2 || K3 || ... self.transport.debug.write(ssh_debug.DEBUG_2, 'get_encryption_key: computed key is too small len(key)=%i required_size=%i', (len(key), required_size)) key_data = [key] key_data_len = len(key) while key_data_len < required_size: additional_key_data = self.get_hash_object(shared_secret, self.exchange_hash, ''.join(key_data)).digest() key_data.append(additional_key_data) key_data_len += len(additional_key_data) key = ''.join(key_data)[:required_size] else: # Key is just the right length. pass return key
def handle_request(self, request_type, want_reply, type_specific_packet_data): #W ('interactive_session_server: handle_request %r %r %r\n' % (request_type, want_reply, type_specific_packet_data)) if self.request_handlers.has_key(request_type): self.request_handlers[request_type] (self, want_reply, type_specific_packet_data) elif want_reply: packet = ssh_packet.pack_payload(SSH_MSG_CHANNEL_FAILURE_PAYLOAD, (self.remote_channel.channel_id,)) self.transport.send_packet(packet)
def send_extended(self, data, data_type_code): """send_extended(self, data, data_type_code) -> None Send the given data string as extended data with the given data_type_code. """ data_start = 0 while data_start < len(data): if self.remote_channel.closed: raise Channel_Closed_Error while self.remote_channel.window_data_left == 0: # Currently waiting for window update. self.window_data_added_cv.wait() # check again inside loop since if we're closed, the window # might never update if self.remote_channel.closed: raise Channel_Closed_Error # Send what we can. max_size = min(self.remote_channel.window_data_left, self.remote_channel.max_packet_size) data_to_send = data[data_start:data_start + max_size] data_start += max_size pkt = packet.pack_payload( SSH_MSG_CHANNEL_EXTENDED_DATA_PAYLOAD, (SSH_MSG_CHANNEL_EXTENDED_DATA, self.remote_channel.channel_id, data_type_code, data_to_send)) self.remote_channel.window_data_left -= len(data_to_send) self.transport.send_packet(pkt)
def send(self, data): """send(self, data) -> None Send the given data string. """ data_start = 0 while data_start < len(data): if self.remote_channel.closed: raise Channel_Closed_Error while self.remote_channel.window_data_left == 0: # Currently waiting for window update. self.window_data_added_cv.wait() # check again inside loop since if we're closed, the window # might never update if self.remote_channel.closed: raise Channel_Closed_Error # Send what we can. max_size = min(self.remote_channel.window_data_left, self.remote_channel.max_packet_size) data_to_send = data[data_start : data_start + max_size] data_start += max_size pkt = packet.pack_payload( SSH_MSG_CHANNEL_DATA_PAYLOAD, (SSH_MSG_CHANNEL_DATA, self.remote_channel.channel_id, data_to_send) ) self.transport.debug.write( debug.DEBUG_3, "channel %i window lowered by %i to %i", (self.remote_channel.channel_id, len(data_to_send), self.remote_channel.window_data_left), ) self.remote_channel.window_data_left -= len(data_to_send) self.transport.send_packet(pkt)
def open(self): """open(self) -> None Opens the channel to the remote side. """ assert self.closed self.connection_service.register_channel(self) self.transport.debug.write( debug.DEBUG_2, 'sending channel open request channel ID %i', (self.channel_id, )) # Send the open request. additional_data = self.get_additional_open_data() packet_payload = SSH_MSG_CHANNEL_OPEN_PAYLOAD + self.additional_packet_data_types packet_data = (SSH_MSG_CHANNEL_OPEN, self.name, self.channel_id, self.window_size, self.max_packet_size) + additional_data pkt = packet.pack_payload(packet_payload, packet_data) self.transport.send_packet(pkt) success, data = self.channel_open_cv.wait() if success: self.set_additional_open_data(data) else: reason_code, reason_text, language = data raise Channel_Open_Error(self.channel_id, reason_code, reason_text, language)
def send_extended(self, data, data_type_code): """send_extended(self, data, data_type_code) -> None Send the given data string as extended data with the given data_type_code. """ data_start = 0 while data_start < len(data): if self.remote_channel.closed: raise Channel_Closed_Error while self.remote_channel.window_data_left == 0: # Currently waiting for window update. self.window_data_added_cv.wait() # check again inside loop since if we're closed, the window # might never update if self.remote_channel.closed: raise Channel_Closed_Error # Send what we can. max_size = min(self.remote_channel.window_data_left, self.remote_channel.max_packet_size) data_to_send = data[data_start : data_start + max_size] data_start += max_size pkt = packet.pack_payload( SSH_MSG_CHANNEL_EXTENDED_DATA_PAYLOAD, (SSH_MSG_CHANNEL_EXTENDED_DATA, self.remote_channel.channel_id, data_type_code, data_to_send), ) self.remote_channel.window_data_left -= len(data_to_send) self.transport.send_packet(pkt)
def authenticate(self, username, service_name): password = self.get_password(username) packet = pack_payload(PAYLOAD_MSG_USERAUTH_REQUEST_PASSWORD, (SSH_MSG_USERAUTH_REQUEST, username, service_name, 'password', 0, password )) self.transport.send_packet(packet) # While loop in case we get a CHANGEREQ packet. while 1: try: message_type, packet = self.transport.receive_message(( SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_PASSWD_CHANGEREQ)) except EOFError: # In case of an expired user, an EOFError is raised # Expired accounts are also considered as authentication errors raise Authentication_Error if message_type == SSH_MSG_USERAUTH_SUCCESS: # Success! return elif message_type == SSH_MSG_USERAUTH_FAILURE: self.msg_userauth_failure(packet) # XXX: Could ask for user's password again? # XXX: Should handle partial_success flag for CHANGEREQ response. raise Authentication_Error elif message_type == SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: self.msg_userauth_passwd_changereq(packet, username, service_name) else: # Should never happen. raise ValueError(message_type)
def _send_kexinit(self): """_send_kexinit(self) -> None Sets self2remote.kexinit_packet. Separate function to help with unittests. """ cookie = random.get_random_data(16) server_keys = [x.name for x in self.self2remote.supported_server_keys] server_keys.reverse() packet = ssh_packet.pack_payload( ssh_packet.PAYLOAD_MSG_KEXINIT, ( SSH_MSG_KEXINIT, cookie, [x.name for x in self.self2remote.supported_key_exchanges], # [x.name for x in self.self2remote.supported_server_keys], server_keys, [x.name for x in self.c2s.supported_ciphers], [x.name for x in self.s2c.supported_ciphers], [x.name for x in self.c2s.supported_macs], [x.name for x in self.s2c.supported_macs], [x.name for x in self.c2s.supported_compressions], [x.name for x in self.s2c.supported_compressions], [x.name for x in self.c2s.supported_languages], [x.name for x in self.s2c.supported_languages], self.self2remote.proactive_kex, # first_kex_packet_follows 0 # reserved )) self.self2remote.kexinit_packet = packet return packet
def handle_request(self, request_type, want_reply, type_specific_packet_data): # Default is always to fail. Specific channel types override this method. if want_reply: pkt = packet.pack_payload( SSH_MSG_CHANNEL_FAILURE_PAYLOAD, (SSH_MSG_CHANNEL_FAILURE, self.remote_channel.channel_id) ) self.transport.send_packet(pkt)
def get_public_key_blob(self): if self.public_key != (0, 0, 0, 0): p, q, g, y = self.public_key else: p, q, g, y, x = self.private_key return packet.pack_payload(DSS_PUBLIC_KEY_PAYLOAD, ('ssh-dss', p, q, g, y))
def send_unimplemented(self, sequence_number): self.debug.write(ssh_debug.DEBUG_3, 'send_unimplemented(sequence_number=%i)', (sequence_number,)) self.send_packet( ssh_packet.pack_payload(ssh_packet.PAYLOAD_MSG_UNIMPLEMENTED, (SSH_MSG_UNIMPLEMENTED, sequence_number) ) )
def send_window_adjustment(self, bytes_to_add): self.transport.debug.write(debug.DEBUG_2, "sending window adjustment to add %i bytes", (bytes_to_add,)) pkt = packet.pack_payload( SSH_MSG_CHANNEL_WINDOW_ADJUST_PAYLOAD, (SSH_MSG_CHANNEL_WINDOW_ADJUST, self.remote_channel.channel_id, bytes_to_add), ) self.transport.send_packet(pkt) self.window_data_left += bytes_to_add
def handle_request(self, request_type, want_reply, type_specific_packet_data): # Default is always to fail. Specific channel types override this method. if want_reply: pkt = packet.pack_payload(SSH_MSG_CHANNEL_FAILURE_PAYLOAD, ( SSH_MSG_CHANNEL_FAILURE, self.remote_channel.channel_id, )) self.transport.send_packet(pkt)
def authenticate(self, username, service_name): local_username = os.getlogin() for key_storage in self.transport.supported_key_storages: self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: Trying to load keytype "%s" for user "%s".', (key_storage.__class__.__name__, local_username)) loaded_keys = key_storage.load_keys(username=local_username) if loaded_keys: for loaded_key in loaded_keys: # Test this key type. self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: Sending PK test for keytype "%s".', (loaded_key.name, )) packet = pack_payload( PAYLOAD_MSG_USERAUTH_REQUEST_PK_TEST, (SSH_MSG_USERAUTH_REQUEST, username, service_name, 'publickey', 0, loaded_key.name, loaded_key.get_public_key_blob())) self.transport.send_packet(packet) message_type, packet = self.transport.receive_message(( SSH_MSG_USERAUTH_PK_OK, SSH_MSG_USERAUTH_FAILURE, )) if message_type == SSH_MSG_USERAUTH_PK_OK: # This public key is ok to try. try: self._try_auth(packet, loaded_key, username, service_name) except Authentication_Error: # Nope, didn't work. Loop and try next. pass else: # Done! return elif message_type == SSH_MSG_USERAUTH_FAILURE: # Key type not allowed. self.msg_userauth_failure(packet) # Loop through and try next key. else: # Should never happen. raise ValueError(message_type) else: self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: No more key storage types left.') else: self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: No keys found of this key storage type.') else: self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: No more storage key types left to try.') raise Authentication_Error
def sign(self, message): n, e, d, p, q = self.private_key rsa_obj = RSA.construct((n, e, d, p, q)) modulus_n_length_in_octets = rsa_obj.size() / 8 encoded_message = self.emsa_pkcs1_v1_5_encode( message, modulus_n_length_in_octets) signature = rsa_obj.sign(encoded_message, '')[0] # Returns tuple of 1 element. signature = number.long_to_bytes(signature) return packet.pack_payload(RSA_SIG_PAYLOAD, ('ssh-rsa', signature))
def sign(self, message): n, e, d, p, q = self.private_key rsa_obj = RSA.construct((n, e, d, p, q)) modulus_n_length_in_octets = rsa_obj.size() / 8 encoded_message = self.emsa_pkcs1_v1_5_encode(message, modulus_n_length_in_octets) signature = rsa_obj.sign(encoded_message, '')[0] # Returns tuple of 1 element. signature = number.long_to_bytes(signature) return packet.pack_payload(RSA_SIG_PAYLOAD, ('ssh-rsa', signature))
def send_window_adjustment(self, bytes_to_add): self.transport.debug.write( debug.DEBUG_2, 'sending window adjustment to add %i bytes', (bytes_to_add, )) pkt = packet.pack_payload( SSH_MSG_CHANNEL_WINDOW_ADJUST_PAYLOAD, (SSH_MSG_CHANNEL_WINDOW_ADJUST, self.remote_channel.channel_id, bytes_to_add)) self.transport.send_packet(pkt) self.window_data_left += bytes_to_add
def _try_auth(self, packet, loaded_key, username, service_name): self.transport.debug.write(ssh_debug.DEBUG_1, 'Publickey Auth: Got OK for this key type.') msg, key_algorithm_name, key_blob = unpack_payload(PAYLOAD_MSG_USERAUTH_PK_OK, packet) assert (key_algorithm_name == loaded_key.name) # XXX: Check key_blob, too? # Send the actual request. # Compute signature. session_id = self.transport.key_exchange.session_id sig_data = pack_payload(PAYLOAD_USERAUTH_REQUEST_PK_SIGNATURE, (session_id, SSH_MSG_USERAUTH_REQUEST, username, service_name, 'publickey', 1, loaded_key.name, loaded_key.get_public_key_blob() )) signature = loaded_key.sign(sig_data) self.transport.debug.write(ssh_debug.DEBUG_1, 'Publickey Auth: Sending userauth request.') packet = pack_payload(PAYLOAD_MSG_USERAUTH_REQUEST_PK, (SSH_MSG_USERAUTH_REQUEST, username, service_name, 'publickey', 1, loaded_key.name, loaded_key.get_public_key_blob(), signature)) self.transport.send_packet(packet) message_type, packet = self.transport.receive_message((SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE)) if message_type == SSH_MSG_USERAUTH_SUCCESS: # Success. return elif message_type == SSH_MSG_USERAUTH_FAILURE: self.msg_userauth_failure(packet) raise Authentication_Error else: # Should never happen. raise ValueError(message_type)
def sign(self, message): p, q, g, y, x = self.private_key dsa_obj = DSA.construct( (y, g, p, q, x) ) message_hash = hashlib.sha1(message).digest() # Get a random number that is greater than 2 and less than q. random_number = random.get_random_number_from_range(2, q) random_data = number.long_to_bytes(random_number) r, s = dsa_obj.sign(message_hash, random_data) signature = number.long_to_bytes(r, 20) + number.long_to_bytes(s, 20) return packet.pack_payload(DSS_SIG_PAYLOAD, ('ssh-dss', signature))
def sign(self, message): p, q, g, y, x = self.private_key dsa_obj = DSA.construct((y, g, p, q, x)) message_hash = hashlib.sha1(message).digest() # Get a random number that is greater than 2 and less than q. random_number = random.get_random_number_from_range(2, q) random_data = number.long_to_bytes(random_number) r, s = dsa_obj.sign(message_hash, random_data) signature = number.long_to_bytes(r, 20) + number.long_to_bytes(s, 20) return packet.pack_payload(DSS_SIG_PAYLOAD, ('ssh-dss', signature))
def get_initial_client_kex_packet(self): self.transport.debug.write(ssh_debug.DEBUG_3, 'get_initial_kex_packet()') # Send initial key. # This is x. self.client_random_value = ssh_random.get_random_number(512) # p is large safe prime (DH_PRIME) # g is a generator for a subgroup of GF(p) (DH_GENERATOR) # compute e=g**x mod p self.client_exchange_value = pow(DH_GENERATOR, self.client_random_value, DH_PRIME) return ssh_packet.pack_payload(KEXDH_INIT_PAYLOAD, (SSH_MSG_KEXDH_INIT, self.client_exchange_value) )
def send_channel_request(self, request_type, payload, data, want_reply=1, default_reply_handler=True): """send_channel_request(self, request_type, payload, data, want_reply=1, default_reply_handler=True) -> None This is a generic mechanism for sending a channel request packet. <request_type>: Request type to send. <payload>: The ssh_packet format definition. <data>: The data to go with the payload. <want_reply>: The want_reply flag. <default_reply_handler>: If true and want_reply is True, then the default reply handler will be used. The default reply handler is pretty simple. Will just return if CHANNEL_REQUEST_SUCCESS is received. Will raise Channel_Request_Failure if FAILURE is received. """ # XXX: There is a problem with the spec. It does not indicate how to # match requests with responses. IIRC, they talked about it on # the mailing list and updated the spec. Need to investigate if # they have clarified the spec on how to handle this. # XXX: Or maybe concurrent channel requests on the same channel are # not allowed? if self.remote_channel.closed: raise Channel_Closed_Error pkt = packet.pack_payload( SSH_MSG_CHANNEL_REQUEST_PAYLOAD, (SSH_MSG_CHANNEL_REQUEST, self.remote_channel.channel_id, request_type, int(want_reply))) pkt_data = packet.pack_payload(payload, data) self.transport.send_packet(pkt + pkt_data) if want_reply and default_reply_handler: # Wait for response. assert len(self.channel_request_cv ) == 0, 'Concurrent channel requests not supported!' if not self.channel_request_cv.wait(): raise Channel_Request_Failure
def send_disconnect(self, reason_code, description): """send_disconnect(self, reason_code, description) -> None """ self.debug.write(ssh_debug.DEBUG_3, 'send_disconnect(reason_code=%r, description=%r)', (reason_code, description)) # Language tag currently set to the empty string. language_tag = '' self.send_packet( ssh_packet.pack_payload( ssh_packet.PAYLOAD_MSG_DISCONNECT, (SSH_MSG_DISCONNECT, reason_code, description, language_tag))) self.disconnect() raise SSH_Protocol_Error(reason_code, description)
def authenticate(self, session_id, serv, user, alg, blob, sig): # build the signable data to_sign = pack_payload(PAYLOAD_USERAUTH_REQUEST_PK_SIGNATURE, (session_id, SSH_MSG_USERAUTH_REQUEST, user, serv, 'publickey', 1, alg, blob)) try: keys = self.keys[user][serv] for key in keys: if key.name == alg and key.get_public_key_blob( ) == blob and key.verify(to_sign, sig): return True return False except KeyError: return False
def get_initial_client_kex_packet(self): self.transport.debug.write(ssh_debug.DEBUG_3, 'get_initial_kex_packet()') # Send initial key. # This is x. self.client_random_value = ssh_random.get_random_number(512) # p is large safe prime (DH_PRIME) # g is a generator for a subgroup of GF(p) (DH_GENERATOR) # compute e=g**x mod p self.client_exchange_value = pow(DH_GENERATOR, self.client_random_value, DH_PRIME) return ssh_packet.pack_payload( KEXDH_INIT_PAYLOAD, (SSH_MSG_KEXDH_INIT, self.client_exchange_value))
def msg_kexdh_reply(self, packet): # string server public host key and certificates (K_S) # mpint f # string signature of H msg, public_host_key, server_exchange_value, signature_of_h = ssh_packet.unpack_payload( KEXDH_REPLY_PAYLOAD, packet) # Create a SSH_Public_Private_Key instance from the packed string. self.server_public_host_key = parse_public_key(public_host_key) # Verify that this is a known host key. self.transport.verify_public_host_key(self.server_public_host_key) # Make sure f is a valid number if server_exchange_value <= 1 or server_exchange_value >= DH_PRIME - 1: self.transport.send_disconnect( constants.SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 'Key exchange did not succeed: Server exchange value not valid.' ) # K = f**x mod p self.shared_secret = pow(server_exchange_value, self.client_random_value, DH_PRIME) # Verify hash. # string V_C, the client's version string (CR and NL excluded) # string V_S, the server's version string (CR and NL excluded) # string I_C, the payload of the client's SSH_MSG_KEXINIT # string I_S, the payload of the server's SSH_MSG_KEXINIT # string K_S, the host key # mpint e, exchange value sent by the client # mpint f, exchange value sent by the server # mpint K, the shared secret H = ssh_packet.pack_payload( KEXDH_HASH_PAYLOAD, (self.c2s_version_string, self.s2c_version_string, self.c2s_kexinit_packet, self.s2c_kexinit_packet, public_host_key, self.client_exchange_value, server_exchange_value, self.shared_secret)) # Double check that the signature from the server matches our signature. hash = hashlib.sha1(H) self.exchange_hash = hash.digest() if self.session_id is None: # The session id is the first exchange hash. self.session_id = self.exchange_hash if not self.server_public_host_key.verify(self.exchange_hash, signature_of_h): self.transport.send_disconnect( constants.SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 'Key exchange did not succeed: Signature did not match.')
def msg_kexdh_init (self, packet): # mpint e msg, self.client_exchange_value = ssh_packet.unpack_payload (KEXDH_INIT_PAYLOAD, packet) # XXX make sure e is a valid number # This is y. self.server_random_value = ssh_random.get_random_number(512) # p is large safe prime (DH_PRIME) # g is a generator for a subgroup of GF(p) (DH_GENERATOR) # compute f=g**y mod p self.server_exchange_value = pow(DH_GENERATOR, self.server_random_value, DH_PRIME) self.shared_secret = pow (self.client_exchange_value, self.server_random_value, DH_PRIME) K_S = self.transport.server_key.get_public_key_blob() payload_inputs = ( self.c2s_version_string, self.s2c_version_string, self.c2s_kexinit_packet, self.s2c_kexinit_packet, K_S, self.client_exchange_value, self.server_exchange_value, self.shared_secret ) H = ssh_packet.pack_payload (KEXDH_HASH_PAYLOAD, payload_inputs) self.exchange_hash = hashlib.sha1(H).digest() if self.session_id is None: # The session id is the first exchange hash. self.session_id = self.exchange_hash H_sig = self.transport.server_key.sign (self.exchange_hash) packet = ssh_packet.pack_payload ( KEXDH_REPLY_PAYLOAD, ( SSH_MSG_KEXDH_REPLY, K_S, self.server_exchange_value, H_sig ) ) self.transport.send_packet (packet)
def msg_kexdh_reply(self, packet): # string server public host key and certificates (K_S) # mpint f # string signature of H msg, public_host_key, server_exchange_value, signature_of_h = ssh_packet.unpack_payload(KEXDH_REPLY_PAYLOAD, packet) W ('kexdh: KEXDH_REPLY public host blob = %r\n' % (public_host_key,)) # Create a SSH_Public_Private_Key instance from the packed string. self.server_public_host_key = parse_public_key(public_host_key) # Verify that this is a known host key. self.transport.verify_public_host_key(self.server_public_host_key) # Make sure f is a valid number if server_exchange_value <= 1 or server_exchange_value >= DH_PRIME-1: self.transport.send_disconnect(constants.SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 'Key exchange did not succeed: Server exchange value not valid.') # K = f**x mod p self.shared_secret = pow(server_exchange_value, self.client_random_value, DH_PRIME) # Verify hash. # string V_C, the client's version string (CR and NL excluded) # string V_S, the server's version string (CR and NL excluded) # string I_C, the payload of the client's SSH_MSG_KEXINIT # string I_S, the payload of the server's SSH_MSG_KEXINIT # string K_S, the host key # mpint e, exchange value sent by the client # mpint f, exchange value sent by the server # mpint K, the shared secret H = ssh_packet.pack_payload(KEXDH_HASH_PAYLOAD, (self.c2s_version_string, self.s2c_version_string, self.c2s_kexinit_packet, self.s2c_kexinit_packet, public_host_key, self.client_exchange_value, server_exchange_value, self.shared_secret)) # Double check that the signature from the server matches our signature. hash = hashlib.sha1(H) self.exchange_hash = hash.digest() if self.session_id is None: # The session id is the first exchange hash. self.session_id = self.exchange_hash if not self.server_public_host_key.verify(self.exchange_hash, signature_of_h): self.transport.send_disconnect(constants.SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 'Key exchange did not succeed: Signature did not match.') # Finished... self.transport.send_newkeys()
def authenticate(self, username, service_name): local_username = os.getlogin() for key_storage in self.transport.supported_key_storages: self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: Trying to load keytype "%s" for user "%s".', (key_storage.__class__.__name__, local_username)) loaded_keys = key_storage.load_keys(username=local_username) if loaded_keys: for loaded_key in loaded_keys: # Test this key type. self.transport.debug.write( ssh_debug.DEBUG_1, 'Publickey Auth: Sending PK test for keytype "%s".', (loaded_key.name,)) packet = pack_payload(PAYLOAD_MSG_USERAUTH_REQUEST_PK_TEST, (SSH_MSG_USERAUTH_REQUEST, username, service_name, 'publickey', 0, loaded_key.name, loaded_key.get_public_key_blob() )) self.transport.send_packet(packet) message_type, packet = self.transport.receive_message((SSH_MSG_USERAUTH_PK_OK, SSH_MSG_USERAUTH_FAILURE, )) if message_type == SSH_MSG_USERAUTH_PK_OK: # This public key is ok to try. try: self._try_auth(packet, loaded_key, username, service_name) except Authentication_Error: # Nope, didn't work. Loop and try next. pass else: # Done! return elif message_type == SSH_MSG_USERAUTH_FAILURE: # Key type not allowed. self.msg_userauth_failure(packet) # Loop through and try next key. else: # Should never happen. raise ValueError(message_type) else: self.transport.debug.write(ssh_debug.DEBUG_1, 'Publickey Auth: No more key storage types left.') else: self.transport.debug.write(ssh_debug.DEBUG_1, 'Publickey Auth: No keys found of this key storage type.') else: self.transport.debug.write(ssh_debug.DEBUG_1, 'Publickey Auth: No more storage key types left to try.') raise Authentication_Error
def authenticate (self, session_id, serv, user, alg, blob, sig): # build the signable data to_sign = pack_payload ( PAYLOAD_USERAUTH_REQUEST_PK_SIGNATURE, ( session_id, SSH_MSG_USERAUTH_REQUEST, user, serv, 'publickey', 1, alg, blob ) ) try: keys = self.keys[user][serv] for key in keys: if key.name == alg and key.get_public_key_blob() == blob and key.verify (to_sign, sig): return True return False except KeyError: return False
def send_disconnect(self, reason_code, description): """send_disconnect(self, reason_code, description) -> None """ self.debug.write(ssh_debug.DEBUG_3, 'send_disconnect(reason_code=%r, description=%r)', (reason_code, description)) # Language tag currently set to the empty string. language_tag = '' self.send_packet( ssh_packet.pack_payload ( ssh_packet.PAYLOAD_MSG_DISCONNECT, (SSH_MSG_DISCONNECT, reason_code, description, language_tag) ) ) self.disconnect() raise SSH_Protocol_Error, (reason_code, description)
def msg_userauth_passwd_changereq(self, packet, username, service_name): # User's password has expired. Allow the user to enter a new password. msg, prompt, language = unpack_payload( PAYLOAD_MSG_USERAUTH_PASSWD_CHANGEREQ, packet) print safe_string(prompt) old_password = self.get_password('%s\'s old password> ' % username) while 1: new_password = self.get_password('%s\'s new password> ' % username) new_password2 = self.get_password('Retype new password> ') if new_password != new_password2: print 'Passwords did not match! Try again.' else: break packet = pack_payload( PAYLOAD_MSG_USERAUTH_REQUEST_CHANGE_PASSWD, (SSH_MSG_USERAUTH_REQUEST, username, service_name, 'password', 1, old_password, new_password)) self.transport.send_packet(packet)
def close(self): """close(self) -> None Tell remote side to close its channel. Our side is not considered "closed" until after we receive SSH_MSG_CHANNEL_CLOSE from the remote side. """ if not self.remote_channel.closed: self.remote_channel.closed = 1 pkt = packet.pack_payload( SSH_MSG_CHANNEL_CLOSE_PAYLOAD, (SSH_MSG_CHANNEL_CLOSE, self.remote_channel.channel_id) ) self.transport.send_packet(pkt) # We need to cause any threads that were trying to write on # this channel to stop trying to write. If they were asleep # waiting for one of the three condition variables, we need to # wake them up. They will notice that self.remote_channel.closed # is now true, and will do the right thing. self.window_data_added_cv.wake_all() self.channel_request_cv.wake_all(False) self.channel_open_cv.wake_all((False, (SSH_OPEN_CONNECT_FAILED, "Channel has been closed", None)))
def msg_userauth_passwd_changereq(self, packet, username, service_name): # User's password has expired. Allow the user to enter a new password. msg, prompt, language = unpack_payload(PAYLOAD_MSG_USERAUTH_PASSWD_CHANGEREQ, packet) print safe_string(prompt) old_password = self.get_password('%s\'s old password> ' % username) while 1: new_password = self.get_password('%s\'s new password> ' % username) new_password2 = self.get_password('Retype new password> ') if new_password != new_password2: print 'Passwords did not match! Try again.' else: break packet = pack_payload(PAYLOAD_MSG_USERAUTH_REQUEST_CHANGE_PASSWD, (SSH_MSG_USERAUTH_REQUEST, username, service_name, 'password', 1, old_password, new_password)) self.transport.send_packet(packet)
def close(self): """close(self) -> None Tell remote side to close its channel. Our side is not considered "closed" until after we receive SSH_MSG_CHANNEL_CLOSE from the remote side. """ if not self.remote_channel.closed: self.remote_channel.closed = 1 pkt = packet.pack_payload( SSH_MSG_CHANNEL_CLOSE_PAYLOAD, (SSH_MSG_CHANNEL_CLOSE, self.remote_channel.channel_id)) self.transport.send_packet(pkt) # We need to cause any threads that were trying to write on # this channel to stop trying to write. If they were asleep # waiting for one of the three condition variables, we need to # wake them up. They will notice that self.remote_channel.closed # is now true, and will do the right thing. self.window_data_added_cv.wake_all() self.channel_request_cv.wake_all(False) self.channel_open_cv.wake_all( (False, (SSH_OPEN_CONNECT_FAILED, 'Channel has been closed', None)))
def get_encryption_key(self, letter, required_size): """get_encryption_key(self, letter, required_size) -> key Computes an encryption key with the given letter. <required_size> is the length of the key that you require (in bytes). """ shared_secret = ssh_packet.pack_payload((ssh_packet.MPINT,), (self.shared_secret,)) key = self.get_hash_object( shared_secret, self.exchange_hash, letter, self.session_id).digest() if len(key) > required_size: # Key is too big...return only what is needed. key = key[:required_size] elif len(key) < required_size: # Key is not big enough...compute additional hashes until big enough. # K1 = HASH(K || H || X || session_id) (X is e.g. "A") # K2 = HASH(K || H || K1) # K3 = HASH(K || H || K1 || K2) # ... # key = K1 || K2 || K3 || ... self.transport.debug.write( ssh_debug.DEBUG_2, 'get_encryption_key: computed key is too small len(key)=%i required_size=%i', (len(key), required_size)) key_data = [key] key_data_len = len(key) while key_data_len < required_size: additional_key_data = self.get_hash_object( shared_secret, self.exchange_hash, ''.join(key_data)).digest() key_data.append(additional_key_data) key_data_len += len(additional_key_data) key = ''.join(key_data)[:required_size] else: # Key is just the right length. pass return key
def get_private_key_blob(self): p, q, g, y, x = self.private_key return packet.pack_payload(DSS_PRIVATE_KEY_PAYLOAD, ('ssh-dss', p, q, g, y, x))
def get_public_key_blob(self): e, n = self.public_key return packet.pack_payload(RSA_PUBLIC_KEY_PAYLOAD, ('ssh-rsa', e, n))
def get_private_key_blob(self): n, e, d, p, q = self.public_key return packet.pack_payload(RSA_PRIVATE_KEY_PAYLOAD, ('ssh-rsa', n, e, d, p, q))
def send(self, format, values): self.transport.send_packet(pack_payload(format, values))
def send_newkeys(self): self.debug.write(ssh_debug.DEBUG_3, 'send_newkeys()') packet = ssh_packet.pack_payload(ssh_packet.PAYLOAD_MSG_NEWKEYS, (SSH_MSG_NEWKEYS, )) self.send_packet(packet)
def _connect(self, transport, authenticator): # transport is already connected # Send identification string. self.transport = transport if self.s2c.comments: comments = ' ' + self.s2c.comments else: comments = '' self.s2c.version_string = 'SSH-' + self.s2c.protocol_version + '-' + self.s2c.software_version + comments transport.write(self.s2c.version_string + '\r\n') # Receive client's identification string. while 1: line = transport.read_line() if line.startswith('SSH-'): # Got the identification string. self.c2s.version_string = line # See if there are any optional comments. i = line.find(' ') if i != -1: self.c2s.comments = line[i + 1:] line = line[:i] # Break up the identification string into its parts. parts = line.split('-') if len(parts) != 3: self.send_disconnect( ssh_transport.SSH_DISCONNECT_PROTOCOL_ERROR, 'server identification invalid: %r' % line) self.c2s.protocol_version = parts[1] self.c2s.software_version = parts[2] if self.c2s.protocol_version not in ('1.99', '2.0'): self.send_disconnect( ssh_transport. SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, 'protocol version not supported: %r' % self.c2s.protocol_version) break self.send_kexinit() self.s2c.set_preferred('key_exchange') self.s2c.set_preferred('server_key') if self.self2remote.proactive_kex: # Go ahead and send our kex packet with our preferred algorithm. # This will assume the client side supports the algorithm. self.s2c.set_preferred('key_exchange') self.s2c.set_preferred('server_key') self.set_key_exchange() self.debug.write( debug.DEBUG_3, 'key exchange: sending proactive server kex packet') packet = self.c2s.key_exchange.get_initial_server_kex_packet() self.send_packet(packet) self.start_receive_thread() # Receive server kexinit self._process_kexinit() self.debug.write(debug.DEBUG_3, 'key exchange: got kexinit') self.debug.write( debug.DEBUG_3, 'key exchange: self.server_key=%r' % (self.server_key, )) if not self.self2remote.proactive_kex: self.debug.write( debug.DEBUG_3, 'key exchange: sending initial server kex packet') packet = self.key_exchange.get_initial_server_kex_packet() if packet: # It is possible for a key exchange algorithm to not have # an initial packet to send on the client side. self.send_packet(packet) message_type, packet = self.receive_message( (SSH_MSG_SERVICE_REQUEST, )) msg, service_name = ssh_packet.unpack_payload( ssh_packet.PAYLOAD_MSG_SERVICE_REQUEST, packet) self.debug.write(debug.DEBUG_1, 'service_request: %r' % (service_name, )) # XXX consider other possibilities if service_name != 'ssh-userauth': self.send_disconnect(SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, "not today, zurg") else: self.send_packet( ssh_packet.pack_payload( ssh_packet.PAYLOAD_MSG_SERVICE_ACCEPT, ( ssh_transport.SSH_MSG_SERVICE_ACCEPT, 'ssh-userauth', ))) authenticator.authenticate(service_name)
def send (self, format, values): self.send_packet (ssh_packet.pack_payload (format, values))
def send (self, format, values): self.transport.send_packet (pack_payload (format, values))
def send(self, format, values): self.send_packet(ssh_packet.pack_payload(format, values))
def send_newkeys(self): self.debug.write(ssh_debug.DEBUG_3, 'send_newkeys()') packet = ssh_packet.pack_payload(ssh_packet.PAYLOAD_MSG_NEWKEYS, (SSH_MSG_NEWKEYS,)) self.send_packet(packet)