Пример #1
0
 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_string() != '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)
Пример #2
0
 def _read_private_key(self, tag, f, password=None):
     lines = f.readlines()
     start = 0
     while (start < len(lines)) and (lines[start].strip() != '-----BEGIN ' + tag + ' PRIVATE KEY-----'):
         start += 1
     if start >= len(lines):
         raise SSHException('not a valid ' + tag + ' private key file')
     # parse any headers first
     headers = {}
     start += 1
     while start < len(lines):
         l = lines[start].split(': ')
         if len(l) == 1:
             break
         headers[l[0].lower()] = l[1].strip()
         start += 1
     # find end
     end = start
     while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)):
         end += 1
     # if we trudged to the end of the file, just try to cope.
     try:
         data = base64.decodestring(''.join(lines[start:end]))
     except base64.binascii.Error, e:
         raise SSHException('base64 decoding error: ' + str(e))
Пример #3
0
 def _read_all(self, wanted):
     result = self.conn.recv(wanted)
     while len(result) < wanted:
         if len(result) == 0:
             raise SSHException('lost ssh-agent')
         extra = self.conn.recv(wanted - len(result))
         if len(extra) == 0:
             raise SSHException('lost ssh-agent')
         result += extra
     return result
Пример #4
0
 def _encode_key(self):
     if self.x is None:
         raise SSHException('Not enough key information')
     keylist = [ 0, self.p, self.q, self.g, self.y, self.x ]
     try:
         b = BER()
         b.encode(keylist)
     except BERException:
         raise SSHException('Unable to create ber encoding of key')
     return str(b)
Пример #5
0
 def _encode_key(self):
     if (self.p is None) or (self.q is None):
         raise SSHException('Not enough key info to write private key file')
     keylist = [
         0, self.n, self.e, self.d, self.p, self.q, self.d % (self.p - 1),
         self.d % (self.q - 1),
         util.mod_inverse(self.q, self.p)
     ]
     try:
         b = BER()
         b.encode(keylist)
     except BERException:
         raise SSHException('Unable to create ber encoding of key')
     return str(b)
Пример #6
0
 def _parse_kexdh_gex_reply(self, m):
     host_key = m.get_string()
     self.f = m.get_mpint()
     sig = m.get_string()
     if (self.f < 1) or (self.f > self.p - 1):
         raise SSHException('Server kex "f" is out of range')
     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 || min || n || max || p || g || 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,
            host_key)
     if not self.old_style:
         hm.add_int(self.min_bits)
     hm.add_int(self.preferred_bits)
     if not self.old_style:
         hm.add_int(self.max_bits)
     hm.add_mpint(self.p)
     hm.add_mpint(self.g)
     hm.add_mpint(self.e)
     hm.add_mpint(self.f)
     hm.add_mpint(K)
     self.transport._set_K_H(K, SHA.new(str(hm)).digest())
     self.transport._verify_key(host_key, sig)
     self.transport._activate_outbound()
Пример #7
0
 def get_modulus(self, min, prefer, max):
     bitsizes = self.pack.keys()
     bitsizes.sort()
     if len(bitsizes) == 0:
         raise SSHException('no moduli available')
     good = -1
     # find nearest bitsize >= preferred
     for b in bitsizes:
         if (b >= prefer) and (b < max) and ((b < good) or (good == -1)):
             good = b
     # if that failed, find greatest bitsize >= min
     if good == -1:
         for b in bitsizes:
             if (b >= min) and (b < max) and (b > good):
                 good = b
     if good == -1:
         # their entire (min, max) range has no intersection with our range.
         # if their range is below ours, pick the smallest.  otherwise pick
         # the largest.  it'll be out of their range requirement either way,
         # but we'll be sending them the closest one we have.
         good = bitsizes[0]
         if min > good:
             good = bitsizes[-1]
     # now pick a random modulus of this bitsize
     n = _roll_random(self.randpool, len(self.pack[good]))
     return self.pack[good][n]
