예제 #1
0
파일: messages.py 프로젝트: yyolk/pyspnego
    def __init__(self, flags=0, domain_name=None, workstation=None, version=None, encoding=None, _b_data=None):
        # type: (int, Optional[text_type], Optional[text_type], Optional[Version], Optional[str], Optional[bytes]) -> None  # noqa
        super(Negotiate, self).__init__(encoding=encoding, _b_data=_b_data)

        if not _b_data:
            b_payload = bytearray()

            payload_offset = 32

            b_version = b""
            if version:
                flags |= NegotiateFlags.version
                b_version = version.pack()
            payload_offset = _pack_payload(b_version, b_payload, payload_offset)[1]

            b_domain_name = b""
            if domain_name:
                flags |= NegotiateFlags.oem_domain_name_supplied
                b_domain_name = to_bytes(domain_name, encoding=self._encoding)
            b_domain_name_fields, payload_offset = _pack_payload(b_domain_name, b_payload, payload_offset)

            b_workstation = b""
            if workstation:
                flags |= NegotiateFlags.oem_workstation_supplied
                b_workstation = to_bytes(workstation, encoding=self._encoding)
            b_workstation_fields = _pack_payload(b_workstation, b_payload, payload_offset)[0]

            b_data = bytearray(self.signature)
            b_data.extend(struct.pack("<I", flags))
            b_data.extend(b_domain_name_fields)
            b_data.extend(b_workstation_fields)
            b_data.extend(b_payload)

            self._data = memoryview(b_data)
예제 #2
0
파일: messages.py 프로젝트: yyolk/pyspnego
    def __init__(self, flags=0, lm_challenge_response=None, nt_challenge_response=None, domain_name=None,
                 username=None, workstation=None, encrypted_session_key=None, version=None, mic=None, encoding=None,
                 _b_data=None):
        # type: (int, Optional[bytes], Optional[bytes], Optional[text_type], Optional[text_type], Optional[text_type], Optional[bytes], Optional[Version], Optional[bytes], Optional[str], Optional[bytes]) -> None  # noqa
        super(Authenticate, self).__init__(encoding=encoding, _b_data=_b_data)

        if _b_data:
            self._encoding = 'utf-16-le' if self.flags & NegotiateFlags.unicode else self._encoding

        else:
            self._encoding = 'utf-16-le' if flags & NegotiateFlags.unicode else self._encoding

            b_payload = bytearray()

            payload_offset = 64

            # While MS server accept a blank version field, other implementations aren't so kind. No need to be strict
            # about it and only add the version bytes if it's present.
            b_version = b""
            if version:
                flags |= NegotiateFlags.version
                b_version = version.pack()
            payload_offset = _pack_payload(b_version, b_payload, payload_offset)[1]

            # MIC
            payload_offset = _pack_payload(b"\x00" * 16, b_payload, payload_offset)[1]

            b_lm_response_fields, payload_offset = _pack_payload(lm_challenge_response, b_payload, payload_offset)
            b_nt_response_fields, payload_offset = _pack_payload(nt_challenge_response, b_payload, payload_offset)
            b_domain_fields, payload_offset = _pack_payload(domain_name, b_payload, payload_offset,
                                                            lambda d: to_bytes(d, encoding=self._encoding))
            b_username_fields, payload_offset = _pack_payload(username, b_payload, payload_offset,
                                                              lambda d: to_bytes(d, encoding=self._encoding))
            b_workstation_fields, payload_offset = _pack_payload(workstation, b_payload, payload_offset,
                                                                 lambda d: to_bytes(d, encoding=self._encoding))
            if encrypted_session_key:
                flags |= NegotiateFlags.key_exch
            b_session_key_fields = _pack_payload(encrypted_session_key, b_payload, payload_offset)[0]

            b_data = bytearray(self.signature)
            b_data.extend(b_lm_response_fields)
            b_data.extend(b_nt_response_fields)
            b_data.extend(b_domain_fields)
            b_data.extend(b_username_fields)
            b_data.extend(b_workstation_fields)
            b_data.extend(b_session_key_fields)
            b_data.extend(struct.pack("<I", flags))
            b_data.extend(b_payload)

            self._data = memoryview(b_data)

            if mic:
                self.mic = mic
