def verify(self, data, sig):
        """Verifies data and its signature.  If verification fails, an sspi.error
        will be raised.
        """
        sigbuf = win32security.PySecBufferDescType()
        sigbuf.append(
            win32security.PySecBufferType(len(data), sspicon.SECBUFFER_DATA))
        sigbuf.append(
            win32security.PySecBufferType(len(sig), sspicon.SECBUFFER_TOKEN))

        sigbuf[0].Buffer = data
        sigbuf[1].Buffer = sig
        self.ctxt.VerifySignature(sigbuf, self._get_next_seq_num())
Example #2
0
    def encrypt(self, data):
        """Encrypt a string, returning a tuple of (encrypted_data, encryption_data).
        These can be passed to decrypt to get back the original string.
        """
        pkg_size_info=self.ctxt.QueryContextAttributes(sspicon.SECPKG_ATTR_SIZES)
        trailersize=pkg_size_info['SecurityTrailer']

        encbuf=win32security.PySecBufferDescType()
        encbuf.append(win32security.PySecBufferType(len(data), sspicon.SECBUFFER_DATA))
        encbuf.append(win32security.PySecBufferType(trailersize, sspicon.SECBUFFER_TOKEN))
        encbuf[0].Buffer=data
        self.ctxt.EncryptMessage(0,encbuf,self._get_next_seq_num())
        return encbuf[0].Buffer, encbuf[1].Buffer
    def authorize(self, sec_buffer_in):
        """Perform *one* step of the server authentication process."""
        if sec_buffer_in is not None and type(
                sec_buffer_in) != win32security.PySecBufferDescType:
            # User passed us the raw data - wrap it into a SecBufferDesc
            sec_buffer_new = win32security.PySecBufferDescType()
            tokenbuf = win32security.PySecBufferType(self.pkg_info['MaxToken'],
                                                     sspicon.SECBUFFER_TOKEN)
            tokenbuf.Buffer = sec_buffer_in
            sec_buffer_new.append(tokenbuf)
            sec_buffer_in = sec_buffer_new

        sec_buffer_out = win32security.PySecBufferDescType()
        tokenbuf = win32security.PySecBufferType(self.pkg_info['MaxToken'],
                                                 sspicon.SECBUFFER_TOKEN)
        sec_buffer_out.append(tokenbuf)
        ## input context handle is None initially, then handle returned from last call thereafter
        ctxtin = self.ctxt
        if self.ctxt is None:
            self.ctxt = win32security.PyCtxtHandleType()
        err, attr, exp = win32security.AcceptSecurityContext(
            self.credentials, ctxtin, sec_buffer_in, self.scflags,
            self.datarep, self.ctxt, sec_buffer_out)

        # Stash these away incase someone needs to know the state from the
        # final call.
        self.ctxt_attr = attr
        self.ctxt_expiry = exp

        if err in (sspicon.SEC_I_COMPLETE_NEEDED,
                   sspicon.SEC_I_COMPLETE_AND_CONTINUE):
            self.ctxt.CompleteAuthToken(sec_buffer_out)

        self.authenticated = err == 0
        if self.authenticated:
            self._amend_ctx_name()

        return err, sec_buffer_out
Example #4
0
    def sign(self, data):
        """sign a string suitable for transmission, returning the signature.
        Passing the data and signature to verify will determine if the data
        is unchanged.
        """
        pkg_size_info=self.ctxt.QueryContextAttributes(sspicon.SECPKG_ATTR_SIZES)
        sigsize=pkg_size_info['MaxSignature']
        sigbuf=win32security.PySecBufferDescType()
        sigbuf.append(win32security.PySecBufferType(len(data), sspicon.SECBUFFER_DATA))
        sigbuf.append(win32security.PySecBufferType(sigsize, sspicon.SECBUFFER_TOKEN))
        sigbuf[0].Buffer=data

        self.ctxt.MakeSignature(0,sigbuf,self._get_next_seq_num())
        return sigbuf[1].Buffer
