Example #1
0
 def create_cert(self):
     """This is a wrapper of the functions from the CertManagment class.
     """
     try:
         # Check if work path exists, if it does, raise error and exit.
         if os.path.exists(os.path.expanduser(
                 self.cert_config['WORKPATH'])):
             print('Error: directory \"' + self.cert_config['WORKPATH'] +
                   '\" already exists. ' +
                   'Please remove the directory or change to another one.')
             raise
         cm = certmngr.CertManager()
         # Read certificate environment configuration file
         cm.init_config(config=self.cert_cnf)
         # Create certificate and setup environment.
         cm.create_sk()
         cm.create_csr()
         cm.create_cert()
     except Exception as e:
         print(e)
Example #2
0
    def setup(self, install_all):
        """Configure a private Certificate Authority (CA).
           This function creates a new CA certificate and set directories for the CA.
        """
        c_in = ''
        if not install_all:
            c_in = raw_input(
                "Do you want to install OpenSSL? [Y/N] (skip if installed): ")
        if install_all or c_in == 'Y' or c_in == 'y':
            self.utl.call('sudo apt-get -y --force-yes install openssl',
                          shell=True)

        print('Create CA directory')
        try:
            ca_path = os.path.expanduser(self.openssl_config['CAPATH'])
            if os.path.exists(ca_path):
                raise IOError(
                    'Error: CA directory \"' + ca_path +
                    '\" already exists. Please remove it or choose another name.'
                )
            self.utl.check_call('sudo mkdir -p ' + ca_path, shell=True)
        except subprocess.CalledProcessError:
            print('Error: cannot create CA directory.')
            print "".join(traceback.print_exc(file=sys.stdout))
        except IOError as ioe:
            print ioe
            print "".join(traceback.print_exc(file=sys.stdout))
        os.chdir(ca_path)
        self.utl.makedir(self.cert_config['CERTS'])
        self.utl.makedir(os.path.dirname(self.cert_config['SK']))
        self.utl.call('sudo echo \'01\' > serial  && touch index.txt',
                      shell=True)

        # Configure CA
        print('Configure CA')
        os.chdir(self.package_path)
        fp = open(self.opensslcnf, 'r')
        lines = fp.readlines()
        fp.close()
        begin = -1
        end = -1
        flag = 0
        # Read configuration to set CA directory
        for line in lines:
            if begin == -1 and line.find('Begin SMIT Config') != -1:
                begin = lines.index(line)
            elif end == -1 and line.find('End SMIT Config') != -1:
                end = lines.index(line)
            elif line.find('$CAPATH') > -1:
                lines[lines.index(line)] = line.replace('$CAPATH', ca_path)
                flag = 1
            if begin != -1 and end != -1 and flag == 1:
                break

        if end > begin >= 0:
            i = 0
            while i < end - begin + 1:
                lines.pop(begin)
                i += 1

        # Output openssl configuration to the specified path.
        try:
            fp = open(os.path.expanduser(self.openssl_config['OPENSSL_PATH']),
                      'w')
            for line in lines:
                fp.write(line)
            fp.close()

            # Create CA certificate to setup the CA. The output files, i.e certificate and private key are placed in the
            # path specified by keywords CERT and SK in the configuration file.
            cm = certmngr.CertManager()
            # Read certificate environment configuration file
            cm.init_config(config=self.cert_cnf)
            # Create CA certificate and setup environment.
            cm.create_cert()
            print('CA certificate and private key generated successfully.')
            print('CA configuration complete.')
        except Exception as e:
            print(e)