Пример #8
0
 def _parse_kexdh_gex_request(self, m):
     minbits = m.get_int()
     preferredbits = m.get_int()
     maxbits = m.get_int()
     # smoosh the user's preferred size into our own limits
     if preferredbits > self.max_bits:
         preferredbits = self.max_bits
     if preferredbits < self.min_bits:
         preferredbits = self.min_bits
     # fix min/max if they're inconsistent.  technically, we could just pout
     # and hang up, but there's no harm in giving them the benefit of the
     # doubt and just picking a bitsize for them.
     if minbits > preferredbits:
         minbits = preferredbits
     if maxbits < preferredbits:
         maxbits = preferredbits
     # now save a copy
     self.min_bits = minbits
     self.preferred_bits = preferredbits
     self.max_bits = maxbits
     # generate prime
     pack = self.transport._get_modulus_pack()
     if pack is None:
         raise SSHException(
             'Can\'t do server-side gex with no modulus pack')
     self.transport._log(
         DEBUG, 'Picking p (%d <= %d <= %d bits)' %
         (minbits, preferredbits, maxbits))
     self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits)
     m = Message()
     m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
     m.add_mpint(self.p)
     m.add_mpint(self.g)
     self.transport._send_message(m)
     self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
Пример #9
0
    def __init__(self, sock):
        """
        Create an SFTP client from an existing L{Channel}.  The channel
        should already have requested the C{"sftp"} subsystem.

        An alternate way to create an SFTP client context is by using
        L{from_transport}.

        @param sock: an open L{Channel} using the C{"sftp"} subsystem
        @type sock: L{Channel}

        @raise SSHException: if there's an exception while negotiating
            sftp
        """
        BaseSFTP.__init__(self)
        self.sock = sock
        self.ultra_debug = False
        self.request_number = 1
        # lock for request_number
        self._lock = threading.Lock()
        self._cwd = None
        # request # -> SFTPFile
        self._expecting = weakref.WeakValueDictionary()
        if type(sock) is Channel:
            # override default logger
            transport = self.sock.get_transport()
            self.logger = util.get_logger(transport.get_log_channel() +
                                          '.sftp')
            self.ultra_debug = transport.get_hexdump()
        try:
            server_version = self._send_version()
        except EOFError, x:
            raise SSHException('EOF during negotiation')
Пример #10
0
 def _parse_kexdh_init(self, m):
     # server mode
     self.e = m.get_mpint()
     if (self.e < 1) or (self.e > P - 1):
         raise SSHException('Client kex "e" is out of range')
     K = pow(self.e, self.x, P)
     key = str(self.transport.get_server_key())
     # 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 = SHA.new(str(hm)).digest()
     self.transport._set_K_H(K, H)
     # sign it
     sig = self.transport.get_server_key().sign_ssh_data(
         self.transport.randpool, H)
     # send reply
     m = Message()
     m.add_byte(chr(_MSG_KEXDH_REPLY))
     m.add_string(key)
     m.add_mpint(self.f)
     m.add_string(str(sig))
     self.transport._send_message(m)
     self.transport._activate_outbound()
Пример #11
0
 def _decode_key(self, data):
     # private key file contains:
     # DSAPrivateKey = { version = 0, p, q, g, y, x }
     try:
         keylist = BER(data).decode()
     except BERException, x:
         raise SSHException('Unable to parse key file: ' + str(x))
Пример #12
0
 def _read_response(self, waitfor=None):
     while True:
         try:
             t, data = self._read_packet()
         except EOFError, e:
             raise SSHException('Server connection dropped: %s' %
                                (str(e), ))
         msg = Message(data)
         num = msg.get_int()
         if num not in self._expecting:
             # might be response for a file that was closed before responses came back
             self._log(DEBUG, 'Unexpected response #%d' % (num, ))
             if waitfor is None:
                 # just doing a single check
                 break
             continue
         fileobj = self._expecting[num]
         del self._expecting[num]
         if num == waitfor:
             # synchronous
             if t == CMD_STATUS:
                 self._convert_status(msg)
             return t, msg
         if fileobj is not type(None):
             fileobj._async_response(t, msg)
         if waitfor is None:
             # just doing a single check
             break
Пример #13
0
 def _decode_key(self, data):
     # private key file contains:
     # RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p }
     try:
         keylist = BER(data).decode()
     except BERException:
         raise SSHException('Unable to parse key file')
     if (type(keylist)
             is not list) or (len(keylist) < 4) or (keylist[0] != 0):
         raise SSHException(
             'Not a valid RSA private key file (bad ber encoding)')
     self.n = keylist[1]
     self.e = keylist[2]
     self.d = keylist[3]
     # not really needed
     self.p = keylist[4]
     self.q = keylist[5]
     self.size = util.bit_length(self.n)