Example #5
0
    def testSecBufferRepr(self):
        desc = win32security.PySecBufferDescType()
        assert re.match('PySecBufferDesc\(ulVersion: 0 \| cBuffers: 0 \| pBuffers: 0x[\da-fA-F]{8,16}\)', repr(desc))

        buffer1 = win32security.PySecBufferType(0, sspicon.SECBUFFER_TOKEN)
        assert re.match('PySecBuffer\(cbBuffer: 0 \| BufferType: 2 \| pvBuffer: 0x[\da-fA-F]{8,16}\)', repr(buffer1))
        'PySecBuffer(cbBuffer: 0 | BufferType: 2 | pvBuffer: 0x000001B8CC6D8020)'
        desc.append(buffer1)

        assert re.match('PySecBufferDesc\(ulVersion: 0 \| cBuffers: 1 \| pBuffers: 0x[\da-fA-F]{8,16}\)', repr(desc))

        buffer2 = win32security.PySecBufferType(4, sspicon.SECBUFFER_DATA)
        assert re.match('PySecBuffer\(cbBuffer: 4 \| BufferType: 1 \| pvBuffer: 0x[\da-fA-F]{8,16}\)', repr(buffer2))
        desc.append(buffer2)

        assert re.match('PySecBufferDesc\(ulVersion: 0 \| cBuffers: 2 \| pBuffers: 0x[\da-fA-F]{8,16}\)', repr(desc))
Example #6
0
    def _step(self, token):
        success_codes = [
            sspicon.SEC_E_OK,
            sspicon.SEC_I_COMPLETE_AND_CONTINUE,
            sspicon.SEC_I_COMPLETE_NEEDED,
            sspicon.SEC_I_CONTINUE_NEEDED
        ]

        sec_tokens = []
        if token is not None:
            sec_token = win32security.PySecBufferType(
                self._context.pkg_info['MaxToken'],
                sspicon.SECBUFFER_TOKEN
            )
            sec_token.Buffer = token
            sec_tokens.append(sec_token)
        if self.cbt_app_data is not None:
            sec_token = win32security.PySecBufferType(
                len(self.cbt_app_data),
                sspicon.SECBUFFER_CHANNEL_BINDINGS
            )
            sec_token.Buffer = self.cbt_app_data
            sec_tokens.append(sec_token)

        if len(sec_tokens) > 0:
            sec_buffer = win32security.PySecBufferDescType()
            for sec_token in sec_tokens:
                sec_buffer.append(sec_token)
        else:
            sec_buffer = None

        rc, out_buffer = self._context.authorize(sec_buffer_in=sec_buffer)
        self._call_counter += 1
        if rc not in success_codes:
            rc_name = "Unknown Error"
            for name, value in vars(sspicon).items():
                if isinstance(value, int) and name.startswith("SEC_") and \
                        value == rc:
                    rc_name = name
                    break
            raise AuthenticationError(
                "InitializeSecurityContext failed on call %d: (%d) %s 0x%s"
                % (self._call_counter, rc, rc_name, format(rc, 'x'))
            )

        return out_buffer[0].Buffer
Example #7
0
    def _create_buffer_array(
        self,
        max_token_size: int,
        server_sasl_creds: Optional[bytes],
        peercert: Optional[X509],
    ):
        """
        Creates a buffer array to be passed to an SSPI client authenticator.

        If serverSaslCreds are provided, they will be appended to the created
        buffer. If a peercert is provided, a channel binding token will be
        appended to the buffer

        Args:
            max_token_size: As defined by ClientAuth this value is the maximum size of a token for the handshake
            server_sasl_creds: The serverSaslCreds received from the server
                that need to be included on the next call to authorize()
            peercert: Peer SSL certificate taken off of a transport
        Returns:
            PySecBufferDescType: The array of PySecBufferTypes
        """
        buffer_array = win32security.PySecBufferDescType()

        if server_sasl_creds:
            server_sasl_creds_buffer = win32security.PySecBufferType(
                max_token_size, sspicon.SECBUFFER_TOKEN)
            server_sasl_creds_buffer.Buffer = server_sasl_creds
            buffer_array.append(server_sasl_creds_buffer)

        # To support servers that have turned on LdapEnforceChannelBinding we add this token
        if peercert:
            try:
                appdata = util.create_appdata_from_peercert(peercert)
            except UnsupportedAlgorithm:
                log.msg(
                    "Skipping the creation of the CBT due to unsupported hash algorithm"
                )
            else:
                cbt_buffer = self._create_sspi_channel_binding_token(
                    max_token_size, appdata)
                buffer_array.append(cbt_buffer)

        return buffer_array