Example #3
0
class SinkClientDTLS(object):
    """
    This is a class to implement a client which can connect and send messages to a sink server via DTLS protected channel.
    This class needs a configuration file 'client_expcnf' to configure certificate, network and experiment parameters.
    This class depends on the certificate management class CertManager.
    """
    dw = None
    seq = 0
    utl = utils.Utils()
    cm = certmngr.CertManager()  # for certificate generation
    config = {
        'CAIP': '',
        'CAPORT': '',
        'CERT': '',
        'TIMESERVER': '',
        'CACERT': '',
        'SK': '',
        'SERVERIP': '',
        'SERVERPORT': '',
        'CACHAIN': '',
        'TYPE': '',
        'CERT_REQS': 'CERT_REQUIRED',
        'TIMEZONE': '',
        'SYNCTIME': 0,
        'REFLOWPAN': 0,
        'SYSWAIT': 0,
        'PAYLOADLEN': 0,
        'DATE': '',
        'SENDTIME': 0,
        'SENDRATE': 0,
        'DEVNUM': 0
    }
    client_cnf = 'client_expcnf'  # the path to configuration file for this client package
    package_path = ''  # the path to this pakcage
    MAX_LEN = 1024  # the max length of packet which can be sent and received
    TIMER = 8200
    send_logger = None  # a log handler for recording the information of sending messages.
    counter = 0
    term = 0  # number of terms per second. For example, term=10 if the sending timer is in 100 milliseconds
    sent_counter = 0  # the number of times of sent messages.
    is_first_time = 1  # indicate if it is an initial time synchronization.

    def __init__(self):
        self.package_path = os.path.dirname(
            os.path.abspath(inspect.getfile(inspect.currentframe())))
        os.chdir(self.package_path)
        self.config = self.utl.read_config(self.client_cnf, self.config)
        self.cm.init_config(config=self.client_cnf)

    def create_conn_log(self, dst_addr):
        """This function creates a connection log file which contains local network
            interface information and destination address.
        :param dst_addr: destination address (host, port)
        :type dst_addr: tuple
        """
        if os.path.exists('conn.log'):
            self.utl.call('sudo rm -f conn.log', shell=True)
            print 'Old connection log has been removed.'
        formatter = logging.Formatter(fmt='%(asctime)s\t\n%(message)s',
                                      datefmt='%d/%m/%Y %H:%M:%S')
        conn_logger = self.create_logger('Connection log.', 'conn.log',
                                         logging.INFO, formatter)
        p = subprocess.Popen('/sbin/ifconfig',
                             stdout=subprocess.PIPE,
                             shell=True)
        (out, err) = p.communicate()
        if out is None:
            out = 'No output!'
        # get local ip for 6LoWPAN
        tmp_pos = out.find('lowpan0')
        end_pos = tmp_pos + out[tmp_pos:].find(
            '  prefixlen 64  scopeid 0x0<global>')
        begin_pos = out[:end_pos].rfind('inet6 ')
        host = out[begin_pos:end_pos].strip()
        print("OUT: " + host)
        # local_ip, netmask = host.split()[1].split('/')  # inet6 addr: ip/mask Scope:Global
        local_ip = host
        out = out + '\nClient|' + str(local_ip)
        out = out + '\nSink|' + str(dst_addr[0]) + '.' + str(dst_addr[1])
        tail = '\n' + "#" * 40 + '\n'
        conn_log = out + tail
        conn_logger.info(conn_log)

    def init_config(self, **args):
        """This function initializes the configuration for the class object, where the parameters are read from a
        configuration file. This function should be called before other (class member) function call. The acceptable
        arguments are: config, C, ST, L, O, OU, CN, emailAddress, ECCPARAM, CAIP, CAPORT, CERT, CSR, MSG, SIG,
        CACERT, SK, SERVERIP, SERVERPORT, CACHAIN, TYPE, CERT_REQS. Specifically, the keyword "config" sets the path
        to configuration file. If arguments are passed to this function, the specified configuration file will be
        updated.

        :param args: dictionary of passed arguments.
        """

        if args.get('config', '') != '':
            self.client_cnf = os.path.expanduser(args['config'])
        if not os.path.isfile(self.client_cnf):
            raise IOError('configuration file \"' + self.client_cnf +
                          '\" doesn\'t exist or it is not a file.')
        self.config = self.utl.read_config(self.client_cnf, self.config)
        # Set run-time configuration which will overwrite the settings in configuration file.
        update = 0
        for key, value in self.config.iteritems():
            if args.get(key, '') != '' and args[key] is not None:
                self.config[key] = args[key]
                update += 1
        if update > 0:
            self.utl.update_config(self.client_cnf, self.config)
        # Backup configuration file
        self.utl.call('cp -f ' + self.client_cnf + ' ' + self.config['CN'] +
                      '.clientcnf.bck',
                      shell=True)

    def install_dependencies(self):
        """Install some dependencies.
        """
        self.utl.call('sudo apt-get -y --force-yes install ntp ntpdate',
                      shell=True)
        self.utl.call('sudo apt-get -y --force-yes install tcpdump',
                      shell=True)
        self.utl.call('sudo apt-get -y --force-yes install sshpass',
                      shell=True)

    def create_logger(self, log_name, logfile, level, formatter=None):
        """Create and return a reference to a logoer. This is used to create difference log files in different settings.

        :param log_name: the name of a log.
        :param logfile: the file name of the log.
        :param level: the log level.
        :param formatter: the format of log items.
        :type log_name: str.
        :type logfile: str.
        :type level: int.
        :type formatter: logging.Formatter
        """
        logger = logging.getLogger(log_name)
        logger.setLevel(level)
        handler = logging.FileHandler(logfile)
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        return logger

    def signal_handler(self, signum, frame):
        """This function handles message sending task.
        """
        # send data in some probability (decided from configuration file) per each sending request.
        # by default, every 100 milliseconds, it tries to send a message.
        packet_per_sec = float(self.config['SENDRATE']) / float(
            self.config['DEVNUM'])
        probability = int(packet_per_sec /
                          (1.0 / float(self.config['SENDTIME'])) * 100)
        coin = random.randint(0, 99)
        if coin in range(
                0, probability
        ):  # coin is in {0,1,...,probability}, then send a message, otherwise, ignore.
            timestamp = datetime.now().strftime('%H%M%S%f')[:-3]
            self.seq += 1
            # data = str(self.seq).zfill(8) + '-' + timestamp
            # create payload
            seq_str = str(self.seq).zfill(8)
            other_len = int(
                self.config['PAYLOADLEN']) - len(seq_str) - len(timestamp)
            if other_len < 0:
                other_len = 0
            data = seq_str + '-' * other_len + timestamp
            if data == '' or data is None:
                self.send_logger.info(
                    'DATA ERRORS: the generated date is empty or None.')
            sent = self.dw.sendto(data)
            if sent == 0:
                self.send_logger.info(
                    'SENDING ERRORS: sent message length is ' + str(sent) +
                    ' bytes.')
                raise RuntimeError('Socket connection broken.')
            else:
                self.send_logger.info('Sent message length: ' + str(sent) +
                                      ' | Message content: ' + data)

    def refresh(self, timer, cmd, logger):
        """Refresh net information periodically. If the parameter num is a negative number, the function will execute in
         endless mode.

        :param timer: a timer (in seconds) which specifies a period.
        :param cmd: the scheduled command to execute.
        :param logger: a handler to record log into a file.
        :type timer: long
        :type cmd: str.
        :type logger: Logger object.
        """
        try:
            while True:
                i = 0
                while i < 30:
                    p = subprocess.Popen(cmd,
                                         stdout=subprocess.PIPE,
                                         shell=True)
                    (out, err) = p.communicate()
                    if out is None:
                        out = 'No output!'
                    if err is None:
                        err = 'None.'
                    logger.info(out + '\nError (if any): ' + err +
                                '\n=======================')
                    if out.find('No response.') == -1:
                        break
                    else:
                        i += 1
                time.sleep(float(timer))
        except KeyboardInterrupt:
            return

    def sync_time(self, timezone, server_addr, timer, logger):
        """Synchronize time with a specified server.
        :param timezone: local timezone.
        :param server_addr: server IP address.
        :param timer: a periodical time to do the time synchronization.
        :param logger: a handler to record log into a file.
        :type timezone: str.
        :type server_addr: str.
        :type timer: float
        :type logger: Logger object.
        """
        try:
            while True:
                print('Time synchronizing...')
                if self.is_first_time == 1:
                    self.utl.call('sudo date --set=\'' + self.config['DATE'] +
                                  '\'',
                                  shell=True)
                    self.is_first_time = 0
                if not os.path.exists('/usr/share/zoneinfo/' + timezone):
                    print('Error: timezone is invalid.')
                    return
                self.utl.call('timedatectl set-timezone ' + timezone,
                              shell=True)
                p = subprocess.Popen('sudo ntpdate -u ' + server_addr,
                                     stdout=subprocess.PIPE,
                                     shell=True)
                (out, err) = p.communicate()
                if err is None:
                    err = 'None.'
                logger.info(out + '\nError (if any): ' + err +
                            '\n=======================')
                print('Time synchronization finished.')
                time.sleep(timer)
        except KeyboardInterrupt:
            return

    def run_tcpdump(self, tcpdump_cmd):
        """Run tcpdump by using the given command.

        :param tcpdump_cmd: a command to run tcpdump.
        :type tcpdump_cmd: str.
        """
        try:
            subprocess.check_call(tcpdump_cmd, shell=True)
        except KeyboardInterrupt:
            return
        except:
            raise

    def get_scope_id_inet6(self, addr):
        """This function returns the scope id of a given lladdr if the address exists.

        :param addr: IPv6 address
        :return: int. scope id of interface.
        """
        scope_id = -1
        try:
            full_addr = self.expand_address_inet6(addr)
            key = full_addr.replace(':', '')
            with open('/proc/net/if_inet6', 'r') as ifaces:
                for line in ifaces:
                    if line.lower().find(
                            key.lower()) != -1:  # found the interface
                        scope_id = int(line.split()[1])
                        break
            return scope_id
        except:
            raise

    def expand_address_inet6(self, addr):
        """This function expand an IPv6 address to a full description.

        :param addr: an IPv6 address

        Return:
                str. - a full IPv6 address.
        """
        try:
            prefix, suffix = addr.strip().split('::')
            num_items = str(prefix).count(':') + str(suffix).count(':')
            fill_items = 7 - num_items  # calculate the items missed in given ip address
            items = prefix.split(':')
            for index, item in enumerate(items):
                if len(item) < 4:
                    items[index] = item.zfill(4)
                elif len(item) > 4:
                    raise ValueError('Passed IPv6 address is invalid.')
            prefix = ':'.join(items)
            items = suffix.split(':')
            for index, item in enumerate(items):
                if len(item) < 4:
                    items[index] = item.zfill(4)
                elif len(item) > 4:
                    raise ValueError('Passed IPv6 address is invalid.')
            suffix = ':'.join(items)
            prefix += ':'
            for i in range(0, fill_items - 1):
                prefix += '0000:'
                i += 1
            return prefix + suffix
        except:
            raise ValueError('Passed IPv6 address is invalid.')

    def start(self):
        """This function start a client which can interact with a DTLS server and transmit messages.
           This function checks whether the certificate and private key are valid.
           This function depends on DTLSWrap class and the related arguments are configured in the configuration file.
        """
        print('Start client.')
        print('Checking certificate...')
        # Check certificate environment, if check failed with errors, try to request (enroll) to the CA.
        try:
            ca_path = os.path.expanduser(self.config.get('CACERT', ''))
            chain_path = os.path.expanduser(self.config.get('CACHAIN', ''))
            if self.cm.verify_cert_key() and self.cm.verify_cert(
                    chain_path, ca_path, ''):
                print('Verification of local certificate succeeded.')
            else:
                return
        except Exception as e:
            print(e)
            return
        try:
            print('Initializing program...')
            # create a process to refresh 6LoWPAN connection periodically.
            # if REFLOWPAN <= 0, disable the refresh process.
            if int(self.config['REFLOWPAN']) > 0:
                formatter = logging.Formatter(fmt='%(asctime)s\n\t%(message)s',
                                              datefmt='%d/%m/%Y %H:%M:%S')
                rf_logger = self.create_logger('Connection refreshing log',
                                               'rs.log', logging.INFO,
                                               formatter)
                rf = Process(target=self.refresh,
                             args=(
                                 float(self.config['REFLOWPAN']),
                                 'sudo rdisc6 lowpan0',
                                 rf_logger,
                             ))
                rf.start()
            # create a process to synchronize time periodically.
            # if SYNCTIME <= 0, disable the time synchronization.
            if int(self.config['SYNCTIME']) > 0:
                formatter = logging.Formatter(fmt='%(asctime)s\n\t%(message)s',
                                              datefmt='%d/%m/%Y %H:%M:%S')
                sync_logger = self.create_logger('Time synchronization log.',
                                                 'sync.log', logging.INFO,
                                                 formatter)
                sync = Process(target=self.sync_time,
                               args=(
                                   self.config['TIMEZONE'],
                                   self.config['TIMESERVER'],
                                   float(self.config['SYNCTIME']),
                                   sync_logger,
                               ))
                sync.start()
            # create a process to run tcpdump
            tcpdump_lowpan = Process(
                target=self.run_tcpdump,
                args=(
                    'sudo nohup tcpdump -i lowpan0 -ttttnnvvS -w lowpan0.log &',
                ))
            tcpdump_lowpan.start()
            tcpdump_wpan = Process(
                target=self.run_tcpdump,
                args=(
                    'sudo nohup tcpdump -i wpan0 -ttttnnvvS -w wpan0.log &', ))
            tcpdump_wpan.start()
            time.sleep(15)  # wait for initial time synchronization
            print('Program initialization finished.')
            # Create socket for DTLS handshake.
            self.dw = DTLSWrap.DtlsWrap()
            print('Read DTLS configuration.')
            self.dw.init_config(config=self.client_cnf)
            print('Create DTLS socket.')
            sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
            host = self.config.get('SERVERIP', '::1')
            port = self.config.get('SERVERPORT', 12345)
            self.dw.wrap_socket(sock)
            print('Connect to the sink (DTLS) server: ' + host)
            print('host: ' + (host.split('%')[0]))

            # get local ip for 6LoWPAN
            p = subprocess.Popen('/sbin/ifconfig',
                                 stdout=subprocess.PIPE,
                                 shell=True)
            (out, err) = p.communicate()
            if out is None:
                out = 'No output!'

            tmp_pos = out.find('lowpan0')
            end_pos = tmp_pos + out[tmp_pos:].find(
                '  prefixlen 64  scopeid 0x20<link>')
            begin_pos = out[:end_pos].rfind('inet6 ')
            local_ip = out[(begin_pos + 5):end_pos].strip()
            print("local_ip: [" + local_ip + "]")
            # local_ip = "fe80::d9eb:c371:783f:f22"
            scope_id = self.get_scope_id_inet6(local_ip)
            print('Scope id: %d ' % scope_id)
            self.dw.connect((host, int(port), 0, scope_id))

            print('Sink server connected.')
            self.create_conn_log((host, str(port)))

            # wait a while for other devices to finish DTLS handshake.
            print('Wait ' + self.config['SYSWAIT'] +
                  ' seconds for other devices to finish DTLS handshake.')
            time.sleep(float(self.config['SYSWAIT']))
            print('Start to send packages.')

            # Create a logger for recording sending information.
            formatter = logging.Formatter(fmt='%(asctime)s\t%(message)s',
                                          datefmt='%d/%m/%Y %H:%M:%S')
            self.send_logger = self.create_logger('Sending information log.',
                                                  'send.log', logging.INFO,
                                                  formatter)
            signal.signal(signal.SIGALRM, self.signal_handler)
            signal.setitimer(signal.ITIMER_REAL,
                             float(self.config['SENDTIME']),
                             float(self.config['SENDTIME']))
            # sleep main function to let signal handlers execute
            while True:
                time.sleep(1000)

            self.dw.shutdown(socket.SHUT_RDWR)
            self.dw.close()
            print('DTLS connection closed.')
        except KeyboardInterrupt:
            self.dw.shutdown(socket.SHUT_RDWR)
            self.dw.close()
        except Exception as e:
            print(e)
