1# Copyright: (c) 2020, Jordan Borean (@jborean93) <jborean93@gmail.com>
2# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
3
4import enum
5import struct
6import typing
7
8
9def _pack_value(addr_type: typing.Optional["AddressType"], b: typing.Optional[bytes]) -> bytes:
10    """ Packs an type/data entry into the byte structure required. """
11    if not b:
12        b = b""
13
14    return (struct.pack("<I", addr_type) if addr_type is not None else b"") + struct.pack("<I", len(b)) + b
15
16
17def _unpack_value(b_mem: memoryview, offset: int) -> typing.Tuple[bytes, int]:
18    """ Unpacks a raw C struct value to a byte string. """
19    length = struct.unpack("<I", b_mem[offset:offset + 4].tobytes())[0]
20    new_offset = offset + length + 4
21
22    data = b""
23    if length:
24        data = b_mem[offset + 4:offset + 4 + length].tobytes()
25
26    return data, new_offset
27
28
29class AddressType(enum.IntEnum):
30    unspecified = 0  # GSS_C_AF_UNSPEC
31    local = 1  # GSS_C_AF_LOCAL
32    inet = 2  # GSS_C_AF_INET
33    implink = 3  # GSS_C_AF_IMPLINK
34    pup = 4  # GSS_C_AF_PUP
35    chaos = 5  # GSS_C_AF_CHAOS
36    ns = 6  # GSS_C_AF_NS
37    nbs = 8  # GSS_C_AF_NBS
38    ecma = 8  # GSS_C_AF_ECMA
39    datakit = 9  # GSS_C_AF_DATAKIT
40    ccitt = 10  # GSS_C_AF_CCITT
41    sna = 11  # GSS_C_AF_SNA
42    decnet = 12  # GSS_C_AF_DECnet
43    dli = 13  # GSS_C_AF_DLI
44    lat = 14  # GSS_C_AF_LAT
45    hylink = 15  # GSS_C_AF_HYLINK
46    appletalk = 16  # GSS_C_AF_APPLETALK
47    bsc = 17  # GSS_C_AF_BSC
48    dss = 18  # GSS_C_AF_DSS
49    osi = 19  # GSS_C_AF_OSI
50    x25 = 21  # GSS_C_AF_X25
51    inet6 = 24  # GSS_C_AF_INET6
52    nulladdr = 255  # GSS_C_AF_NULLADDR
53
54
55class GssChannelBindings:
56    """Python representation of a GSSAPI Channel Binding data structure.
57
58    A common representation for a GSSAPI Channel Binding data structure that can be passed into a context to bind
59    against that security context. Channel bindings are tags that identify the particular data channel that is used.
60    Because these tags are specific to the originator and recipient applications, they offer more proof of a valid
61    identity. Most HTTPS based authentications just set the application data to b'tls-server-end-point:<cert hash>'.
62
63    Args:
64        initiator_addrtype: The address type of the initiator address.
65        initiator_address: The initiator's address.
66        acceptor_addrtype: The address type of the acceptor address.
67        acceptor_address: The acceptor's address.
68        application_data: Any extra application data to set on the bindings struct.
69    """
70
71    def __init__(
72        self,
73        initiator_addrtype: AddressType = AddressType.unspecified,
74        initiator_address: typing.Optional[bytes] = None,
75        acceptor_addrtype: AddressType = AddressType.unspecified,
76        acceptor_address: typing.Optional[bytes] = None,
77        application_data: typing.Optional[bytes] = None,
78    ) -> None:
79        self.initiator_addrtype = AddressType(initiator_addrtype)
80        self.initiator_address = initiator_address
81        self.acceptor_addrtype = AddressType(acceptor_addrtype)
82        self.acceptor_address = acceptor_address
83        self.application_data = application_data
84
85    def __repr__(self) -> str:
86        return "{0}.{1} initiator_addrtype={2}|initiator_address={3}|acceptor_addrtype={4}|acceptor_address={5}|" \
87               "application_data={6}".format(type(self).__module__, type(self).__name__, repr(self.initiator_addrtype),
88                                             repr(self.initiator_address), repr(self.acceptor_addrtype),
89                                             repr(self.acceptor_address), repr(self.application_data))
90
91    def __str__(self) -> str:
92        return "{0} initiator_addr({1}|{2!r}) | acceptor_addr({3}|{4!r}) | application_data({5!r})".format(
93            type(self).__name__, str(self.initiator_addrtype), self.initiator_address, str(self.acceptor_addrtype),
94            self.acceptor_address, self.application_data
95        )
96
97    def __eq__(self, other: object) -> bool:
98        if not isinstance(other, (bytes, GssChannelBindings)):
99            return False
100
101        if isinstance(other, GssChannelBindings):
102            other = other.pack()
103
104        return self.pack() == other
105
106    def pack(self) -> bytes:
107        """ Pack struct into a byte string. """
108        return b"".join([
109            _pack_value(self.initiator_addrtype, self.initiator_address),
110            _pack_value(self.acceptor_addrtype, self.acceptor_address),
111            _pack_value(None, self.application_data)
112        ])
113
114    @staticmethod
115    def unpack(b_data: bytes) -> "GssChannelBindings":
116        b_mem = memoryview(b_data)
117
118        initiator_addrtype = struct.unpack("<I", b_mem[:4].tobytes())[0]
119        initiator_address, offset = _unpack_value(b_mem, 4)
120
121        acceptor_addrtype = struct.unpack("<I", b_mem[offset:offset + 4].tobytes())[0]
122        acceptor_address, offset = _unpack_value(b_mem, offset + 4)
123
124        application_data = _unpack_value(b_mem, offset)[0]
125
126        return GssChannelBindings(initiator_addrtype=initiator_addrtype, initiator_address=initiator_address,
127                                  acceptor_addrtype=acceptor_addrtype, acceptor_address=acceptor_address,
128                                  application_data=application_data)
129