Пример #14
0
 def sign_ssh_data(self, randpool, data):
     msg = Message()
     msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST))
     msg.add_string(self.blob)
     msg.add_string(data)
     msg.add_int(0)
     ptype, result = self.agent._send_message(msg)
     if ptype != SSH2_AGENT_SIGN_RESPONSE:
         raise SSHException('key cannot be used for signing')
     return result.get_string()
Пример #15
0
 def parse_next(self, ptype, m):
     if ptype == _MSG_KEXDH_GEX_REQUEST:
         return self._parse_kexdh_gex_request(m)
     elif ptype == _MSG_KEXDH_GEX_GROUP:
         return self._parse_kexdh_gex_group(m)
     elif ptype == _MSG_KEXDH_GEX_INIT:
         return self._parse_kexdh_gex_init(m)
     elif ptype == _MSG_KEXDH_GEX_REPLY:
         return self._parse_kexdh_gex_reply(m)
     elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD:
         return self._parse_kexdh_gex_request_old(m)
     raise SSHException('KexGex asked to handle packet type %d' % ptype)
Пример #16
0
 def _parse_userauth_info_response(self, m):
     if not self.transport.server_mode:
         raise SSHException('Illegal info response from server')
     n = m.get_int()
     responses = []
     for i in range(n):
         responses.append(m.get_string())
     result = self.transport.server_object.check_auth_interactive_response(
         responses)
     if isinstance(type(result), InteractiveQuery):
         # make interactive query instead of response
         self._interactive_query(result)
         return
     self._send_auth_result(self.auth_username, 'keyboard-interactive',
                            result)
Пример #17
0
 def _parse_kexdh_gex_group(self, m):
     self.p = m.get_mpint()
     self.g = m.get_mpint()
     # reject if p's bit length < 1024 or > 8192
     bitlen = util.bit_length(self.p)
     if (bitlen < 1024) or (bitlen > 8192):
         raise SSHException(
             'Server-generated gex p (don\'t ask) is out of range (%d bits)'
             % bitlen)
     self.transport._log(DEBUG, 'Got server p (%d bits)' % bitlen)
     self._generate_x()
     # now compute e = g^x mod p
     self.e = pow(self.g, self.x, self.p)
     m = Message()
     m.add_byte(chr(_MSG_KEXDH_GEX_INIT))
     m.add_mpint(self.e)
     self.transport._send_message(m)
     self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY)
Пример #18
0
    def _parse_userauth_info_request(self, m):
        if self.auth_method != 'keyboard-interactive':
            raise SSHException('Illegal info request from server')
        title = m.get_string()
        instructions = m.get_string()
        m.get_string()  # lang
        prompts = m.get_int()
        prompt_list = []
        for i in range(prompts):
            prompt_list.append((m.get_string(), m.get_boolean()))
        response_list = self.interactive_handler(title, instructions,
                                                 prompt_list)

        m = Message()
        m.add_byte(chr(MSG_USERAUTH_INFO_RESPONSE))
        m.add_int(len(response_list))
        for r in response_list:
            m.add_string(r)
        self.transport._send_message(m)