예제 #3
0
파일: crypto.py 프로젝트: yyolk/pyspnego
def ntowfv2(
        username, nt_hash,
        domain_name):  # type: (text_type, bytes, Optional[text_type]) -> bytes
    """NTLMv2 NTOWFv2 function

    The NT v2 one way function as documented under `NTLM v2 Authentication`_.

    The pseudo-code for this function is::

        Define NTOWFv2(Passwd, User, UserDom) as

            HMAC_MD5(MD4(UNICODE(Passwd)), UNICODE(ConcatenationOf(Uppercase(User), UserDom)))

    Args:
        username: The username.
        nt_hash: The NT hash from :meth:`ntowfv1`.
        domain_name: The optional domain name of the user.

    Returns:
        bytes: The NTv2 one way has of the user's credentials.

    .. _NTLM v2 Authentication:
        https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/5e550938-91d4-459f-b67d-75d70009e3f3
    """
    b_user = to_bytes(username.upper() + (domain_name or u""),
                      encoding='utf-16-le')
    return hmac_md5(nt_hash, b_user)
예제 #4
0
파일: crypto.py 프로젝트: yyolk/pyspnego
def lmowfv1(password):  # type: (text_type) -> bytes
    """NTLMv1 LMOWFv1 function

    The Lan Manager v1 one way function as documented under `NTLM v1 Authentication`_.

    The pseudo-code for this function is::

        Define LMOWFv1(Passwd, User, UserDom) as
            ConcatenationOf(
                DES(UpperCase(Passwd)[0..6], "KGS!@#$%"),
                DES(UpperCase(Passwd)[7..13], "KGS!@#$%"),
            );

    Args:
        password: The password for the user.

    Returns:
        bytes: The LMv1 one way hash of the user's password.

    .. _NTLM v1 Authentication:
        https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/464551a8-9fc4-428e-b3d3-bc5bfb2e73a5
    """
    if _is_ntlm_hash(password):
        return base64.b16decode(password.split(':')[0].upper())

    # Fix the password to upper case and pad the length to exactly 14 bytes. While it is true LM only authentication
    # will fail if the password exceeds 14 bytes typically it is used in conjunction with the NTv1 hash which has no
    # such restrictions.
    b_password = to_bytes(password.upper()).ljust(14, b"\x00")[:14]

    b_hash = io.BytesIO()
    for start, end in [(0, 7), (7, 14)]:
        b_hash.write(des(b_password[start:end], b'KGS!@#$%'))

    return b_hash.getvalue()
예제 #5
0
파일: _asn1.py 프로젝트: yyolk/pyspnego
def pack_asn1_general_string(value, tag=True, encoding='ascii'):  # type: (Union[str, bytes], bool, str) -> bytes
    """ Packs an string value into an ASN.1 GeneralString byte value with optional universal tagging. """
    b_data = to_bytes(value, encoding=encoding)
    if tag:
        b_data = pack_asn1(TagClass.universal, False, TypeTagNumber.general_string, b_data)

    return b_data
예제 #6
0
파일: conftest.py 프로젝트: yyolk/pyspnego
def ntlm_cred(tmpdir, monkeypatch):
    cleanup = None
    try:
        # Use unicode credentials to test out edge cases when dealing with non-ascii chars.
        username = u'ÜseӜ'
        password = u'Pӓ$sw0r̈d'

        if HAS_SSPI:
            domain = to_text(socket.gethostname())

            # Can only test this out with Windows due to issue with gss-ntlmssp when dealing with surrogate pairs.
            # https://github.com/gssapi/gss-ntlmssp/issues/20
            clef = to_text(b"\xF0\x9D\x84\x9E")
            username += clef
            password += clef

            buff = {
                'name': username,
                'password': password,
                'priv': win32netcon.USER_PRIV_USER,
                'comment': 'Test account for pypsnego tests',
                'flags': win32netcon.UF_NORMAL_ACCOUNT,
            }
            try:
                win32net.NetUserAdd(None, 1, buff)
            except win32net.error as err:
                if err.winerror != 2224:  # Account already exists
                    raise

            def cleanup():
                win32net.NetUserDel(None, username)
        else:
            domain = u'Dȫm̈Ąiᴞ'

            # gss-ntlmssp does a string comparison of the user/domain part using the current process locale settings.
            # To ensure it matches the credentials we specify with the non-ascii chars we need to ensure the locale is
            # something that can support UTF-8 character comparison. macOS can fail with unknown locale on getlocale(),
            # just default to env vars if this get fails.
            try:
                original_locale = locale.getlocale(locale.LC_CTYPE)
            except ValueError:
                original_locale = (None, None)

            def cleanup():
                locale.setlocale(locale.LC_CTYPE, original_locale)

            locale.setlocale(locale.LC_CTYPE, 'en_US.UTF-8')

        tmp_creds = os.path.join(to_text(tmpdir), u'pÿspᴞӛgӫ TÈ$''.creds')
        with open(tmp_creds, mode='wb') as fd:
            fd.write(to_bytes(u'%s:%s:%s' % (domain, username, password)))

        monkeypatch.setenv('NTLM_USER_FILE', to_native(tmp_creds))

        yield u"%s\\%s" % (domain, username), password

    finally:
        if cleanup:
            cleanup()
