diff --git a/gssapi/raw/oids.pyx b/gssapi/raw/oids.pyx index a92d25b6..cd54d90f 100644 --- a/gssapi/raw/oids.pyx +++ b/gssapi/raw/oids.pyx @@ -1,5 +1,7 @@ GSSAPI="BASE" # This ensures that a full module is generated by Cython +import six + from libc.string cimport memcmp, memcpy from libc.stdlib cimport free, malloc @@ -61,6 +63,54 @@ cdef class OID: memcpy(self.raw_oid.elements, byte_str, self.raw_oid.length) return 0 + @classmethod + def from_int_seq(cls, integer_sequence): + """Create a OID from a sequence of integers + + This method creates an OID from a sequence of integers. + The sequence can either be in dotted form as a string, + or in list form. + + This method is not for BER-encoded byte strings, which + can be passed directly to the OID constructor. + + Args: + integer_sequence: either a list of integers or + a string in dotted form + + Returns: + OID: the OID represented by the given integer sequence + + Raises: + ValueError: the sequence is less than two elements long + """ + + if isinstance(integer_sequence, six.string_types): + integer_sequence = integer_sequence.split('.') + + oid_seq = [int(x) for x in integer_sequence] + + elements = cls._encode_asn1ber(oid_seq) + + return cls(elements=elements) + + @staticmethod + def _encode_asn1ber(oid_seq): + if len(oid_seq) < 2: + raise ValueError("Sequence must be 2 or more elements long.") + + byte_seq = bytearray([oid_seq[0] * 40 + oid_seq[1]]) + for element in oid_seq[2:]: + element_seq = [element & 0x7f] + + while element > 127: + element >>= 7 + element_seq.insert(0, (element & 0x7f) | 0x80) + + byte_seq.extend(element_seq) + + return bytes(byte_seq) + def __dealloc__(self): # NB(directxman12): MIT Kerberos has gss_release_oid # for this purpose, but it's not in the RFC @@ -70,8 +120,33 @@ cdef class OID: def __bytes__(self): return (self.raw_oid.elements)[:self.raw_oid.length] + def _decode_asn1ber(self): + ber_encoding = self.__bytes__() + # NB(directxman12): indexing a byte string yields an int in Python 3, + # but yields a substring in Python 2 + if six.PY2: + ber_encoding = [ord(c) for c in ber_encoding] + + decoded = [ber_encoding[0] // 40, ber_encoding[0] % 40] + pos = 1 + value = 0 + while pos < len(ber_encoding): + byte = ber_encoding[pos] + if byte & 0x80: + # This is one of the leading bytes + value <<= 7 + value += ((byte & 0x7f) * 128) + else: + # This is the last byte of this value + value += (byte & 0x7f) + decoded.append(value) + value = 0 + pos += 1 + return decoded + def __repr__(self): - return "".format([ord(c) for c in self.__bytes__()]) + dotted_oid = '.'.join(str(x) for x in self._decode_asn1ber()) + return "".format(dotted_oid) def __hash__(self): return hash(self.__bytes__()) diff --git a/gssapi/tests/test_raw.py b/gssapi/tests/test_raw.py index 92c4d4ce..174f74c7 100644 --- a/gssapi/tests/test_raw.py +++ b/gssapi/tests/test_raw.py @@ -780,3 +780,34 @@ def test_basic_wrap_unwrap(self): unwrapped_message.should_be_a(bytes) unwrapped_message.shouldnt_be_empty() unwrapped_message.should_be(b'test message') + + +TEST_OIDS = {'SPNEGO': {'bytes': b'\053\006\001\005\005\002', + 'string': '1.3.6.1.5.5.2'}, + 'KRB5': {'bytes': b'\052\206\110\206\367\022\001\002\002', + 'string': '1.2.840.113554.1.2.2'}, + 'KRB5_OLD': {'bytes': b'\053\005\001\005\002', + 'string': '1.3.5.1.5.2'}, + 'KRB5_WRONG': {'bytes': b'\052\206\110\202\367\022\001\002\002', + 'string': '1.2.840.48018.1.2.2'}, + 'IAKERB': {'bytes': b'\053\006\001\005\002\005', + 'string': '1.3.6.1.5.2.5'}} + + +class TestOIDTransforms(unittest.TestCase): + def test_decode_from_bytes(self): + for oid in TEST_OIDS.values(): + o = gb.OID(elements=oid['bytes']) + text = repr(o) + text.should_be("".format(oid['string'])) + + def test_encode_from_string(self): + for oid in TEST_OIDS.values(): + o = gb.OID.from_int_seq(oid['string']) + o.__bytes__().should_be(oid['bytes']) + + def test_encode_from_int_seq(self): + for oid in TEST_OIDS.values(): + int_seq = oid['string'].split('.') + o = gb.OID.from_int_seq(int_seq) + o.__bytes__().should_be(oid['bytes']) diff --git a/setup.py b/setup.py index b9d91144..558dcb18 100755 --- a/setup.py +++ b/setup.py @@ -194,7 +194,8 @@ def gssapi_modules(lst): keywords=['gssapi', 'security'], install_requires=[ 'enum34', - 'decorator' + 'decorator', + 'six' ], tests_require=[ 'tox'