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)
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)
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)
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)
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)
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.')