Пример #19
0
 def _parse_kexdh_reply(self, m):
     # client mode
     host_key = m.get_string()
     self.f = m.get_mpint()
     if (self.f < 1) or (self.f > P - 1):
         raise SSHException('Server kex "f" is out of range')
     sig = m.get_string()
     K = pow(self.f, self.x, 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(host_key)
     hm.add_mpint(self.e)
     hm.add_mpint(self.f)
     hm.add_mpint(K)
     self.transport._set_K_H(K, SHA.new(str(hm)).digest())
     self.transport._verify_key(host_key, sig)
     self.transport._activate_outbound()
Пример #20
0
 def _parse_kexdh_gex_request_old(self, m):
     # same as above, but without min_bits or max_bits (used by older clients like putty)
     self.preferred_bits = m.get_int()
     # smoosh the user's preferred size into our own limits
     if self.preferred_bits > self.max_bits:
         self.preferred_bits = self.max_bits
     if self.preferred_bits < self.min_bits:
         self.preferred_bits = self.min_bits
     # generate prime
     pack = self.transport._get_modulus_pack()
     if pack is None:
         raise SSHException(
             'Can\'t do server-side gex with no modulus pack')
     self.transport._log(DEBUG,
                         'Picking p (~ %d bits)' % (self.preferred_bits, ))
     self.g, self.p = pack.get_modulus(self.min_bits, self.preferred_bits,
                                       self.max_bits)
     m = Message()
     m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
     m.add_mpint(self.p)
     m.add_mpint(self.g)
     self.transport._send_message(m)
     self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
     self.old_style = True
Пример #21
0
 def _parse_service_accept(self, m):
     service = m.get_string()
     if service == 'ssh-userauth':
         self.transport._log(DEBUG, 'userauth is OK')
         m = Message()
         m.add_byte(chr(MSG_USERAUTH_REQUEST))
         m.add_string(self.username)
         m.add_string('ssh-connection')
         m.add_string(self.auth_method)
         if self.auth_method == 'password':
             m.add_boolean(False)
             password = self.password
             if isinstance(password, unicode):
                 password = password.encode('UTF-8')
             m.add_string(password)
         elif self.auth_method == 'publickey':
             m.add_boolean(True)
             m.add_string(self.private_key.get_name())
             m.add_string(str(self.private_key))
             blob = self._get_session_blob(self.private_key,
                                           'ssh-connection', self.username)
             sig = self.private_key.sign_ssh_data(self.transport.randpool,
                                                  blob)
             m.add_string(str(sig))
         elif self.auth_method == 'keyboard-interactive':
             m.add_string('')
             m.add_string(self.submethods)
         elif self.auth_method == 'none':
             pass
         else:
             raise SSHException('Unknown auth method "%s"' %
                                self.auth_method)
         self.transport._send_message(m)
     else:
         self.transport._log(DEBUG,
                             'Service request "%s" accepted (?)' % service)
Пример #22
0
 def _parse_kexdh_gex_init(self, m):
     self.e = m.get_mpint()
     if (self.e < 1) or (self.e > self.p - 1):
         raise SSHException('Client kex "e" is out of range')
     self._generate_x()
     self.f = pow(self.g, self.x, self.p)
     K = pow(self.e, self.x, self.p)
     key = str(self.transport.get_server_key())
     # okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || 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,
            key)
     if not self.old_style:
         hm.add_int(self.min_bits)
     hm.add_int(self.preferred_bits)
     if not self.old_style:
         hm.add_int(self.max_bits)
     hm.add_mpint(self.p)
     hm.add_mpint(self.g)
     hm.add_mpint(self.e)
     hm.add_mpint(self.f)
     hm.add_mpint(K)
     H = SHA.new(str(hm)).digest()
     self.transport._set_K_H(K, H)
     # sign it
     sig = self.transport.get_server_key().sign_ssh_data(
         self.transport.randpool, H)
     # send reply
     m = Message()
     m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
     m.add_string(key)
     m.add_mpint(self.f)
     m.add_string(str(sig))
     self.transport._send_message(m)
     self.transport._activate_outbound()
Пример #23
0
    def __init__(self):
        """
        Open a session with the local machine's SSH agent, if one is running.
        If no agent is running, initialization will succeed, but L{get_keys}
        will return an empty tuple.
        
        @raise SSHException: if an SSH agent is found, but speaks an
            incompatible protocol
        """
        self.keys = ()
        if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'):
            conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            try:
                conn.connect(os.environ['SSH_AUTH_SOCK'])
            except:
                # probably a dangling env var: the ssh agent is gone
                return
            self.conn = conn
        elif sys.platform == 'win32':
            import win_pageant
            if win_pageant.can_talk_to_agent():
                self.conn = win_pageant.PageantConnection()
            else:
                return
        else:
            # no agent support
            return

        ptype, result = self._send_message(chr(SSH2_AGENTC_REQUEST_IDENTITIES))
        if ptype != SSH2_AGENT_IDENTITIES_ANSWER:
            raise SSHException('could not get keys from ssh-agent')
        keys = []
        for i in range(result.get_int()):
            keys.append(AgentKey(self, result.get_string()))
            result.get_string()
        self.keys = tuple(keys)