Example #4
0
class Client(object):
    """
    This is a class to implement client functions s.t, obtain certificate from the CA and start client side for DTLS
      communication with DTLS server.
    This class needs a configuration file 'clientcnf' to configure certificate, network and other information.
    This class depends on the certificate management class CertManager.
    """
    utl = utils.Utils()
    cm = certmngr.CertManager()  # for certificate generation
    config = {
        'C': '',
        'ST': '',
        'L': '',
        'O': '',
        'OU': '',
        'CN': '',
        'emailAddress': '',
        'ECCPARAM': '',
        'CAIP': '',
        'CAPORT': '',
        'CERT': '',
        'CSR': '',
        'MSG': '',
        'SIG': '',
        'CACERT': '',
        'SK': '',
        'SERVERIP': '',
        'SERVERPORT': '',
        'CACHAIN': '',
        'TYPE': '',
        'CERT_REQS': ''
    }  # configuration keywords
    client_cnf = 'clientcnf'  # the path to configuration file for this client package
    package_path = ''  # the path to this package
    MAX_LEN = 1536  # the max length of packet which can be sent and received

    def __init__(self):
        self.package_path = os.path.dirname(
            os.path.abspath(inspect.getfile(inspect.currentframe())))
        os.chdir(self.package_path)
        self.config = self.utl.read_config(self.client_cnf, self.config)
        self.cm.init_config(config=self.client_cnf)

    def init_config(self, **args):
        """This function initializes the configuration for the class object, where the parameters are read from a
           configuration file. This function should be called before other (class member) function call.
           The acceptable arguments are: config, C, ST, L, O, OU, CN, emailAddress, ECCPARAM, CAIP, CAPORT, CERT, CSR,
           MSG, SIG, CACERT, SK, SERVERIP, SERVERPORT, CACHAIN, TYPE, CERT_REQS.
           Specifically, the keyword "config" sets the path to configuration file.
           If arguments are passed to this function, the specified configuration file will be updated.

        :param args: dictionary of passed arguments.
        """

        if args.get('config', '') != '':
            self.client_cnf = os.path.expanduser(args['config'])
        if not os.path.isfile(self.client_cnf):
            raise IOError('configuration file \"' + self.client_cnf +
                          '\" does not exist or it is not a file.')
        self.config = self.utl.read_config(self.client_cnf, self.config)
        # Set run-time configuration which will overwrite the settings in configuration file.
        update = 0
        for key, value in self.config.iteritems():
            if args.get(key, '') != '' and args[key] is not None:
                self.config[key] = args[key]
                update += 1
        if update > 0:
            self.utl.update_config(self.client_cnf, self.config)
        # Backup configuration file
        self.utl.call('cp -f ' + self.client_cnf + ' ' + self.config['CN'] +
                      '.clientcnf.bck',
                      shell=True)

    def enroll(self):
        """This function request a certificate from the internal private CA.
           The function requires the input of three files: CSR, message and manufacturer's signature. Note that these
            files are configured in the configuration file.
        """
        csr_path = os.path.expanduser(self.config.get('CSR', ''))
        msg_path = os.path.expanduser(self.config.get('MSG', ''))
        sig_path = os.path.expanduser(self.config.get('SIG', ''))

        try:
            # Check if the required files are accessible
            if not os.path.isfile(csr_path):
                raise IOError(
                    'Path \"' + csr_path +
                    '\" to the CSR file is invalid or it is not a file. Please check '
                    'the configuration file \"' + self.client_cnf + '\".')
            if not os.path.isfile(msg_path):
                raise IOError(
                    'Path \"' + msg_path +
                    '\" to the message file is invalid or it is not a file. Please '
                    'check the configuration file \"' + self.client_cnf +
                    '\".')
            if not os.path.isfile(sig_path):
                raise IOError(
                    'Path \"' + sig_path +
                    '\" to the signature file is invalid or it is not a file. Please '
                    'check the configuration file \"' + self.client_cnf +
                    '\".')

            # Generate socket and connect to CA through TCP connection
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            host = self.config.get('CAIP', '127.0.0.1')
            port = int(self.config.get('CAPORT', 12345))
            sock.connect((host, port))
            print('CA connected.')
            # Send three files to CA for certificate generation request.
            csr = self.utl.read_file(csr_path, 'r')
            msg = self.utl.read_file(msg_path, 'r')
            sig = self.utl.read_file(sig_path, 'r')
            buf = '#$c$#' + csr
            sock.settimeout(180)  # socket timeout after 180 seconds.
            data_len = sock.send(buf)
            print str(data_len) + ' bytes sent for CSR file.'
            ack = sock.recv(self.MAX_LEN)  # receive acknowledgement from CA.
            if ack != 'ok':
                raise RuntimeError(
                    'CA\'s acknowledgement for receiving CSR file is failedis failed.'
                )
            buf = '#$m$#' + msg
            data_len = sock.send(buf)
            print str(data_len) + ' bytes sent for message file.'
            ack = sock.recv(self.MAX_LEN)
            if ack != 'ok':
                raise RuntimeError(
                    'CA\'s acknowledgement for receiving message file is failed.'
                )
            buf = '#$s$#' + sig
            data_len = sock.send(buf)
            print str(data_len) + ' bytes sent for signature file.'
            ack = sock.recv(self.MAX_LEN)
            if ack != 'ok':
                raise RuntimeError(
                    'CA\'s acknowledgement for receiving signature file is failed.'
                )

            data = ''  # received data
            ca_cert_ok = False  # indicate if CA certificate received.
            sv_cert_ok = False  # indicate if client certificate received.
            while True:
                data = data + sock.recv(self.MAX_LEN)
                if data == 'failed':  # Certificate request failed.
                    print(
                        'Certificate request rejected because provide files may not be valid.'
                    )
                    raise
                if data.count('#^#*') == 2 and data.find(
                        '#$t$#') != -1 and data.find('#$a$#') != -1:
                    # Wait the transmission complete then process data. If connection broken, this will timeout after
                    # 180 seconds. extract my certificate
                    begin = data.find('#$t$#') + len('#$t$#')
                    end = data.find('#^#*')
                    path = os.path.dirname(self.config['CERT'])
                    if path != '':
                        self.utl.makedir(path)
                    self.utl.write_file(data[begin:end], self.config['CERT'],
                                        'w')
                    sv_cert_ok = True
                    data = data[end + len('#^#*'):]
                    print('My certificate received and stored at: \"' +
                          self.config['CERT'] + '\".')

                    # extract CA certificate
                    begin = data.find('#$a$#') + len('#$a$#')
                    end = data.rfind('#^#*')
                    path = os.path.dirname(self.config['CACERT'])
                    if path != '':
                        self.utl.makedir(path)
                    self.utl.write_file(data[begin:end], self.config['CACERT'],
                                        'w')
                    ca_cert_ok = True
                    print('CA certificate received and stored at: \"' +
                          self.config['CACERT'] + '\".')
                if ca_cert_ok and sv_cert_ok:
                    break
            sock.close()
        except socket.timeout:
            print 'Request timeout.'
        except Exception as e:
            print(e)

    def start(self, install_all):
        """This function start a client which can interact with a DTLS server and transmit messages. This function
        checks whether the certificate and private key are valid, if not, it automatically request a new certificate
        from the CA. This function depends on DTLSWrap class and the related arguments are configured in the
        configuration file.
        """
        if install_all:
            print('Installing dependencies.')
            self.utl.call('sudo apt-get -y --force-yes install python-pip',
                          shell=True)
            self.utl.call('pip install Dtls', shell=True)
        print('Start client.')
        print('Checking certificate...')
        # Check certificate environment, if check failed with errors, try to request (enroll) to the CA.
        try:
            ca_path = os.path.expanduser(self.config.get('CACERT', ''))
            chain_path = os.path.expanduser(self.config.get('CACHAIN', ''))
            if self.cm.verify_cert_key() and self.cm.verify_cert(
                    chain_path, ca_path, ''):
                print('Verification of local certificate succeeded.')
            else:
                return
        except Exception as e:
            print('Certificate environment verification failed.')
            print(
                '================================================================================'
            )
            print(
                'Trying to request a new certificate from CA based on the local CSR and private key.\n'
            )
            print(
                'Note: if new certificate is still invalid, please make sure you have the '
                'correct private key regarding the CSR file.')
            print(
                '================================================================================'
            )
            print(e)
            # Generate CSR file according to the CSR information from the configuration file.
            # Generate private key if the file is missing. Note: the CSR information must be different from the
            # previous ones.
            try:
                sk_path = os.path.expanduser(self.config.get('SK', ''))
                if not os.path.isfile(sk_path):
                    self.cm.create_sk()
                self.cm.create_csr()
                print('Connect to CA.')
                self.enroll()
                print(
                    '================================================================================'
                )
            except Exception as e:
                print(e)

        try:
            # Create socket for DTLS handshake.
            dw = DTLSWrap.DtlsWrap()
            print('Read DTLS configuration.')
            dw.init_config(config=self.client_cnf)
            print('Create DTLS socket.')
            sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
            host = self.config.get('SERVERIP', '::1')
            port = self.config.get('SERVERPORT', 12345)
            dw.wrap_socket(sock)
            print('Connect to the DTLS server')
            dw.connect((host, int(port)))

            # for demo client/server, no multithreading implemented.
            while True:
                print('=================================')
                c_in = raw_input('Enter a message (\'q\' for quite): ')
                sent = dw.sendto(c_in)
                if sent == 0:
                    raise RuntimeError('Socket connection broken.')
                if c_in == 'q':
                    break
                print('Awaiting message from server...')
                data = dw.recvfrom(self.MAX_LEN)
                if data == 'q':
                    break
                print('Message received: ' + data)

            dw.shutdown(socket.SHUT_RDWR)
            dw.close()
            print('DTLS connection closed.')
        except KeyboardInterrupt:
            dw.shutdown(socket.SHUT_RDWR)
            dw.close()
        except Exception as e:
            print(e)