Example #8
0
    def _doTestEncrypt(self, pkg_name):

        sspiclient, sspiserver = self._doAuth(pkg_name)

        pkg_size_info=sspiclient.ctxt.QueryContextAttributes(sspicon.SECPKG_ATTR_SIZES)
        msg=str2bytes('some data to be encrypted ......')

        trailersize=pkg_size_info['SecurityTrailer']
        encbuf=win32security.PySecBufferDescType()
        encbuf.append(win32security.PySecBufferType(len(msg), sspicon.SECBUFFER_DATA))
        encbuf.append(win32security.PySecBufferType(trailersize, sspicon.SECBUFFER_TOKEN))
        encbuf[0].Buffer=msg
        sspiclient.ctxt.EncryptMessage(0,encbuf,1)
        sspiserver.ctxt.DecryptMessage(encbuf,1)
        self.failUnlessEqual(msg, encbuf[0].Buffer)
        # and test.py the higher-level functions
        data_in = str2bytes("hello")
        data, sig = sspiclient.encrypt(data_in)
        self.assertEqual(sspiserver.decrypt(data, sig), data_in)

        data, sig = sspiserver.encrypt(data_in)
        self.assertEqual(sspiclient.decrypt(data, sig), data_in)
    def wrap(self, msg, encrypt=False):
        """
            GSSAPI's wrap with SSPI.
            https://docs.microsoft.com/en-us/windows/win32/secauthn/sspi-kerberos-interoperability-with-gssapi

            Usable mainly with Kerberos SSPI package, but this is not enforced.

            Wrap a message to be sent to the other side. Encrypted if encrypt is True.
        """

        size_info = self.ctxt.QueryContextAttributes(sspicon.SECPKG_ATTR_SIZES)
        trailer_size = size_info['SecurityTrailer']
        block_size = size_info['BlockSize']

        buffer = win32security.PySecBufferDescType()

        # This buffer will contain unencrypted data to wrap, and maybe encrypt.
        buffer.append(
            win32security.PySecBufferType(len(msg), sspicon.SECBUFFER_DATA))
        buffer[0].Buffer = msg

        # Will receive the token that forms the beginning of the msg
        buffer.append(
            win32security.PySecBufferType(trailer_size,
                                          sspicon.SECBUFFER_TOKEN))

        # The trailer is needed in case of block encryption
        buffer.append(
            win32security.PySecBufferType(block_size,
                                          sspicon.SECBUFFER_PADDING))

        fQOP = 0 if encrypt else sspicon.SECQOP_WRAP_NO_ENCRYPT
        self.ctxt.EncryptMessage(fQOP, buffer, self._get_next_seq_num())

        # Sec token, then data, then padding
        r = buffer[1].Buffer + buffer[0].Buffer + buffer[2].Buffer
        return r
    def unwrap(self, token):
        """
            GSSAPI's unwrap with SSPI.
            https://docs.microsoft.com/en-us/windows/win32/secauthn/sspi-kerberos-interoperability-with-gssapi

            Usable mainly with Kerberos SSPI package, but this is not enforced.

            Return the clear text, and a boolean that is True if the token was encrypted.
        """
        buffer = win32security.PySecBufferDescType()
        # This buffer will contain a "stream", which is the token coming from the other side
        buffer.append(
            win32security.PySecBufferType(len(token),
                                          sspicon.SECBUFFER_STREAM))
        buffer[0].Buffer = token

        # This buffer will receive the clear, or just unwrapped text if no encryption was used.
        # Will be resized by the lib.
        buffer.append(win32security.PySecBufferType(0, sspicon.SECBUFFER_DATA))

        pfQOP = self.ctxt.DecryptMessage(buffer, self._get_next_seq_num())

        r = buffer[1].Buffer
        return r, not (pfQOP == sspicon.SECQOP_WRAP_NO_ENCRYPT)
