Home > Mobile >  Attempting secure socket communication between client and server and getting An operation was attemp
Attempting secure socket communication between client and server and getting An operation was attemp

Time:11-11

I am attempting to piece together a secure socket client server communication solution. I do not have experience in doing so, so have cobbled together what I believe are relevant sections. The idea is that the Server waits for connections, the client creates a connection that is secure and then communication can take place.

The code also utilizes secure communication in authorization with client and server keys and certificates.

client code:


class Client:
    def __init__(self):
        try:
            self.host, self.port = "127.0.0.1", 65416
            self.client_cert = os.path.join(os.path.dirname(__file__), "client.crt")
            self.client_key = os.path.join(os.path.dirname(__file__), "client.key")
            self._context = ssl.SSLContext()
            self._context.load_cert_chain(self.client_cert, self.client_key)
            self._sock = None
            self._ssock = None
        except Exception as e:
            print("Error in Initializing")

    def checkvalidclient(self):
        # ---- Client Communication Setup ----

        HOST = self.host  # The server's hostname or IP address
        PORT = self.port        # The port used by the server
        try:
            self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self._ssock = self._context.wrap_socket(self._sock,)
            self._ssock.connect((HOST, PORT))
            print ("Socket successfully created")
        except socket.error as err:
            print ("socket creation failed with error %s" %(err))

        print('Waiting for connection')
        Response = self._ssock.recv(1024)
        while True:
            Input = input('Say Something: ')
            # s.send(str.encode(Input))
            send_msg(self._ssock, str.encode(Input))
            # Response = s.recv(1024)
            Response = recv_msg(self._ssock)
            if Response is not None:
                print(Response.decode('utf-8'))

    def closesockconnection(self):
        self._ssock.close()

# ---- To Avoid Message Boundary Problem on top of TCP protocol ----
def send_msg(sock: socket, msg):  # ---- Use this to send
    # Prefix each message with a 4-byte length (network byte order)
    msg = struct.pack('>I', len(msg))   msg
    sock.sendall(msg)

def recv_msg(sock: socket):       # ---- Use this to receive
    # Read message length and unpack it into an integer
    raw_msglen = recvall(sock, 4)
    if not raw_msglen:
        return None
    msglen = struct.unpack('>I', raw_msglen)[0]
    # Read the message data
    return recvall(sock, msglen)

def recvall(sock: socket, n: int):
    # Helper function to receive n bytes or return None if EOF is hit
    data = bytearray()
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data.extend(packet)
    return data

client = Client()
client.checkvalidclient()

Server code:

import socket
import os
import ssl
from os import path
from _thread import *
import struct # Here to convert Python data types into byte streams (in string) and back

# ---- To Avoid Message Boundary Problem on top of TCP protocol ----
def send_msg(sock: socket, msg):  # ---- Use this to send
    # Prefix each message with a 4-byte length (network byte order)
    msg = struct.pack('>I', len(msg))   msg
    sock.sendall(msg)

def recv_msg(sock: socket):       # ---- Use this to receive
    # Read message length and unpack it into an integer
    raw_msglen = recvall(sock, 4)
    if not raw_msglen:
        return None
    msglen = struct.unpack('>I', raw_msglen)[0]
    # Read the message data
    return recvall(sock, msglen)

def recvall(sock: socket, n: int):
    # Helper function to receive n bytes or return None if EOF is hit
    try:
        data = bytearray()
        while len(data) < n:
            packet = sock.recv(n - len(data))
            if not packet:
                return None
            data.extend(packet)
        return data
    except Exception as e:
        print("Exception in recvall : "   str(e))

# ---- Server Communication Setup
class Server:

    def __init__(self):
        self.HOST = '127.0.0.1'  # Standard loopback interface address (localhost)
        self.PORT = 65416        # Port to listen on (non-privileged ports are > 1023)
        self.ThreadCount = 0
        self.server_cert = path.join(path.dirname(__file__), "server.crt")
        self.server_key = path.join(path.dirname(__file__), "server.key")
        self.client_cert = path.join(path.dirname(__file__), "client.crt")

        self._context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        self._context.verify_mode = ssl.CERT_REQUIRED
        self._context.load_cert_chain(self.server_cert, self.server_key)
        self._context.load_verify_locations(self.client_cert)
        self.sock = None

    def connect(self):
        try: # create socket
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            print ("Socket successfully created")
        except socket.error as err:
            print ("socket creation failed with error %s" %(err))

        try: # bind socket to an address
            self.sock.bind((self.HOST, self.PORT))
        except socket.error as e:
            print(str(e))

        print('Waiting for a Connection..')
        self.sock.listen(3)

    def threaded_client(self, conn: socket):
        conn.send(str.encode('Welcome to the Server'))
        while True:
            # data = conn.recv(2048) # receive message from client
            data = recv_msg(conn)
            print(data)
            if data is not None:
                reply = 'Server Says:  '   data.decode('utf-8')
                if not data:
                    break
                # conn.sendall(str.encode(reply))
                send_msg(conn, str.encode(reply))
        #conn.close()

    def waitforconnection(self):
        while True:
            Client, addr = self.sock.accept()
            self._context.wrap_socket(Client, server_side=True)
            print('Connected to: '   addr[0]   ':'   str(addr[1]))

            start_new_thread(self.threaded_client, (Client, )) # Calling threaded_client() on a new thread
            self.ThreadCount  = 1
            print('Thread Number: '   str(self.ThreadCount))
        #self.sock.close()

