def peap_challenge_change_cipher_spec(cls, request: AuthRequest,
                                          eap: EapPacket, peap: EapPeapPacket,
                                          session: EapPeapSession):
        assert peap.tls_data

        p_tls_in_data = ctypes.create_string_buffer(peap.tls_data)
        tls_in_data_len = ctypes.c_ulonglong(len(peap.tls_data))

        p_tls_in, p_tls_out = None, None
        try:
            p_tls_in = libhostapd.call_py_wpabuf_alloc(p_tls_in_data,
                                                       tls_in_data_len)
            p_tls_out = libhostapd.call_tls_connection_server_handshake(
                tls_connection=session.tls_connection, p_tls_in=p_tls_in)
            tls_out_data_len = p_tls_out.contents.used
            tls_out_data = ctypes.string_at(p_tls_out.contents.buf,
                                            tls_out_data_len)
            peap_reply = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                       id=session.current_eap_id,
                                       tls_data=tls_out_data)
            reply = AuthResponse.create_peap_challenge(
                request=request,
                peap=peap_reply,
                session_id=session.session_id)
            request.reply_to(reply)
            session.set_reply(reply)
        finally:
            libhostapd.call_free_alloc(p_tls_in)
            libhostapd.call_free_alloc(p_tls_out)

        # judge next move
        session.next_state = cls.PEAP_CHALLENGE_PHASE2_IDENTITY
        return
    def peap_challenge_phase2_identity(cls, request: AuthRequest,
                                       eap: EapPacket, peap: EapPeapPacket,
                                       session: EapPeapSession):
        # 返回数据
        eap_identity = EapPacket(code=EapPacket.CODE_EAP_REQUEST,
                                 id=session.current_eap_id,
                                 type_dict={
                                     'type': EapPacket.TYPE_EAP_IDENTITY,
                                     'type_data': b''
                                 })
        tls_plaintext = eap_identity.ReplyPacket()

        # 加密
        tls_out_data = libhostapd.encrypt(session.tls_connection,
                                          tls_plaintext)
        #
        peap_reply = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                   id=session.current_eap_id,
                                   tls_data=tls_out_data)
        reply = AuthResponse.create_peap_challenge(
            request=request, peap=peap_reply, session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        session.next_state = cls.PEAP_CHALLENGE_GTC_PASSWORD
        return
    def peap_challenge_phase2_identity(cls, request: AuthRequest,
                                       eap: EapPacket, peap: EapPeapPacket,
                                       session: EapPeapSession):
        # 返回数据
        eap_identity = EapPacket(code=EapPacket.CODE_EAP_REQUEST,
                                 id=session.current_eap_id,
                                 type_dict={
                                     'type': EapPacket.TYPE_EAP_IDENTITY,
                                     'type_data': b''
                                 })
        tls_plaintext: bytes = eap_identity.ReplyPacket()

        # 加密
        # EAP-PEAP: Encrypting Phase 2 data - hexdump(len=5): 01 06 00 05 01
        tls_out_data: bytes = libhostapd.encrypt(
            session.tls_connection,
            tls_plaintext,
            peap_version=session.peap_version)
        #
        peap_reply = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                   id=session.current_eap_id,
                                   tls_data=tls_out_data,
                                   flag_version=session.peap_version)
        reply = AuthResponse.create_peap_challenge(
            request=request, peap=peap_reply, session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        session.next_state = cls.PEAP_CHALLENGE_MSCHAPV2_RANDOM
        return
    def peap_challenge_server_hello(cls, request: AuthRequest, eap: EapPacket,
                                    peap: EapPeapPacket,
                                    session: EapPeapSession):
        # 客户端 PEAP 版本
        log.debug(f'eap header, peap version: {peap.flag_version}')
        session.set_peap_version(peap.flag_version)

        # 初始化 tls_connection
        if session.tls_connection is None:
            session.tls_connection = libhostapd.call_tls_connection_init()

        assert peap.tls_data
        p_tls_in_data = ctypes.create_string_buffer(peap.tls_data)
        tls_in_data_len = ctypes.c_ulonglong(len(peap.tls_data))

        p_tls_in, p_tls_out = None, None
        try:
            p_tls_in = libhostapd.call_py_wpabuf_alloc(p_tls_in_data,
                                                       tls_in_data_len)
            p_tls_out = libhostapd.call_tls_connection_server_handshake(
                tls_connection=session.tls_connection, p_tls_in=p_tls_in)
            tls_out_data_len = p_tls_out.contents.used
            tls_out_data = ctypes.string_at(p_tls_out.contents.buf,
                                            tls_out_data_len)
            session.certificate_fragment = EapPeapPacket(
                code=EapPeapPacket.CODE_EAP_REQUEST,
                id=session.current_eap_id,
                tls_data=tls_out_data)
            reply = AuthResponse.create_peap_challenge(
                request=request,
                peap=session.certificate_fragment,
                session_id=session.session_id)
            request.reply_to(reply)
            session.set_reply(reply)
        finally:
            libhostapd.call_free_alloc(p_tls_in)
            libhostapd.call_free_alloc(p_tls_out)

        # judge next move
        if session.certificate_fragment.is_last_fragment():
            # 不用分包
            session.next_state = cls.PEAP_CHALLENGE_CHANGE_CIPHER_SPEC
        else:
            # 需要分包
            session.next_state = cls.PEAP_CHALLENGE_SERVER_HELLO_FRAGMENT
            session.certificate_fragment.go_next_fragment()
        return
    def peap_challenge_gtc_password(cls, request: AuthRequest, eap: EapPacket,
                                    peap: EapPeapPacket,
                                    session: EapPeapSession):
        assert peap.tls_data

        # 解密
        tls_decrypt_data = libhostapd.decrypt(session.tls_connection,
                                              peap.tls_data)
        eap_identity = EapPacket.parse(packet=tls_decrypt_data)
        account_name = eap_identity.type_data.decode()
        # 保存用户名
        session.auth_user.set_peap_username(account_name)

        # 查找用户密码
        account = Account.get(username=account_name)
        if not account or account.is_expired():
            raise AccessReject()
        # 保存用户密码
        session.auth_user.set_user_password(account.radius_password)

        # 返回数据
        response_data = b'Password'
        type_data = struct.pack(f'!{len(response_data)}s', response_data)
        eap_password = EapPacket(code=EapPacket.CODE_EAP_REQUEST,
                                 id=session.current_eap_id,
                                 type_dict={
                                     'type': EapPacket.TYPE_EAP_GTC,
                                     'type_data': type_data
                                 })
        tls_plaintext = eap_password.ReplyPacket()

        # 加密
        tls_out_data = libhostapd.encrypt(session.tls_connection,
                                          tls_plaintext)
        #
        peap_reply = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                   id=session.current_eap_id,
                                   tls_data=tls_out_data)
        reply = AuthResponse.create_peap_challenge(
            request=request, peap=peap_reply, session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        session.next_state = cls.PEAP_CHALLENGE_SUCCESS
        return
    def peap_challenge_success(cls, request: AuthRequest, eap: EapPacket,
                               peap: EapPeapPacket, session: EapPeapSession):
        # 解密.
        # v0: EAP-PEAP: Decrypted Phase 2 EAP - hexdump(len=2): 1a 03
        # v1: EAP-PEAP: Decrypted Phase 2 EAP - hexdump(len=6): 02 08 00 06 1a 03
        tls_decrypt_data = libhostapd.decrypt(session.tls_connection,
                                              peap.tls_data)

        # 返回数据 eap_tlv_success
        if session.peap_version == 0:
            type_data = struct.pack(f'!B B H H', 0x80,
                                    EapPacket.TYPE_RESULT_TLV, 2,
                                    EapPacket.TYPE_RESULT_TLV_SUCCESS)
            eap_tlv_success = EapPacket(code=EapPacket.CODE_EAP_REQUEST,
                                        id=session.current_eap_id,
                                        type_dict={
                                            'type': EapPacket.TYPE_EAP_TLV,
                                            'type_data': type_data
                                        })
            tls_plaintext: bytes = eap_tlv_success.ReplyPacket()
        else:
            # 返回数据 eap_success
            eap_success = EapPacket(code=EapPacket.CODE_EAP_SUCCESS,
                                    id=session.current_eap_id)
            tls_plaintext = eap_success.ReplyPacket()

        # 加密.
        # v0: EAP-PEAP: Encrypting Phase 2 TLV data - hexdump(len=11): 01 08 00 0b 21 80 03 00 02 00 01
        # v1: EAP-PEAP: Encrypting Phase 2 data - hexdump(len=4): 03 09 00 04
        tls_out_data: bytes = libhostapd.encrypt(session.tls_connection,
                                                 tls_plaintext)
        #
        peap_reply = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                   id=session.current_eap_id,
                                   tls_data=tls_out_data,
                                   flag_version=session.peap_version)
        reply = AuthResponse.create_peap_challenge(
            request=request, peap=peap_reply, session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        session.next_state = cls.PEAP_ACCESS_ACCEPT
        return
    def peap_challenge_success(cls, request: AuthRequest, eap: EapPacket,
                               peap: EapPeapPacket, session: EapPeapSession):
        # 解密
        tls_decrypt_data = libhostapd.decrypt(session.tls_connection,
                                              peap.tls_data)
        eap_password = EapPacket.parse(packet=tls_decrypt_data)
        auth_password = eap_password.type_data.decode()
        log.debug(
            f'PEAP account: {session.auth_user.peap_username}, packet_password: {auth_password}'
        )

        def is_correct_password() -> bool:
            return session.auth_user.user_password == auth_password

        if not is_correct_password():
            log.error(
                f'user_password: {session.auth_user.user_password} not correct'
            )
            # 返回数据 eap_failure
            eap_failure = EapPacket(code=EapPacket.CODE_EAP_FAILURE,
                                    id=session.current_eap_id)
            tls_plaintext = eap_failure.ReplyPacket()
        else:
            # 返回数据 eap_success
            eap_success = EapPacket(code=EapPacket.CODE_EAP_SUCCESS,
                                    id=session.current_eap_id)
            tls_plaintext = eap_success.ReplyPacket()

        # 加密
        tls_out_data = libhostapd.encrypt(session.tls_connection,
                                          tls_plaintext)
        #
        peap_reply = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                   id=session.current_eap_id,
                                   tls_data=tls_out_data)
        reply = AuthResponse.create_peap_challenge(
            request=request, peap=peap_reply, session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        session.next_state = cls.PEAP_ACCESS_ACCEPT
        return
    def peap_challenge_server_hello_fragment(cls, request: AuthRequest,
                                             eap: EapPacket,
                                             peap: EapPeapPacket,
                                             session: EapPeapSession):
        session.certificate_fragment.id = session.current_eap_id
        reply = AuthResponse.create_peap_challenge(
            request=request,
            peap=session.certificate_fragment,
            session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        if session.certificate_fragment.is_last_fragment():
            # 分包结束
            session.next_state = cls.PEAP_CHALLENGE_CHANGE_CIPHER_SPEC
        else:
            # 继续分包
            session.next_state = cls.PEAP_CHALLENGE_SERVER_HELLO_FRAGMENT
            session.certificate_fragment.go_next_fragment()
        return
    def peap_challenge_start(cls, request: AuthRequest, eap: EapPacket,
                             peap: EapPeapPacket, session: EapPeapSession):
        # EAP-Message: b'\x02\x01\x00\r\x01testuser'
        assert eap.type == EapPacket.TYPE_EAP_IDENTITY
        identity = eap.type_data.decode()
        log.debug(f'before PEAP Start, identity: {identity}')

        # 返回
        support_peap_version = 1
        eap_start = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                  id=session.current_eap_id,
                                  flag_start=1,
                                  flag_version=support_peap_version)
        reply = AuthResponse.create_peap_challenge(
            request=request, peap=eap_start, session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        session.next_state = cls.PEAP_CHALLENGE_SERVER_HELLO
        return
    def peap_challenge_mschapv2_nt(cls, request: AuthRequest, eap: EapPacket,
                                   peap: EapPeapPacket,
                                   session: EapPeapSession):
        assert peap.tls_data
        # 解密
        # v0: EAP-PEAP: Decrypted Phase 2 EAP - hexdump(len=63): 1a 02 06 00 3e 31 b1 3a 4c 4f 8d 2a 09 3d 89 b2 f8 eb c1 ec 53 f0 00 00
        # 00 00 00 00 00 00 e5 39 9d 11 d6 06 0b b9 95 8e 16 f2 20 fc 4b c9 b0 ab 4e fd bc 62 01 39 00 74 65 73 74 75 73 65 72
        # v1: EAP-PEAP: Decrypted Phase 2 EAP - hexdump(len=67): 02 07 00 43 1a 02 07 00 3e 31 16 79 ba 65 ad 16 7f 92 5c 74 c9 80 53 d6
        # fc 4c 00 00 00 00 00 00 00 00 72 0e 3d a8 8d bd f8 a9 e8 bd 1a 95 d9 5f 08 03 7e 10 db 9f 01 d4 a5 fc 00 74 65 73 74 75 73 65 72
        tls_decrypt_data = libhostapd.decrypt(session.tls_connection,
                                              peap.tls_data)
        # MSCHAPV2_OP_RESPONSE(02) + 与EAP_id相同(07) + MSCHAPV2_OP 到结束的长度(00 3e) +
        # 随机数长度(31) +
        # 24位随机数内含8位0(16 79 ba 65 ad 16 7f 92 5c 74 c9 80 53 d6 fc 4c + 00 00 00 00 00 00 00 00) +
        # 24位NT-Response(72 0e 3d a8 8d bd f8 a9 e8 bd 1a 95 d9 5f 08 03 7e 10 db 9f 01 d4 a5 fc) +
        # Flags(00) +
        # 用户名(74 65 73 74 75 73 65 72)
        mschapv2_random: EapMschapv2Packet = EapMschapv2Packet.parse(
            packet=tls_decrypt_data, peap_version=session.peap_version)
        log.trace(f'mschapv2_random: {mschapv2_random}')
        if mschapv2_random.type != EapPacket.TYPE_EAP_MSCHAPV2:
            log.error('not receive mschapv2_random')
            raise AccessReject()
        mschapv2_type, eap_id, mschapv2_length, fix_length = struct.unpack(
            '!B B H B', mschapv2_random.type_data[:5])
        assert fix_length == 0x31 == 49
        username_len = mschapv2_length - 5 - fix_length
        peer_challenge: bytes
        nt_response: bytes
        flag: bytes
        identity: bytes
        peer_challenge, nt_response, flag, identity = struct.unpack(
            f'!24s 24s B {username_len}s', mschapv2_random.type_data[5:])
        peer_challenge = peer_challenge[:16]
        # 保存客户端随机数
        session.auth_user.set_peer_challenge(peer_challenge)

        assert identity.decode() == session.auth_user.peap_username
        # 计算期望密码哈希值
        p_username = ctypes.create_string_buffer(
            session.auth_user.peap_username.encode())
        l_username_len = ctypes.c_ulonglong(username_len)
        p_password = ctypes.create_string_buffer(
            session.auth_user.user_password.encode())
        l_password_len = ctypes.c_ulonglong(
            len(session.auth_user.user_password))
        p_expect = libhostapd.call_generate_nt_response(
            p_auth_challenge=session.auth_user.server_challenge,
            p_peer_challenge=session.auth_user.peer_challenge,
            p_username=p_username,
            l_username_len=l_username_len,
            p_password=p_password,
            l_password_len=l_password_len,
        )
        expect: bytes = ctypes.string_at(p_expect, len(p_expect))
        log.trace(f'nt_response: {nt_response}')
        log.trace(f'expect: {expect}')

        # 判断密码是否正确
        def is_correct_password() -> bool:
            return nt_response == expect

        if not is_correct_password():
            # 密码整错
            log.error(f'user_password not correct')
            # 返回数据 eap_failure
            eap_failure = EapPacket(code=EapPacket.CODE_EAP_FAILURE,
                                    id=session.current_eap_id)
            tls_plaintext: bytes = eap_failure.ReplyPacket()
        else:
            # 计算 md4(password)
            p_password_md4 = libhostapd.call_nt_password_hash(
                p_password=p_password, l_password_len=l_password_len)
            # 计算返回报文中的 authenticator_response
            p_peer_challenge = ctypes.create_string_buffer(
                session.auth_user.peer_challenge)
            p_auth_challenge = ctypes.create_string_buffer(
                session.auth_user.server_challenge)
            p_nt_response = ctypes.create_string_buffer(nt_response)
            p_out_auth_response = libhostapd.call_generate_authenticator_response_pwhash(
                p_password_md4=p_password_md4,
                p_peer_challenge=p_peer_challenge,
                p_auth_challenge=p_auth_challenge,
                p_username=p_username,
                l_username_len=l_username_len,
                p_nt_response=p_nt_response,
            )
            authenticator_response: bytes = ctypes.string_at(
                p_out_auth_response, len(p_out_auth_response))
            authenticator_response: bytes = authenticator_response.hex().upper(
            ).encode()
            # 返回数据
            # MSCHAPV2_OP_SUCCESS(03) + EAP_id减一(07) + MSCHAPV2_OP 到结束的长度(00 33) +
            # S=(53 3d) +
            # 40个字符: generate_authenticator_response_pwhash 计算出来的哈希值再换成hex大写(37 43 36 39 38 34 37 38 39 44 34 39 44 30 38 32 33 34 35 45 35 31 43 44 45 38 46 35 36 30 33 42 41 44 31 43 34 34 37 33)
            # + 空格(20) +
            # M=(4d 3d) +
            # OK(4f 4b)
            response_msg = b'OK'
            response_msg_len = len(response_msg)
            size_of_auth_response = 20
            size_of_mschapv2_hdr = 4
            message = ''
            type_data_length = size_of_mschapv2_hdr + 2 + (
                2 * size_of_auth_response) + 1 + 2 + response_msg_len
            type_data = struct.pack(
                f'!B B H 2s {2 * size_of_auth_response}s 3s {response_msg_len}s',
                EapPacket.CODE_MSCHAPV2_SUCCESS, session.current_eap_id - 1,
                type_data_length, b'S=', authenticator_response, b' M=',
                response_msg)
            eap_ok = EapPacket(code=EapPacket.CODE_EAP_REQUEST,
                               id=session.current_eap_id,
                               type_dict={
                                   'type': EapPacket.TYPE_EAP_MSCHAPV2,
                                   'type_data': type_data
                               })
            tls_plaintext: bytes = eap_ok.ReplyPacket()
        # 加密
        # v0, v1: EAP-PEAP: Encrypting Phase 2 data - hexdump(len=56): 01 07 00 38 1a 03 06 00 33 53 3d 45 37 35 35 44 37 30 42 43 42 42 35 44 31
        # 43 38 41 45 33 35 35 42 30 38 41 42 31 39 36 42 37 45 33 44 42 43 38 46 31 36 20 4d 3d 4f 4b
        tls_out_data: bytes = libhostapd.encrypt(
            session.tls_connection,
            tls_plaintext,
            peap_version=session.peap_version)
        #
        peap_reply = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                   id=session.current_eap_id,
                                   tls_data=tls_out_data,
                                   flag_version=session.peap_version)
        reply = AuthResponse.create_peap_challenge(
            request=request, peap=peap_reply, session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        session.next_state = cls.PEAP_CHALLENGE_SUCCESS
        return
    def peap_challenge_mschapv2_random(cls, request: AuthRequest,
                                       eap: EapPacket, peap: EapPeapPacket,
                                       session: EapPeapSession):
        assert peap.tls_data
        # 解密
        # v0: EAP-PEAP: Decrypted Phase 2 EAP - hexdump(len=9): 01 74 65 73 74 75 73 65 72
        # v1: EAP-PEAP: Decrypted Phase 2 EAP - hexdump(len=13): 02 06 00 0d 01 74 65 73 74 75 73 65 72
        tls_decrypt_data = libhostapd.decrypt(session.tls_connection,
                                              peap.tls_data)
        eap_identity = EapMschapv2Packet.parse(
            packet=tls_decrypt_data, peap_version=session.peap_version)
        log.trace(f'eap_identity: {eap_identity}')
        if eap_identity.type != EapPacket.TYPE_EAP_IDENTITY:
            log.error('not receive eap_identity')
            raise AccessReject()
        account_name = eap_identity.type_data.decode()
        # 保存用户名
        session.auth_user.set_peap_username(account_name)
        # 查找用户密码
        account = Account.get(username=account_name)
        if not account or account.is_expired():
            raise AccessReject()
        # 保存用户密码
        session.auth_user.set_user_password(account.radius_password)

        # 返回数据
        # MSCHAPV2_OP_CHALLENGE(01) + 与EAP_id相同(07) + MSCHAPV2_OP 到结束的长度(00 1c) +
        # 随机数长度固定值(10) +
        # 16位随机数(2d ae 52 bf 07 d0 de 7b 28 c4 d8 d9 8f 87 da 6a) + server_id(68 6f 73 74 61 70 64)
        size_of_mschapv2_hdr = 4
        server_id = b'hostapd'
        server_id_len = len(server_id)
        server_challenge_len = 16
        server_challenge: bytes = EapPeapPacket.random_string(
            length=server_challenge_len)
        type_data_length = size_of_mschapv2_hdr + 1 + server_challenge_len + server_id_len
        type_data = struct.pack(f'!B B H B 16s {server_id_len}s',
                                EapPacket.CODE_MSCHAPV2_CHALLENGE,
                                session.current_eap_id, type_data_length,
                                server_challenge_len, server_challenge,
                                server_id)
        eap_random = EapPacket(code=EapPacket.CODE_EAP_REQUEST,
                               id=session.current_eap_id,
                               type_dict={
                                   'type': EapPacket.TYPE_EAP_MSCHAPV2,
                                   'type_data': type_data
                               })
        tls_plaintext: bytes = eap_random.ReplyPacket()
        # 保存服务端随机数
        session.auth_user.set_server_challenge(server_challenge)

        # 加密.
        # v0, v1: EAP-PEAP: Encrypting Phase 2 data - hexdump(len=33): 01 07 00 21 1a 01 07 00 1c 10 2d ae 52 bf 07 d0 de 7b 28 c4 d8 d9 8f 87 da 6a 68
        # 6f 73 74 61 70 64
        tls_out_data: bytes = libhostapd.encrypt(
            session.tls_connection,
            tls_plaintext,
            peap_version=session.peap_version)
        #
        peap_reply = EapPeapPacket(code=EapPeapPacket.CODE_EAP_REQUEST,
                                   id=session.current_eap_id,
                                   tls_data=tls_out_data,
                                   flag_version=session.peap_version)
        reply = AuthResponse.create_peap_challenge(
            request=request, peap=peap_reply, session_id=session.session_id)
        request.reply_to(reply)
        session.set_reply(reply)

        # judge next move
        session.next_state = cls.PEAP_CHALLENGE_MSCHAPV2_NT
        return