Example #5
0
class Sink(object):
    """
    This is a class to implement server functions s.t, accept DTLS client to receive messages.
    This class needs a configuration file 'server_expcnf' to configure certificate, network and experiment parameters.
    This class depends on the certificate management class CertManager.
    """
    utl = utils.Utils()
    cm = certmngr.CertManager()  # for certificate generation
    config = {'CERT': '', 'CACERT': '', 'SK': '', 'SERVERIP': '', 'SERVERPORT': '', 'CACHAIN': '',
              'TYPE': '', 'CERT_REQS': 'CERT_REQUIRED'}  # configuration keywords
    server_cnf = 'sink_expcnf'  # the path to configuration file for this client package
    package_path = ''  # the path to this pakcage
    MAX_LEN = 1536  # the max length of packet which can be sent and received

    def __init__(self):
        self.package_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
        os.chdir(self.package_path)
        self.config = self.utl.read_config(self.server_cnf, self.config)
        self.cm.init_config(config=self.server_cnf)

    def init_config(self, **args):
        """This function initializes the configuration for the class object, where the parameters are read from a
               configuration file. This function should be called before other (class member) function call.
               The acceptable arguments are: config, CACERT, SK, SERVERIP, SERVERPORT, CACHAIN, TYPE, CERT_REQS.
               Specifically, the keyword "config" sets the path to configuration file.
               If arguments are passed to this function, the specified configuration file will be updated.

        :param args: dictionary of passed arguments.
        """
        if args.get('config', '') != '':
            self.server_cnf = os.path.expanduser(args['config'])
        if not os.path.isfile(self.server_cnf):
            raise IOError('configuration file \"' + self.server_cnf + '\" doesn\'t exist or it is not a file.')
        self.config = self.utl.read_config(self.server_cnf, self.config)
        # Set run-time configuration which will overwrite the settings in configuration file.
        update = 0
        for key, value in self.config.iteritems():
            if args.get(key, '') != '' and args[key] is not None:
                self.config[key] = args[key]
                update += 1
        if update > 0:
            self.utl.update_config(self.server_cnf, self.config)
        # Backup configuration file
        self.utl.call('cp -f ' + self.server_cnf + ' ' + self.server_cnf + '.bck', shell=True)

    def connection_handler(self, sock, addr):
        """This is a connection handler for subthread. It process a new connection for certificate request.
           This function takes a client socket for DTLS packets transmission and the parameter addr is used to show
           the client IP address.
           This function will be called in individual thread to process a specific client's connection.

        :param sock: client socket.
        :param addr: client IP address
        :type sock: int.
        :type addr: tuple.
        """
        print ('New connection from: ' + addr[0] + ', ' + str(addr[1]))
        try:
            # Create a directory to contain log files generated during the interaction.
            dt = time.strftime('%d-%m-%Y-%H-%M-%S')  # get the date and current time of connection.
            path = addr[0]  # create direct name for this connection's log files.
            self.utl.makedir('exp/' + path)
            os.chdir('exp/' + path)  # change the current working directory
            log_name = addr[0] + '--' + dt
            logging.basicConfig(filename=log_name, level=logging.INFO, format='%(asctime)s\t%(message)s',
                                datefmt='%d/%m/%Y %H:%M:%S')
            while True:
                data = sock.recvfrom(self.MAX_LEN)
                timestamp = datetime.now().strftime('%H%M%S%f')[:-3]
                # Calculate length of message other than the sequence number and timestamp.
                # The length of sequence number is fixed 8 bytes.
                other_len = len(data) - 8 - len(timestamp)
                # Log client connection information including client (IP, port) and the port assigned to client on sink.
                conn_info = 'client|' + str(addr[0]) + '.' + str(addr[1]) + '|sink|' + str(
                    self.config['SERVERIP']) + '.' + str(self.config['SERVERPORT'])
                record = data[0:8] + '\t' + data[8:8 + other_len] + '\t' + data[8 + other_len:] + '\t' + timestamp + \
                         '\t' + conn_info
                logging.info(record)
        except socket.timeout:
            print ('Time out.')
        except KeyboardInterrupt:
            print ('DTLS connection closed.')
        except Exception as e:
            print ('Unknown errors.')
            print (e)
        finally:
            sock.shutdown(socket.SHUT_RDWR)
            sock.close()

    def start(self):
        """This function start a server which can interact with a DTLS client and receive messages.
           This function depends on DTLSWrap class and the related arguments are configured in the configuration file.
        """
        print ('Start server.')
        print ('Checking certificate...')
        # Check certificate environment
        try:
            ca_path = os.path.expanduser(self.config.get('CACERT', ''))
            chain_path = os.path.expanduser(self.config.get('CACHAIN', ''))
            if self.cm.verify_cert_key() and self.cm.verify_cert(chain_path, ca_path, ''):
                print ('Verification of local certificate succeeded.')
            else:
                return
        except Exception as e:
            print (e)
            return
        try:
            # Create socket for DTLS handshake.
            server = DTLSWrap.DtlsWrap()
            client = DTLSWrap.DtlsWrap()
            print ('Read DTLS configuration.')
            server.init_config(config=self.server_cnf)
            print ('Create DTLS socket.')
            sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
            host = self.config.get('SERVERIP', '::1')
            port = self.config.get('SERVERPORT', 12345)
            sock.bind((host, int(port)))
            server.wrap_socket(sock)

            # Listen connection request.
            server.listen(5)
            print ('Listen DTLS handshake request ...')
            while True:
                acc = server.accept()
                if acc:
                    print ('=================================')
                    print ('Client connected.')
                    client.set_dtls_socket(
                        acc[0])  # acc[0] is SSLSocket object, acc[1] is a tuple (host, port, long, long)
                    p = Process(target=self.connection_handler, args=(client, acc[1],))
                    p.start()
        except KeyboardInterrupt:
            server.shutdown(socket.SHUT_RDWR)
            server.close()
        except Exception as e:
            print (e)