Пример #24
0
        
        if not look_for_keys:
            keyfiles = []

        for pkey_class, filename in keyfiles:
            try:
                key = pkey_class.from_private_key_file(filename, password)
                self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename))
                self._transport.auth_publickey(username, key)
                return
            except SSHException, e:
                saved_exception = e
            except IOError, e:
                saved_exception = e
        
        if password is not None:
            try:
                self._transport.auth_password(username, password)
                return
            except SSHException, e:
                saved_exception = e

        # if we got an auth-failed exception earlier, re-raise it
        if saved_exception is not None:
            raise saved_exception
        raise SSHException('No authentication methods available')

    def _log(self, level, msg):
        self._transport._log(level, msg)

Пример #25
0
    def read_message(self):
        """
        Only one thread should ever be in this function (no other locking is
        done).
        
        @raise SSHException: if the packet is mangled
        @raise NeedRekeyException: if the transport should rekey
        """
        header = self.read_all(self.__block_size_in, check_rekey=True)
        if self.__block_engine_in != None:
            header = self.__block_engine_in.decrypt(header)
        if self.__dump_packets:
            self._log(DEBUG, util.format_binary(header, 'IN: '))
        packet_size = struct.unpack('>I', header[:4])[0]
        # leftover contains decrypted bytes from the first block (after the length field)
        leftover = header[4:]
        if (packet_size - len(leftover)) % self.__block_size_in != 0:
            raise SSHException('Invalid packet blocking')
        buf = self.read_all(packet_size + self.__mac_size_in - len(leftover))
        packet = buf[:packet_size - len(leftover)]
        post_packet = buf[packet_size - len(leftover):]
        if self.__block_engine_in != None:
            packet = self.__block_engine_in.decrypt(packet)
        if self.__dump_packets:
            self._log(DEBUG, util.format_binary(packet, 'IN: '))
        packet = leftover + packet

        if self.__mac_size_in > 0:
            mac = post_packet[:self.__mac_size_in]
            mac_payload = struct.pack('>II', self.__sequence_number_in,
                                      packet_size) + packet
            my_mac = compute_hmac(self.__mac_key_in, mac_payload,
                                  self.__mac_engine_in)[:self.__mac_size_in]
            if my_mac != mac:
                raise SSHException('Mismatched MAC')
        padding = ord(packet[0])
        payload = packet[1:packet_size - padding]
        randpool.add_event()
        if self.__dump_packets:
            self._log(
                DEBUG,
                'Got payload (%d bytes, %d padding)' % (packet_size, padding))

        if self.__compress_engine_in is not None:
            payload = self.__compress_engine_in(payload)

        msg = Message(payload[1:])
        msg.seqno = self.__sequence_number_in
        self.__sequence_number_in = (self.__sequence_number_in +
                                     1) & 0xffffffffL

        # check for rekey
        self.__received_bytes += packet_size + self.__mac_size_in + 4
        self.__received_packets += 1
        if self.__need_rekey:
            # we've asked to rekey -- give them 20 packets to comply before
            # dropping the connection
            self.__received_packets_overflow += 1
            if self.__received_packets_overflow >= 20:
                raise SSHException(
                    'Remote transport is ignoring rekey requests')
        elif (self.__received_packets >= self.REKEY_PACKETS) or \
             (self.__received_bytes >= self.REKEY_BYTES):
            # only ask once for rekeying
            self._log(
                DEBUG, 'Rekeying (hit %d packets, %d bytes received)' %
                (self.__received_packets, self.__received_bytes))
            self.__received_packets_overflow = 0
            self._trigger_rekey()

        cmd = ord(payload[0])
        if cmd in MSG_NAMES:
            cmd_name = MSG_NAMES[cmd]
        else:
            cmd_name = '$%x' % cmd
        if self.__dump_packets:
            self._log(DEBUG,
                      'Read packet <%s>, length %d' % (cmd_name, len(payload)))
        return cmd, msg
Пример #26
0
 def parse_next(self, ptype, m):
     if self.transport.server_mode and (ptype == _MSG_KEXDH_INIT):
         return self._parse_kexdh_init(m)
     elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY):
         return self._parse_kexdh_reply(m)
     raise SSHException('KexGroup1 asked to handle packet type %d' % ptype)