예제 #7
0
def test_seal_ntlmv2_no_key_exch():
    flags = NegotiateFlags.seal | NegotiateFlags.sign | NegotiateFlags.extended_session_security | \
        NegotiateFlags.key_128

    seal_key = sealkey(flags, TEST_RANDOM_SESSION_KEY, usage='initiate')
    seal_handle = rc4init(seal_key)
    sign_key = signkey(flags, TEST_RANDOM_SESSION_KEY, usage='initiate')

    b_data = to_bytes(u"Plaintext", encoding='utf-16-le')
    actual_msg, actual_signature = seal(flags, seal_handle, sign_key, 0,
                                        b_data)

    assert actual_msg == b"\x54\xE5\x01\x65\xBF\x19\x36\xDC\x99\x60\x20\xC1\x81\x1B\x0F\x06" \
                         b"\xFB\x5F"
    assert actual_signature == b"\x01\x00\x00\x00\x70\x35\x28\x51\xF2\x56\x43\x09\x00\x00\x00\x00"
예제 #8
0
def test_seal_ntlmv2():
    # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/54973495-20d2-49e8-9925-c399a403ed4a
    flags = NegotiateFlags.seal | NegotiateFlags.sign | NegotiateFlags.extended_session_security | \
        NegotiateFlags.key_exch | NegotiateFlags.key_128

    seal_key = sealkey(flags, TEST_RANDOM_SESSION_KEY, usage='initiate')
    seal_handle = rc4init(seal_key)
    sign_key = signkey(flags, TEST_RANDOM_SESSION_KEY, usage='initiate')

    b_data = to_bytes(u"Plaintext", encoding='utf-16-le')
    actual_msg, actual_signature = seal(flags, seal_handle, sign_key, 0,
                                        b_data)

    assert actual_msg == b"\x54\xE5\x01\x65\xBF\x19\x36\xDC\x99\x60\x20\xC1\x81\x1B\x0F\x06" \
                         b"\xFB\x5F"
    assert actual_signature == b"\x01\x00\x00\x00\x7F\xB3\x8E\xC5\xC5\x5D\x49\x76\x00\x00\x00\x00"
예제 #9
0
def test_get_credential_from_file(line, username, domain, lm_hash, nt_hash,
                                  explicit, tmpdir, monkeypatch):
    tmp_creds = os.path.join(to_text(tmpdir), u'pÿspᴞӛgӫ TÈ$' '.creds')
    monkeypatch.setenv('NTLM_USER_FILE', to_native(tmp_creds))
    with open(tmp_creds, mode='wb') as fd:
        fd.write(to_bytes(line))

    if explicit:
        actual = ntlm._NTLMCredential(domain, username)

    else:
        actual = ntlm._NTLMCredential()

    assert actual.username == username
    assert actual.domain == domain
    assert actual.lm_hash == base64.b16decode(lm_hash)
    assert actual.nt_hash == base64.b16decode(nt_hash)
