def encode_tlv(self, ident, val): # no need to support ident > 31 here self.content += byte_chr(ident) if len(val) > 0x7f: lenstr = util.deflate_long(len(val)) self.content += byte_chr(0x80 + len(lenstr)) + lenstr else: self.content += byte_chr(len(val)) self.content += val
def safe_string(s): out = b'' for c in s: i = byte_ord(c) if 32 <= i <= 127: out += byte_chr(i) else: out += b('%{:02X}'.format(i)) return out
""" Ephemeral Elliptic Curve Diffie-Hellman (ECDH) key exchange RFC 5656, Section 4 """ from hashlib import sha256, sha384, sha512 from message import Message from py3compat import byte_chr, long from ssh_exception import SSHException from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec from binascii import hexlify _MSG_KEXECDH_INIT, _MSG_KEXECDH_REPLY = range(30, 32) c_MSG_KEXECDH_INIT, c_MSG_KEXECDH_REPLY = [byte_chr(c) for c in range(30, 32)] class KexNistp256(): name = "ecdh-sha2-nistp256" hash_algo = sha256 curve = ec.SECP256R1() def __init__(self, transport): self.transport = transport # private key, client public and server public keys self.P = long(0) self.Q_C = None self.Q_S = None def start_kex(self):
def _send_packet(self, t, packet): packet = asbytes(packet) out = struct.pack('>I', len(packet) + 1) + byte_chr(t) + packet if self.ultra_debug: self._log(DEBUG, util.format_binary(out, 'OUT: ')) self._write_all(out)
class KexGSSGroup1(object): """ GSS-API / SSPI Authenticated Diffie-Hellman Key Exchange as defined in `RFC 4462 Section 2 <https://tools.ietf.org/html/rfc4462.html#section-2>`_ """ # draft-ietf-secsh-transport-09.txt, page 17 P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF # noqa G = 2 b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7 # noqa b0000000000000000 = zero_byte * 8 # noqa NAME = "gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==" def __init__(self, transport): self.transport = transport self.kexgss = self.transport.kexgss_ctxt self.gss_host = None self.x = 0 self.e = 0 self.f = 0 def start_kex(self): """ Start the GSS-API / SSPI Authenticated Diffie-Hellman Key Exchange. """ self._generate_x() if self.transport.server_mode: # compute f = g^x mod p, but don't send it yet self.f = pow(self.G, self.x, self.P) self.transport._expect_packet(MSG_KEXGSS_INIT) return # compute e = g^x mod p (where g=2), and send it self.e = pow(self.G, self.x, self.P) # Initialize GSS-API Key Exchange self.gss_host = self.transport.gss_host m = Message() m.add_byte(c_MSG_KEXGSS_INIT) m.add_string(self.kexgss.ssh_init_sec_context(target=self.gss_host)) m.add_mpint(self.e) self.transport._send_message(m) self.transport._expect_packet(MSG_KEXGSS_HOSTKEY, MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE, MSG_KEXGSS_ERROR) def parse_next(self, ptype, m): """ Parse the next packet. :param ptype: The (string) type of the incoming packet :param `.Message` m: The paket content """ if self.transport.server_mode and (ptype == MSG_KEXGSS_INIT): return self._parse_kexgss_init(m) elif not self.transport.server_mode and (ptype == MSG_KEXGSS_HOSTKEY): return self._parse_kexgss_hostkey(m) elif self.transport.server_mode and (ptype == MSG_KEXGSS_CONTINUE): return self._parse_kexgss_continue(m) elif not self.transport.server_mode and (ptype == MSG_KEXGSS_COMPLETE): return self._parse_kexgss_complete(m) elif ptype == MSG_KEXGSS_ERROR: return self._parse_kexgss_error(m) msg = 'GSS KexGroup1 asked to handle packet type {:d}' raise SSHException(msg.format(ptype)) # ## internals... def _generate_x(self): """ generate an "x" (1 < x < q), where q is (p-1)/2. p is a 128-byte (1024-bit) number, where the first 64 bits are 1. therefore q can be approximated as a 2^1023. we drop the subset of potential x where the first 63 bits are 1, because some of those will be larger than q (but this is a tiny tiny subset of potential x). """ while 1: x_bytes = os.urandom(128) x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:] first = x_bytes[:8] if first not in (self.b7fffffffffffffff, self.b0000000000000000): break self.x = util.inflate_long(x_bytes) def _parse_kexgss_hostkey(self, m): """ Parse the SSH2_MSG_KEXGSS_HOSTKEY message (client mode). :param `.Message` m: The content of the SSH2_MSG_KEXGSS_HOSTKEY message """ # client mode host_key = m.get_string() self.transport.host_key = host_key sig = m.get_string() self.transport._verify_key(host_key, sig) self.transport._expect_packet(MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE) def _parse_kexgss_continue(self, m): """ Parse the SSH2_MSG_KEXGSS_CONTINUE message. :param `.Message` m: The content of the SSH2_MSG_KEXGSS_CONTINUE message """ if not self.transport.server_mode: srv_token = m.get_string() m = Message() m.add_byte(c_MSG_KEXGSS_CONTINUE) m.add_string( self.kexgss.ssh_init_sec_context(target=self.gss_host, recv_token=srv_token)) self.transport.send_message(m) self.transport._expect_packet(MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE, MSG_KEXGSS_ERROR) else: pass def _parse_kexgss_complete(self, m): """ Parse the SSH2_MSG_KEXGSS_COMPLETE message (client mode). :param `.Message` m: The content of the SSH2_MSG_KEXGSS_COMPLETE message """ # client mode if self.transport.host_key is None: self.transport.host_key = NullHostKey() self.f = m.get_mpint() if (self.f < 1) or (self.f > self.P - 1): raise SSHException('Server kex "f" is out of range') mic_token = m.get_string() # This must be TRUE, if there is a GSS-API token in this message. bool = m.get_boolean() srv_token = None if bool: srv_token = m.get_string() K = pow(self.f, self.x, self.P) # okay, build up the hash H of # (V_C || V_S || I_C || I_S || K_S || e || f || K) hm = Message() hm.add(self.transport.local_version, self.transport.remote_version, self.transport.local_kex_init, self.transport.remote_kex_init) hm.add_string(self.transport.host_key.__str__()) hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) H = sha1(str(hm)).digest() self.transport._set_K_H(K, H) if srv_token is not None: self.kexgss.ssh_init_sec_context(target=self.gss_host, recv_token=srv_token) self.kexgss.ssh_check_mic(mic_token, H) else: self.kexgss.ssh_check_mic(mic_token, H) self.transport.gss_kex_used = True self.transport._activate_outbound() def _parse_kexgss_init(self, m): """ Parse the SSH2_MSG_KEXGSS_INIT message (server mode). :param `.Message` m: The content of the SSH2_MSG_KEXGSS_INIT message """ # server mode client_token = m.get_string() self.e = m.get_mpint() if (self.e < 1) or (self.e > self.P - 1): raise SSHException('Client kex "e" is out of range') K = pow(self.e, self.x, self.P) self.transport.host_key = NullHostKey() key = self.transport.host_key.__str__() # okay, build up the hash H of # (V_C || V_S || I_C || I_S || K_S || e || f || K) hm = Message() hm.add(self.transport.remote_version, self.transport.local_version, self.transport.remote_kex_init, self.transport.local_kex_init) hm.add_string(key) hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) H = sha1(hm.asbytes()).digest() self.transport._set_K_H(K, H) srv_token = self.kexgss.ssh_accept_sec_context(self.gss_host, client_token) m = Message() if self.kexgss._gss_srv_ctxt_status: mic_token = self.kexgss.ssh_get_mic(self.transport.session_id, gss_kex=True) m.add_byte(c_MSG_KEXGSS_COMPLETE) m.add_mpint(self.f) m.add_string(mic_token) if srv_token is not None: m.add_boolean(True) m.add_string(srv_token) else: m.add_boolean(False) self.transport._send_message(m) self.transport.gss_kex_used = True self.transport._activate_outbound() else: m.add_byte(c_MSG_KEXGSS_CONTINUE) m.add_string(srv_token) self.transport._send_message(m) self.transport._expect_packet(MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE, MSG_KEXGSS_ERROR) def _parse_kexgss_error(self, m): """ Parse the SSH2_MSG_KEXGSS_ERROR message (client mode). The server may send a GSS-API error message. if it does, we display the error by throwing an exception (client mode). :param `.Message` m: The content of the SSH2_MSG_KEXGSS_ERROR message :raise SSHException: Contains GSS-API major and minor status as well as the error message and the language tag of the message """ maj_status = m.get_int() min_status = m.get_int() err_msg = m.get_string() m.get_string() # we don't care about the language! raise SSHException("""GSS-API Error: Major Status: {} Minor Status: {} Error Message: {} """.format(maj_status, min_status, err_msg))
import os from hashlib import sha1 from common import DEBUG, max_byte, zero_byte from . import util from message import Message from py3compat import byte_chr, byte_mask, byte_ord from ssh_exception import SSHException MSG_KEXGSS_INIT, MSG_KEXGSS_CONTINUE, MSG_KEXGSS_COMPLETE, MSG_KEXGSS_HOSTKEY,\ MSG_KEXGSS_ERROR = range(30, 35) MSG_KEXGSS_GROUPREQ, MSG_KEXGSS_GROUP = range(40, 42) c_MSG_KEXGSS_INIT, c_MSG_KEXGSS_CONTINUE, c_MSG_KEXGSS_COMPLETE,\ c_MSG_KEXGSS_HOSTKEY, c_MSG_KEXGSS_ERROR = [ byte_chr(c) for c in range(30, 35) ] c_MSG_KEXGSS_GROUPREQ, c_MSG_KEXGSS_GROUP = [ byte_chr(c) for c in range(40, 42) ] class KexGSSGroup1(object): """ GSS-API / SSPI Authenticated Diffie-Hellman Key Exchange as defined in `RFC 4462 Section 2 <https://tools.ietf.org/html/rfc4462.html#section-2>`_ """ # draft-ietf-secsh-transport-09.txt, page 17 P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF # noqa G = 2 b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7 # noqa
import os from hashlib import sha1, sha256 from . import util from common import DEBUG from message import Message from py3compat import byte_chr, byte_ord, byte_mask from ssh_exception import SSHException _MSG_KEXDH_GEX_REQUEST_OLD, _MSG_KEXDH_GEX_GROUP, _MSG_KEXDH_GEX_INIT, \ _MSG_KEXDH_GEX_REPLY, _MSG_KEXDH_GEX_REQUEST = range(30, 35) c_MSG_KEXDH_GEX_REQUEST_OLD, c_MSG_KEXDH_GEX_GROUP, c_MSG_KEXDH_GEX_INIT, \ c_MSG_KEXDH_GEX_REPLY, c_MSG_KEXDH_GEX_REQUEST = \ [byte_chr(c) for c in range(30, 35)] class KexGex(object): name = 'diffie-hellman-group-exchange-sha1' min_bits = 1024 max_bits = 8192 preferred_bits = 2048 hash_algo = sha1 def __init__(self, transport): self.transport = transport self.p = None self.q = None self.g = None
Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of 1024 bit key halves, using a known "p" prime and "g" generator. """ import os from hashlib import sha1 from . import util from common import max_byte, zero_byte from message import Message from py3compat import byte_chr, long, byte_mask from ssh_exception import SSHException _MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32) c_MSG_KEXDH_INIT, c_MSG_KEXDH_REPLY = [byte_chr(c) for c in range(30, 32)] b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7 b0000000000000000 = zero_byte * 8 class KexGroup1(object): # draft-ietf-secsh-transport-09.txt, page 17 P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF # noqa G = 2 name = 'diffie-hellman-group1-sha1' hash_algo = sha1 def __init__(self, transport):
MSG_KEXINIT, MSG_NEWKEYS = range(20, 22) MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, MSG_USERAUTH_SUCCESS, \ MSG_USERAUTH_BANNER = range(50, 54) MSG_USERAUTH_PK_OK = 60 MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE = range(60, 62) MSG_USERAUTH_GSSAPI_RESPONSE, MSG_USERAUTH_GSSAPI_TOKEN = range(60, 62) MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, MSG_USERAUTH_GSSAPI_ERROR,\ MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC = range(63, 67) HIGHEST_USERAUTH_MESSAGE_ID = 79 MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE = range(80, 83) MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \ MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \ MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST, \ MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101) cMSG_DISCONNECT = byte_chr(MSG_DISCONNECT) cMSG_IGNORE = byte_chr(MSG_IGNORE) cMSG_UNIMPLEMENTED = byte_chr(MSG_UNIMPLEMENTED) cMSG_DEBUG = byte_chr(MSG_DEBUG) cMSG_SERVICE_REQUEST = byte_chr(MSG_SERVICE_REQUEST) cMSG_SERVICE_ACCEPT = byte_chr(MSG_SERVICE_ACCEPT) cMSG_KEXINIT = byte_chr(MSG_KEXINIT) cMSG_NEWKEYS = byte_chr(MSG_NEWKEYS) cMSG_USERAUTH_REQUEST = byte_chr(MSG_USERAUTH_REQUEST) cMSG_USERAUTH_FAILURE = byte_chr(MSG_USERAUTH_FAILURE) cMSG_USERAUTH_SUCCESS = byte_chr(MSG_USERAUTH_SUCCESS) cMSG_USERAUTH_BANNER = byte_chr(MSG_USERAUTH_BANNER) cMSG_USERAUTH_PK_OK = byte_chr(MSG_USERAUTH_PK_OK) cMSG_USERAUTH_INFO_REQUEST = byte_chr(MSG_USERAUTH_INFO_REQUEST) cMSG_USERAUTH_INFO_RESPONSE = byte_chr(MSG_USERAUTH_INFO_RESPONSE) cMSG_USERAUTH_GSSAPI_RESPONSE = byte_chr(MSG_USERAUTH_GSSAPI_RESPONSE)
import struct import sys import threading import time import tempfile import stat from select import select from common import asbytes, io_sleep from py3compat import byte_chr from ssh_exception import SSHException, AuthenticationException from message import Message from pkey import PKey from util import retry_on_signal cSSH2_AGENTC_REQUEST_IDENTITIES = byte_chr(11) SSH2_AGENT_IDENTITIES_ANSWER = 12 cSSH2_AGENTC_SIGN_REQUEST = byte_chr(13) SSH2_AGENT_SIGN_RESPONSE = 14 class AgentSSH(object): def __init__(self): self._conn = None self._keys = () def get_keys(self): """ Return the list of keys available through the SSH agent, if any. If no SSH agent was running (or it couldn't be contacted), an empty list will be returned.