Example #11
0
# The server can now impersonate the client.  In this demo the 2 users will
# always be the same.
sspiserver.ctxt.ImpersonateSecurityContext()
print('Impersonated user: '******'Reverted to self: ', win32api.GetUserName())

pkg_size_info = sspiclient.ctxt.QueryContextAttributes(
    sspicon.SECPKG_ATTR_SIZES)
# Now sign some data
msg = 'some data to be encrypted ......'

sigsize = pkg_size_info['MaxSignature']
sigbuf = win32security.PySecBufferDescType()
sigbuf.append(win32security.PySecBufferType(len(msg), sspicon.SECBUFFER_DATA))
sigbuf.append(win32security.PySecBufferType(sigsize, sspicon.SECBUFFER_TOKEN))
sigbuf[0].Buffer = msg
sspiclient.ctxt.MakeSignature(0, sigbuf, 1)
sspiserver.ctxt.VerifySignature(sigbuf, 1)

# And finally encrypt some.
trailersize = pkg_size_info['SecurityTrailer']
encbuf = win32security.PySecBufferDescType()
encbuf.append(win32security.PySecBufferType(len(msg), sspicon.SECBUFFER_DATA))
encbuf.append(
    win32security.PySecBufferType(trailersize, sspicon.SECBUFFER_TOKEN))