예제 #10
0
파일: messages.py 프로젝트: yyolk/pyspnego
    def __init__(self, flags=0, server_challenge=None, target_name=None, target_info=None, version=None, encoding=None,
                 _b_data=None):
        # type: (int, Optional[bytes], Optional[text_type], Optional[TargetInfo], Optional[Version], Optional[str], Optional[bytes]) -> None  # noqa
        super(Challenge, self).__init__(encoding=encoding, _b_data=_b_data)

        if _b_data:
            self._encoding = 'utf-16-le' if self.flags & NegotiateFlags.unicode else self._encoding

        else:
            self._encoding = 'utf-16-le' if flags & NegotiateFlags.unicode else self._encoding

            b_payload = bytearray()
            payload_offset = 48

            b_version = b""
            if version:
                flags |= NegotiateFlags.version
                b_version = version.pack()
            payload_offset = _pack_payload(b_version, b_payload, payload_offset)[1]

            b_target_name = b""
            if target_name:
                flags |= NegotiateFlags.request_target
                b_target_name = to_bytes(target_name, encoding=self._encoding)
            b_target_name_fields, payload_offset = _pack_payload(b_target_name, b_payload, payload_offset)

            b_target_info = b""
            if target_info:
                flags |= NegotiateFlags.target_info
                b_target_info = target_info.pack()
            b_target_info_fields = _pack_payload(b_target_info, b_payload, payload_offset)[0]

            b_data = bytearray(self.signature)
            b_data.extend(b_target_name_fields)
            b_data.extend(struct.pack("<I", flags))
            b_data.extend(b"\x00" * 8)  # ServerChallenge, set after self._data is initialised.
            b_data.extend(b"\x00" * 8)  # Reserved
            b_data.extend(b_target_info_fields)
            b_data.extend(b_payload)

            self._data = memoryview(b_data)

            if server_challenge:
                self.server_challenge = server_challenge
예제 #11
0
def test_seal_ntlmv1():
    # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/9e2b483e-d185-4feb-aa4f-db6e2c0c49d9
    seal_key = sealkey(TEST_NTLMV1_FLAGS,
                       TEST_RANDOM_SESSION_KEY,
                       usage='initiate')
    seal_handle = rc4init(seal_key)
    sign_key = signkey(TEST_NTLMV1_FLAGS,
                       TEST_RANDOM_SESSION_KEY,
                       usage='initiate')

    b_data = to_bytes(u"Plaintext", encoding='utf-16-le')
    actual_msg, actual_signature = seal(TEST_NTLMV1_FLAGS, seal_handle,
                                        sign_key, 0, b_data)

    assert actual_msg == b"\x56\xFE\x04\xD8\x61\xF9\x31\x9A\xF0\xD7\x23\x8A\x2E\x3B\x4D\x45" \
                         b"\x7F\xB8"

    # The docs example seems to keep the random pad in the signature even though the actual function definition sets
    # that to 0x00000000. Assert the actual working implementation that has been tested against MS servers.
    assert actual_signature == b"\x01\x00\x00\x00\x00\x00\x00\x00\x09\xDC\xD1\xDF\x2E\x45\x9D\x36"
예제 #12
0
파일: crypto.py 프로젝트: yyolk/pyspnego
def ntowfv1(password):  # type: (text_type) -> bytes
    """NTLMv1 NTOWFv1 function

    The NT v1 one way function as documented under `NTLM v1 Authentication`_.

    The pseudo-code for this function is::

        Define NTOWFv1(Passwd, User, UserDom) as MD4(UNICODE(Passwd))

    Args:
        password: The password for the user.

    Returns:
        bytes: The NTv1 one way hash of the user's password.

    .. _NTLM v1 Authentication:
        https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/464551a8-9fc4-428e-b3d3-bc5bfb2e73a5
    """
    if _is_ntlm_hash(password):
        return base64.b16decode(password.split(':')[1].upper())

    return md4(to_bytes(password, encoding='utf-16-le'))
예제 #13
0
def test_seal_ntlmv1_with_ess():
    # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/052aef59-b55b-4800-b4a8-e93eca1600d6
    key_exchange_key = compute_response_v1(TEST_NTLMV1_CLIENT_CHALLENGE_FLAGS,
                                           ntowfv1(TEST_PASSWD),
                                           lmowfv1(TEST_PASSWD),
                                           TEST_SERVER_CHALLENGE,
                                           TEST_CLIENT_CHALLENGE)[2]
    seal_key = sealkey(TEST_NTLMV1_CLIENT_CHALLENGE_FLAGS,
                       key_exchange_key,
                       usage='initiate')
    seal_handle = rc4init(seal_key)
    sign_key = signkey(TEST_NTLMV1_CLIENT_CHALLENGE_FLAGS,
                       key_exchange_key,
                       usage='initiate')

    b_data = to_bytes(u"Plaintext", encoding='utf-16-le')
    actual_msg, actual_signature = seal(TEST_NTLMV1_CLIENT_CHALLENGE_FLAGS,
                                        seal_handle, sign_key, 0, b_data)

    assert actual_msg == b"\xA0\x23\x72\xF6\x53\x02\x73\xF3\xAA\x1E\xB9\x01\x90\xCE\x52\x00" \
                         b"\xC9\x9D"
    assert actual_signature == b"\x01\x00\x00\x00\xFF\x2A\xEB\x52\xF6\x81\x79\x3A\x00\x00\x00\x00"