Example #6
0
    def connection_handler(self, sock, addr):
        """This is a connection handler for subthread. It process a new connection for certificate request.
           This function takes a client socket for DTLS packets transmission and the parameter addr is used to show
           the client IP address.
           This function will be called in individual thread to process a specific client's connection.

        :param sock: client socket.
        :param addr: client IP address
        :type sock: int.
        :type addr: tuple.
        """
        sock.settimeout(
            180)  # request timeout after 180 seconds to close the thread.
        print('===== New connection from: ' + addr[0])
        try:
            # Create a directory to contain temporary files (e.g., certificate and received files)
            # generated during the interaction.
            path = ''.join(
                random.choice(string.ascii_uppercase + string.digits)
                for _ in range(10))
            cm = certmngr.CertManager()
            cm.init_config(config=self.app_cnf)
            self.utl.makedir('tmp/' + path)
            os.chdir('tmp/' + path)  # change the current working directory

            # The While loop processes enroll request according to the designed message follow.
            # The specified indicators like 'c' and 'm' may be changed in later version. It is currently for the test
            # and simplicity.
            while True:
                data = sock.recv(self.MAX_LEN)
                if data.startswith(
                        '#$c$#'
                ):  # it is a CSR string and then write it to a file.
                    self.write_file(data[len('#$c$#'):], self.config['CSR'])
                    print('CSR file received.')
                    sock.send('ok')  # send acknowledgement to the applicant
                if data.startswith(
                        '#$m$#'
                ):  # it is a message string and then write it to a file.
                    self.write_file(data[len('#$m$#'):], self.config['MSG'])
                    print('Message file received.')
                    sock.send('ok')
                if data.startswith(
                        '#$s$#'
                ):  # it is a signature file then check if it is valid.
                    self.write_file(data[len('#$s$#'):], self.config['SIG'])
                    print('Signature file received.')
                    sock.send('ok')
                    if not cm.verify_sig(
                    ):  # signature verification failed and send result to applicant.
                        sock.send('failed')
                        print(
                            'Certificate request rejected: manufacturer\'s signature verification failed.'
                        )
                        break
                    else:  # signature verified so that generate certificate.
                        print('Manufacturer\'s signature verified.')
                        if not cm.verify_cert(self.config.get('SIGCHAIN', ''),
                                              self.config.get('SIGCERT', ''),
                                              self.config.get('OCSP', '')):
                            return
                        cm.create_cert()
                        cert_path = cm.find_cert(
                        )  # path to the generated certificate.
                        cert = self.utl.read_file(cert_path, 'r')
                        cert = '#$t$#' + cert + '#^#*'
                        sock.send(cert)  # send client certificate.
                        print('Client certificate delivered.')
                        ca_path = os.path.expanduser(
                            self.config.get('CACERT',
                                            ''))  # path to CA certificate
                        if not os.path.isfile(ca_path):
                            raise IOError(
                                'Path \"' + ca_path +
                                '\" to the CA certificate is invalid or it is not a '
                                'file. Please check the configuration file \"'
                                + self.app_cnf + '\".')
                        cert = self.utl.read_file(ca_path, 'r')
                        cert = '#$a$#' + cert + '#^#*'
                        sock.send(cert)  # send CA certificate.
                        print('CA certificate delivered.')
                        break
        except socket.timeout:
            pass
        except KeyboardInterrupt:
            sock.shutdown(socket.SHUT_RDWR)
            sock.close()
        except Exception as e:
            print(e)
        finally:
            # delete temporary directory to clean up session.
            print('Clean up...')
            self.utl.call('rm -rf ../' + path, shell=True)
            print('Session completed.')