Пример #27
0
class PKey (object):
    """
    Base class for public keys.
    """

    # known encryption types for private key files:
    _CIPHER_TABLE = {
        'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC }
    }


    def __init__(self, msg=None, data=None):
        """
        Create a new instance of this public key type.  If C{msg} is given,
        the key's public part(s) will be filled in from the message.  If
        C{data} is given, the key's public part(s) will be filled in from
        the string.

        @param msg: an optional SSH L{Message} containing a public key of this
        type.
        @type msg: L{Message}
        @param data: an optional string containing a public key of this type
        @type data: str

        @raise SSHException: if a key cannot be created from the C{data} or
        C{msg} given, or no key was passed in.
        """
        pass

    def __str__(self):
        """
        Return a string of an SSH L{Message} made up of the public part(s) of
        this key.  This string is suitable for passing to L{__init__} to
        re-create the key object later.

        @return: string representation of an SSH key message.
        @rtype: str
        """
        return ''

    def __cmp__(self, other):
        """
        Compare this key to another.  Returns 0 if this key is equivalent to
        the given key, or non-0 if they are different.  Only the public parts
        of the key are compared, so a public key will compare equal to its
        corresponding private key.

        @param other: key to compare to.
        @type other: L{PKey}
        @return: 0 if the two keys are equivalent, non-0 otherwise.
        @rtype: int
        """
        hs = hash(self)
        ho = hash(other)
        if hs != ho:
            return cmp(hs, ho)
        return cmp(str(self), str(other))

    def get_name(self):
        """
        Return the name of this private key implementation.

        @return: name of this private key type, in SSH terminology (for
        example, C{"ssh-rsa"}).
        @rtype: str
        """
        return ''

    def get_bits(self):
        """
        Return the number of significant bits in this key.  This is useful
        for judging the relative security of a key.

        @return: bits in the key.
        @rtype: int
        """
        return 0

    def can_sign(self):
        """
        Return C{True} if this key has the private part necessary for signing
        data.

        @return: C{True} if this is a private key.
        @rtype: bool
        """
        return False

    def get_fingerprint(self):
        """
        Return an MD5 fingerprint of the public part of this key.  Nothing
        secret is revealed.

        @return: a 16-byte string (binary) of the MD5 fingerprint, in SSH
            format.
        @rtype: str
        """
        return MD5.new(str(self)).digest()

    def get_base64(self):
        """
        Return a base64 string containing the public part of this key.  Nothing
        secret is revealed.  This format is compatible with that used to store
        public key files or recognized host keys.

        @return: a base64 string containing the public part of the key.
        @rtype: str
        """
        return base64.encodestring(str(self)).replace('\n', '')

    def sign_ssh_data(self, randpool, data):
        """
        Sign a blob of data with this private key, and return a L{Message}
        representing an SSH signature message.

        @param randpool: a secure random number generator.
        @type randpool: L{Crypto.Util.randpool.RandomPool}
        @param data: the data to sign.
        @type data: str
        @return: an SSH signature message.
        @rtype: L{Message}
        """
        return ''

    def verify_ssh_sig(self, data, msg):
        """
        Given a blob of data, and an SSH message representing a signature of
        that data, verify that it was signed with this key.

        @param data: the data that was signed.
        @type data: str
        @param msg: an SSH signature message
        @type msg: L{Message}
        @return: C{True} if the signature verifies correctly; C{False}
            otherwise.
        @rtype: boolean
        """
        return False
   
    def from_private_key_file(cls, filename, password=None):
        """
        Create a key object by reading a private key file.  If the private
        key is encrypted and C{password} is not C{None}, the given password
        will be used to decrypt the key (otherwise L{PasswordRequiredException}
        is thrown).  Through the magic of python, this factory method will
        exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but
        is useless on the abstract PKey class.

        @param filename: name of the file to read
        @type filename: str
        @param password: an optional password to use to decrypt the key file,
            if it's encrypted
        @type password: str
        @return: a new key object based on the given private key
        @rtype: L{PKey}

        @raise IOError: if there was an error reading the file
        @raise PasswordRequiredException: if the private key file is
            encrypted, and C{password} is C{None}
        @raise SSHException: if the key file is invalid
        """
        key = cls(filename=filename, password=password)
        return key
    from_private_key_file = classmethod(from_private_key_file)

    def from_private_key(cls, file_obj, password=None):
        """
        Create a key object by reading a private key from a file (or file-like)
        object.  If the private key is encrypted and C{password} is not C{None},
        the given password will be used to decrypt the key (otherwise
        L{PasswordRequiredException} is thrown).
        
        @param file_obj: the file to read from
        @type file_obj: file
        @param password: an optional password to use to decrypt the key, if it's
            encrypted
        @type password: str
        @return: a new key object based on the given private key
        @rtype: L{PKey}
        
        @raise IOError: if there was an error reading the key
        @raise PasswordRequiredException: if the private key file is encrypted,
            and C{password} is C{None}
        @raise SSHException: if the key file is invalid
        """
        key = cls(file_obj=file_obj, password=password)
        return key
    from_private_key = classmethod(from_private_key)

    def write_private_key_file(self, filename, password=None):
        """
        Write private key contents into a file.  If the password is not
        C{None}, the key is encrypted before writing.

        @param filename: name of the file to write
        @type filename: str
        @param password: an optional password to use to encrypt the key file
        @type password: str

        @raise IOError: if there was an error writing the file
        @raise SSHException: if the key is invalid
        """
        raise Exception('Not implemented in PKey')
    
    def write_private_key(self, file_obj, password=None):
        """
        Write private key contents into a file (or file-like) object.  If the
        password is not C{None}, the key is encrypted before writing.
        
        @param file_obj: the file object to write into
        @type file_obj: file
        @param password: an optional password to use to encrypt the key
        @type password: str
        
        @raise IOError: if there was an error writing to the file
        @raise SSHException: if the key is invalid
        """
        raise Exception('Not implemented in PKey')

    def _read_private_key_file(self, tag, filename, password=None):
        """
        Read an SSH2-format private key file, looking for a string of the type
        C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we
        find, and return it as a string.  If the private key is encrypted and
        C{password} is not C{None}, the given password will be used to decrypt
        the key (otherwise L{PasswordRequiredException} is thrown).

        @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
        @type tag: str
        @param filename: name of the file to read.
        @type filename: str
        @param password: an optional password to use to decrypt the key file,
            if it's encrypted.
        @type password: str
        @return: data blob that makes up the private key.
        @rtype: str

        @raise IOError: if there was an error reading the file.
        @raise PasswordRequiredException: if the private key file is
            encrypted, and C{password} is C{None}.
        @raise SSHException: if the key file is invalid.
        """
        f = open(filename, 'r')
        data = self._read_private_key(tag, f, password)
        f.close()
        return data
    
    def _read_private_key(self, tag, f, password=None):
        lines = f.readlines()
        start = 0
        while (start < len(lines)) and (lines[start].strip() != '-----BEGIN ' + tag + ' PRIVATE KEY-----'):
            start += 1
        if start >= len(lines):
            raise SSHException('not a valid ' + tag + ' private key file')
        # parse any headers first
        headers = {}
        start += 1
        while start < len(lines):
            l = lines[start].split(': ')
            if len(l) == 1:
                break
            headers[l[0].lower()] = l[1].strip()
            start += 1
        # find end
        end = start
        while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)):
            end += 1
        # if we trudged to the end of the file, just try to cope.
        try:
            data = base64.decodestring(''.join(lines[start:end]))
        except base64.binascii.Error, e:
            raise SSHException('base64 decoding error: ' + str(e))
        if 'proc-type' not in headers:
            # unencryped: done
            return data
        # encrypted keyfile: will need a password
        if headers['proc-type'] != '4,ENCRYPTED':
            raise SSHException('Unknown private key structure "%s"' % headers['proc-type'])
        try:
            encryption_type, saltstr = headers['dek-info'].split(',')
        except:
            raise SSHException('Can\'t parse DEK-info in private key file')
        if encryption_type not in self._CIPHER_TABLE:
            raise SSHException('Unknown private key cipher "%s"' % encryption_type)
        # if no password was passed in, raise an exception pointing out that we need one
        if password is None:
            raise PasswordRequiredException('Private key file is encrypted')
        cipher = self._CIPHER_TABLE[encryption_type]['cipher']
        keysize = self._CIPHER_TABLE[encryption_type]['keysize']
        mode = self._CIPHER_TABLE[encryption_type]['mode']
        salt = unhexlify(saltstr)
        key = util.generate_key_bytes(MD5, salt, password, keysize)
        return cipher.new(key, mode, salt).decrypt(data)