encbuf[0].Buffer = msg
sspiclient.ctxt.EncryptMessage(0, encbuf, 1)
print('Encrypted data:', repr(encbuf[0].Buffer))
Example #12
0
    def _retry_using_http_Negotiate_auth(self, response, scheme, args):
        if 'Authorization' in response.request.headers:
            return response

        if self._host is None:
            targeturl = urlparse(response.request.url)
            self._host = targeturl.hostname
            try:
                self._host = socket.getaddrinfo(self._host, None, 0, 0, 0,
                                                socket.AI_CANONNAME)[0][3]
            except socket.gaierror as e:
                _logger.info(
                    'Skipping canonicalization of name %s due to error: %s',
                    self._host, e)

        targetspn = '{}/{}'.format(self._service, self._host)

        # Set up SSPI connection structure
        pkg_info = win32security.QuerySecurityPackageInfo(scheme)
        clientauth = sspi.ClientAuth(scheme,
                                     targetspn=targetspn,
                                     auth_info=self._auth_info)
        sec_buffer = win32security.PySecBufferDescType()

        # Channel Binding Hash (aka Extended Protection for Authentication)
        # If this is a SSL connection, we need to hash the peer certificate, prepend the RFC5929 channel binding type,
        # and stuff it into a SEC_CHANNEL_BINDINGS structure.
        # This should be sent along in the initial handshake or Kerberos auth will fail.
        if hasattr(response, 'peercert') and response.peercert is not None:
            md = hashlib.sha256()
            md.update(response.peercert)
            appdata = 'tls-server-end-point:'.encode('ASCII') + md.digest()
            cbtbuf = win32security.PySecBufferType(
                pkg_info['MaxToken'], sspicon.SECBUFFER_CHANNEL_BINDINGS)
            cbtbuf.Buffer = struct.pack('LLLLLLLL{}s'.format(len(appdata)), 0,
                                        0, 0, 0, 0, 0, len(appdata), 32,
                                        appdata)
            sec_buffer.append(cbtbuf)

        content_length = int(response.request.headers.get(
            'Content-Length', '0'),
                             base=10)

        if hasattr(response.request.body, 'seek'):
            if content_length > 0:
                response.request.body.seek(-content_length, 1)
            else:
                response.request.body.seek(0, 0)

        # Consume content and release the original connection
        # to allow our new request to reuse the same one.
        response.content
        response.raw.release_conn()
        request = response.request.copy()

        # this is important for some web applications that store
        # authentication-related info in cookies
        if response.headers.get('set-cookie'):
            request.headers['Cookie'] = response.headers.get('set-cookie')

        # Send initial challenge auth header
        try:
            error, auth = clientauth.authorize(sec_buffer)
            request.headers['Authorization'] = '{} {}'.format(
                scheme,
                base64.b64encode(auth[0].Buffer).decode('ASCII'))
            _logger.debug(
                'Sending Initial Context Token - error={} authenticated={}'.
                format(error, clientauth.authenticated))
        except pywintypes.error as e:
            _logger.error('Error calling {}: {}'.format(e[1], e[2]),
                          exc_info=e)
            return response

        # A streaming response breaks authentication.
        # This can be fixed by not streaming this request, which is safe
        # because the returned response3 will still have stream=True set if
        # specified in args. In addition, we expect this request to give us a
        # challenge and not the real content, so the content will be short
        # anyway.
        args_nostream = dict(args, stream=False)
        response2 = response.connection.send(request, **args_nostream)

        # Should get another 401 if we are doing challenge-response (NTLM)
        if response2.status_code != 401:
            if response2.status_code == 200:
                # Kerberos may have succeeded; if so, finalize our auth context
                final = response2.headers.get('WWW-Authenticate')
                if final is not None:
                    try:
                        # Sometimes Windows seems to forget to prepend 'Negotiate' to the success response,
                        # and we get just a bare chunk of base64 token. Not sure why.
                        final = final.replace(scheme, '', 1).lstrip()
                        tokenbuf = win32security.PySecBufferType(
                            pkg_info['MaxToken'], sspicon.SECBUFFER_TOKEN)
                        tokenbuf.Buffer = base64.b64decode(final)
                        sec_buffer.append(tokenbuf)
                        error, auth = clientauth.authorize(sec_buffer)
                        _logger.debug(
                            'Kerberos Authentication succeeded - error={} authenticated={}'
                            .format(error, clientauth.authenticated))
                    except TypeError:
                        pass

            # Regardless of whether or not we finalized our auth context,
            # without a 401 we've got nothing to do. Update the history and return.
            response2.history.append(response)
            return response2

        # Consume content and release the original connection
        # to allow our new request to reuse the same one.
        response2.content
        response2.raw.release_conn()
        request = response2.request.copy()

        # Keep passing the cookies along
        if response2.headers.get('set-cookie'):
            request.headers['Cookie'] = response2.headers.get('set-cookie')

        # Extract challenge message from server
        challenge = [
            val[len(scheme) + 1:] for val in response2.headers.get(
                'WWW-Authenticate', '').split(', ') if scheme in val
        ]
        if len(challenge) != 1:
            raise HTTPError(
                'Did not get exactly one {} challenge from server.'.format(
                    scheme))

        # Add challenge to security buffer
        tokenbuf = win32security.PySecBufferType(pkg_info['MaxToken'],
                                                 sspicon.SECBUFFER_TOKEN)
        tokenbuf.Buffer = base64.b64decode(challenge[0])
        sec_buffer.append(tokenbuf)
        _logger.debug('Got Challenge Token (NTLM)')

        # Perform next authorization step
        error, auth = clientauth.authorize(sec_buffer)
        request.headers['Authorization'] = '{} {}'.format(
            scheme,
            base64.b64encode(auth[0].Buffer).decode('ASCII'))
        _logger.debug('Sending Response - error={} authenticated={}'.format(
            error, clientauth.authenticated))

        response3 = response2.connection.send(request, **args)

        # Update the history and return
        response3.history.append(response)
        response3.history.append(response2)

        return response3
    def retry_using_http_NTLM_auth(self, auth_header_field, auth_header,
                                   response, auth_type, args):
        """Attempt to authenticate using HTTP NTLM challenge/response."""
        if auth_header in response.request.headers:
            return response

        content_length = int(response.request.headers.get(
            'Content-Length', '0'),
                             base=10)
        if hasattr(response.request.body, 'seek'):
            if content_length > 0:
                response.request.body.seek(-content_length, 1)
            else:
                response.request.body.seek(0, 0)

        # Consume content and release the original connection
        # to allow our new request to reuse the same one.
        response.content
        response.raw.release_conn()
        request = response.request.copy()

        # initial auth header with username. will result in challenge
        if self._use_default_credentials:
            pkg_info = win32security.QuerySecurityPackageInfo(_package)
            clientauth = sspi.ClientAuth(_package)
            sec_buffer = win32security.PySecBufferDescType()
            error, auth = clientauth.authorize(sec_buffer)
            request.headers[auth_header] = '{} {}'.format(
                _package,
                b64encode(auth[0].Buffer).decode('ascii'))
        else:
            msg = "%s\\%s" % (self.domain,
                              self.username) if self.domain else self.username

            # ntlm returns the headers as a base64 encoded bytestring. Convert to
            # a string.
            auth = '%s %s' % (auth_type, ntlm.create_NTLM_NEGOTIATE_MESSAGE(
                msg).decode('ascii'))
            request.headers[auth_header] = auth

        # A streaming response breaks authentication.
        # This can be fixed by not streaming this request, which is safe
        # because the returned response3 will still have stream=True set if
        # specified in args. In addition, we expect this request to give us a
        # challenge and not the real content, so the content will be short
        # anyway.
        args_nostream = dict(args, stream=False)
        response2 = response.connection.send(request, **args_nostream)

        # needed to make NTLM auth compatible with requests-2.3.0

        # Consume content and release the original connection
        # to allow our new request to reuse the same one.
        response2.content
        response2.raw.release_conn()
        request = response2.request.copy()

        # this is important for some web applications that store
        # authentication-related info in cookies (it took a long time to
        # figure out)
        if response2.headers.get('set-cookie'):
            request.headers['Cookie'] = response2.headers.get('set-cookie')

        # get the challenge
        auth_header_value = response2.headers[auth_header_field]

        auth_strip = auth_type + ' '

        ntlm_header_value = next(
            s for s in (val.lstrip() for val in auth_header_value.split(','))
            if s.startswith(auth_strip)).strip()

        challenge_value = ntlm_header_value[len(auth_strip):]

        # build response
        if self._use_default_credentials:
            # Add challenge to security buffer
            tokenbuf = win32security.PySecBufferType(pkg_info['MaxToken'],
                                                     sspicon.SECBUFFER_TOKEN)
            tokenbuf.Buffer = b64decode(challenge_value)
            sec_buffer.append(tokenbuf)

            # Perform next authorization step
            error, auth = clientauth.authorize(sec_buffer)
            request.headers[auth_header] = '{} {}'.format(
                _package,
                b64encode(auth[0].Buffer).decode('ascii'))
        else:
            ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE(
                challenge_value)
            # ntlm returns the headers as a base64 encoded bytestring. Convert to a
            # string.
            auth = '%s %s' % (auth_type,
                              ntlm.create_NTLM_AUTHENTICATE_MESSAGE(
                                  ServerChallenge, self.username, self.domain,
                                  self.password,
                                  NegotiateFlags).decode('ascii'))
            request.headers[auth_header] = auth

        response3 = response2.connection.send(request, **args)

        # Update the history.
        response3.history.append(response)
        response3.history.append(response2)

        return response3