From 64c3ac9a2cd4702be9a1c9eeaf4f1ed86b43699f Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Mon, 18 Sep 2023 21:15:17 +0000 Subject: [PATCH] Added query context attributes --- setup.py | 2 +- src/sspi/__init__.py | 12 +++ src/sspi/_query_context.pyi | 96 ++++++++++++++++++ src/sspi/_query_context.pyx | 184 ++++++++++++++++++++++++++++++++++ src/sspi/_security_buffer.pyi | 2 +- src/sspi/_security_buffer.pyx | 3 +- tests/conftest.py | 91 +++++++++++++++++ tests/test_query_context.py | 60 +++++++++++ tests/test_security_buffer.py | 109 ++++++++++++++++++++ 9 files changed, 555 insertions(+), 4 deletions(-) create mode 100644 src/sspi/_query_context.pyi create mode 100644 src/sspi/_query_context.pyx create mode 100644 tests/conftest.py create mode 100644 tests/test_query_context.py diff --git a/setup.py b/setup.py index f281498..9447c32 100644 --- a/setup.py +++ b/setup.py @@ -51,11 +51,11 @@ def make_extension( for e in [ "credential", "ntstatus", + "query_context", "security_buffer", "security_context", "security_package", "text", - # ("acquire_credentials_handle", "AcquireCredentialsHandleW"), ]: name = e libraries = ["Secur32"] diff --git a/src/sspi/__init__.py b/src/sspi/__init__.py index e43154d..2481b97 100644 --- a/src/sspi/__init__.py +++ b/src/sspi/__init__.py @@ -10,6 +10,13 @@ acquire_credentials_handle, ) from ._ntstatus import NtStatus +from ._query_context import ( + SecPkgBuffer, + SecPkgContextNames, + SecPkgContextSessionKey, + SecPkgContextSizes, + query_context_attributes, +) from ._security_buffer import ( SECBUFFER_VERSION, SecBuffer, @@ -58,6 +65,10 @@ "SecBufferFlags", "SecBufferType", "SecChannelBindings", + "SecPkgBuffer", + "SecPkgContextNames", + "SecPkgContextSessionKey", + "SecPkgContextSizes", "SecPkgInfo", "SecurityPackageCapability", "SecurityContext", @@ -69,4 +80,5 @@ "complete_auth_token", "enumerate_security_packages", "initialize_security_context", + "query_context_attributes", ] diff --git a/src/sspi/_query_context.pyi b/src/sspi/_query_context.pyi new file mode 100644 index 0000000..6bfad60 --- /dev/null +++ b/src/sspi/_query_context.pyi @@ -0,0 +1,96 @@ +# Copyright: (c) 2023 Jordan Borean (@jborean93) +# MIT License (see LICENSE or https://opensource.org/licenses/MIT) + +from __future__ import annotations + +import typing as t + +from ._security_context import SecurityContext + +T = t.TypeVar("T", bound=SecPkgBuffer) + +class SecPkgBuffer: + """Base class for context attribute types.""" + +class SecPkgContextNames(SecPkgBuffer): + """Security Package Names + + The structure indicates the name of the user associated with a security + context. + + This wraps the `SecPkgContext_NamesW`_ Win32 structure. + + .. _SecPkgContext_NamesW: + https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secpkgcontext_namesw + """ + + @property + def username(self) -> str: + """The user represented by the context.""" + +class SecPkgContextSessionKey(SecPkgBuffer): + """Security Package session key. + + The structure contains information about the session key used for the + security context. + + This wraps the `SecPkgContext_SessionKey`_ Win32 structure. + + .. _SecPkgContext_SessionKey: + https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secpkgcontext_sessionkey + """ + + @property + def session_key(self) -> bytes: + """The session key for the security context.""" + +class SecPkgContextSizes(SecPkgBuffer): + """Security Package sizes. + + The structure indicates the sizes of important structures used in the + message support functions. Use :meth:`query_context_attributes` to generate + this instance. + + This wraps the `SecPkgContext_Sizes`_ Win32 structure. + + .. _SecPkgContextSizes: + https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secpkgcontext_sizes + """ + + @property + def max_token(self) -> int: + """Maximum security token sizes.""" + @property + def max_signature(self) -> int: + """Maximum signature size.""" + @property + def block_size(self) -> int: + """Preferred integral size of messages.""" + @property + def security_trailer(self) -> int: + """Size of the security trailer appended to messages.""" + +def query_context_attributes( + context: SecurityContext, + attribute: type[T], +) -> T: + """Queries an attribute of a security context. + + Enables a transport application to query a security package for certain + attributes of a security context. + + The attribute must be a type that is a subclass of :class:`SecPkgBuffer`. + The instance is created, populated, and returned by this function. + + This wraps the `QueryContextAttributes`_ Win32 function. + + Args: + context: The security context to query. + attribute: The attribute type to query and return. + + Returns: + The instance of the attribute type provided. + + .. _QueryContextAttributes: + https://learn.microsoft.com/en-us/windows/win32/secauthn/querycontextattributes--general + """ diff --git a/src/sspi/_query_context.pyx b/src/sspi/_query_context.pyx new file mode 100644 index 0000000..d057e76 --- /dev/null +++ b/src/sspi/_query_context.pyx @@ -0,0 +1,184 @@ +# Copyright: (c) 2023 Jordan Borean (@jborean93) +# MIT License (see LICENSE or https://opensource.org/licenses/MIT) + +from __future__ import annotations + +import collections +import enum + +from cpython.exc cimport PyErr_SetFromWindowsErr + +from sspi._security_buffer cimport FreeContextBuffer +from sspi._security_context cimport PCtxtHandle, SecurityContext +from sspi._text cimport wide_char_to_str +from sspi._win32_types cimport * + +from sspi._ntstatus import NtStatus + + +cdef extern from "Security.h": + unsigned long SECPKG_ATTR_SIZES + unsigned long SECPKG_ATTR_NAMES + unsigned long SECPKG_ATTR_LIFESPAN + unsigned long SECPKG_ATTR_DCE_INFO + unsigned long SECPKG_ATTR_STREAM_SIZES + unsigned long SECPKG_ATTR_KEY_INFO + unsigned long SECPKG_ATTR_AUTHORITY + unsigned long SECPKG_ATTR_PROTO_INFO + unsigned long SECPKG_ATTR_PASSWORD_EXPIRY + unsigned long SECPKG_ATTR_SESSION_KEY + unsigned long SECPKG_ATTR_PACKAGE_INFO + unsigned long SECPKG_ATTR_USER_FLAGS + unsigned long SECPKG_ATTR_NEGOTIATION_INFO + unsigned long SECPKG_ATTR_NATIVE_NAMES + unsigned long SECPKG_ATTR_FLAGS + unsigned long SECPKG_ATTR_USE_VALIDATED + unsigned long SECPKG_ATTR_CREDENTIAL_NAME + unsigned long SECPKG_ATTR_TARGET_INFORMATION + unsigned long SECPKG_ATTR_ACCESS_TOKEN + unsigned long SECPKG_ATTR_TARGET + unsigned long SECPKG_ATTR_AUTHENTICATION_ID + unsigned long SECPKG_ATTR_LOGOFF_TIME + unsigned long SECPKG_ATTR_NEGO_KEYS + unsigned long SECPKG_ATTR_PROMPTING_NEEDED + unsigned long SECPKG_ATTR_UNIQUE_BINDINGS + unsigned long SECPKG_ATTR_ENDPOINT_BINDINGS + unsigned long SECPKG_ATTR_CLIENT_SPECIFIED_TARGET + unsigned long SECPKG_ATTR_LAST_CLIENT_TOKEN_STATUS + unsigned long SECPKG_ATTR_NEGO_PKG_INFO + unsigned long SECPKG_ATTR_NEGO_STATUS + unsigned long SECPKG_ATTR_CONTEXT_DELETED + unsigned long SECPKG_ATTR_DTLS_MTU + unsigned long SECPKG_ATTR_DATAGRAM_SIZES + unsigned long SECPKG_ATTR_SUBJECT_SECURITY_ATTRIBUTES + unsigned long SECPKG_ATTR_APPLICATION_PROTOCOL + unsigned long SECPKG_ATTR_NEGOTIATED_TLS_EXTENSIONS + unsigned long SECPKG_ATTR_IS_LOOPBACK + + cdef struct _SecPkgContext_NamesW: + LPWSTR sUserName + ctypedef _SecPkgContext_NamesW SecPkgContext_NamesW + ctypedef SecPkgContext_NamesW *PSecPkgContext_NamesW + + cdef struct _SecPkgContext_Sizes: + unsigned long cbMaxToken + unsigned long cbMaxSignature + unsigned long cbBlockSize + unsigned long cbSecurityTrailer + ctypedef _SecPkgContext_Sizes SecPkgContext_Sizes + ctypedef SecPkgContext_Sizes *PSecPkgContext_Sizes + + cdef struct _SecPkgContext_SessionKey: + unsigned long SessionKeyLength + unsigned char *SessionKey + ctypedef _SecPkgContext_SessionKey SecPkgContext_SessionKey + ctypedef SecPkgContext_SessionKey *PSecPkgContext_SessionKey + + # https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-querycontextattributesw + SECURITY_STATUS QueryContextAttributesW( + PCtxtHandle phContext, + unsigned long ulAttribute, + void *pBuffer + ) nogil + +cdef class SecPkgBuffer: + + cdef (unsigned long, void *) __c_value__(SecPkgBuffer self): + return (0, NULL) + +cdef class SecPkgContextNames(SecPkgBuffer): + cdef SecPkgContext_NamesW raw + + def __dealloc__(SecPkgContextNames self): + if self.raw.sUserName: + FreeContextBuffer(self.raw.sUserName) + self.raw.sUserName = NULL + + cdef (unsigned long, void *) __c_value__(SecPkgContextNames self): + return (SECPKG_ATTR_NAMES, &self.raw) + + def __repr__(SecPkgContextNames self): + return f"SecPkgContextNames(username={self.username!r})" + + @property + def username(SecPkgContextNames self) -> str: + if self.raw.sUserName == NULL: + return "" + else: + return wide_char_to_str(self.raw.sUserName) + +cdef class SecPkgContextSessionKey(SecPkgBuffer): + cdef SecPkgContext_SessionKey raw + + def __dealloc__(SecPkgContextSessionKey self): + if self.raw.SessionKey: + FreeContextBuffer(self.raw.SessionKey) + self.raw.SessionKeyLength = 0 + self.raw.SessionKey = NULL + + cdef (unsigned long, void *) __c_value__(SecPkgContextSessionKey self): + return (SECPKG_ATTR_SESSION_KEY, &self.raw) + + def __repr__(SecPkgContextSessionKey self): + return f"SecPkgContextSessionKey(session_key={self.session_key!r})" + + @property + def session_key(SecPkgContextSessionKey self) -> bytes: + if self.raw.SessionKeyLength and self.raw.SessionKey != NULL: + return (self.raw.SessionKey)[:self.raw.SessionKeyLength] + else: + return b"" + +cdef class SecPkgContextSizes(SecPkgBuffer): + cdef SecPkgContext_Sizes raw + + cdef (unsigned long, void *) __c_value__(SecPkgContextSizes self): + return (SECPKG_ATTR_SIZES, &self.raw) + + def __repr__(SecPkgContextSizes self) -> str: + kwargs = [f"{k}={v}" for k, v in { + 'max_token': self.max_token, + 'max_signature': self.max_signature, + 'block_size': self.block_size, + 'security_trailer': self.security_trailer, + }.items()] + + return f"SecPkgContextSizes({', '.join(kwargs)})" + + @property + def max_token(SecPkgContextSizes self) -> int: + return self.raw.cbMaxToken + + @property + def max_signature(SecPkgContextSizes self) -> int: + return self.raw.cbMaxSignature + + @property + def block_size(SecPkgContextSizes self) -> int: + return self.raw.cbBlockSize + + @property + def security_trailer(SecPkgContextSizes self) -> int: + return self.raw.cbSecurityTrailer + +def query_context_attributes( + SecurityContext context not None, + type attribute not None, +) -> SecPkgBuffer: + if not issubclass(attribute, SecPkgBuffer): + raise TypeError("attribute must be a type of SecPkgBuffer") + + cdef SecPkgBuffer value = attribute() + cdef (unsigned long, void*) raw = value.__c_value__() + + with nogil: + res = QueryContextAttributesW( + &context.raw, + raw[0], + raw[1], + ) + + if res: + PyErr_SetFromWindowsErr(res) + + return value diff --git a/src/sspi/_security_buffer.pyi b/src/sspi/_security_buffer.pyi index 5ae34d0..23dee82 100644 --- a/src/sspi/_security_buffer.pyi +++ b/src/sspi/_security_buffer.pyi @@ -106,7 +106,7 @@ class SecBufferDesc: ) -> None: ... def __iter__(self) -> list[SecBuffer]: """Creates an iterable of the contained buffers.""" - def __l0en__(self) -> int: + def __len__(self) -> int: """Returns the number of buffers in this structure.""" def __getitem__(self, key: int) -> SecBuffer: """Gets the buffer at the specified index.""" diff --git a/src/sspi/_security_buffer.pyx b/src/sspi/_security_buffer.pyx index 77809e8..86bc866 100644 --- a/src/sspi/_security_buffer.pyx +++ b/src/sspi/_security_buffer.pyx @@ -142,7 +142,6 @@ cdef class SecBufferDesc: return self.raw.cBuffers def __getitem__(SecBufferDesc self, key: int) -> SecBuffer: - # FIXME: Add checks for the index. return self._buffers[key] cdef void mark_as_allocated(SecBufferDesc self): @@ -192,7 +191,7 @@ cdef class SecBuffer: def __repr__(SecBuffer self) -> str: kwargs = [f"{k}={v}" for k, v in { - 'data': repr(self.data), + 'data': f"bytearray({self.data!r})", 'buffer_type': self.buffer_type, 'buffer_flags': self.buffer_flags, }.items()] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..c3b490c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,91 @@ +# Copyright: (c) 2023 Jordan Borean (@jborean93) +# MIT License (see LICENSE or https://opensource.org/licenses/MIT) + +from __future__ import annotations + +import socket +import typing as t + +import pytest + +import sspi + + +@pytest.fixture() +def authenticated_contexts() -> tuple[sspi.InitiatorSecurityContext, sspi.AcceptorSecurityContext]: + spn = f"host/{socket.gethostname()}" + c_cred = sspi.acquire_credentials_handle(None, "Negotiate", sspi.CredentialUse.SECPKG_CRED_OUTBOUND) + s_cred = sspi.acquire_credentials_handle(None, "Negotiate", sspi.CredentialUse.SECPKG_CRED_INBOUND) + isc_req = ( + sspi.IscReq.ISC_REQ_ALLOCATE_MEMORY + | sspi.IscReq.ISC_REQ_CONFIDENTIALITY + | sspi.IscReq.ISC_REQ_INTEGRITY + | sspi.IscReq.ISC_REQ_SEQUENCE_DETECT + | sspi.IscReq.ISC_REQ_REPLAY_DETECT + ) + asc_req = ( + sspi.AscReq.ASC_REQ_ALLOCATE_MEMORY + | sspi.AscReq.ASC_REQ_CONFIDENTIALITY + | sspi.AscReq.ASC_REQ_INTEGRITY + | sspi.AscReq.ASC_REQ_SEQUENCE_DETECT + | sspi.AscReq.ASC_REQ_REPLAY_DETECT + ) + + c_ctx = s_ctx = server_token = None + while True: + c_input_buffers = None + if server_token: + c_input_buffers = sspi.SecBufferDesc( + [ + sspi.SecBuffer(server_token, sspi.SecBufferType.SECBUFFER_TOKEN), + ] + ) + + c_output_buffers = sspi.SecBufferDesc( + [ + sspi.SecBuffer(None, sspi.SecBufferType.SECBUFFER_TOKEN), + ] + ) + c_ctx, c_status = sspi.initialize_security_context( + credential=c_cred, + context=c_ctx, + target_name=spn, + context_req=isc_req, + target_data_rep=sspi.TargetDataRep.SECURITY_NATIVE_DREP, + input_buffers=c_input_buffers, + output_buffers=c_output_buffers, + ) + + if c_output_buffers[0].count: + s_input_buffers = sspi.SecBufferDesc( + [ + sspi.SecBuffer(c_output_buffers[0].dangerous_get_view(), sspi.SecBufferType.SECBUFFER_TOKEN), + ] + ) + s_output_buffers = sspi.SecBufferDesc( + [ + sspi.SecBuffer(None, sspi.SecBufferType.SECBUFFER_TOKEN), + ] + ) + s_ctx, s_status = sspi.accept_security_context( + credential=s_cred, + context=s_ctx, + input_buffers=s_input_buffers, + context_req=asc_req, + target_data_rep=sspi.TargetDataRep.SECURITY_NATIVE_DREP, + output_buffers=s_output_buffers, + ) + + if s_output_buffers[0].count: + server_token = bytearray(s_output_buffers[0].data) + elif s_status == sspi.NtStatus.SEC_E_OK: + break + else: + raise ValueError(f"Expected SEC_E_OK but got {c_status.name}") + + elif c_status == sspi.NtStatus.SEC_E_OK: + break + else: + raise ValueError(f"Expected SEC_E_OK but got {c_status.name}") + + return c_ctx, t.cast(sspi.AcceptorSecurityContext, s_ctx) diff --git a/tests/test_query_context.py b/tests/test_query_context.py new file mode 100644 index 0000000..8f929e3 --- /dev/null +++ b/tests/test_query_context.py @@ -0,0 +1,60 @@ +# Copyright: (c) 2023 Jordan Borean (@jborean93) +# MIT License (see LICENSE or https://opensource.org/licenses/MIT) + +from __future__ import annotations + +import sspi + + +def test_query_session_keys( + authenticated_contexts: tuple[sspi.InitiatorSecurityContext, sspi.AcceptorSecurityContext], +) -> None: + c_actual = sspi.query_context_attributes(authenticated_contexts[0], sspi.SecPkgContextSessionKey) + assert isinstance(c_actual, sspi.SecPkgContextSessionKey) + assert isinstance(c_actual.session_key, bytes) + + s_actual = sspi.query_context_attributes(authenticated_contexts[1], sspi.SecPkgContextSessionKey) + assert isinstance(s_actual, sspi.SecPkgContextSessionKey) + assert isinstance(s_actual.session_key, bytes) + + assert c_actual.session_key == s_actual.session_key + assert repr(c_actual) == repr(s_actual) + + +def test_query_names( + authenticated_contexts: tuple[sspi.InitiatorSecurityContext, sspi.AcceptorSecurityContext], +) -> None: + c_actual = sspi.query_context_attributes(authenticated_contexts[0], sspi.SecPkgContextNames) + assert isinstance(c_actual, sspi.SecPkgContextNames) + assert isinstance(c_actual.username, str) + + s_actual = sspi.query_context_attributes(authenticated_contexts[1], sspi.SecPkgContextNames) + assert isinstance(s_actual, sspi.SecPkgContextNames) + assert isinstance(s_actual.username, str) + + assert c_actual.username == s_actual.username + assert repr(c_actual) == repr(s_actual) + + +def test_query_sizes( + authenticated_contexts: tuple[sspi.InitiatorSecurityContext, sspi.AcceptorSecurityContext], +) -> None: + c_actual = sspi.query_context_attributes(authenticated_contexts[0], sspi.SecPkgContextSizes) + assert isinstance(c_actual, sspi.SecPkgContextSizes) + assert isinstance(c_actual.max_signature, int) + assert isinstance(c_actual.max_token, int) + assert isinstance(c_actual.block_size, int) + assert isinstance(c_actual.security_trailer, int) + + s_actual = sspi.query_context_attributes(authenticated_contexts[1], sspi.SecPkgContextSizes) + assert isinstance(s_actual, sspi.SecPkgContextSizes) + assert isinstance(s_actual.max_signature, int) + assert isinstance(s_actual.max_token, int) + assert isinstance(s_actual.block_size, int) + assert isinstance(s_actual.security_trailer, int) + + assert c_actual.max_signature == s_actual.max_signature + assert c_actual.max_token == s_actual.max_token + assert c_actual.block_size == s_actual.block_size + assert c_actual.security_trailer == s_actual.security_trailer + assert repr(c_actual) == repr(s_actual) diff --git a/tests/test_security_buffer.py b/tests/test_security_buffer.py index a9512d2..cfb5c48 100644 --- a/tests/test_security_buffer.py +++ b/tests/test_security_buffer.py @@ -3,9 +3,118 @@ from __future__ import annotations +import pytest + import sspi +def test_empty_sec_buffer_desc() -> None: + buffers = sspi.SecBufferDesc([]) + assert len(buffers) == 0 + assert list(iter(buffers)) == [] + assert buffers.version == 0 + + +def test_sec_buffer_desc_version() -> None: + buffers = sspi.SecBufferDesc([], version=1) + assert buffers.version == 1 + + +def test_sec_buffer_desc_indexing() -> None: + buffers = sspi.SecBufferDesc( + [ + sspi.SecBuffer(bytearray(b"1"), sspi.SecBufferType.SECBUFFER_DATA, sspi.SecBufferFlags.SECBUFFER_READONLY), + sspi.SecBuffer(bytearray(b"2"), sspi.SecBufferType.SECBUFFER_TOKEN), + sspi.SecBuffer(None, sspi.SecBufferType.SECBUFFER_PADDING), + ] + ) + + assert len(buffers) == 3 + + assert isinstance(buffers[0], sspi.SecBuffer) + assert buffers[0].buffer_type == sspi.SecBufferType.SECBUFFER_DATA + assert buffers[0].buffer_flags == sspi.SecBufferFlags.SECBUFFER_READONLY + assert buffers[0].count == 1 + assert buffers[0].data == b"1" + + assert isinstance(buffers[1], sspi.SecBuffer) + assert buffers[1].buffer_type == sspi.SecBufferType.SECBUFFER_TOKEN + assert buffers[1].buffer_flags == sspi.SecBufferFlags.SECBUFFER_NONE + assert buffers[1].count == 1 + assert buffers[1].data == b"2" + + assert isinstance(buffers[2], sspi.SecBuffer) + assert buffers[2].buffer_type == sspi.SecBufferType.SECBUFFER_PADDING + assert buffers[2].buffer_flags == sspi.SecBufferFlags.SECBUFFER_NONE + assert buffers[2].count == 0 + assert buffers[2].data == b"" + + assert buffers[-1] == buffers[2] + assert buffers[-2] == buffers[1] + assert buffers[-3] == buffers[0] + + with pytest.raises(IndexError): + buffers[3] + + with pytest.raises(IndexError): + buffers[-4] + + +def test_sec_buffer_bytearray() -> None: + data = bytearray(b"data") + buffer = sspi.SecBuffer(data, sspi.SecBufferType.SECBUFFER_DATA) + + assert buffer.count == 4 + assert buffer.data == b"data" + assert buffer.buffer_type == sspi.SecBufferType.SECBUFFER_DATA + assert buffer.buffer_flags == sspi.SecBufferFlags.SECBUFFER_NONE + assert repr(buffer) == "SecBuffer(data=bytearray(b'data'), buffer_type=1, buffer_flags=0)" + assert str(buffer) == "SECBUFFER_DATA" + assert bytes(buffer.dangerous_get_view()) == b"data" + + +def test_sec_buffer_memoryview() -> None: + data = bytearray(b"data") + buffer = sspi.SecBuffer( + memoryview(data), + sspi.SecBufferType.SECBUFFER_TOKEN, + sspi.SecBufferFlags.SECBUFFER_READONLY_WITH_CHECKSUM, + ) + + assert buffer.count == 4 + assert buffer.data == b"data" + assert buffer.buffer_type == sspi.SecBufferType.SECBUFFER_TOKEN + assert buffer.buffer_flags == sspi.SecBufferFlags.SECBUFFER_READONLY_WITH_CHECKSUM + assert repr(buffer) == "SecBuffer(data=bytearray(b'data'), buffer_type=2, buffer_flags=268435456)" + assert str(buffer) == "SECBUFFER_TOKEN|SECBUFFER_READONLY_WITH_CHECKSUM" + assert bytes(buffer.dangerous_get_view()) == b"data" + + +def test_sec_buffer_empty() -> None: + data = bytearray(b"") + buffer = sspi.SecBuffer(data, sspi.SecBufferType.SECBUFFER_DATA) + + assert buffer.count == 0 + assert buffer.data == b"" + assert buffer.buffer_type == sspi.SecBufferType.SECBUFFER_DATA + assert buffer.buffer_flags == sspi.SecBufferFlags.SECBUFFER_NONE + assert repr(buffer) == "SecBuffer(data=bytearray(b''), buffer_type=1, buffer_flags=0)" + assert str(buffer) == "SECBUFFER_DATA" + assert bytes(buffer.dangerous_get_view()) == b"" + + +def test_sec_buffer_none() -> None: + buffer = sspi.SecBuffer(None, sspi.SecBufferType.SECBUFFER_DATA) + + assert buffer.count == 0 + assert buffer.data == b"" + assert buffer.buffer_type == sspi.SecBufferType.SECBUFFER_DATA + assert buffer.buffer_flags == sspi.SecBufferFlags.SECBUFFER_NONE + assert repr(buffer) == "SecBuffer(data=bytearray(b''), buffer_type=1, buffer_flags=0)" + assert str(buffer) == "SECBUFFER_DATA" + assert bytes(buffer.dangerous_get_view()) == b"" + + def test_sec_channel_bindings_nothing() -> None: buffer = sspi.SecChannelBindings() assert (