예제 #14
0
파일: messages.py 프로젝트: yyolk/pyspnego
    def pack(self):  # type: () -> bytes
        """ Packs the structure to bytes. """
        b_data = io.BytesIO()

        for av_id, value in self.items():
            # MsvAvEOL should only be set at the end, will just ignore these entries.
            if av_id == AvId.eol:
                continue

            if av_id in self._FIELD_TYPES['text']:
                b_value = to_bytes(value, encoding='utf-16-le')
            elif av_id in self._FIELD_TYPES['int32']:
                b_value = struct.pack("<I", value)
            elif av_id in self._FIELD_TYPES['struct']:
                b_value = value.pack()
            else:
                b_value = value

            b_data.write(struct.pack("<HH", av_id, len(b_value)) + b_value)

        b_data.write(b"\x00\x00\x00\x00")  # MsvAvEOL
        return b_data.getvalue()
예제 #15
0
def _get_gssapi_credential(mech,
                           usage,
                           username=None,
                           password=None,
                           context_req=None):
    # type: (gssapi.OID, str, Optional[text_type], Optional[text_type]) -> gssapi.creds.Credentials
    """Gets a set of credential(s).

    Will get a set of GSSAPI credential(s) for the mech specified. If the username and password is specified then a new
    set of credentials are explicitly required for the mech specified. Otherwise the credentials are retrieved by the
    cache as defined by the mech.

    The behaviour of this function is highly dependent on the GSSAPI implementation installed as well as what NTLM mech
    is available. Here are some of the known behaviours of each mech.

    Kerberos:
        Works for any GSSAPI implementation. The cache is the CCACHE which can be managed with `kinit`.

    NTLM:
        Only works with MIT krb5 and requires `gss-ntlmssp`_ to be installed. The cache that this mech uses is either
        a plaintext file specified by `NTLM_USER_FILE` in the format `DOMAIN:USERNAME:PASSWORD` or
        `:USER_UPN@REALM:PASSWORD` or it can be configured with winbind to a standalone Samba setup or with AD.

    SPNEGO:
        To work properly it requires both Kerberos and NTLM to be available where the latter only works with MIT krb5,
        see `NTLM` for more details. It attempts to get a credential for the all the mechs that SPNEGO supports so it
        will retrieve a Kerberos cred then NTLM.

    Args:
        mech: The mech OID to get the credentials for.
        usage: Either `initiate` for a client context or `accept` for a server context.
        username: The username to get the credentials for, if omitted then the default user is gotten from the cache.
        password: The password for the user, if omitted then the cached credentials is retrieved.

    Returns:
        gssapi.creds.Credentials: The credential set that was created/retrieved.

    .. _gss-ntlmssp:
        https://github.com/gssapi/gss-ntlmssp
    """
    if username:
        name_type = getattr(
            gssapi.NameType,
            'user' if usage == 'initiate' else 'hostbased_service')
        username = gssapi.Name(base=username, name_type=name_type)

    if username and password:
        # NOTE: MIT krb5 < 1.14 would store this cred in the global cache but later versions used a private cache in
        # memory. There's not much we can do about this but document this behaviour and hope people upgrade to a newer
        # version.
        # GSSAPI offers no way to specify custom flags like forwardable. We use a temp conf file to ensure an explicit
        # cred with the delegate flag will actually be forwardable.
        forwardable = False
        forwardable_mechs = [
            gssapi.OID.from_int_seq(GSSMech.kerberos.value),
            gssapi.OID.from_int_seq(GSSMech.spnego.value)
        ]
        if context_req and context_req & ContextReq.delegate and mech in forwardable_mechs:
            forwardable = True

        with _krb5_conf(forwardable=forwardable):
            cred = acquire_cred_with_password(username,
                                              to_bytes(password),
                                              usage=usage,
                                              mechs=[mech])

        return cred.creds

    cred = gssapi.Credentials(name=username, usage=usage, mechs=[mech])

    # We don't need to check the actual lifetime, just trying to get the valid will have gssapi check the lifetime and
    # raise an ExpiredCredentialsError if it is expired.
    _ = cred.lifetime

    return cred