server = Server()
server.connect()
server.waitforconnection()

The lines:

    def threaded_client(self, conn: socket):
        conn.send(str.encode('Welcome to the Server'))

result in the error: [WinError 10038] An operation was attempted on something that is not a socket

When I removed the certificate related lines in client:

            self.client_cert = os.path.join(os.path.dirname(__file__), "client.crt")
            self.client_key = os.path.join(os.path.dirname(__file__), "client.key")
            self._context = ssl.SSLContext()
            self._context.load_cert_chain(self.client_cert, self.client_key)

and the certificate related lines in the server:

        self.server_cert = path.join(path.dirname(__file__), "server.crt")
        self.server_key = path.join(path.dirname(__file__), "server.key")
        self.client_cert = path.join(path.dirname(__file__), "client.crt")

        self._context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        self._context.verify_mode = ssl.CERT_REQUIRED
        self._context.load_cert_chain(self.server_cert, self.server_key)
        self._context.load_verify_locations(self.client_cert)
        self.sock = None

and a couple of small changes to remove the certificate related functionality, everything seemed to work, the client could send messages to the server and the server could respond (and the client displayed the response).

When however I added the context related certificates I start getting the error: An operation was attempted on something that is not a socket

The server waits at:

Client, addr = self.sock.accept()

and continues to run once the client has called (in the client.py file):

self._ssock.connect((HOST, PORT))

The server then reaches the lines:

    def threaded_client(self, conn: socket):
        conn.send(str.encode('Welcome to the Server'))

where it fails on this error.

Printing the terminal, a traceback and exception error results in:

Socket successfully created
Waiting for a Connection..
Connected to: 127.0.0.1:57434
Thread Number: 1
Traceback (most recent call last):
  File "c:\testcode\Server.py", line 71, in threaded_client
    conn.send(str.encode('Welcome to the Server'))
OSError: [WinError 10038] An operation was attempted on something that is not a socket

My knowledge is limited and I cannot find more examples of secure multi threaded two way communication client to server socket code. The idea is to ensure the client is authorized to communicate with the server before transmission happens.

Any ideas on where I am failing?

Thanks

CodePudding user response:

Ok, It seems like I was close, but had a couple of tweaks to do.

The solution of: SSL/TLS client certificate verification with Python v3.4 SSLContext

and the commenters here, helped me get over the finish line.

Server code:

import socket
import os
from socket import AF_INET, SOCK_STREAM, SO_REUSEADDR, SOL_SOCKET, SHUT_RDWR
import ssl
from os import path
from _thread import *
import struct # Here to convert Python data types into byte streams (in string) and back
import traceback

# ---- To Avoid Message Boundary Problem on top of TCP protocol ----
def send_msg(sock: socket, msg):  # ---- Use this to send
    # Prefix each message with a 4-byte length (network byte order)
    msg = struct.pack('>I', len(msg))   msg
    sock.sendall(msg)

def recv_msg(sock: socket):       # ---- Use this to receive
    # Read message length and unpack it into an integer
    raw_msglen = recvall(sock, 4)
    if not raw_msglen:
        return None
    msglen = struct.unpack('>I', raw_msglen)[0]
    # Read the message data
    return recvall(sock, msglen)

def recvall(sock: socket, n: int):
    # Helper function to receive n bytes or return None if EOF is hit
    try:
        data = bytearray()
        while len(data) < n:
            packet = sock.recv(n - len(data))
            if not packet:
                return None
            data.extend(packet)
        return data
    except Exception as e:
        print("Exception in recvall : "   str(e))

