# Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>
# Copyright (C) 2013-2014 science + computing ag
# Author: Sebastian Deiss <sebastian.deiss@t-online.de>
#
#
# This file is part of paramiko.
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
"""
This module provides GSS-API / SSPI Key Exchange as defined in :rfc:`4462`.
.. note:: Credential delegation is not supported in server mode.
.. note::
    `RFC 4462 Section 2.2
    <https://tools.ietf.org/html/rfc4462.html#section-2.2>`_ says we are not
    required to implement GSS-API error messages. Thus, in many methods within
    this module, if an error occurs an exception will be thrown and the
    connection will be terminated.
.. seealso:: :doc:`/api/ssh_gss`
.. versionadded:: 1.15
"""
import os
from hashlib import sha1
from paramiko.common import DEBUG, max_byte, zero_byte
from paramiko import util
from paramiko.message import Message
from paramiko.py3compat import byte_chr, byte_mask, byte_ord
from paramiko.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
    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)
        raise SSHException('GSS KexGroup1 asked to handle packet type %d'
                           % 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:\nMajor Status: %s\nMinor Status: %s\
                            \nError Message: %s\n") % (str(maj_status),
                                                       str(min_status),
                                                       err_msg)
class KexGSSGroup14(KexGSSGroup1):
    """
    GSS-API / SSPI Authenticated Diffie-Hellman Group14 Key Exchange as defined
    in `RFC 4462 Section 2
    <https://tools.ietf.org/html/rfc4462.html#section-2>`_
    """
    P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF  # noqa
    G = 2
    NAME = "gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g=="
class KexGSSGex(object):
    """
    GSS-API / SSPI Authenticated Diffie-Hellman Group Exchange as defined in
    `RFC 4462 Section 2 <https://tools.ietf.org/html/rfc4462.html#section-2>`_
    """
    NAME = "gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g=="
    min_bits = 1024
    max_bits = 8192
    preferred_bits = 2048
    def __init__(self, transport):
        self.transport = transport
        self.kexgss = self.transport.kexgss_ctxt
        self.gss_host = None
        self.p = None
        self.q = None
        self.g = None
        self.x = None
        self.e = None
        self.f = None
        self.old_style = False
    def start_kex(self):
        """
        Start the GSS-API / SSPI Authenticated Diffie-Hellman Group Exchange
        """
        if self.transport.server_mode:
            self.transport._expect_packet(MSG_KEXGSS_GROUPREQ)
            return
        # request a bit range: we accept (min_bits) to (max_bits), but prefer
        # (preferred_bits).  according to the spec, we shouldn't pull the
        # minimum up above 1024.
        self.gss_host = self.transport.gss_host
        m = Message()
        m.add_byte(c_MSG_KEXGSS_GROUPREQ)
        m.add_int(self.min_bits)
        m.add_int(self.preferred_bits)
        m.add_int(self.max_bits)
        self.transport._send_message(m)
        self.transport._expect_packet(MSG_KEXGSS_GROUP)
    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 ptype == MSG_KEXGSS_GROUPREQ:
            return self._parse_kexgss_groupreq(m)
        elif ptype == MSG_KEXGSS_GROUP:
            return self._parse_kexgss_group(m)
        elif ptype == MSG_KEXGSS_INIT:
            return self._parse_kexgss_gex_init(m)
        elif ptype == MSG_KEXGSS_HOSTKEY:
            return self._parse_kexgss_hostkey(m)
        elif ptype == MSG_KEXGSS_CONTINUE:
            return self._parse_kexgss_continue(m)
        elif ptype == MSG_KEXGSS_COMPLETE:
            return self._parse_kexgss_complete(m)
        elif ptype == MSG_KEXGSS_ERROR:
            return self._parse_kexgss_error(m)
        raise SSHException('KexGex asked to handle packet type %d' % ptype)
    # ##  internals...
    def _generate_x(self):
        # generate an "x" (1 < x < (p-1)/2).
        q = (self.p - 1) // 2
        qnorm = util.deflate_long(q, 0)
        qhbyte = byte_ord(qnorm[0])
        byte_count = len(qnorm)
        qmask = 0xff
        while not (qhbyte & 0x80):
            qhbyte <<= 1
            qmask >>= 1
        while True:
            x_bytes = os.urandom(byte_count)
            x_bytes = byte_mask(x_bytes[0], qmask) + x_bytes[1:]
            x = util.inflate_long(x_bytes, 1)
            if (x > 1) and (x < q):
                break
        self.x = x
    def _parse_kexgss_groupreq(self, m):
        """
        Parse the SSH2_MSG_KEXGSS_GROUPREQ message (server mode).
        :param `.Message` m: The content of the
            SSH2_MSG_KEXGSS_GROUPREQ message
        """
        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,  # noqa
            'Picking p (%d <= %d <= %d bits)' % (
                minbits, preferredbits, maxbits))
        self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits)
        m = Message()
        m.add_byte(c_MSG_KEXGSS_GROUP)
        m.add_mpint(self.p)
        m.add_mpint(self.g)
        self.transport._send_message(m)
        self.transport._expect_packet(MSG_KEXGSS_INIT)
    def _parse_kexgss_group(self, m):
        """
        Parse the SSH2_MSG_KEXGSS_GROUP message (client mode).
        :param `Message` m: The content of the SSH2_MSG_KEXGSS_GROUP message
        """
        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)  # noqa
        self._generate_x()
        # now compute e = g^x mod p
        self.e = pow(self.g, self.x, self.p)
        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_kexgss_gex_init(self, m):
        """
        Parse the SSH2_MSG_KEXGSS_INIT message (server mode).
        :param `Message` m: The content of the SSH2_MSG_KEXGSS_INIT message
        """
        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')
        self._generate_x()
        self.f = pow(self.g, self.x, self.p)
        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 || min || n || max || p || g || e || f || K)  # noqa
        hm = Message()
        hm.add(self.transport.remote_version, self.transport.local_version,
               self.transport.remote_kex_init, self.transport.local_kex_init,
               key)
        hm.add_int(self.min_bits)
        hm.add_int(self.preferred_bits)
        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 = 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_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
        """
        if self.transport.host_key is None:
            self.transport.host_key = NullHostKey()
        self.f = m.get_mpint()
        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()


Answer:         if (self.f < 1) or (self.f > self.p - 1):