Пример #28
0
 def missing_host_key(self, client, hostname, key):
     client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
                 (key.get_name(), hostname, hexlify(key.get_fingerprint())))
     raise SSHException('Unknown server %s' % hostname)
Пример #29
0
class DSSKey (PKey):
    """
    Representation of a DSS key which can be used to sign an verify SSH2
    data.
    """

    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_string() != '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 __str__(self):
        m = Message()
        m.add_string('ssh-dss')
        m.add_mpint(self.p)
        m.add_mpint(self.q)
        m.add_mpint(self.g)
        m.add_mpint(self.y)
        return str(m)

    def __hash__(self):
        h = hash(self.get_name())
        h = h * 37 + hash(self.p)
        h = h * 37 + hash(self.q)
        h = h * 37 + hash(self.g)
        h = h * 37 + hash(self.y)
        # h might be a long by now...
        return hash(h)

    def get_name(self):
        return 'ssh-dss'

    def get_bits(self):
        return self.size
        
    def can_sign(self):
        return self.x is not None

    def sign_ssh_data(self, rpool, data):
        digest = SHA.new(data).digest()
        dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x)))
        # generate a suitable k
        qsize = len(util.deflate_long(self.q, 0))
        while True:
            k = util.inflate_long(rpool.get_bytes(qsize), 1)
            if (k > 2) and (k < self.q):
                break
        r, s = dss.sign(util.inflate_long(digest, 1), k)
        m = Message()
        m.add_string('ssh-dss')
        # apparently, in rare cases, r or s may be shorter than 20 bytes!
        rstr = util.deflate_long(r, 0)
        sstr = util.deflate_long(s, 0)
        if len(rstr) < 20:
            rstr = '\x00' * (20 - len(rstr)) + rstr
        if len(sstr) < 20:
            sstr = '\x00' * (20 - len(sstr)) + sstr
        m.add_string(rstr + sstr)
        return m

    def verify_ssh_sig(self, data, msg):
        if len(str(msg)) == 40:
            # spies.com bug: signature has no header
            sig = str(msg)
        else:
            kind = msg.get_string()
            if kind != 'ssh-dss':
                return 0
            sig = msg.get_string()

        # pull out (r, s) which are NOT encoded as mpints
        sigR = util.inflate_long(sig[:20], 1)
        sigS = util.inflate_long(sig[20:], 1)
        sigM = util.inflate_long(SHA.new(data).digest(), 1)

        dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q)))
        return dss.verify(sigM, (sigR, sigS))

    def _encode_key(self):
        if self.x is None:
            raise SSHException('Not enough key information')
        keylist = [ 0, self.p, self.q, self.g, self.y, self.x ]
        try:
            b = BER()
            b.encode(keylist)
        except BERException:
            raise SSHException('Unable to create ber encoding of key')
        return str(b)

    def write_private_key_file(self, filename, password=None):
        self._write_private_key_file('DSA', filename, self._encode_key(), password)

    def write_private_key(self, file_obj, password=None):
        self._write_private_key('DSA', file_obj, self._encode_key(), password)

    def generate(bits=1024, progress_func=None):
        """
        Generate a new private DSS key.  This factory function can be used to
        generate a new host key or authentication key.

        @param bits: number of bits the generated key should be.
        @type bits: int
        @param progress_func: an optional function to call at key points in
            key generation (used by C{pyCrypto.PublicKey}).
        @type progress_func: function
        @return: new private key
        @rtype: L{DSSKey}
        """
        randpool.stir()
        dsa = DSA.generate(bits, randpool.get_bytes, progress_func)
        key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
        key.x = dsa.x
        return key
    generate = staticmethod(generate)


    ###  internals...


    def _from_private_key_file(self, filename, password):
        data = self._read_private_key_file('DSA', filename, password)
        self._decode_key(data)
    
    def _from_private_key(self, file_obj, password):
        data = self._read_private_key('DSA', file_obj, password)
        self._decode_key(data)
    
    def _decode_key(self, data):
        # private key file contains:
        # DSAPrivateKey = { version = 0, p, q, g, y, x }
        try:
            keylist = BER(data).decode()
        except BERException, x:
            raise SSHException('Unable to parse key file: ' + str(x))
        if (type(keylist) is not list) or (len(keylist) < 6) or (keylist[0] != 0):
            raise SSHException('not a valid DSA private key file (bad ber encoding)')
        self.p = keylist[1]
        self.q = keylist[2]
        self.g = keylist[3]
        self.y = keylist[4]
        self.x = keylist[5]
        self.size = util.bit_length(self.p)