# ---- Server Communication Setup
class Server:

    def __init__(self):
        self.HOST = '127.0.0.1'  # Standard loopback interface address (localhost)
        self.PORT = 65416        # Port to listen on (non-privileged ports are > 1023)
        self.ThreadCount = 0
        self.server_cert = path.join(path.dirname(__file__), "server.crt")
        self.server_key = path.join(path.dirname(__file__), "server.key")
        self.client_cert = path.join(path.dirname(__file__), "client.crt")

        self._context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        self._context.verify_mode = ssl.CERT_REQUIRED
        self._context.load_cert_chain(certfile=self.server_cert, keyfile=self.server_key)
        self._context.load_verify_locations(cafile=self.client_cert)

        self.sock = None

    def connect(self):
        try: # create socket
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) ###<-- socket.socket() ???
            print ("Socket successfully created")
        except socket.error as err:
            print ("socket creation failed with error %s" %(err))

        try: # bind socket to an address
            self.sock.bind((self.HOST, self.PORT))
        except socket.error as e:
            print(str(e))

        print('Waiting for a Connection..')
        self.sock.listen(3)

    def threaded_client(self, conn: socket):
        try:
            conn.send(str.encode('Welcome to the Server'))
            while True:
                data = recv_msg(conn)
                print("data")
                print(data)
                if data is not None:
                    reply = 'Server Says: '   data.decode('utf-8')
                    if not data:
                        break
                    send_msg(conn, str.encode(reply))
        except Exception as e:
            print(traceback.format_exc())
            print(str(e))
        finally:
            print("Closing connection")
            conn.shutdown(socket.SHUT_RDWR)
            conn.close()

        #conn.close()

    def waitforconnection(self):
        while True:
            Client, addr = self.sock.accept()
            conn = self._context.wrap_socket(Client, server_side=True)

            print('Connected to: '   addr[0]   ':'   str(addr[1]))
            print("SSL established. Peer: {}".format(conn.getpeercert()))
            start_new_thread(self.threaded_client, (conn, )) # Calling threaded_client() on a new thread
            self.ThreadCount  = 1
            print('Thread Number: '   str(self.ThreadCount))
        #self.sock.close()

server = Server()
server.connect()
server.waitforconnection()

Client code:

import socket
import struct # Here to convert Python data types into byte streams (in string) and back 
import sys
import ssl
import socket
import selectors
import types
import io
import os
import time
import requests
from pathlib import Path
import mysql.connector as mysql
from loguru import logger as log
from utils.misc import read_py_config
import json
import rsa
import base64

class Client:
    def __init__(self):
        self.host, self.port = "127.0.0.1", 65416
        self.client_cert = os.path.join(os.path.dirname(__file__), "client.crt")
        self.client_key = os.path.join(os.path.dirname(__file__), "client.key")
        self.server_crt = os.path.join(os.path.dirname(__file__), "server.crt")
        
        self.sni_hostname = "example.com"
        self._context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=self.server_crt)

        self._context.load_cert_chain(certfile=self.client_cert, keyfile=self.client_key)

        self._sock = None
        self._ssock = None

    def checkvalidclient(self):
        # ---- Client Communication Setup ----

        HOST = self.host  # The server's hostname or IP address
        PORT = self.port        # The port used by the server

        try:
            self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

            self._ssock = self._context.wrap_socket(self._sock, server_side=False, server_hostname=self.sni_hostname)

            self._ssock.connect((HOST, PORT))

            print ("Socket successfully created")
        except socket.error as err:
            print ("socket creation failed with error %s" %(err))

        print('Waiting for connection')

        Response = self._ssock.recv(1024)
        if Response is not None:
            print(Response.decode('utf-8'))
        while True:
            Input = input('Say Something: ')
            send_msg(self._ssock, str.encode(Input))
            Response = recv_msg(self._ssock)
            if Response is not None:
                print(Response.decode('utf-8'))

    def closesockconnection(self):
        self._ssock.close()

# ---- To Avoid Message Boundary Problem on top of TCP protocol ----
def send_msg(sock: socket, msg):  # ---- Use this to send
    # Prefix each message with a 4-byte length (network byte order)
    msg = struct.pack('>I', len(msg))   msg
    sock.sendall(msg)

def recv_msg(sock: socket):       # ---- Use this to receive
    # Read message length and unpack it into an integer
    raw_msglen = recvall(sock, 4)
    if not raw_msglen:
        return None
    msglen = struct.unpack('>I', raw_msglen)[0]
    # Read the message data
    return recvall(sock, msglen)

def recvall(sock: socket, n: int):
    # Helper function to receive n bytes or return None if EOF is hit
    data = bytearray()
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data.extend(packet)
    return data

Also ensure (as per the link) that the certificate creation is correct.

There is also another useful link at: Exploring HTTPS With Python

Which covers HTTPS, specifically the Wireshark section allows you to monitor the traffic from client to server. After completing the above and deploying Wireshark I see that the data is encrypted. Any editing of the certificates (manually) causes the app to fail.

There still needs to be additions of try and except if the communication is halted midway etc. But hoping it will smooth the journey for others.

Thanks to the commenters, helped lead me on the way to solution.

  • Related