class OpenADCInterface_NAEUSBChip(object): _name = "NewAE USB (CWLite/CW1200)" def __init__(self, oadcInstance): self.ser = None self.dev = None self.scope = None self.last_id = None if (openadc_qt is None) or (usb is None): missingInfo = "" if openadc_qt is None: missingInfo += "openadc.qt " if usb is None: missingInfo += " usb" raise ImportError("Needed imports for ChipWhisperer missing: %s" % missingInfo) else: self.cwFirmwareConfig = { 0xACE2: FWLoaderConfig(CWLite_Loader()), 0xACE3: FWLoaderConfig(CW1200_Loader()) } self.scope = oadcInstance def con(self, sn=None): if self.ser is None: self.dev = CWL.CWLiteUSB() try: nae_products = [0xACE2, 0xACE3] possible_sn = self.dev.get_possible_devices(nae_products) serial_numbers = [] if len(possible_sn) > 1: #Update list... if sn is None: snlist = DictType({'Select Device to Connect': None}) for d in possible_sn: snlist[str(d['sn']) + " (" + str(d['product']) + ")"] = d['sn'] serial_numbers.append("sn = {} ({})".format( str(d['sn']), str(d['product']))) pass raise Warning( "Multiple ChipWhisperers detected. Please specify device from the following list using cw.scope(sn=<SN>): \n{}" .format(serial_numbers)) else: pass #if possible_sn[0]['sn'] != #sn = None found_id = self.dev.con(idProduct=nae_products, serial_number=sn) except (IOError, ValueError): raise Warning( 'Could not connect to "%s". It may have been disconnected, is in an error state, or is being used by another tool.' % self.getName()) if found_id != self.last_id: logging.info( "Detected ChipWhisperer with USB ID %x - switching firmware loader" % found_id) self.last_id = found_id self.getFWConfig().setInterface(self.dev.fpga) try: self.getFWConfig().loadRequired() except: self.dev.dis() self.dev.usbdev().close() raise self.ser = self.dev.usbdev() try: self.scope.con(self.ser) logging.info('OpenADC Found, Connecting') except IOError as e: exctype, value = sys.exc_info()[:2] raise IOError("OpenADC: " + (str(exctype) + str(value))) def dis(self): if self.ser is not None: self.getFWConfig().setInterface(None) self.scope.close() self.ser.close() self.ser = None if self.dev is not None: self.dev.dis() self.dev = None def __del__(self): if self.ser is not None: self.ser.close() def getFWConfig(self): try: return self.cwFirmwareConfig[self.last_id] except KeyError as e: return FWLoaderConfig(CWLite_Loader()) def get_name(self): return self._name getName = camel_case_deprecated(get_name)
class Results(object): """ Results type used for attacks generating peaks indicating the 'best' success. Examples include standard DPA & CPA attacks. """ def __init__(self, numSubkeys=16, numPerms=256): self.numSubkeys = numSubkeys self.numPerms = numPerms self.known_key = None self.clear() def best_guesses(self): """ Gets best subkey guesses from attack results Returns: List of OrderedDicts with keys 'guess', 'correlation' and 'pge'. """ guess_list = [] stats = self.find_maximums() for i, subkey in enumerate(stats): dict = OrderedDict() dict['guess'] = subkey[0][0] dict['correlation'] = subkey[0][2] dict['pge'] = self.simple_PGE(i) guess_list.append(dict) return guess_list def __str__(self): ret = "" ret += "Subkey KGuess Correlation\n" guesses = self.best_guesses() for i, subkey in enumerate(guesses): ret += " {:02d} 0x{:02X} {:7.5f}\n".format( i, subkey['guess'], subkey['correlation']) return ret def clear(self): #Diffs from CPA/DPA Attack self.diffs = [None] * self.numSubkeys #Maximum diff & location of maximum self.maxes = [0] * self.numSubkeys for i in range(0, self.numSubkeys): self.maxes[i] = np.zeros(self.numPerms, dtype=[('hyp', 'i2'), ('point', 'i4'), ('value', 'f8')]) #If maximum diffs are valid & sorted correctly self.maxValid = [False] * self.numSubkeys self.pge = [255] * self.numSubkeys self.diffs_tnum = [None] * self.numSubkeys self.pge_total = [] self.maxes_list = [list() for i in range(0, self.numSubkeys)] #TODO: Ensure this gets called by attack algorithms when rerunning def simple_PGE(self, bnum): """Returns the partial guessing entropy of subkey.""" if self.maxValid[bnum] == False: #TODO: should sort return 1 return self.pge[bnum] simplePGE = camel_case_deprecated(simple_PGE) def set_known_key(self, known_key): """Sets the known encryption key.""" self.known_key = known_key setKnownkey = camel_case_deprecated(set_known_key) def update_subkey(self, bnum, data, copy=True, force_update=False, tnum=None): """Update the specific subkey. Args: bnum (int): The index of the subkey. data (int): The new subkey byte. copy (int): """ if (id(data) != id(self.diffs[bnum])) or force_update: self.maxValid[bnum] = False if data is not None and copy: self.diffs[bnum] = data[:] self.diffs_tnum[bnum] = tnum else: self.diffs[bnum] = data self.diffs_tnum[bnum] = tnum updateSubkey = camel_case_deprecated(update_subkey) def find_key(self, use_absolute=True): """ Find the best guess for the key from the attack. Args: use_absolute (bool, optional): Use the absolute value of the correlation during analysis. Returns: The best guess for a key from the attack as a list. """ res = self.find_maximums(use_absolute=use_absolute) return [subkey[0][0] for subkey in res] def find_maximums(self, bytelist=None, use_absolute=True, use_single=False): """Information from the attack: Args: bytelist (list): Iterable of subkeys to compute and organize results for. use_absolute (bool): Use absolute value of correlation to find highest correlation. use_single (bool): All table values are taken from the same point the maximum is taken from. Returns: list: Ordered by subkey index:: [subkey0_data, subkey1_data, subkey2_data, ...] *subkey0_data* is another list containing guesses ordered by strength of correlation:: [guess0, guess1, guess2, ...] *guess0* is a tuple containing:: (key_guess, location_of_max, correlation) For example, if you want to print the correlation of the best guess of the 4th subkey, you would run:: print(attack_results.find_maximums()[4][0][2]) Note the "point location of the max" is normally not calculated/tracked, and thus returns as a 0. """ if bytelist is None: bytelist = list(range(0, self.numSubkeys)) # print useAbsolute for i in bytelist: if self.diffs[i] is None: self.maxValid[i] = False continue if self.maxValid[i] == False: for hyp in range(0, self.numPerms): if use_absolute: v = np.nanmax(np.fabs(self.diffs[i][hyp])) else: v = np.nanmax(self.diffs[i][hyp]) mvalue = v #Get maximum value for this hypothesis try: mindex = np.amin(np.where(v == mvalue)) except ValueError: mindex = self.numPerms - 1 self.maxes[i][hyp]['hyp'] = hyp self.maxes[i][hyp]['point'] = mindex self.maxes[i][hyp]['value'] = mvalue #TODO: why does this fail? #self.maxes[i][np.isnan(self.maxes[i]['value'])]['value'] = 0 #TODO: workaround for PGE, as NaN's get ranked first numnans = np.isnan(self.maxes[i]['value']).sum() if use_single: #All table values are taken from same point MAX is taken from where = self.maxes[i][0]['point'] for j in range(0, self.numPerms): self.maxes[i][j]['point'] = where self.maxes[i][j]['value'] = self.diffs[i][ self.maxes[i][j]['hyp']][where] self.maxes[i][::-1].sort( order='value' ) # sorts nunpy array in place and in reverse order self.maxValid[i] = True if self.known_key is not None: try: self.pge[i] = np.where(self.maxes[i]['hyp'] == self. known_key[i])[0][0] - numnans if self.pge[i] < 0: self.pge[i] = self.numPerms / 2 except IndexError: self.pge[i] = self.numPerms - 1 tnum = self.diffs_tnum[i] self.pge_total.append({ 'trace': tnum, 'subkey': i, 'pge': self.pge[i] }) if len(self.maxes_list[i] ) == 0 or self.maxes_list[i][-1]['trace'] != tnum: self.maxes_list[i].append({ 'trace': tnum, 'maxes': np.array(self.maxes[i]) }) return self.maxes findMaximums = camel_case_deprecated(find_maximums)
class SimpleSerial(TargetTemplate, util.DisableNewAttr): """SimpleSerial target object. This class contains the public API for a target that uses serial communication. The easiest way to connect to the target is:: import chipwhisperer as cw scope = cw.scope() target = cw.target(scope) The target is automatically connected to if the default configuration adequate. For more help use the help() function with one of the submodules (target.baud, target.write, target.read, ...). * :attr:`target.baud <.SimpleSerial.baud>` * :meth:`target.write <.SimpleSerial.write>` * :meth:`target.read <.SimpleSerial.read>` * :meth:`target.simpleserial_wait_ack <.SimpleSerial.simpleserial_wait_ack>` * :meth:`target.simpleserial_write <.SimpleSerial.simpleserial_write>` * :meth:`target.simpleserial_read <.SimpleSerial.simpleserial_read>` * :meth:`target.set_key <.SimpleSerial.set_key>` * :meth:`target.close <.SimpleSerial.close>` * :meth:`target.con <.SimpleSerial.con>` """ _name = "Simple Serial" def __init__(self): TargetTemplate.__init__(self) self.ser = SimpleSerial_ChipWhispererLite() self._protver = 'auto' self.protformat = 'hex' self.last_key = bytearray(16) self._output_len = 16 self._proto_ver = "auto" self._proto_timeoutms = 20 self._simpleserial_last_read = "" self._simpleserial_last_sent = "" self.disable_newattr() def __repr__(self): ret = "SimpleSerial Settings =" for line in dict_to_str(self._dict_repr()).split("\n"): ret += "\n\t" + line return ret def __str__(self): return self.__repr__() def _dict_repr(self): dict = OrderedDict() dict['output_len'] = self.output_len dict['baud'] = self.baud dict['simpleserial_last_read'] = self.simpleserial_last_read dict['simpleserial_last_sent'] = self.simpleserial_last_sent #dict['protver'] = self.protver return dict @property def simpleserial_last_read(self): """The last raw string read by a simpleserial_read* command""" return self._simpleserial_last_read @property def simpleserial_last_sent(self): """The last raw string written via simpleserial_write""" return self._simpleserial_last_sent @property def output_len(self): """The length of the output expected from the crypto algorithm (in bytes)""" return self._output_len @output_len.setter def output_len(self, length): self._output_len = length @property def baud(self): """The current baud rate of the serial connection. :Getter: Return the current baud rate. :Setter: Set a new baud rate. Valid baud rates are any integer in the range [500, 2000000]. Raises: AttributeError: Target doesn't allow baud to be changed. """ if hasattr(self.ser, 'baud') and callable(self.ser.baud): return self.ser.baud() else: raise AttributeError("Can't access baud rate") @baud.setter def baud(self, new_baud): if hasattr(self.ser, 'baud') and callable(self.ser.baud): self.ser.setBaud(new_baud) else: raise AttributeError("Can't access baud rate") @property def protver(self): """Get the protocol version used for the target """ return self._proto_ver @protver.setter def protver(self, value): """Set the protocol version used for the target ('1.1', '1.0', or 'auto') """ self._proto_ver = value def setConnection(self, con): """I don't think this does anything""" self.ser = con self.ser.connectStatus = self.connectStatus self.ser.selectionChanged() def _con(self, scope=None): if not scope or not hasattr(scope, "qtadc"): Warning( "You need a scope with OpenADC connected to use this Target") self.ser.con(scope) # 'x' flushes everything & sets system back to idle self.ser.write("xxxxxxxxxxxxxxxxxxxxxxxx") self.ser.flush() def close(self): if self.ser != None: self.ser.close() def init(self): self.ser.flush() def is_done(self): """Always returns True""" return True def write(self, data): """ Writes data to the target over serial. Args: data (str): Data to write over serial. Raises: Warning: Target not connected .. versionadded:: 5.1 Added target.write() """ if not self.connectStatus: raise Warning("Target not connected") try: self.ser.write(data) except USBError: self.dis() raise Warning("Error in target. It may have been disconnected") except Exception as e: self.dis() raise e def read(self, num_char=0, timeout=250): """ Reads data from the target over serial. Args: num_char (int, optional): Number of byte to read. If 0, read all data available. Defaults to 0. timeout (int, optional): How long in ms to wait before returning. If 0, block until data received. Defaults to 250. Returns: String of received data. .. versionadded:: 5.1 Added target.read() """ if not self.connectStatus: raise Warning("Target not connected") try: if num_char == 0: num_char = self.ser.inWaiting() return self.ser.read(num_char, timeout) except USBError: self.dis() raise Warning("Error in target. It may have been disconnected") except Exception as e: self.dis() raise e def simpleserial_wait_ack(self, timeout=500): """Waits for an ack from the target for timeout ms Args: timeout (int, optional): Time to wait for an ack in ms. If 0, block until we get an ack. Defaults to 500. Returns: The return code from the ChipWhisperer command or None if the target failed to ack Raises: Warning: Target not connected. .. versionadded:: 5.1 Added target.simpleserial_wait_ack .. versionadded:: 5.2 Defined return value """ data = self.read(4, timeout=timeout) if len(data) < 4: logging.error("Target did not ack") return None if data[0] != 'z': logging.error("Ack error: {}".format(data)) return None return int(data[1:3], 16) def simpleserial_write(self, cmd, num, end='\n'): """ Writes a simpleserial command to the target over serial. Writes 'cmd' + ascii(num) + 'end' over serial. Flushes the read and write buffers before writing. Args: cmd (str): String to start the simpleserial command with. For 'p'. num (bytearray): Number to write as part of command. For example, the 16 byte plaintext for the 'p' command. Converted to hex-ascii before being sent. If set to 'none' is omitted. end (str, optional): String to end the simpleserial command with. Defaults to '\\n'. Example: Sending a 'p' command:: key, pt = ktp.new_pair() target.simpleserial_write('p', pt) Raises: Warning: Write attempted while disconnected or error during write. .. versionadded:: 5.1 Added target.simpleserial_write() """ self.ser.flush() if cmd: cmd += binascii.hexlify(num).decode() cmd += end self.write(cmd) self._simpleserial_last_sent = cmd def simpleserial_read(self, cmd, pay_len, end='\n', timeout=250, ack=True): r""" Reads a simpleserial command from the target over serial. Reads a command starting with <start> with an ASCII encoded bytearray payload of length exp_len*2 (i.e. exp_len=16 for an AES128 key) and ending with <end>. Converts the payload to a bytearray. Will ignore non-ASCII bytes in the payload, but warn the user of them. Args: cmd (str): Expected start of the command. Will warn the user if the received command does not start with this string. pay_len (int): Expected length of the returned bytearray in number of bytes. Note that SimpleSerial commands send data as ASCII; this is the length of the data that was encoded. end (str, optional): Expected end of the command. Will warn the user if the received command does not end with this string. Defaults to '\n' timeout (int, optional): Value to use for timeouts during reads in ms. If 0, block until all expected data is returned. Defaults to 250. ack (bool, optional): Expect an ack at the end for SimpleSerial >= 1.1. Defaults to True. Returns: The received payload as a bytearray or None if the read failed. Example: Reading ciphertext back from the target after a 'p' command:: ct = target.simpleserial_read('r', 16) Raises: Warning: Device did not ack or error during read. .. versionadded:: 5.1 Added target.simpleserial_read() """ cmd_len = len(cmd) ascii_len = pay_len * 2 recv_len = cmd_len + ascii_len + len(end) response = self.read(recv_len, timeout=timeout) self._simpleserial_last_read = response payload = bytearray(pay_len) if cmd_len > 0: if response[0:cmd_len] != cmd: logging.warning("Unexpected start to command: {}".format( response[0:cmd_len])) return None idx = cmd_len for i in range(0, pay_len): try: payload[i] = int(response[idx:(idx + 2)], 16) except ValueError as e: logging.warning("ValueError: {}".format(e)) idx += 2 if len(end) > 0: if response[(idx):(idx + len(end))] != end: logging.warning("Unexpected end to command: {}".format( response[(idx):(idx + len(end))])) return None if ack: if self.simpleserial_wait_ack(timeout) is None: raise Warning("Device failed to ack") return payload def simpleserial_read_witherrors(self, cmd, pay_len, end="\n", timeout=250, glitch_timeout=8000, ack=True): r""" Reads a simpleserial command from the target over serial, but returns invalid responses. Reads a command starting with <start> with an ASCII encoded bytearray payload of length exp_len*2 (i.e. exp_len=16 for an AES128 key) and ending with <end>. Converts the payload to a bytearray, returns a dictionary showing if processing was successful along with decoded and raw values. This function is designed to be used with glitching where you may have invalid responses. Args: cmd (str): Expected start of the command. Will warn the user if the received command does not start with this string. pay_len (int): Expected length of the returned bytearray in number of bytes. Note that SimpleSerial commands send data as ASCII; this is the length of the data that was encoded. end (str, optional): Expected end of the command. Will warn the user if the received command does not end with this string. Defaults to '\n' timeout (int, optional): Value to use for timeouts during initial read of expected data in ms. If 0, block until all expected data is returned. Defaults to 250. glitch_timeout (int, optional): Value to wait for additional data if expected data isn't returned. Useful to have a longer timeout for a reset or other unexpected event. ack (bool, optional): Expect an ack at the end for SimpleSerial >= 1.1. Defaults to True. Returns: A dictionary with these elements: valid (bool): Did response look valid? payload: Bytearray of decoded data (only if valid is 'True', otherwise None) full_response: Raw output of serial port. rv: If 'ack' in command, includes return value Example: Reading the output of one of the glitch tests when no error: resp = target.simpleserial_read_witherrors('r', 4) print(resp) >{'valid': True, 'payload': CWbytearray(b'c4 09 00 00'), 'full_response': 'rC4090000\n', 'rv': 0} Reading the output of one of the glitch tests when an error happened: resp = target.simpleserial_read_witherrors('r', 4) print(resp) >{'valid': False, 'payload': None, 'full_response': '\x00\x00\x00\x00\x00\x00\x00rRESET \n', 'rv': None} Raises: Warning: Device did not ack or error during read. .. versionadded:: 5.2 Added target.simpleserial_read_witherrors() """ cmd_len = len(cmd) ascii_len = pay_len * 2 recv_len = cmd_len + ascii_len + len(end) response = self.read(recv_len, timeout=timeout) payload = bytearray(pay_len) valid = False rv = None if len(response) != recv_len or response[0:cmd_len] != cmd: # Switch to robust mode - likely a glitch happened. Get all response first... response += self.read(1000, timeout=glitch_timeout) payload = None else: valid = True idx = cmd_len for i in range(0, pay_len): try: payload[i] = int(response[idx:(idx + 2)], 16) except ValueError as e: payload = None logging.warning("ValueError: {}".format(e)) idx += 2 if len(end) > 0: if response[(idx):(idx + len(end))] != end: logging.warning("Unexpected end to command: {}".format( response[(idx):(idx + len(end))])) payload = None if ack: rv = self.simpleserial_wait_ack(timeout) # return payload, valid, response self._simpleserial_last_read = response return { 'valid': valid, 'payload': payload, 'full_response': response, 'rv': rv } def set_key(self, key, ack=True, timeout=250): """Checks if key is different than the last one sent. If so, send it. Uses simpleserial_write('k') Args: key (bytearray): key to send ack (bool, optional): Wait for ack after sending key. Defaults to True. timeout (int, optional): How long in ms to wait for the ack. Defaults to 250. Raises: Warning: Device did not ack or error during read. .. versionadded:: 5.1 Added target.set_key() """ if self.last_key != key: self.last_key = key self.simpleserial_write('k', key) if ack: if self.simpleserial_wait_ack(timeout) is None: raise Warning("Device failed to ack") def in_waiting(self): """Returns the number of characters available from the serial buffer. Returns: The number of characters available via a target.read() call. .. versionadded:: 5.1 Added target.in_waiting() """ return self.ser.inWaiting() inWaiting = camel_case_deprecated(in_waiting) def flush(self): """Removes all data from the serial buffer. .. versionadded:: 5.1 Added target.flush() """ self.ser.flush()
class AcqKeyTextPattern_Basic(AcqKeyTextPattern_Base): """Class for getting basic keys and plaintexts. Basic usage:: import chipwhisperer as cw ktp = cw.ktp.Basic() key, text = ktp.next() """ _name = "Basic" def __init__(self): AcqKeyTextPattern_Base.__init__(self) self._fixedKey = True self._fixedPlain = False self.inittext = '00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F' self.initkey = '2B 7E 15 16 28 AE D2 A6 AB F7 15 88 09 CF 4F 3C' self._key = util.hexStrToByteArray(self.initkey) self._textin = util.hexStrToByteArray(self.inittext) self.types = {'Random': False, 'Fixed': True} @property def fixed_key(self): """Generate fixed key (True) or not (False). :Getter: Return True if using fixed key or False if not. :Setter: Set whether to use fixed key (True) or not (False). .. versionadded:: 5.1 Added fixed_key property """ return self._fixedKey @fixed_key.setter def fixed_key(self, enabled): self._fixedKey = enabled @property def fixed_text(self): """Generate fixed plaintext (True) or not (False). :Getter: Return True if using fixed plaintext or False if not. :Setter: Set whether to use fixed plaintext (True) or not (False). .. versionadded:: 5.1 Added fixed_text property """ return self._fixedPlain @fixed_text.setter def fixed_text(self, enabled): self._fixedPlain = enabled def get_key_type(self): return self._fixedKey getKeyType = camel_case_deprecated(get_key_type) def set_key_type(self, t): self._fixedKey = t setKeyType = camel_case_deprecated(set_key_type) def getPlainType(self): return self._fixedPlain def setPlainType(self, t): self._fixedPlain = t def getInitialKey(self): return " ".join(["%02X" % b for b in self._key]) def setInitialKey(self, initialKey, binaryKey=False): if initialKey: if binaryKey: keyStr = '' for s in initialKey: keyStr += '%02x ' % s self._key = bytearray(initialKey) else: keyStr = initialKey self._key = util.hexStrToByteArray(initialKey) self.initkey = keyStr def getInitialText(self): return " ".join(["%02X" % b for b in self._textin]) def setInitialText(self, initialText, binaryText=False): if initialText: if binaryText: textStr = '' for s in initialText: textStr += '%02x ' % s self._textin = bytearray(initialText) else: textStr = initialText self._textin = util.hexStrToByteArray(initialText) self.inittext = textStr def initPair(self, maxtraces): pass def new_pair(self): if self._fixedKey is False: self._key = bytearray(self.keyLen()) for i in range(0, self.keyLen()): self._key[i] = random.randint(0, 255) if self._fixedPlain is False: self._textin = bytearray(self.textLen()) for i in range(0, self.textLen()): self._textin[i] = random.randint(0, 255) # Check pair works with target self.validateKey() self.validateText() return self._key, self._textin newPair = new_pair def next(self): """Returns the next key text pair Updates last key and text Returns: (key (bytearray), text (bytearray)) .. versionadded:: 5.1 Added next """ return self.next_key(), self.next_text() def next_text(self): """ Returns the next plaintext Does not update key. If text is fixed, returns the same plaintext as last time Returns: text (bytearray) .. versionadded:: 5.1 Added next_text """ if self._fixedPlain is False: self._textin = bytearray(self.textLen()) for i in range(0, self.textLen()): self._textin[i] = random.randint(0, 255) self.validateText() return self._textin def next_key(self): """ Returns the next key Does not update text. If key is fixed, returns the same key as last time Returns: key (bytearray) .. versionadded:: 5.1 Added next_key """ if self._fixedKey is False: self._key = bytearray(self.keyLen()) for i in range(0, self.keyLen()): self._key[i] = random.randint(0, 255) self.validateKey() return self._key """def __str__(self):
class OpenADC(ScopeTemplate, util.DisableNewAttr): """OpenADC scope object. This class contains the public API for the OpenADC hardware, including the ChipWhisperer Lite/ CW1200 Pro boards. It includes specific settings for each of these devices. To connect to one of these devices, the easiest method is:: import chipwhisperer as cw scope = cw.scope(type=scopes.OpenADC) Some sane default settings are available via:: scope.default_setup() This code will automatically detect an attached ChipWhisperer device and connect to it. For more help about scope settings, try help() on each of the ChipWhisperer scope submodules (scope.gain, scope.adc, scope.clock, scope.io, scope.trigger, and scope.glitch): * :attr:`scope.gain <.OpenADC.gain>` * :attr:`scope.adc <.OpenADC.adc>` * :attr:`scope.clock <.OpenADC.clock>` * :attr:`scope.io <.OpenADC.io>` * :attr:`scope.trigger <.OpenADC.trigger>` * :attr:`scope.glitch <.OpenADC.glitch>` * :meth:`scope.default_setup <.OpenADC.default_setup>` * :meth:`scope.con <.OpenADC.con>` * :meth:`scope.dis <.OpenADC.dis>` * :meth:`scope.arm <.OpenADC.arm>` * :meth:`scope.get_last_trace <.OpenADC.get_last_trace>` * :meth:`scope.get_serial_ports <.OpenADC.get_serial_ports>` If you have a CW1200 ChipWhisperer Pro, you have access to some additional features: * :attr:`scope.SAD <.OpenADC.SAD>` * :attr:`scope.DecodeIO <.OpenADC.DecodeIO>` * :attr:`scope.adc.stream_mode (see scope.adc for more information)` """ _name = "ChipWhisperer/OpenADC" def __init__(self): ScopeTemplate.__init__(self) self.qtadc = openadc_qt.OpenADCQt() self.qtadc.dataUpdated.connect(self.newDataReceived) # Bonus Modules for ChipWhisperer self.advancedSettings = None self.advancedSAD = None self.digitalPattern = None self._is_connected = False self.scopetype = OpenADCInterface_NAEUSBChip(self.qtadc) @property def latest_fw(self): cw_type = self._getCWType() if cw_type == "cwlite": from chipwhisperer.hardware.firmware.cwlite import fwver elif cw_type == "cw1200": from chipwhisperer.hardware.firmware.cw1200 import fwver ret = OrderedDict() return {"major": fwver[0], "minor": fwver[1]} @property def fw_version(self): a = self.qtadc.sc.serial.readFwVersion() return {"major": a[0], "minor": a[1], "debug": a[2]} @property def sn(self): return self.scopetype.ser.snum def reload_fpga(self, bitstream=None): """(Re)loads a FPGA bitstream (even if already configured). Will cause a reconnect event, all settings become default again. If no bitstream specified default is used based on current configuration settings. """ self.scopetype.reload_fpga(bitstream) def _getNAEUSB(self): return self.scopetype.dev._cwusb def get_serial_ports(self): """ Get the CDC serial ports associated with this scope Returns: A list of a dict with elements {'port', 'interface'} """ return self._getNAEUSB().get_serial_ports() def default_setup(self): """Sets up sane capture defaults for this scope * 45dB gain * 5000 capture samples * 0 sample offset * rising edge trigger * 7.37MHz clock output on hs2 * 4*7.37MHz ADC clock * tio1 = serial rx * tio2 = serial tx * CDC settings change off .. versionadded:: 5.1 Added default setup for OpenADC """ self.gain.db = 25 self.adc.samples = 5000 self.adc.offset = 0 self.adc.basic_mode = "rising_edge" self.clock.clkgen_freq = 7.37e6 self.trigger.triggers = "tio4" self.io.tio1 = "serial_rx" self.io.tio2 = "serial_tx" self.io.hs2 = "clkgen" self.clock.adc_src = "clkgen_x4" self.io.cdc_settings = 0 count = 0 while not self.clock.clkgen_locked: self.clock.reset_dcms() time.sleep(0.05) count += 1 if count == 5: logging.info("Could not lock clock for scope. This is typically safe to ignore. Reconnecting and retrying...") self.dis() time.sleep(0.25) self.con() time.sleep(0.25) self.gain.db = 25 self.adc.samples = 5000 self.adc.offset = 0 self.adc.basic_mode = "rising_edge" self.clock.clkgen_freq = 7.37e6 self.trigger.triggers = "tio4" self.io.tio1 = "serial_rx" self.io.tio2 = "serial_tx" self.io.hs2 = "clkgen" self.clock.adc_src = "clkgen_x4" if count > 10: raise OSError("Could not lock DCM. Try rerunning this function or calling scope.clock.reset_dcms(): {}".format(self)) def dcmTimeout(self): if self.connectStatus: try: self.qtadc.sc.getStatus() except USBError: self.dis() raise Warning("Error in the scope. It may have been disconnected.") except Exception as e: self.dis() raise e def getCurrentScope(self): return self.scopetype def setCurrentScope(self, scope): self.scopetype = scope def _getCWType(self): """Find out which type of ChipWhisperer this device is. Returns: One of the following: - "" - "cwlite" - "cw1200" - "cwrev2" """ hwInfoVer = self.qtadc.sc.hwInfo.versions()[2] if "ChipWhisperer" in hwInfoVer: if "Lite" in hwInfoVer: return "cwlite" elif "CW1200" in hwInfoVer: return "cw1200" else: return "cwrev2" return "" def get_name(self): """ Gets the name of the attached scope Returns: 'ChipWhisperer Lite' if a Lite, 'ChipWhisperer Pro' if a Pro """ name = self._getCWType() if name == "cwlite": return "ChipWhisperer Lite" elif name == "cw1200": return "ChipWhisperer Pro" def _con(self, sn=None): if self.scopetype is not None: self.scopetype.con(sn) if hasattr(self.scopetype, "ser") and hasattr(self.scopetype.ser, "_usbdev"): self.qtadc.sc.usbcon = self.scopetype.ser._usbdev #self.qtadc.sc.usbcon = self.scopetype.ser._usbdev cwtype = self._getCWType() if cwtype != "": self.advancedSettings = ChipWhispererExtra.ChipWhispererExtra(cwtype, self.scopetype, self.qtadc.sc) util.chipwhisperer_extra = self.advancedSettings if cwtype == "cwrev2" or cwtype == "cw1200": self.SAD = ChipWhispererSAD.ChipWhispererSAD(self.qtadc.sc) if cwtype == "cw1200": self.decode_IO = ChipWhispererDecodeTrigger.ChipWhispererDecodeTrigger(self.qtadc.sc) #self.advancedSettings.cwEXTRA.triggermux._set_is_pro(True) if cwtype == "cwcrev2": self.digitalPattern = ChipWhispererDigitalPattern.ChipWhispererDigitalPattern(self.qtadc.sc) self.adc = self.qtadc.parm_trigger self.gain = self.qtadc.parm_gain self.clock = self.qtadc.parm_clock if cwtype == "cw1200": self.adc._is_pro = True if self.advancedSettings: self.io = self.advancedSettings.cwEXTRA.gpiomux self.trigger = self.advancedSettings.cwEXTRA.triggermux self.glitch = self.advancedSettings.glitch.glitchSettings if cwtype == "cw1200": self.trigger = self.advancedSettings.cwEXTRA.protrigger self.disable_newattr() self._is_connected = True return True return False def _dis(self): if self.scopetype is not None: self.scopetype.dis() if self.advancedSettings is not None: self.advancedSettings = None util.chipwhisperer_extra = None if self.advancedSAD is not None: self.advancedSAD = None if self.digitalPattern is not None: self.digitalPattern = None # TODO Fix this hack if hasattr(self.scopetype, "ser") and hasattr(self.scopetype.ser, "_usbdev"): self.qtadc.sc.usbcon = None self.enable_newattr() self._is_connected = False return True def arm(self): """Setup scope to begin capture/glitching when triggered. The scope must be armed before capture or glitching (when set to 'ext_single') can begin. Raises: OSError: Scope isn't connected. Exception: Error when arming. This method catches these and disconnects before reraising them. """ if self.connectStatus is False: raise OSError("Scope is not connected. Connect it first...") # with DelayedKeyboardInterrupt(): try: if self.advancedSettings: self.advancedSettings.armPreScope() self.qtadc.arm() if self.advancedSettings: self.advancedSettings.armPostScope() self.qtadc.startCaptureThread() except Exception: self.dis() raise def capture(self): """Captures trace. Scope must be armed before capturing. Returns: True if capture timed out, false if it didn't. Raises: IOError: Unknown failure. """ # need adc offset, adc_freq, samples cached # with DelayedKeyboardInterrupt(): if not self.adc.stream_mode: return self.qtadc.capture(self.adc.offset, self.clock.adc_freq, self.adc.samples) else: return self.qtadc.capture(None) def get_last_trace(self): """Return the last trace captured with this scope. Returns: Numpy array of the last capture trace. """ return self.qtadc.datapoints getLastTrace = util.camel_case_deprecated(get_last_trace) def capture_segmented(self): """Captures trace in segment mode, returns as many segments as buffer holds. Timeouts not handled yet properly (function will lock). Be sure you are generating enough triggers for segmented mode. Returns: True if capture timed out, false if it didn't. Raises: IOError: Unknown failure. """ if self.adc.fifo_fill_mode != "segment": raise IOError("ADC is not in 'segment' mode - aborting.") with DelayedKeyboardInterrupt(): max_fifo_size = self.adc.oa.hwMaxSamples #self.adc.offset should maybe be ignored - passing for now but untested timeout = self.qtadc.sc.capture(self.adc.offset, self.clock.adc_freq, max_fifo_size) timeout2 = self.qtadc.read(max_fifo_size-256) return timeout or timeout2 def get_last_trace_segmented(self): """Return last trace assuming it was captued with segmented mode. NOTE: The length of each returned trace is 1 less sample than requested. Returns: 2-D numpy array of the last captured traces. """ seg_len = self.adc.samples-1 num_seg = int(len(self.qtadc.datapoints) / seg_len) return np.reshape(self.qtadc.datapoints[:num_seg*seg_len], (num_seg, seg_len)) def _dict_repr(self): dict = OrderedDict() dict['sn'] = self.sn dict['fw_version'] = self.fw_version dict['gain'] = self.gain._dict_repr() dict['adc'] = self.adc._dict_repr() dict['clock'] = self.clock._dict_repr() dict['trigger'] = self.trigger._dict_repr() dict['io'] = self.io._dict_repr() dict['glitch'] = self.glitch._dict_repr() if self._getCWType() == "cw1200": dict['SAD'] = self.SAD._dict_repr() dict['decode_IO'] = self.decode_IO._dict_repr() return dict def __repr__(self): # Add some extra information about ChipWhisperer type here if self._is_connected: ret = "%s Device\n" % self._getCWType() return ret + dict_to_str(self._dict_repr()) else: ret = "ChipWhisperer/OpenADC device (disconnected)" return ret def __str__(self): return self.__repr__()
class AttackBaseClass(PassiveTraceObserver): """Generic Attack Interface""" _name= 'Attack Settings' _algos = {} def __init__(self): PassiveTraceObserver.__init__(self) self._itNum = 0 self.getParams().getChild("Input").hide() self._traceStart = 0 self._iterations = 1 self._tracePerAttack = 1 self._reportingInterval = 10 self._pointRange = (0,0) self._targetSubkeys = [] self._project = None self.useAbs = True self.attack = None self.attackModel = None self.getParams().addChildren([ {'name':'Attack Algorithm', 'type':'list', 'values':self._algos, 'get':self.getAlgorithm, 'set':self.setAlgorithm, 'action':self.updateScript, 'childmode': 'parent'} ]) models = None self.getParams().addChildren([ {'name':'Crypto Algorithm', 'type':'list', 'values':models, 'action':self.refreshByteList, 'childmode':'child'}, {'name':'Points Range', 'key':'prange', 'type':'range', 'get':self.get_point_range, 'set':self.set_point_range, 'action':self.updateScript}, ]) self.getParams().addChildren([ {'name':'Starting Trace', 'key':'strace', 'type':'int', 'get':self.get_trace_start, 'set':self.set_trace_start, 'action':self.updateScript}, {'name':'Traces per Attack', 'key':'atraces', 'type':'int', 'limits':(1, 1E6), 'get':self.get_traces_per_attack, 'set':self.set_traces_per_attack, 'action':self.updateScript}, {'name':'Iterations', 'key':'runs', 'type':'int', 'limits':(1, 1E6), 'get':self.get_iterations, 'set':self.set_iterations, 'action':self.updateScript}, {'name':'Reporting Interval', 'key':'reportinterval', 'type':'int', 'get':self.get_reporting_interval, 'set':self.set_reporting_interval, 'action':self.updateScript}, ]) # self.getParams().init() self.setAlgorithm(self._analysisAlgorithm) self.refreshByteList() self.updateScript() if __debug__: logging.debug('Created: ' + str(self)) def tracesUpdated(self): if self._analysisAlgorithm is not None: self._analysisAlgorithm.tracesUpdated(self._traceSource) self.updateTraceLimits() self.updateScript() def getAlgorithm(self): return self._analysisAlgorithm @setupSetParam('Attack Algorithm') def setAlgorithm(self, analysisAlgorithm): self._analysisAlgorithm = analysisAlgorithm self._analysisAlgorithm.tracesUpdated(self._traceSource) if hasattr(self._analysisAlgorithm, 'scriptsUpdated'): self._analysisAlgorithm.scriptsUpdated.connect(self.updateScript) if hasattr(self._analysisAlgorithm, 'runScriptFunction'): self._analysisAlgorithm.runScriptFunction.connect(self.runScriptFunction.emit) def set_leak_model(self, leakage_object): """Set the leak model to leakage_object""" self.attackModel = leakage_object setLeakModel = camel_case_deprecated(set_leak_model) def get_leak_model(self): """Get the leak model for the attack""" return self.attackModel leakModel = get_leak_model def set_analysis_algorithm(self, analysis_algorithm, leakage_object=None): """Sets the algorithm used for analyzing the trace data You probably want cpa_algorithms.Progressive Args: analysis_algorithm (AlgorithmsBase): Algorithm used for analyzing trace data. Only use cpa_algorithms.Progressive for now. leakage_object (ModelsBase, optional): Model used to get the leakage of the target (i.e. the sbox output). Needs to be set before the attack can happen """ self.attack = analysis_algorithm() self.attack.setProject(self._project) self.attackModel = leakage_object setAnalysisAlgorithm = camel_case_deprecated(set_analysis_algorithm) def process_known_key(self, inpkey): """ Passes known first-round key (if available, may pass None). Returns key under attack which should be highlighted in graph """ return inpkey def process_traces(self, callback=None): """ Run the attack! Args: callback (function(), optional): Function called every reporting interval. Not called if None. Defaults to None. Returns: Statistics object for the attack """ progressBar = None self.attack.setModel(self.attackModel) self.attack.get_statistics().clear() self.attack.setReportingInterval(self.get_reporting_interval()) self.attack.setTargetSubkeys(self.get_target_subkeys()) self.attack.setStatsReadyCallback(callback) for itNum in range(self.get_iterations()): startingTrace = self.get_traces_per_attack() * itNum + self.get_trace_start() endingTrace = startingTrace + self.get_traces_per_attack() - 1 # TODO:support start/end point different per byte self.attack.addTraces(self.getTraceSource(), (startingTrace, endingTrace), progressBar, pointRange=self.get_point_range(None)) return self.attack.get_statistics() processTraces = camel_case_deprecated(process_traces) # for backwards compatability def processTracesNoGUI(self, callback=None, show_progress_bar=False): return self.processTraces(callback, show_progress_bar) def setProject(self, project): self._project = project def project(self): return self._project def get_trace_start(self): return self._traceStart getTraceStart = camel_case_deprecated(get_trace_start) @setupSetParam("Starting Trace") def set_trace_start(self, tnum): self._traceStart = tnum setTraceStart = camel_case_deprecated(set_trace_start) def get_iterations(self): return self._iterations getIterations = camel_case_deprecated(get_iterations) @setupSetParam("Iterations") def set_iterations(self, its): self._iterations = its setIterations = camel_case_deprecated(set_iterations) def get_traces_per_attack(self): return self._tracePerAttack getTracesPerAttack = camel_case_deprecated(get_traces_per_attack) @setupSetParam("Traces per Attack") def set_traces_per_attack(self, trace): if trace < 0: #Get maximum traces from source ts = self.getTraceSource() if ts is None: raise ValueError("traceSource not yet set in attack - set TraceSource first to use automatic getTracesPerAttack") trace = self.getTraceSource().num_traces() self._tracePerAttack = trace setTracesPerAttack = camel_case_deprecated(set_traces_per_attack) def get_reporting_interval(self): return self._reportingInterval getReportingInterval = camel_case_deprecated(get_reporting_interval) @setupSetParam("Reporting Interval") def set_reporting_interval(self, ri): self._reportingInterval = ri setReportingInterval = camel_case_deprecated(set_reporting_interval) def get_point_range(self, bnum=None): return self._pointRange getPointRange = camel_case_deprecated(get_point_range) @setupSetParam("Points Range") def set_point_range(self, rng): if rng[1] < 0: ts = self.getTraceSource() if ts is None: raise ValueError("traceSource not yet set in attack - set TraceSource first to use automatic setPointRange") rng = (rng[0], ts.num_points()) self._pointRange = rng setPointRange = camel_case_deprecated(set_point_range) def known_key(self): """Get the known key via attack""" key = self.process_known_key(self.getTraceSource().get_known_key(self.get_trace_start())) if key is None: key = [None] * len(self.get_statistics().diffs) return key knownKey = camel_case_deprecated(known_key) def set_target_subkeys(self, blist): self._targetSubkeys = blist setTargetSubkeys = camel_case_deprecated(set_target_subkeys) def get_target_subkeys(self): return self._targetSubkeys getTargetSubkeys = camel_case_deprecated(get_target_subkeys) def __del__(self): if __debug__: logging.debug('Deleted: ' + str(self)) def getAbsoluteMode(self): return self.useAbs def refreshByteList(self, _=None): pass def getEnabledSubkeys(self): return None def get_statistics(self): return self.attack.getStatistics() getStatistics = camel_case_deprecated(get_statistics) def updateScript(self, _=None): if self._traceSource is None: return # Add attack 'other' functions such as template generators etc runs = self.findParam('runs') atraces = self.findParam('atraces') strace = self.findParam('strace') ri = self.findParam('reportinterval') #print "runs = %d\natraces= %d\nstrace = %d\n"%(runs.value(), atraces.value(), strace.value()) if (runs.getValue() * atraces.getValue() + strace.getValue()) > self._traceSource.num_traces() or atraces.getValue()<=0: solv = (self._traceSource.num_traces() - strace.getValue()) / runs.getValue() solv = int(solv) atraces.setValue(1, blockAction = True) atraces.setLimits((1, solv)) atraces.setValue(solv, blockAction = True) else: atraces.setLimits((1, self._traceSource.num_traces())) pointrng = self.findParam('prange').getValue() def updateTraceLimits(self): if self._traceSource is None: return self.findParam('prange').setLimits((0, self._traceSource.num_points()-1)) self.findParam('prange').setValue((0, self._traceSource.num_points()-1)) strace = self.findParam('strace') self.findParam('runs').setValue(1) atrace = self.findParam('atraces') strace.setLimits((0, self._traceSource.num_traces()-1)) atrace.setValue(1, blockAction=True) atrace.setLimits((1, self._traceSource.num_traces())) atrace.setValue(self._traceSource.num_traces(), blockAction=True)
class CWNano(ScopeTemplate, util.DisableNewAttr): """CWNano scope object. This class contains the public API for the CWNano hardware. It includes specific settings for each of these devices. To connect to one of these devices, the easiest method is:: import chipwhisperer as cw scope = cw.scope(type=scopes.CWNano) Some sane default settings can be set using:: scope.default_setup() For more help about scope settings, try help() on each of the ChipWhisperer scope submodules (scope.adc, scope.io, scope.glitch): * :attr:`scope.adc <.CWNano.adc>` * :attr:`scope.io <.CWNano.io>` * :attr:`scope.glitch <.CWNano.glitch>` * :meth:`scope.default_setup <.CWNano.default_setup>` * :meth:`scope.con <.CWNano.con>` * :meth:`scope.dis <.CWNano.dis>` * :meth:`scope.get_last_trace <.CWNano.get_last_trace>` * :meth:`scope.arm <.CWNano.arm>` * :meth:`scope.capture <.CWNano.capture>` """ _name = "ChipWhisperer Nano" REQ_ARM = 0x29 REQ_SAMPLES = 0x2A def __init__(self): ScopeTemplate.__init__(self) self._is_connected = False self._cwusb = NAEUSB() self.ser = self._cwusb self.scopetype = self self.dev = self self.xmega = XMEGAPDI(self._cwusb) self.avr = AVRISP(self._cwusb) self.usart = USART(self._cwusb) self.serialstm32f = STM32FSerial(cwserial=self.usart) self.serialstm32f.scope = self self.io = GPIOSettings(self._cwusb) self.adc = ADCSettings(self._cwusb) self.glitch = GlitchSettings(self._cwusb) self._timeout = 2 self._lasttrace = None self.disable_newattr() def default_setup(self): """ Sets up sane capture defaults for this scope * 7.5MHz ADC clock * 7.5MHz output clock * 5000 capture samples * tio1 = serial rx * tio2 = serial tx * glitch module off .. versionadded:: 5.1 Added default setup for CWNano """ self.adc.clk_freq = 7.5E6 self.io.clkout = 7.5E6 self.adc.samples = 5000 self.io.tio1 = "serial_rx" self.io.tio2 = "serial_tx" self.glitch.repeat = 0 def getCurrentScope(self): return self def _getNAEUSB(self): return self._cwusb @property def sn(self): return self._cwusb.snum @property def latest_fw(self): from chipwhisperer.hardware.firmware.cwnano import fwver return {"major": fwver[0], "minor": fwver[1]} @property def fw_version(self): a = self._cwusb.readFwVersion() return {"major": a[0], "minor": a[1], "debug": a[2]} def _con(self, sn=None): try: possible_sn = self._cwusb.get_possible_devices(idProduct=[0xACE0]) serial_numbers = [] if len(possible_sn) > 1: if sn is None: for d in possible_sn: serial_numbers.append("sn = {} ({})".format(str(d['sn']), str(d['product']))) raise Warning("Multiple ChipWhisperers detected. Please specify device from the following list using cw.scope(sn=<SN>): \n{}".format(serial_numbers)) else: sn = None found_id = self._cwusb.con(idProduct=[0xACE0], serial_number=sn) except (IOError, ValueError): raise Warning("Could not connect to cwnano. It may have been disconnected, is in an error state, or is being used by another tool.") self.disable_newattr() self._is_connected = True return True def _dis(self): self.enable_newattr() self.usbdev().close() self._is_connected = False return True def arm(self): """Arm the ADC, the trigger will be GPIO4 rising edge (fixed trigger).""" with DelayedKeyboardInterrupt(): if self.connectStatus is False: raise Warning("Scope \"" + self.getName() + "\" is not connected. Connect it first...") self._cwusb.sendCtrl(self.REQ_ARM, 1) def capture(self): """Raises IOError if unknown failure, returns 'True' if timeout, 'False' if no timeout""" with DelayedKeyboardInterrupt(): starttime = datetime.datetime.now() while self._cwusb.readCtrl(self.REQ_ARM, dlen=1)[0] == 0: # Wait for a moment before re-running the loop time.sleep(0.001) diff = datetime.datetime.now() - starttime # If we've timed out, don't wait any longer for a trigger if (diff.total_seconds() > self._timeout): logging.warning('Timeout in cwnano capture()') return True self._lasttrace = self._cwusb.cmdReadMem(0, self.adc.samples) # can just keep rerunning this until it works I think i = 0 while len(self._lasttrace) < self.adc.samples: logging.debug("couldn't read ADC data from Nano, retrying...") self._lasttrace = self._cwusb.cmdReadMem(0, self.adc.samples) i+= 1 if i > 20: logging.warning("Couldn't read trace data back from Nano") return True self._lasttrace = np.array(self._lasttrace) / 256.0 - 0.5 #self.newDataReceived(0, self._lasttrace, 0, self.adc.clk_freq) return False def get_last_trace(self): """Return the last trace captured with this scope. """ return self._lasttrace getLastTrace = camel_case_deprecated(get_last_trace) def _dict_repr(self): dict = OrderedDict() dict['fw_version'] = self.fw_version dict['io'] = self.io._dict_repr() dict['adc'] = self.adc._dict_repr() dict['glitch'] = self.glitch._dict_repr() return dict def __repr__(self): # Add some extra information about ChipWhisperer type here if self._is_connected: ret = "ChipWhisperer Nano Device\n" return ret + dict_to_str(self._dict_repr()) else: ret = "ChipWhisperer Nano device (disconnected)" return ret def __str__(self): return self.__repr__() def get_possible_devices(self, idProduct): return self._cwusb.get_possible_devices(idProduct=idProduct) def usbdev(self): return self._cwusb
class AES128_8bit(ModelsBase): """Leakage model for AES128 attacks. Make sure to set the actual leakage model (i.e. SBox_Output) """ _name = 'AES 128' hwModels = OrderedDict((mod.name, mod) for mod in (enc_list + dec_list)) hw_models = OrderedDict( (mod.__name__, mod) for mod in (enc_list + dec_list)) def __init__(self, model=SBox_output, bitmask=0xFF): ModelsBase.__init__(self, 16, 256, model=model) self.numRoundKeys = 10 self._mask = bitmask def _updateHwModel(self): """" Re-implement this to update leakage model """ self.modelobj = None #Check if they passed an object... if isinstance(self.model, AESLeakageHelper): self.modelobj = self.model #Check if they passed a class... elif inspect.isclass(self.model) and issubclass( self.model, AESLeakageHelper): self.modelobj = self.model() #Otherwise it's probably one of these older keys (kept for backwards-compatability) else: for mod in self.hwModels: if (mod.c_model_enum_value == self.model) or (mod.name == self.model): self.modelobj = mod() break if self.modelobj is None: raise ValueError("Invalid model: %s" % str(self.model)) def processKnownKey(self, inpkey): if hasattr(self.modelobj, 'processKnownKey'): return self.modelobj.processKnownKey(inpkey) return inpkey def leakage(self, pt, ct, guess, bnum, state): """ Leakage as set by model Args: pt (list): Plaintext/textin ct (list): Ciphertext/textout guess (list): Key guess bnum (list): Subkey Byte Number state (list): The state of the key finding Returns: A hamming weight (int) """ try: #Make a copy so we don't screw with anything... key = list(state['knownkey']) except: #We don't log due to time-sensitive nature... but if state doesn't have "knownkey" will result in #unknown knownkey which causes some attacks to fail. Possibly should make this some sort of #flag to indicate we want to ignore the problem? key = [None] * 16 #Guess can be 'none' if we want to use original key as-is if guess is not None: key[bnum] = guess #Get intermediate value intermediate_value = self.modelobj.leakage(pt, ct, key, bnum) #For bit-wise attacks, mask off specific bit value intermediate_value = self._mask & intermediate_value #Return HW of guess return self.HW[intermediate_value] def key_schedule_rounds(self, inputkey, inputround, desiredround): """Changes the round of inputkey from inputround to desiredround Args: inputkey (list): key that you want to change the round of inputround (int): Round that inputkey is currently in desiredround (int): Round that you want inputkey to be in Returns: desired key (list) """ return key_schedule_rounds(inputkey, inputround, desiredround) keyScheduleRounds = camel_case_deprecated(key_schedule_rounds)
class CW305(TargetTemplate): """CW305 target object. This class contains the public API for the CW305 hardware. To connect to the CW305, the easiest method is:: import chipwhisperer as cw scope = cw.scope() # scope can also be None here, unlike with the default SimpleSerial target = cw.target(scope, targets.CW305, bsfile=<valid FPGA bitstream file>) As of CW5.3, you can also specify the following:: # can also be '35t' target = cw.target(scope, fpga_id='100t') To automatically program with the example AES bitstream If you're using the reference designs, typical configuration requires you to set the FPGA VCC-INT voltage and enable and set the clock via the PLL. You'll probably also want to disable the USB clock during encryption to reduce noise:: target.vccint_set(1.0) #set VCC-INT to 1V target.pll.pll_enable_set(True) #Enable PLL chip target.pll.pll_outenable_set(False, 0) # Disable unused PLL0 target.pll.pll_outenable_set(True, 1) # Enable PLL target.pll.pll_outenable_set(False, 2) # Disable unused PLL2 # optional, but reduces power trace noise target.clkusbautooff = True target.clksleeptime = 1 # 1ms typically good for sleep Don't forget to clock the ChipWhisperer ADC off the FPGA instead of the internal clock:: scope.clock.adc_src = "extclk_x4" scope.clock.reset_adc() # make sure the DCM is locked Note that connecting to the CW305 includes programming the CW305 FPGA, if it isn't already. For more help about CW305 settings, try help() on this CW305 submodule: * target.pll """ _name = "ChipWhisperer CW305 (Artix-7)" BATCHRUN_START = 0x1 BATCHRUN_RANDOM_KEY = 0x2 BATCHRUN_RANDOM_PT = 0x4 def __init__(self): TargetTemplate.__init__(self) self._naeusb = NAEUSB() self.pll = PLLCDCE906(self._naeusb, ref_freq=12.0E6) self.fpga = FPGA(self._naeusb) self.hw = None self.oa = None self._woffset = 0x400 self._woffset_sam3U = 0x000 self._clksleeptime = 1 self._clkusbautooff = True self.last_key = bytearray([0] * 16) def _getNAEUSB(self): return self._naeusb def fpga_write(self, addr, data): """Write to an address on the FPGA Args: addr (int): Address to write to data (list): Data to write to addr Raises: IOError: User attempted to write to a read-only location """ if addr < self._woffset: raise IOError("Write to read-only location: 0x%04x" % addr) return self._naeusb.cmdWriteMem(addr, data) def fpga_read(self, addr, readlen): """Read from an address on the FPGA Args: addr (int): Address to read from readlen (int): Length of data to read Returns: Requested data as a list """ if addr > self._woffset: logging.info( 'Read from write address, confirm this is not an error') data = self._naeusb.cmdReadMem(addr, readlen) return data def usb_clk_setenabled(self, status): """ Turn on or off the Data Clock to the FPGA """ if status: self._naeusb.sendCtrl(CW305_USB.REQ_SYSCFG, CW305_USB.SYSCFG_CLKON) else: self._naeusb.sendCtrl(CW305_USB.REQ_SYSCFG, CW305_USB.SYSCFG_CLKOFF) def usb_trigger_toggle(self, _=None): """ Toggle the trigger line high then low """ self._naeusb.sendCtrl(CW305_USB.REQ_SYSCFG, CW305_USB.SYSCFG_TOGGLE) def vccint_set(self, vccint=1.0): """ Set the VCC-INT for the FPGA """ # print "vccint = " + str(vccint) if (vccint < 0.6) or (vccint > 1.15): raise ValueError("VCC-Int out of range 0.6V-1.1V") # Convert to mV vccint = int(vccint * 1000) vccsetting = [vccint & 0xff, (vccint >> 8) & 0xff, 0] # calculate checksum vccsetting[2] = vccsetting[0] ^ vccsetting[1] ^ CW305_USB.VCCINT_XORKEY self._naeusb.sendCtrl(CW305_USB.REQ_VCCINT, 0, vccsetting) resp = self._naeusb.readCtrl(CW305_USB.REQ_VCCINT, dlen=3) if resp[0] != 2: raise IOError("VCC-INT Write Error, response = %d" % resp[0]) def vccint_get(self): """ Get the last set value for VCC-INT """ resp = self._naeusb.readCtrl(CW305_USB.REQ_VCCINT, dlen=3) return float(resp[1] | (resp[2] << 8)) / 1000.0 def _con(self, scope=None, bsfile=None, force=False, fpga_id=None): """Connect to CW305 board, and download bitstream. If the target has already been programmed it skips reprogramming unless forced. Args: scope (ScopeTemplate): An instance of a scope object. bsfile (path): The path to the bitstream file to program the FPGA with. force (bool): Whether or not to force reprogramming. fpga_id (string): '100t', '35t', or None. If bsfile is None and fpga_id specified, program with AES firmware for fpga_id """ from datetime import datetime self._naeusb.con(idProduct=[0xC305]) if not fpga_id is None: if fpga_id not in ('100t', '35t'): raise ValueError(f"Invalid fpga {fpga_id}") self._fpga_id = fpga_id if self.fpga.isFPGAProgrammed() == False or force: if bsfile is None: if not fpga_id is None: from chipwhisperer.hardware.firmware.cw305 import getsome bsdata = getsome(f"AES_{fpga_id}.bit") starttime = datetime.now() status = self.fpga.FPGAProgram(bsdata, exceptOnDoneFailure=False) stoptime = datetime.now() if status: logging.info('FPGA Config OK, time: %s' % str(stoptime - starttime)) else: logging.warning( 'FPGA Done pin failed to go high, check bitstream is for target device.' ) else: print("No FPGA Bitstream file specified.") elif not os.path.isfile(bsfile): print(("FPGA Bitstream not configured or '%s' not a file." % str(bsfile))) else: starttime = datetime.now() status = self.fpga.FPGAProgram(open(bsfile, "rb"), exceptOnDoneFailure=False) stoptime = datetime.now() if status: logging.info('FPGA Config OK, time: %s' % str(stoptime - starttime)) else: logging.warning( 'FPGA Done pin failed to go high, check bitstream is for target device.' ) self.usb_clk_setenabled(True) self.fpga_write(0x100 + self._woffset, [0]) self.pll.cdce906init() def _dis(self): if self._naeusb: self._naeusb.close() def checkEncryptionKey(self, key): """Validate encryption key""" return key def loadEncryptionKey(self, key): """Write encryption key to FPGA""" self.key = key key = key[::-1] self.fpga_write(0x100 + self._woffset, key) def loadInput(self, inputtext): """Write input to FPGA""" self.input = inputtext text = inputtext[::-1] self.fpga_write(0x200 + self._woffset, text) def is_done(self): """Check if FPGA is done""" result = self.fpga_read(0x50, 1)[0] if result == 0x00: return False else: # Clear trigger self.fpga_write(0x40 + self._woffset, [0]) # LED Off self.fpga_write(0x10 + self._woffset, [0]) return True isDone = camel_case_deprecated(is_done) def readOutput(self): """"Read output from FPGA""" data = self.fpga_read(0x200, 16) data = data[::-1] #self.newInputData.emit(util.list2hexstr(data)) return data @property def latest_fw(self): cw_type = self._getCWType() if cw_type == "cwlite": from chipwhisperer.hardware.firmware.cwlite import fwver elif cw_type == "cw1200": from chipwhisperer.hardware.firmware.cw1200 import fwver ret = OrderedDict() return {"major": fwver[0], "minor": fwver[1]} @property def fw_version(self): a = self._naeusb.readFwVersion() return {"major": a[0], "minor": a[1], "debug": a[2]} @property def clkusbautooff(self): """ If set, the USB clock is automatically disabled on capture. The USB clock is re-enabled after self.clksleeptime milliseconds. Reads/Writes to the FPGA will not be possible until after the USB clock is reenabled, meaning :code:`usb_trigger_toggle()` must be used to trigger the FPGA to perform an encryption. :Getter: Gets whether to turn off the USB clock on capture :Setter: Sets whether to turn off the USB clock on capture """ return self._clkusbautooff @clkusbautooff.setter def clkusbautooff(self, state): self._clkusbautooff = state @property def clksleeptime(self): """ Time (in milliseconds) that the USB clock is disabled for upon capture, if self.clkusbautooff is set. """ return self._clksleeptime @clksleeptime.setter def clksleeptime(self, value): self._clksleeptime = value def go(self): """Disable USB clock (if requested), perform encryption, re-enable clock""" if self.clkusbautooff: self.usb_clk_setenabled(False) #LED On self.fpga_write(0x10 + self._woffset, [0x01]) time.sleep(0.001) self.usb_trigger_toggle() # self.FPGAWrite(0x100, [1]) # self.FPGAWrite(0x100, [0]) if self.clkusbautooff: time.sleep(self.clksleeptime / 1000.0) self.usb_clk_setenabled(True) def simpleserial_read(self, cmd, pay_len, end='\n', timeout=250, ack=True): """Read data from target Mimics simpleserial protocol of serial based targets Args: cmd (str): Command to ues. Only accepts 'r' for now. pay_len: Unused end: Unused timeout: Unused ack: Unused Returns: Value from Crypto output register .. versionadded:: 5.1 Added simpleserial_read to CW305 """ if cmd == "r": return self.readOutput() else: raise ValueError("Unknown command {}".format(cmd)) def simpleserial_write(self, cmd, data, end=None): """Write data to target. Mimics simpleserial protocol of serial based targets. Args: cmd (str): Command to use. Target supports 'p' (write plaintext), and 'k' (write key). data (bytearray): Data to write to target end: Unused Raises: ValueError: Unknown command .. versionadded:: 5.1 Added simpleserial_write to CW305 """ if cmd == 'p': self.loadInput(data) self.go() elif cmd == 'k': self.loadEncryptionKey(data) else: raise ValueError("Unknown command {}".format(cmd)) def set_key(self, key, ack=False, timeout=250): """Checks if key is different from the last one sent. If so, send it. Args: key (bytearray): key to send ack: Unused timeout: Unused .. versionadded:: 5.1 Added set_key to CW305 """ if self.last_key != key: self.last_key = key self.simpleserial_write('k', key) def batchRun(self, batchsize=1024, random_key=True, random_pt=True, seed=None): """ Run multiple encryptions on random data Args: batchsize (int): The number of encryption to run (default 1024). random_key (bool): True if the key is random (default False). random_pt (bool): True if the plaintext are random (default True). seed (int): random int32 for the PRG. """ if seed is None: seed = random.randint(0, 2**32) data = [] data.extend( packuint32(1 | (random_key << 1) | (random_pt << 2) | (batchsize << 16))) data.extend(packuint32(seed)) self.sam3u_write(0, data) # generate the inputs if random_key: key = [[0 for x in range(16)] for y in range(batchsize)] else: key = None if random_pt: pt = [[0 for x in range(16)] for y in range(batchsize)] else: pt = None for b in range(batchsize): if random_key: for j in range(16): key[b][15 - j] = seed >> 24 seed += ((seed * seed) & 0xffffffff) | 0x5 seed &= 0xffffffff if random_pt: for j in range(16): pt[b][15 - j] = seed >> 24 seed += ((seed * seed) & 0xffffffff) | 0x5 seed &= 0xffffffff return key, pt def sam3u_write(self, addr, data): """Write to an address on the FPGA Args: addr (int): Address to write to data (list): Data to write to addr Raises: IOError: User attempted to write to a read-only location """ if addr < self._woffset_sam3U: raise IOError("Write to read-only location: 0x%04x" % addr) if len(data) > (256 + addr): raise IOError("Write will overflow at location: 0x%04x" % (256)) return self._naeusb.cmdWriteSam3U(addr, data) @fw_ver_required(0, 30) def spi_mode(self, enable=True, timeout=200, bsfile=None): """Enter programming mode for the onboard SPI chip Reprograms the FPGA with the appropriate bitstream and returns an object with which to program the CW305 SPI chip (see documentation on the returned object for more info) Args: enable (bool): Enable the SPI interface before returning it. Defaults to True timeout (int): USB timeout in ms. Defaults to 200. bsfile (string): If not None, program with a bitstream pointed to by bsfile. If None, program with SPI passthrough bitstream for the chip specified during connection (or cw.target()) Returns: A FPGASPI object which can be used to erase/program/verify/read the SPI chip on the CW305. """ from datetime import datetime if self._fpga_id is None and bsfile is None: logging.warning( "CW305 requires passthrough bitstream to program SPI chip, but file/chip not specified" ) else: bsdata = None if self._fpga_id: from chipwhisperer.hardware.firmware.cw305 import getsome bsdata = getsome(f"SPI_flash_{self._fpga_id}.bit") else: bsdata = open(bsfile, "rb") starttime = datetime.now() status = self.fpga.FPGAProgram(bsdata, exceptOnDoneFailure=False) stoptime = datetime.now() if status: logging.info('FPGA Config OK, time: %s' % str(stoptime - starttime)) else: logging.warning( 'FPGA Done pin failed to go high, check bitstream is for target device.' ) spi = FPGASPI(self._naeusb, timeout) spi.enable_interface(enable) return spi
class CW305(TargetTemplate): """CW305 target object. This class contains the public API for the CW305 hardware. To connect to the CW305, the easiest method is:: import chipwhisperer as cw scope = cw.scope() # scope can also be None here, unlike with the default SimpleSerial target = cw.target(scope, targets.CW305, bsfile=<valid FPGA bitstream file>) As of CW5.3, you can also specify the following:: # can also be '35t' target = cw.target(scope, fpga_id='100t') To automatically program with the example AES bitstream If you're using the reference designs, typical configuration requires you to set the FPGA VCC-INT voltage and enable and set the clock via the PLL. You'll probably also want to disable the USB clock during encryption to reduce noise:: target.vccint_set(1.0) #set VCC-INT to 1V target.pll.pll_enable_set(True) #Enable PLL chip target.pll.pll_outenable_set(False, 0) # Disable unused PLL0 target.pll.pll_outenable_set(True, 1) # Enable PLL target.pll.pll_outenable_set(False, 2) # Disable unused PLL2 # optional, but reduces power trace noise target.clkusbautooff = True target.clksleeptime = 1 # 1ms typically good for sleep Don't forget to clock the ChipWhisperer ADC off the FPGA instead of the internal clock:: scope.clock.adc_src = "extclk_x4" scope.clock.reset_adc() # make sure the DCM is locked Note that connecting to the CW305 includes programming the CW305 FPGA, if it isn't already. For more help about CW305 settings, try help() on this CW305 submodule: * target.pll """ _name = "ChipWhisperer CW305 (Artix-7)" BATCHRUN_START = 0x1 BATCHRUN_RANDOM_KEY = 0x2 BATCHRUN_RANDOM_PT = 0x4 def __init__(self): TargetTemplate.__init__(self) self._naeusb = NAEUSB() self.pll = PLLCDCE906(self._naeusb, ref_freq=12.0E6) self.fpga = FPGA(self._naeusb) self.hw = None self.oa = None self._woffset_sam3U = 0x000 self.default_verilog_defines = 'cw305_defines.v' self.default_verilog_defines_full_path = '../../hardware/victims/cw305_artixtarget/fpga/common/' + self.default_verilog_defines self.registers = 12 # number of registers we expect to find self.bytecount_size = 7 # pBYTECNT_SIZE in Verilog self._clksleeptime = 1 self._clkusbautooff = True self.last_key = bytearray([0] * 16) self.target_name = 'AES' def _getNAEUSB(self): return self._naeusb def slurp_defines(self, defines_files=None): """ Parse Verilog defines file so we can access register and bit definitions by name and avoid 'magic numbers'. Args: defines_files (list): list of Verilog define files to parse """ self.verilog_define_matches = 0 if type(defines_files) != list: logging.error( 'defines_files must be provided as a list (even it it contains a single element)' ) for i, defines_file in enumerate(defines_files): if type(defines_file) == io.BytesIO: defines = io.TextIOWrapper(defines_file) else: if not os.path.isfile(defines_file): logging.error( 'Cannot find %s. Please specify the location of %s on your filesystem.' % (defines_files, self.default_verilog_defines)) defines = open(defines_file, 'r') define_regex_base = re.compile(r'`define') define_regex_reg = re.compile(r'`define\s+?REG_') define_regex_radix = re.compile( r'`define\s+?(\w+).+?\'([bdh])([0-9a-fA-F]+)') define_regex_noradix = re.compile(r'`define\s+?(\w+?)\s+?(\d+?)') block_offset = 0 for define in defines: if define_regex_base.search(define): reg = define_regex_reg.search(define) match = define_regex_radix.search(define) if match: self.verilog_define_matches += 1 if match.group(2) == 'b': radix = 2 elif match.group(2) == 'h': radix = 16 else: radix = 10 setattr(self, match.group(1), int(match.group(3), radix) + block_offset) else: match = define_regex_noradix.search(define) if match: self.verilog_define_matches += 1 setattr(self, match.group(1), int(match.group(2), 10) + block_offset) else: logging.warning("Couldn't parse line: %s", define) defines.close() # make sure everything is cool: if self.verilog_define_matches != self.registers: logging.warning( "Trouble parsing Verilog defines files (%s): didn't find the right number of defines; expected %d, got %d.\n" % (defines_file, self.registers, self.verilog_define_matches) + "Ensure that the Verilog defines files above are the same that were used to build the bitfile." ) def get_fpga_buildtime(self): """Returns date and time when FPGA bitfile was generated. """ raw = self.fpga_read(self.REG_BUILDTIME, 4) # definitions: Xilinx XAPP1232 day = raw[3] >> 3 month = ((raw[3] & 0x7) << 1) + (raw[2] >> 7) year = ((raw[2] >> 1) & 0x3f) + 2000 hour = ((raw[2] & 0x1) << 4) + (raw[1] >> 4) minute = ((raw[1] & 0xf) << 2) + (raw[0] >> 6) return "FPGA build time: {}/{}/{}, {}:{}".format( month, day, year, hour, minute) def fpga_write(self, addr, data): """Write to an address on the FPGA Args: addr (int): Address to write to data (list): Data to write to addr """ addr = addr << self.bytecount_size return self._naeusb.cmdWriteMem(addr, data) def fpga_read(self, addr, readlen): """Read from an address on the FPGA Args: addr (int): Address to read from readlen (int): Length of data to read Returns: Requested data as a list """ addr = addr << self.bytecount_size data = self._naeusb.cmdReadMem(addr, readlen) return data def usb_clk_setenabled(self, status): """ Turn on or off the Data Clock to the FPGA """ if status: self._naeusb.sendCtrl(CW305_USB.REQ_SYSCFG, CW305_USB.SYSCFG_CLKON) else: self._naeusb.sendCtrl(CW305_USB.REQ_SYSCFG, CW305_USB.SYSCFG_CLKOFF) def usb_trigger_toggle(self, _=None): """ Toggle the trigger line high then low """ self._naeusb.sendCtrl(CW305_USB.REQ_SYSCFG, CW305_USB.SYSCFG_TOGGLE) def vccint_set(self, vccint=1.0): """ Set the VCC-INT for the FPGA """ # print "vccint = " + str(vccint) if (vccint < 0.6) or (vccint > 1.15): raise ValueError("VCC-Int out of range 0.6V-1.1V") # Convert to mV vccint = int(vccint * 1000) vccsetting = [vccint & 0xff, (vccint >> 8) & 0xff, 0] # calculate checksum vccsetting[2] = vccsetting[0] ^ vccsetting[1] ^ CW305_USB.VCCINT_XORKEY self._naeusb.sendCtrl(CW305_USB.REQ_VCCINT, 0, vccsetting) resp = self._naeusb.readCtrl(CW305_USB.REQ_VCCINT, dlen=3) if resp[0] != 2: raise IOError("VCC-INT Write Error, response = %d" % resp[0]) def vccint_get(self): """ Get the last set value for VCC-INT """ resp = self._naeusb.readCtrl(CW305_USB.REQ_VCCINT, dlen=3) return float(resp[1] | (resp[2] << 8)) / 1000.0 def _con(self, scope=None, bsfile=None, force=False, fpga_id=None, defines_files=None, slurp=True): """Connect to CW305 board, and download bitstream. If the target has already been programmed it skips reprogramming unless forced. Args: scope (ScopeTemplate): An instance of a scope object. bsfile (path): The path to the bitstream file to program the FPGA with. force (bool): Whether or not to force reprogramming. fpga_id (string): '100t', '35t', or None. If bsfile is None and fpga_id specified, program with AES firmware for fpga_id defines_files (list, optional): path to cw305_defines.v slurp (bool, optional): Whether or not to slurp the Verilog defines. """ from datetime import datetime self._naeusb.con(idProduct=[0xC305]) if not fpga_id is None: if fpga_id not in ('100t', '35t'): raise ValueError(f"Invalid fpga {fpga_id}") self._fpga_id = fpga_id if self.fpga.isFPGAProgrammed() == False or force: if bsfile is None: if not fpga_id is None: from chipwhisperer.hardware.firmware.cw305 import getsome if self.target_name == 'AES': bsdata = getsome(f"AES_{fpga_id}.bit") elif self.target_name == 'Cryptech ecdsa256-v1 pmul': bsdata = getsome(f"ECDSA256v1_pmul_{fpga_id}.bit") starttime = datetime.now() status = self.fpga.FPGAProgram(bsdata, exceptOnDoneFailure=False) stoptime = datetime.now() if status: logging.info('FPGA Config OK, time: %s' % str(stoptime - starttime)) else: logging.warning( 'FPGA Done pin failed to go high, check bitstream is for target device.' ) else: print("No FPGA Bitstream file specified.") elif not os.path.isfile(bsfile): print(("FPGA Bitstream not configured or '%s' not a file." % str(bsfile))) else: starttime = datetime.now() status = self.fpga.FPGAProgram(open(bsfile, "rb"), exceptOnDoneFailure=False) stoptime = datetime.now() if status: logging.info('FPGA Config OK, time: %s' % str(stoptime - starttime)) else: logging.warning( 'FPGA Done pin failed to go high, check bitstream is for target device.' ) self.usb_clk_setenabled(True) self.pll.cdce906init() if defines_files is None: if fpga_id is None: verilog_defines = [self.default_verilog_defines_full_path] else: from chipwhisperer.hardware.firmware.cw305 import getsome verilog_defines = [getsome(self.default_verilog_defines)] else: verilog_defines = defines_files if slurp: self.slurp_defines(verilog_defines) def _dis(self): if self._naeusb: self._naeusb.close() def checkEncryptionKey(self, key): """Validate encryption key""" return key def loadEncryptionKey(self, key): """Write encryption key to FPGA""" self.key = key key = key[::-1] self.fpga_write(self.REG_CRYPT_KEY, key) def loadInput(self, inputtext): """Write input to FPGA""" self.input = inputtext text = inputtext[::-1] self.fpga_write(self.REG_CRYPT_TEXTIN, text) def is_done(self): """Check if FPGA is done""" result = self.fpga_read(self.REG_CRYPT_GO, 1)[0] if result == 0x01: return False else: self.fpga_write(self.REG_USER_LED, [0]) return True isDone = camel_case_deprecated(is_done) def readOutput(self): """"Read output from FPGA""" data = self.fpga_read(self.REG_CRYPT_CIPHEROUT, 16) data = data[::-1] #self.newInputData.emit(util.list2hexstr(data)) return data @property def latest_fw(self): cw_type = self._getCWType() if cw_type == "cwlite": from chipwhisperer.hardware.firmware.cwlite import fwver elif cw_type == "cw1200": from chipwhisperer.hardware.firmware.cw1200 import fwver ret = OrderedDict() return {"major": fwver[0], "minor": fwver[1]} @property def fw_version(self): a = self._naeusb.readFwVersion() return {"major": a[0], "minor": a[1], "debug": a[2]} @property def clkusbautooff(self): """ If set, the USB clock is automatically disabled on capture. The USB clock is re-enabled after self.clksleeptime milliseconds. Reads/Writes to the FPGA will not be possible until after the USB clock is reenabled, meaning :code:`usb_trigger_toggle()` must be used to trigger the FPGA to perform an encryption. :Getter: Gets whether to turn off the USB clock on capture :Setter: Sets whether to turn off the USB clock on capture """ return self._clkusbautooff @clkusbautooff.setter def clkusbautooff(self, state): self._clkusbautooff = state @property def clksleeptime(self): """ Time (in milliseconds) that the USB clock is disabled for upon capture, if self.clkusbautooff is set. """ return self._clksleeptime @clksleeptime.setter def clksleeptime(self, value): self._clksleeptime = value def go(self): """Disable USB clock (if requested), perform encryption, re-enable clock""" if self.clkusbautooff: self.usb_clk_setenabled(False) self.fpga_write(self.REG_USER_LED, [0x01]) time.sleep(0.001) self.usb_trigger_toggle() # it's also possible to 'go' via register write but that won't take if # the USB clock was turned off: #self.fpga_write(self.REG_CRYPT_GO, [1]) if self.clkusbautooff: time.sleep(self.clksleeptime / 1000.0) self.usb_clk_setenabled(True) def simpleserial_read(self, cmd, pay_len, end='\n', timeout=250, ack=True): """Read data from target Mimics simpleserial protocol of serial based targets Args: cmd (str): Command to ues. Only accepts 'r' for now. pay_len: Unused end: Unused timeout: Unused ack: Unused Returns: Value from Crypto output register .. versionadded:: 5.1 Added simpleserial_read to CW305 """ if cmd == "r": return self.readOutput() else: raise ValueError("Unknown command {}".format(cmd)) def simpleserial_write(self, cmd, data, end=None): """Write data to target. Mimics simpleserial protocol of serial based targets. Args: cmd (str): Command to use. Target supports 'p' (write plaintext), and 'k' (write key). data (bytearray): Data to write to target end: Unused Raises: ValueError: Unknown command .. versionadded:: 5.1 Added simpleserial_write to CW305 """ if cmd == 'p': self.loadInput(data) self.go() elif cmd == 'k': self.loadEncryptionKey(data) else: raise ValueError("Unknown command {}".format(cmd)) def set_key(self, key, ack=False, timeout=250): """Checks if key is different from the last one sent. If so, send it. Args: key (bytearray): key to send ack: Unused timeout: Unused .. versionadded:: 5.1 Added set_key to CW305 """ if self.last_key != key: self.last_key = key self.simpleserial_write('k', key) def batchRun(self, batchsize=1024, random_key=True, random_pt=True, seed=None): """ Run multiple encryptions on random data Args: batchsize (int): The number of encryption to run (default 1024). random_key (bool): True if the key is random (default False). random_pt (bool): True if the plaintext are random (default True). seed (int): random int32 for the PRG. """ if seed is None: seed = random.randint(0, 2**32) data = [] data.extend( packuint32(1 | (random_key << 1) | (random_pt << 2) | (batchsize << 16))) data.extend(packuint32(seed)) self.sam3u_write(0, data) # generate the inputs if random_key: key = [[0 for x in range(16)] for y in range(batchsize)] else: key = None if random_pt: pt = [[0 for x in range(16)] for y in range(batchsize)] else: pt = None for b in range(batchsize): if random_key: for j in range(16): key[b][15 - j] = seed >> 24 seed += ((seed * seed) & 0xffffffff) | 0x5 seed &= 0xffffffff if random_pt: for j in range(16): pt[b][15 - j] = seed >> 24 seed += ((seed * seed) & 0xffffffff) | 0x5 seed &= 0xffffffff return key, pt def sam3u_write(self, addr, data): """Write to an address on the FPGA Args: addr (int): Address to write to data (list): Data to write to addr Raises: IOError: User attempted to write to a read-only location """ if addr < self._woffset_sam3U: raise IOError("Write to read-only location: 0x%04x" % addr) if len(data) > (256 + addr): raise IOError("Write will overflow at location: 0x%04x" % (256)) return self._naeusb.cmdWriteSam3U(addr, data) @fw_ver_required(0, 30) def spi_mode(self, enable=True, timeout=200, bsfile=None): """Enter programming mode for the onboard SPI chip Reprograms the FPGA with the appropriate bitstream and returns an object with which to program the CW305 SPI chip (see documentation on the returned object for more info) Args: enable (bool): Enable the SPI interface before returning it. Defaults to True timeout (int): USB timeout in ms. Defaults to 200. bsfile (string): If not None, program with a bitstream pointed to by bsfile. If None, program with SPI passthrough bitstream for the chip specified during connection (or cw.target()) Returns: A FPGASPI object which can be used to erase/program/verify/read the SPI chip on the CW305. """ from datetime import datetime if self._fpga_id is None and bsfile is None: logging.warning( "CW305 requires passthrough bitstream to program SPI chip, but file/chip not specified" ) else: bsdata = None if self._fpga_id: from chipwhisperer.hardware.firmware.cw305 import getsome bsdata = getsome(f"SPI_flash_{self._fpga_id}.bit") else: bsdata = open(bsfile, "rb") starttime = datetime.now() status = self.fpga.FPGAProgram(bsdata, exceptOnDoneFailure=False) stoptime = datetime.now() if status: logging.info('FPGA Config OK, time: %s' % str(stoptime - starttime)) else: logging.warning( 'FPGA Done pin failed to go high, check bitstream is for target device.' ) spi = FPGASPI(self._naeusb, timeout) spi.enable_interface(enable) return spi @fw_ver_required(0, 40) def gpio_mode(self, timeout=200): """Allow arbitrary GPIO access on SAM3U Allows low-level IO access to SAM3U GPIO, and also SPI transfers. (see documentation on the returned object for more info) Args: timeout (int): USB timeout in ms. Defaults to 200. Returns: A FPGAIO object which can be used to access IO on the CW305. """ io = FPGAIO(self._naeusb, timeout) return io
class SimpleSerial(TargetTemplate, util.DisableNewAttr): """SimpleSerial target object. This class contains the public API for a target that uses serial communication. The easiest way to connect to the target is:: import chipwhisperer as cw scope = cw.scope() target = cw.target(scope) The target is automatically connected to if the default configuration adequate. For more help use the help() function with one of the submodules (target.baud, target.write, target.read, ...). * :attr:`target.baud <.SimpleSerial.baud>` * :meth:`target.write <.SimpleSerial.write>` * :meth:`target.read <.SimpleSerial.read>` * :meth:`target.simpleserial_wait_ack <.SimpleSerial.simpleserial_wait_ack>` * :meth:`target.simpleserial_write <.SimpleSerial.simpleserial_write>` * :meth:`target.simpleserial_read <.SimpleSerial.simpleserial_read>` * :meth:`target.set_key <.SimpleSerial.set_key>` * :meth:`target.close <.SimpleSerial.close>` * :meth:`target.con <.SimpleSerial.con>` """ _name = "Simple Serial" def __init__(self): TargetTemplate.__init__(self) self.ser = SimpleSerial_ChipWhispererLite() self.keylength = 16 self.textlength = 16 self.outputlength = 16 self.input = "" self.key = "" self._protver = 'auto' self._read_timeout = 500 self.masklength = 18 self._fixedMask = True self.initmask = '1F 70 D6 3C 23 EB 1A B8 6A D5 E2 0D 5F D9 58 A3 CA 9D' self._mask = util.hexStrToByteArray(self.initmask) self.protformat = 'hex' self.last_key = bytearray(16) # Preset lists are in the form # {'Dropdown Name':['Init Command', 'Load Key Command', 'Load Input Command', 'Go Command', 'Output Format']} # If a command is None, it's left unchanged and the text field is editable; # Otherwise, it's loaded with the value and set to readonly self.presets = { 'Custom': [None, None, None, None, None], 'SimpleSerial Encryption': ['', 'k$KEY$\\n', '', 'p$TEXT$\\n', 'r$RESPONSE$\\n'], 'SimpleSerial Authentication': [ '', 'k$KEY$\\n', 't$EXPECTED$\\n', 'p$TEXT$\\n', 'r$RESPONSE$\\n' ], 'Glitching': [None, None, None, None, '$GLITCH$\\n'], } self._preset = 'Custom' self._linkedmaskgroup = (('maskgroup', 'cmdmask'), ('maskgroup', 'initmask'), ('maskgroup', 'masktype'), ('maskgroup', 'masklen'), ('maskgroup', 'newmask')) self._proto_ver = "auto" self._proto_timeoutms = 20 self._init_cmd = '' self._key_cmd = 'k$KEY$\n' self._input_cmd = '' self._go_cmd = 'p$TEXT$\n' self._output_cmd = 'r$RESPONSE$\n' self._mask_enabled = False self._mask_cmd = 'm$MASK$\n' self.outstanding_ack = False self.setConnection(self.ser) self.disable_newattr() def getInitialMask(self): return " ".join(["%02X" % b for b in self._mask]) def setInitialMask(self, initialMask, binaryMask=False): if initialMask: if binaryMask: maskStr = '' for s in initialMask: maskStr += '%02x' % s self._mask = bytearray(initialMask) else: maskStr = initialMask self._mask = util.hexStrToByteArray(initialMask) self.initmask = maskStr @property def fixed_mask(self): if self.getMaskEnabled(): return self.getInitialMask() return '' @fixed_mask.setter def fixed_mask(self, m): self.setInitialMask(m) def _dict_repr(self): dict = OrderedDict() dict['key_len'] = self.key_len dict['input_len'] = self.input_len dict['output_len'] = self.output_len dict['mask_len'] = self.mask_len dict['read_timeout'] = self.read_timeout dict['init_cmd'] = self.init_cmd dict['key_cmd'] = self.key_cmd dict['input_cmd'] = self.input_cmd dict['go_cmd'] = self.go_cmd dict['output_cmd'] = self.output_cmd dict['mask_cmd'] = self.mask_cmd dict['mask_enabled'] = self.mask_enabled dict['mask_type'] = self.mask_type if dict['mask_type'] == 'fixed': dict['fixed_mask'] = self.fixed_mask dict['baud'] = self.baud dict['protver'] = self.protver return dict @property def key_len(self): """The length of the key (in bytes)""" return self.keyLen() @key_len.setter def key_len(self, length): self.setKeyLen(length) @property def input_len(self): """The length of the input to the crypto algorithm (in bytes)""" return self.textLen() @input_len.setter def input_len(self, length): self.setTextLen(length) @property def output_len(self): """The length of the output expected from the crypto algorithm (in bytes)""" return self.textLen() @output_len.setter def output_len(self, length): return self.setOutputLen(length) @property def read_timeout(self): """Timeout in mS on how long to wait for target to respond.""" return self._read_timeout @read_timeout.setter def read_timeout(self, timeout): self._read_timeout = timeout @property def mask_len(self): """The length of the mask to send (in bytes)""" return self.maskLen() @mask_len.setter def mask_len(self, length): return self.setMaskLen(length) @property def init_cmd(self): """The command sent to the target before starting a capture. This value is a string that is sent to the target via the serial port. It can contain 4 special strings that are replaced during each capture: - "$KEY$": The encryption key - "$TEXT$": The text input - "$EXPECTED$": The expected result of the target's operation - "$MASK$": The mask used for masked-AES implementation These strings are replaced with ASCII values ex: k$KEY$ -> k0011223344556677 Getter: Return the current init command Setter: Set a new init command """ return self._init_cmd @init_cmd.setter def init_cmd(self, cmd): self._init_cmd = cmd @property def key_cmd(self): """The command used to send the key to the target. See init_cmd for details about special strings. Getter: Return the current key command Setter: Set a new key command """ return self._key_cmd @key_cmd.setter def key_cmd(self, cmd): self._key_cmd = cmd @property def input_cmd(self): """The command used to send the text input to the target. See init_cmd for details about special strings. Getter: Return the current text input command Setter: Set a new text input command """ return self._input_cmd @input_cmd.setter def input_cmd(self, cmd): self._input_cmd = cmd @property def go_cmd(self): """The command used to tell the target to start the operation. See init_cmd for details about special strings. Getter: Return the current text input command Setter: Set a new text input command """ return self._go_cmd @go_cmd.setter def go_cmd(self, cmd): self._go_cmd = cmd @property def output_cmd(self): """The expected format of the output string. The output received from the target is compared to this string after capturing a trace. If the format doesn't match, an error is logged. This format string can contain two special strings: * "$RESPONSE$": If the format contains $RESPONSE$, then this part of the received text is converted to the output text (ciphertext or similar). The length of this response string is given in outputLen() and set by setOutputLen(). * "$GLITCH$": If the format starts with $GLITCH$, then all output is redirected to the glitch explorer. Getter: Return the current output format Setter: Set a new output format """ return self._output_cmd @output_cmd.setter def output_cmd(self, cmd): self._output_cmd = cmd @property def mask_cmd(self): """The command used to set a mask for the masked-AES implementation. This command might be ignored by unsupported targets. See init_cmd for details about special strings. Getter: Return the current mask command Setter: Set a new mask command """ return self._mask_cmd @mask_cmd.setter def mask_cmd(self, cmd): self._mask_cmd = cmd @property def mask_type(self): """mask_type is either 'fixed' or 'random'.""" return "fixed" if self.getMaskType() else "random" @mask_type.setter def mask_type(self, masktype): if masktype == 'fixed' or masktype == True: self._fixedMask = True elif masktype == 'random' or masktype == False: self._fixedMask = False else: raise ValueError( 'Invalid value for mask_type. Should be "fixed" or "random"') @property def mask_enabled(self): return self._mask_enabled @mask_enabled.setter def mask_enabled(self, enable): self._mask_enabled = enable @property def baud(self): """The current baud rate of the serial connection. :Getter: Return the current baud rate. :Setter: Set a new baud rate. Valid baud rates are any integer in the range [500, 2000000]. Raises: AttributeError: Target doesn't allow baud to be changed. """ if hasattr(self.ser, 'baud') and callable(self.ser.baud): return self.ser.baud() else: raise AttributeError("Can't access baud rate") @baud.setter def baud(self, new_baud): if hasattr(self.ser, 'baud') and callable(self.ser.baud): self.ser.setBaud(new_baud) else: raise AttributeError("Can't access baud rate") @property def protver(self): """Get the protocol version used for the target """ return self._proto_ver @protver.setter def protver(self, value): """Set the protocol version used for the target ('1.1', '1.0', or 'auto') """ self._proto_ver = value def setKeyLen(self, klen): """ Set key length in bytes """ self.keylength = klen def keyLen(self): """ Return key length in bytes """ return self.keylength def setMaskLen(self, mlen): self.masklength = mlen def maskLen(self): return self.masklength def setTextLen(self, tlen): """ Set plaintext length. tlen given in bytes """ self.textlength = tlen def textLen(self): """ Return plaintext length in bytes """ return self.textlength def setOutputLen(self, tlen): """ Set plaintext length in bytes """ self.outputlength = tlen def outputLen(self): """ Return output length in bytes """ return self.outputlength def setProtFormat(self, protformat): """ Set the protocol format used 'bin' or 'hex' """ self.protformat = protformat def protFormat(self): """ Return the protocol format used 'bin' or 'hex' """ return self.protformat def getConnection(self): return self.ser def setConnection(self, con): self.ser = con self.ser.connectStatus = self.connectStatus self.ser.selectionChanged() def _con(self, scope=None): if not scope or not hasattr(scope, "qtadc"): Warning( "You need a scope with OpenADC connected to use this Target") self.outstanding_ack = False self.ser.con(scope) # 'x' flushes everything & sets system back to idle self.ser.write("xxxxxxxxxxxxxxxxxxxxxxxx") self.ser.flush() def close(self): if self.ser != None: self.ser.close() def getVersion(self): self.ser.flush() self.ser.write("v\n") data = self.ser.read(4, timeout=self._proto_timeoutms) if len(data) > 1 and data[0] == 'z': logging.info("SimpleSerial: protocol V1.1 detected") return '1.1' else: logging.info("SimpleSerial: protocol V1.0 detected") return '1.0' def init(self): self.ser.flush() ver = self.protver if ver == 'auto': self._protver = self.getVersion() else: self._protver = ver self.outstanding_ack = False self.runCommand(self._init_cmd) # If we use a fix mask, set it once at init if self._mask_enabled and self._mask_cmd and self.getMaskType(): self._mask = self.checkMask(self._mask) self.runCommand(self._mask_cmd) def newRandMask(self, _=None): new_mask = [random.randint(0, 255) for _ in range(self.maskLen())] self._mask = bytearray(new_mask) def reinit(self): if self._mask_enabled and self._mask_cmd: # Only set a mask if it's random. Fixed mask is set by init() if not self.getMaskType(): # Random self.newRandMask() self._mask = self.checkMask(self._mask) self.runCommand(self._mask_cmd) def setModeEncrypt(self): pass def setModeDecrypt(self): pass def convertVarToString(self, var): if isinstance(var, str): return var sep = "" s = sep.join(["%02x" % b for b in var]) return s def runCommand(self, cmdstr, flushInputBefore=True): if self.connectStatus == False: raise Warning( "Can't write to the target while disconected. Connect to it first." ) if cmdstr is None or len(cmdstr) == 0: return # Protocol version 1.1 waits for ACK - if we have outstanding ACK, wait now if self._protver == '1.1': if self.outstanding_ack: # TODO - Should be user-defined maybe data = self.ser.read(4, timeout=500) if len(data) > 1: if data[0] != 'z': logging.error("SimpleSerial: ACK ERROR, read %02x" % ord(data[0])) else: logging.error( "SimpleSerial: ACK ERROR, did not see anything - TIMEOUT possible!" ) self.outstanding_ack = False varList = [("$KEY$", self.key, "Hex Encryption Key"), ("$TEXT$", self.input, "Input Plaintext"), ("$MASK$", self._mask, "Mask"), ("$EXPECTED$", self.getExpected(), "Expected Ciphertext")] newstr = cmdstr #Find variables to insert for v in varList: if v[1] is not None: newstr = newstr.replace(v[0], self.convertVarToString(v[1])) #This is dumb newstr = newstr.replace("\\n", "\n") newstr = newstr.replace("\\r", "\r") #print newstr try: if flushInputBefore: self.ser.flushInput() if self.protformat == "bin": newstr = binascii.unhexlify(newstr) self.ser.write(newstr) except USBError: self.dis() raise Warning( "Error in the target. It may have been disconnected.") except Exception as e: self.dis() raise e if self._protver == '1.1': self.outstanding_ack = True def loadEncryptionKey(self, key): """ Updates encryption key on target. The key is updated in this object and sent to the target over serial. """ self.key = key if self.key: self.runCommand(self._key_cmd) def loadInput(self, inputtext): """ Sends plaintext to target Also updates the internal plaintext """ self.input = inputtext self.runCommand(self._input_cmd) def loadMask(self, mask): self.mask = mask self.runCommand(self._mask_cmd) def is_done(self): return True def readOutput(self): dataLen = self.outputlength * 2 fmt = self._output_cmd #This is dumb fmt = fmt.replace("\\n", "\n") fmt = fmt.replace("\\r", "\r") if len(fmt) == 0: return None if fmt.startswith("$GLITCH$"): try: databytes = int(fmt.replace("$GLITCH$", "")) except ValueError: databytes = 64 self.newInputData.emit( self.ser.read(databytes, timeout=self.read_timeout)) return None dataLen += len(fmt.replace("$RESPONSE$", "")) expected = fmt.split("$RESPONSE$") #Read data from serial port response = self.ser.read(dataLen, timeout=self.read_timeout) # If the protocol format is bin convert is back to hex for handling by CW if self.protformat == "bin": response = binascii.hexlify(response.encode('latin1')) if len(response) < dataLen: logging.warning( 'Response length from target shorter than expected (%d<%d): "%s".' % (len(response), dataLen, response)) return None #Go through...skipping expected if applicable #Check expected first #Is a beginning part if len(expected[0]) > 0: if response[0:len(expected[0])] != expected[0]: logging.warning( "Response start doesn't match what was expected:") logging.warning("Got {}, Expected {} + data".format( response, expected[0])) logging.warning("Hex Version: %s" % (" ".join(["%02x" % ord(t) for t in response]))) return None startindx = len(expected[0]) #Is middle part? data = bytearray(self.outputlength) if len(expected) == 2: for i in range(0, self.outputlength): # when glitched, the target might send us corrupted data... try: data[i] = int( response[(i * 2 + startindx):(i * 2 + startindx + 2)], 16) except ValueError as e: logging.warning('ValueError: %s' % str(e)) startindx += self.outputlength * 2 #Is end part? if len(expected[1]) > 0: if response[startindx:startindx + len(expected[1])] != expected[1]: logging.warning("Unexpected end to response:") logging.warning("Got: {}, Expected {}".format( response, expected[1])) return None return data def go(self): self.runCommand(self._go_cmd) def checkEncryptionKey(self, kin): blen = self.keyLen() if len(kin) < blen: logging.warning('Padding key...') newkey = bytearray(kin) newkey += bytearray([0] * (blen - len(kin))) return newkey elif len(kin) > blen: logging.warning('Truncating key...') return kin[0:blen] return kin def checkPlaintext(self, text): blen = self.textLen() if len(text) < blen: logging.warning('Padding plaintext...') newtext = bytearray(text) newtext += bytearray([0] * (blen - len(text))) return newtext elif len(text) > blen: logging.warning('Truncating plaintext...') return text[0:blen] return text def checkMask(self, mask): blen = self.maskLen() if len(mask) < blen: logging.warning('Padding mask...') newmask = bytearray(mask) newmask += bytearray([0] * (blen - len(mask))) return newmask elif len(mask) > blen: logging.warning('Truncating mask...') return mask[0:blen] return mask def getExpected(self): """Based on key & text get expected if known, otherwise returns None""" if self.textLen() == 16: return TargetTemplate.getExpected(self) else: return None def write(self, data): """ Writes data to the target over serial. Args: data (str): Data to write over serial. Raises: Warning: Target not connected .. versionadded:: 5.1 Added target.write() """ if not self.connectStatus: raise Warning("Target not connected") try: self.ser.write(data) except USBError: self.dis() raise Warning("Error in target. It may have been disconnected") except Exception as e: self.dis() raise e def read(self, num_char=0, timeout=250): """ Reads data from the target over serial. Args: num_char (int, optional): Number of byte to read. If 0, read all data available. Defaults to 0. timeout (int, optional): How long in ms to wait before returning. If 0, block until data received. Defaults to 250. Returns: String of received data. .. versionadded:: 5.1 Added target.read() """ if not self.connectStatus: raise Warning("Target not connected") try: if num_char == 0: num_char = self.ser.inWaiting() return self.ser.read(num_char, timeout) except USBError: self.dis() raise Warning("Error in target. It may have been disconnected") except Exception as e: self.dis() raise e def simpleserial_wait_ack(self, timeout=500): """Waits for an ack from the target for timeout ms Args: timeout (int, optional): Time to wait for an ack in ms. If 0, block until we get an ack. Defaults to 500. Raises: Warning: Target not connected. .. versionadded:: 5.1 Added target.simpleserial_wait_ack """ data = self.read(4, timeout=timeout) if len(data) > 1: if data[0] != 'z': logging.error("Ack error: {}".format(data)) return False else: logging.error("Target did not ack") return False return True def simpleserial_write(self, cmd, num, end='\n'): """ Writes a simpleserial command to the target over serial. Writes 'cmd' + ascii(num) + 'end' over serial. Flushes the read and write buffers before writing. Args: cmd (str): String to start the simpleserial command with. For 'p'. num (bytearray): Number to write as part of command. For example, the 16 byte plaintext for the 'p' command. Converted to ascii before being sent. end (str, optional): String to end the simpleserial command with. Defaults to '\\n'. Example: Sending a 'p' command:: key, pt = ktp.new_pair() target.simpleserial_write('p', pt) Raises: Warning: Write attempted while disconnected or error during write. .. versionadded:: 5.1 Added target.simpleserial_write() """ self.ser.flush() cmd += binascii.hexlify(num).decode() + end self.write(cmd) def simpleserial_read(self, cmd, pay_len, end='\n', timeout=250, ack=True): r""" Reads a simpleserial command from the target over serial. Reads a command starting with <start> with an ASCII encoded bytearray payload of length exp_len*2 (i.e. exp_len=16 for an AES128 key) and ending with <end>. Converts the payload to a bytearray. Will ignore non-ASCII bytes in the payload, but warn the user of them. Args: cmd (str): Expected start of the command. Will warn the user if the received command does not start with this string. pay_len (int): Expected length of the returned bytearray in number of bytes. Note that SimpleSerial commands send data as ASCII; this is the length of the data that was encoded. end (str, optional): Expected end of the command. Will warn the user if the received command does not end with this string. Defaults to '\n' timeout (int, optional): Value to use for timeouts during reads in ms. If 0, block until all expected data is returned. Defaults to 250. ack (bool, optional): Expect an ack at the end for SimpleSerial >= 1.1. Defaults to True. Returns: The received payload as a bytearray or None if the read failed. Example: Reading ciphertext back from the target after a 'p' command:: ct = target.simpleserial_read('r', 16) Raises: Warning: Device did not ack or error during read. .. versionadded:: 5.1 Added target.simpleserial_read() """ cmd_len = len(cmd) ascii_len = pay_len * 2 recv_len = cmd_len + ascii_len + len(end) response = self.read(recv_len, timeout=timeout) payload = bytearray(pay_len) if cmd_len > 0: if response[0:cmd_len] != cmd: logging.warning("Unexpected start to command: {}".format( response[0:cmd_len])) return None idx = cmd_len for i in range(0, pay_len): try: payload[i] = int(response[idx:(idx + 2)], 16) except ValueError as e: logging.warning("ValueError: {}".format(e)) idx += 2 if len(end) > 0: if response[(idx):(idx + len(end))] != end: logging.warning("Unexpected end to command: {}".format( response[(idx):(idx + len(end))])) return None if ack: self.simpleserial_wait_ack(timeout) return payload def set_key(self, key, ack=True, timeout=250): """Checks if key is different than the last one sent. If so, send it. Uses simpleserial_write('k') Args: key (bytearray): key to send ack (bool, optional): Wait for ack after sending key. Defaults to True. timeout (int, optional): How long in ms to wait for the ack. Defaults to 250. Raises: Warning: Device did not ack or error during read. .. versionadded:: 5.1 Added target.set_key() """ if self.last_key != key: self.last_key = key self.simpleserial_write('k', key) if ack: self.simpleserial_wait_ack(timeout) def in_waiting(self): """Returns the number of characters available from the serial buffer. Returns: The number of characters available via a target.read() call. .. versionadded:: 5.1 Added target.in_waiting() """ return self.ser.inWaiting() inWaiting = camel_case_deprecated(in_waiting) def flush(self): """Removes all data from the serial buffer. .. versionadded:: 5.1 Added target.flush() """ self.ser.flush()
class ScopeTemplate(object): _name = "None" def __init__(self): self.connectStatus = False def _getNAEUSB(self): raise Warning("Can't find low level USB interface for scope " + self.getName()) def dcmTimeout(self): pass def setAutorefreshDCM(self, enabled): pass def setCurrentScope(self, scope): pass def newDataReceived(self, channelNum, data=None, offset=0, sampleRate=0): pass def getStatus(self): return self.connectStatus def con(self, sn=None): if self._con(sn): self.connectStatus = True def _con(self, sn=None): raise NotImplementedError("Scope \"" + self.getName() + "\" does not implement method " + self.__class__.__name__ + ".con()") def dis(self): if self._dis(): pass self.connectStatus = False def _dis(self): raise NotImplementedError("Scope \"" + self.getName() + "\" does not implement method " + self.__class__.__name__ + ".dis()") def arm(self): """Prepare the scope for capturing""" # NOTE - if reimplementing this, should always check for connection first # if self.connectStatus.value() is False: # raise Exception("Scope \"" + self.getName() + "\" is not connected. Connect it first...") # raise NotImplementedError("Scope \"" + self.getName() + "\" does not implement method " + self.__class__.__name__ + ".arm()") pass def capture(self): """Capture one trace and returns True if timeout has happened.""" # NOTE: If you have a waiting loop (waiting for arm), call the function util.updateUI() inside that loop to keep # the UI responsive: # # while self.done() == False: # time.sleep(0.05) pass def get_name(self): return self._name getName = util.camel_case_deprecated(get_name)
inp = state[(i - 4):i] state[i:(i+4)] = xor(state[i:(i+4)], inp) state[0:4] = xor(state[0:4], g_func(state[(n - 4):n], rcon[rnd])) rnd -= 1 #For AES-256, we use half the generated key at once... if n == 32: if desiredfull % 2: state = state[16:32] else: state = state[0:16] #Return answer return state keyScheduleRounds = camel_case_deprecated(key_schedule_rounds) def test(): #Manual tests right now - need to automate this. ##### AES-128 Tests print("**********AES-128 Tests***************") ik = [0]*16 for i in range(0, 11): result = keyScheduleRounds(ik, 0, i) print((" ".join(["%2x"%d for d in result]))) ok = result # 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # 62 63 63 63 62 63 63 63 62 63 63 63 62 63 63 63
class OpenADC(ScopeTemplate, util.DisableNewAttr): """OpenADC scope object. This class contains the public API for the OpenADC hardware, including the ChipWhisperer Lite/ CW1200 Pro boards. It includes specific settings for each of these devices. To connect to one of these devices, the easiest method is:: import chipwhisperer as cw scope = cw.scope(type=scopes.OpenADC) Some sane default settings are available via:: scope.default_setup() This code will automatically detect an attached ChipWhisperer device and connect to it. For more help about scope settings, try help() on each of the ChipWhisperer scope submodules (scope.gain, scope.adc, scope.clock, scope.io, scope.trigger, and scope.glitch): * :attr:`scope.gain <.OpenADC.gain>` * :attr:`scope.adc <.OpenADC.adc>` * :attr:`scope.clock <.OpenADC.clock>` * :attr:`scope.io <.OpenADC.io>` * :attr:`scope.trigger <.OpenADC.trigger>` * :attr:`scope.glitch <.OpenADC.glitch>` * :meth:`scope.default_setup <.OpenADC.default_setup>` * :meth:`scope.con <.OpenADC.con>` * :meth:`scope.dis <.OpenADC.dis>` * :meth:`scope.arm <.OpenADC.arm>` * :meth:`scope.get_last_trace <.OpenADC.get_last_trace>` If you have a CW1200 ChipWhisperer Pro, you have access to some additional features: * :attr:`scope.SAD <.OpenADC.SAD>` * :attr:`scope.DecodeIO <.OpenADC.DecodeIO>` * :attr:`scope.adc.stream_mode (see scope.adc for more information)` """ _name = "ChipWhisperer/OpenADC" def __init__(self): ScopeTemplate.__init__(self) self.qtadc = openadc_qt.OpenADCQt() self.qtadc.dataUpdated.connect(self.newDataReceived) # Bonus Modules for ChipWhisperer self.advancedSettings = None self.advancedSAD = None self.digitalPattern = None self._is_connected = False self.scopetype = OpenADCInterface_NAEUSBChip(self.qtadc) def _getNAEUSB(self): return self.scopetype.dev._cwusb def default_setup(self): """Sets up sane capture defaults for this scope * 45dB gain * 5000 capture samples * 0 sample offset * rising edge trigger * 7.37MHz clock output on hs2 * 4*7.37MHz ADC clock * tio1 = serial rx * tio2 = serial tx .. versionadded:: 5.1 Added default setup for OpenADC """ self.gain.db = 25 self.adc.samples = 5000 self.adc.offset = 0 self.adc.basic_mode = "rising_edge" self.clock.clkgen_freq = 7.37e6 self.trigger.triggers = "tio4" self.io.tio1 = "serial_rx" self.io.tio2 = "serial_tx" self.io.hs2 = "clkgen" self.clock.adc_src = "clkgen_x4" count = 0 while not self.clock.clkgen_locked: self.clock.reset_dcms() time.sleep(0.05) count += 1 if count == 5: logging.info("Could not lock clock for scope. This is typically safe to ignore. Reconnecting and retrying...") self.dis() time.sleep(0.25) self.con() time.sleep(0.25) self.gain.db = 25 self.adc.samples = 5000 self.adc.offset = 0 self.adc.basic_mode = "rising_edge" self.clock.clkgen_freq = 7.37e6 self.trigger.triggers = "tio4" self.io.tio1 = "serial_rx" self.io.tio2 = "serial_tx" self.io.hs2 = "clkgen" self.clock.adc_src = "clkgen_x4" if count > 10: raise OSError("Could not lock DCM. Try rerunning this function or calling scope.clock.reset_dcms(): {}".format(self)) def dcmTimeout(self): if self.connectStatus: try: self.qtadc.sc.getStatus() except USBError: self.dis() raise Warning("Error in the scope. It may have been disconnected.") except Exception as e: self.dis() raise e def getCurrentScope(self): return self.scopetype def setCurrentScope(self, scope): self.scopetype = scope def _getCWType(self): """Find out which type of ChipWhisperer this device is. Returns: One of the following: - "" - "cwlite" - "cw1200" - "cwrev2" """ hwInfoVer = self.qtadc.sc.hwInfo.versions()[2] if "ChipWhisperer" in hwInfoVer: if "Lite" in hwInfoVer: return "cwlite" elif "CW1200" in hwInfoVer: return "cw1200" else: return "cwrev2" return "" def get_name(self): """ Gets the name of the attached scope Returns: 'ChipWhisperer Lite' if a Lite, 'ChipWhisperer Pro' if a Pro """ name = self._getCWType() if name == "cwlite": return "ChipWhisperer Lite" elif name == "cw1200": return "ChipWhisperer Pro" def _con(self, sn=None): if self.scopetype is not None: self.scopetype.con(sn) self.qtadc.sc.usbcon = self.scopetype.ser._usbdev cwtype = self._getCWType() if cwtype != "": self.advancedSettings = ChipWhispererExtra.ChipWhispererExtra(cwtype, self.scopetype, self.qtadc.sc) util.chipwhisperer_extra = self.advancedSettings if cwtype == "cwrev2" or cwtype == "cw1200": self.SAD = ChipWhispererSAD.ChipWhispererSAD(self.qtadc.sc) if cwtype == "cw1200": self.decode_IO = ChipWhispererDecodeTrigger.ChipWhispererDecodeTrigger(self.qtadc.sc) #self.advancedSettings.cwEXTRA.triggermux._set_is_pro(True) if cwtype == "cwcrev2": self.digitalPattern = ChipWhispererDigitalPattern.ChipWhispererDigitalPattern(self.qtadc.sc) self.adc = self.qtadc.parm_trigger self.gain = self.qtadc.parm_gain self.clock = self.qtadc.parm_clock if cwtype == "cw1200": self.adc._is_pro = True if self.advancedSettings: self.io = self.advancedSettings.cwEXTRA.gpiomux self.trigger = self.advancedSettings.cwEXTRA.triggermux self.glitch = self.advancedSettings.glitch.glitchSettings if cwtype == "cw1200": self.trigger = self.advancedSettings.cwEXTRA.protrigger self.disable_newattr() self._is_connected = True return True return False def _dis(self): if self.scopetype is not None: self.scopetype.dis() if self.advancedSettings is not None: self.advancedSettings = None util.chipwhisperer_extra = None if self.advancedSAD is not None: self.advancedSAD = None if self.digitalPattern is not None: self.digitalPattern = None # TODO Fix this hack if hasattr(self.scopetype, "ser") and hasattr(self.scopetype.ser, "_usbdev"): self.qtadc.sc.usbcon = None self.enable_newattr() self._is_connected = False return True def arm(self): """Setup scope to begin capture/glitching when triggered. The scope must be armed before capture or glitching (when set to 'ext_single') can begin. Raises: OSError: Scope isn't connected. Exception: Error when arming. This method catches these and disconnects before reraising them. """ if self.connectStatus is False: raise OSError("Scope is not connected. Connect it first...") try: if self.advancedSettings: self.advancedSettings.armPreScope() self.qtadc.arm() if self.advancedSettings: self.advancedSettings.armPostScope() self.qtadc.startCaptureThread() except Exception: self.dis() raise def capture(self): """Captures trace. Scope must be armed before capturing. Returns: True if capture timed out, false if it didn't. Raises: IOError: Unknown failure. """ if not self.adc.stream_mode: return self.qtadc.capture(self.adc.offset) else: return self.qtadc.capture(None) return ret def get_last_trace(self): """Return the last trace captured with this scope. Returns: Numpy array of the last capture trace. """ return self.qtadc.datapoints getLastTrace = util.camel_case_deprecated(get_last_trace) def _dict_repr(self): dict = OrderedDict() dict['gain'] = self.gain._dict_repr() dict['adc'] = self.adc._dict_repr() dict['clock'] = self.clock._dict_repr() dict['trigger'] = self.trigger._dict_repr() dict['io'] = self.io._dict_repr() dict['glitch'] = self.glitch._dict_repr() if self._getCWType() == "cw1200": dict['SAD'] = self.SAD._dict_repr() dict['decode_IO'] = self.decode_IO._dict_repr() return dict def __repr__(self): # Add some extra information about ChipWhisperer type here if self._is_connected: ret = "%s Device\n" % self._getCWType() return ret + dict_to_str(self._dict_repr()) else: ret = "ChipWhisperer/OpenADC device (disconnected)" return ret def __str__(self): return self.__repr__()
class PreprocessingBase(TraceSource, PassiveTraceObserver): """ Base Class for all preprocessing modules Derivable Classes work like this: - updateScript is called to update the scripts based on the current status of the object - the other methods are used by the API to apply the preprocessing filtering """ _name = "None" def __init__(self, traceSource=None, name=None): self._enabled = False PassiveTraceObserver.__init__(self) if name is None: TraceSource.__init__(self, self.getName()) else: TraceSource.__init__(self, name=name) if isinstance(traceSource, Project): traceSource = traceSource.trace_manager() self.setTraceSource(traceSource, blockSignal=True) if traceSource: #until new analyzer is implemented traceSource.sigTracesChanged.connect( self.sigTracesChanged.emit ) # Forwards the traceChanged signal to the next observer in the chain self.getParams().addChildren([{ 'name': 'Enabled', 'key': 'enabled', 'type': 'bool', 'default': self._getEnabled(), 'get': self._getEnabled, 'set': self._setEnabled }]) self.findParam('input').hide() self.register() if __debug__: logging.debug('Created: ' + self._name) #Old attribute dict self._attrdict = None self.enabled = True def _getEnabled(self): """Return if it is enable or not""" return self._enabled @setupSetParam("Enabled") def _setEnabled(self, enabled): """Turn on/off this preprocessing module""" self._enabled = enabled @property def enabled(self): """Whether this module is active. If False, the module will have no effect on the traces - it will just pass through the traces from the previous source. Setter raises TypeError if value isn't bool. """ return self._getEnabled() @enabled.setter def enabled(self, en): if not isinstance(en, bool): raise TypeError("Expected bool; got %s" % type(en), en) self._setEnabled(en) def get_trace(self, n): """Get trace number n""" if self.enabled: trace = self._traceSource.get_trace(n) # Do your preprocessing here return trace else: return self._traceSource.get_trace(n) getTrace = camel_case_deprecated(get_trace) def get_textin(self, n): """Get text-in number n""" return self._traceSource.get_textin(n) getTextin = camel_case_deprecated(get_textin) def get_textout(self, n): """Get text-out number n""" return self._traceSource.get_textout(n) getTextout = camel_case_deprecated(get_textout) def get_known_key(self, n=None): """Get known-key number n""" return self._traceSource.get_known_key(n) getKnownKey = camel_case_deprecated(get_known_key) def getSampleRate(self): """Get the Sample Rate""" return self._traceSource.getSampleRate() def init(self): """Do any initialization required once all traces are loaded""" pass def getSegmentList(self): return self._traceSource.get_segment_list() def getAuxData(self, n, auxDic): return self._traceSource.getAuxData(n, auxDic) def get_segment(self, n): return self._traceSource.getSegment(n) getSegment = camel_case_deprecated(get_segment) def num_traces(self): return self._traceSource.num_traces() numTraces = camel_case_deprecated(num_traces) def num_points(self): return self._traceSource.numPoints() numPoints = camel_case_deprecated(num_points) def attrSettings(self): """Return user-added attributes, used in determining cache settings""" if self.__dict__ == self._attrdict: return self._attrdict_trimmed self._attrdict = self.__dict__.copy() attrdict = self.__dict__.copy() del attrdict["_attrdict"] if hasattr(attrdict, "_attrdict_trimmed"): del attrdict["_attrdict_trimmed"] del attrdict["runScriptFunction"] del attrdict["sigTracesChanged"] del attrdict["_smartstatements"] del attrdict["_traceSource"] attrdict["params"] = str(attrdict["params"]) del attrdict["scriptsUpdated"] del attrdict["updateDelayTimer"] self._attrdict_trimmed = attrdict return self._attrdict_trimmed def __del__(self): if __debug__: logging.debug('Deleted: ' + str(self)) def _dict_repr(self): #raise NotImplementedError("Must define target-specific properties.") return {} def __repr__(self): return util.dict_to_str(self._dict_repr()) def __str__(self): return self.__repr__() def preprocess(self): """Process all traces. Returns: Project: A new project containing the processed traces. .. versionadded: 5.1 Add preprocess method to Preprocessing module. """ proj = Project() for i in range(self.num_traces()): if self.get_trace(i) is None: logging.warn("Wave {} ({}) is invalid. Skipping ".format( i, self.get_trace(i))) continue proj.traces.append( Trace(self.get_trace(i), self.get_textin(i), self.get_textout(i), self.get_known_key(i))) return proj
class Project(Parameterized): """Class describing an open ChipWhisperer project. Basic capture usage:: import chipwhisperer as cw proj = cw.create_project("project") trace = cw.Trace(trace_data, plaintext, ciphertext, key) proj.traces.append(trace) proj.save() Basic analyzer usage:: import chipwhisperer as cw import chipwhisperer.analyzer as cwa proj = cw.open_project("project") attack = cwa.cpa(proj) #run attack Use a trace_manager when analyzing traces, since that allows analyzer to work with multiple trace segments. * :attr:`project.location <.Project.location>` * :attr:`project.waves <.Project.waves>` * :attr:`project.textins <.Project.textins>` * :attr:`project.textouts <.Project.textouts>` * :attr:`project.keys <.Project.keys>` * :meth:`project.get_filename <.Project.get_filename>` * :meth:`project.trace_manager <.Project.trace_manager>` * :meth:`project.save <.Project.save>` * :meth:`project.export <.Project.export>` """ untitledFileName = os.path.normpath( os.path.join(Settings().value("project-home-dir"), "tmp", "default.cwp")) def __init__(self, prog_name="ChipWhisperer", prog_ver=""): self.valid_traces = None self._trace_format = None self.params = Parameter(name="Project Settings", type="group") self.params.addChildren([ { 'name': 'Trace Format', 'type': 'list', 'values': self.valid_traces, 'get': self.get_trace_format, 'set': self.set_trace_format }, ]) #self.findParam("Trace Format").setValue(TraceContainerNative(project=self), addToList=True) self._trace_format = TraceContainerNative(project=self) #self.traceParam = Parameter(name="Trace Settings", type='group', addLoadSave=True).register() #self.params.getChild('Trace Format').stealDynamicParameters(self.traceParam) self.sigFilenameChanged = util.Signal() self.sigStatusChanged = util.Signal() self.dirty = util.Observable(True) self.settingsDict = { 'Project Name': "Untitled", 'Project File Version': "1.00", 'Project Author': "Unknown" } self.datadirectory = "" self.config = ConfigObjProj(callback=self.configObjChanged) self._traceManager = TraceManager().register() self._traceManager.dirty.connect(self.__dirtyCallback) self.setFilename(Project.untitledFileName) self.setProgramName(prog_name) self.setProgramVersion(prog_ver) self._segments = Segments(self) self._traces = Traces(self) self._keys = IndividualIterable(self._traceManager.get_known_key, self._traceManager.num_traces) self._textins = IndividualIterable(self._traceManager.get_textin, self._traceManager.num_traces) self._textouts = IndividualIterable(self._traceManager.get_textout, self._traceManager.num_traces) self._waves = IndividualIterable(self._traceManager.get_trace, self._traceManager.num_traces) if __debug__: logging.debug('Created: ' + str(self)) def append_segment(self, segment): """ Adds a new trace segment to the project Args: segment (TraceContainer): Trace segment to add to project """ self._traceManager.appendSegment(copy.copy(segment)) appendSegment = append_segment def new_segment(self): """ Returns the __class__() of the trace container used to store traces in the project You probably want get_new_trace_segment() Returns: __class()__ of the current trace container """ return self._trace_format.__class__() newSegment = new_segment def get_trace_format(self): """ Gets the TraceContainer used to store traces Returns: The trace container used in the project """ return self._trace_format getTraceFormat = get_trace_format def get_new_trace_segment(self): """Return a new TraceContainer object for the specified format. Once you're done working with this segment, you can readd it to the project with append_segment() Returns: a new TraceContainer object """ tmp = copy.copy(self._trace_format) tmp.clear() starttime = datetime.now() prefix = starttime.strftime('%Y.%m.%d-%H.%M.%S') + "_" tmp.config.setConfigFilename( os.path.join(self.datadirectory, "traces", "config_" + prefix + ".cfg")) tmp.config.setAttr("prefix", prefix) tmp.config.setAttr("date", starttime.strftime('%Y-%m-%d %H:%M:%S')) return tmp getNewTraceSegment = get_new_trace_segment @setupSetParam("Trace Format") def set_trace_format(self, trace_format): self._trace_format = trace_format setTraceFormat = util.camel_case_deprecated(set_trace_format) def __dirtyCallback(self): self.dirty.setValue(self._traceManager.dirty.value() or self.dirty.value()) def configObjChanged(self, key): self.dirty.setValue(True) def isUntitled(self): return self.filename == Project.untitledFileName def trace_manager(self): """ Gets the trace manager for the project Returns: The trace manager for the project """ return self._traceManager traceManager = util.camel_case_deprecated(trace_manager) def setProgramName(self, name): self.settingsDict['Program Name'] = name def setProgramVersion(self, ver): self.settingsDict['Program Version'] = ver def setAuthor(self, author): self.settingsDict['Project Author'] = author def setProjectName(self, name): self.settingsDict['Project Name'] = name def setFileVersion(self, ver): self.settingsDict['Project File Version'] = ver def get_filename(self): """Gets the filename associated with the project. Returns: Filename of the project config file ending with ".cwp". """ return self.filename getFilename = util.camel_case_deprecated(get_filename) def setFilename(self, f): self.filename = f self.config.filename = f self.datadirectory = os.path.splitext(self.filename)[0] + "_data" self.createDataDirectory() self.sigStatusChanged.emit() def createDataDirectory(self): # Check if data-directory exists? if not os.path.isdir(self.datadirectory): os.makedirs(self.datadirectory) # Make trace storage directory too if not os.path.isdir(os.path.join(self.datadirectory, 'traces')): os.mkdir(os.path.join(self.datadirectory, 'traces')) # Make analysis storage directory if not os.path.isdir(os.path.join(self.datadirectory, 'analysis')): os.mkdir(os.path.join(self.datadirectory, 'analysis')) # Make glitchresults storage directory if not os.path.isdir(os.path.join(self.datadirectory, 'glitchresults')): os.mkdir(os.path.join(self.datadirectory, 'glitchresults')) def load(self, f=None): if f is not None: self.setFilename(os.path.abspath(os.path.expanduser(f))) if not os.path.isfile(self.filename): raise IOError("File " + self.filename + " does not exist or is not a file") self.config = ConfigObjProj(infile=self.filename, callback=self.configObjChanged) self._traceManager.loadProject(self.filename) self.dirty.setValue(False) def getDataFilepath(self, filename, subdirectory='analysis'): datadir = os.path.join(self.datadirectory, subdirectory) fname = os.path.join(datadir, filename) relfname = os.path.relpath(fname, os.path.split(self.config.filename)[0]) fname = os.path.normpath(fname) relfname = os.path.normpath(relfname) return {"abs": fname, "rel": relfname} def convertDataFilepathAbs(self, relativepath): return os.path.join(os.path.split(self.filename)[0], relativepath) def checkDataConfig(self, config, requiredSettings): """Check a configuration section for various settings. Don't use.""" requiredSettings = util.convert_to_str(requiredSettings) config = util.convert_to_str(config) return set(requiredSettings.items()).issubset(set(config.items())) def getDataConfig(self, sectionName="Aux Data", subsectionName=None, requiredSettings=None): """ Don't use. Get all configuration sections of data type given in __init__() call, and also matching the given sectionName. e.g. if dataName='Aux Data' and sectionName='Frequency', this would return a list of all sections of the type 'Aux Data NNNN - Frequency'. """ sections = [] # Get all section names for sname in list(self.config.keys()): # Find if starts with 'Aux Data' if sname.startswith(sectionName): # print "Found %s" % sname # Find if module name matches if applicable if subsectionName is None or sname.endswith(subsectionName): # print "Found %s" % sname if requiredSettings is None: sections.append(self.config[sname]) else: self.checkDataConfig(self.config[sname], requiredSettings) return sections def addDataConfig(self, settings=None, sectionName="Aux Data", subsectionName=None): # Check configuration file to find incrementing number. Don't use maxNumber = 0 for sname in list(self.config.keys()): # Find if starts with 'Aux Data' if sname.startswith(sectionName): maxNumber = int(re.findall(r'\d+', sname)[0]) + 1 cfgSectionName = "%s %04d" % (sectionName, maxNumber) if subsectionName: cfgSectionName += " - %s" % subsectionName # Generate the configuration section self.config[cfgSectionName] = {} if settings is not None: for k in list(settings.keys()): self.config[cfgSectionName][k] = settings[k] return self.config[cfgSectionName] def saveAllSettings(self, fname=None, onlyVisibles=False): """ Save registered parameters to a file, so it can be loaded again latter. Don't use.""" if fname is None: fname = os.path.join(self.datadirectory, 'settings.cwset') logging.info('Saving settings to file: ' + fname) Parameter.saveRegistered(fname, onlyVisibles) def saveTraceManager(self): #Waveform list is Universal across ALL types. Don't use. if 'Trace Management' not in self.config: self.config['Trace Management'] = {} self._traceManager.save_project(self.config, self.filename) def save(self): """Saves the project. Writes the project to the disk. Before this is called your data is not saved. Filenames for traces are set in this method. """ if self.filename is None: return self.saveTraceManager() #self.config['Waveform List'] = self.config['Waveform List'] + self.waveList #Program-Specific Options pn = self.settingsDict['Program Name'] self.config[pn] = {} self.config[pn]['General Settings'] = self.settingsDict self.config.write() self.sigStatusChanged.emit() self.dirty.setValue(False) def checkDiff(self): """ Don't use. Check if there is a difference - returns True if so, and False if no changes present. Also updates widget with overview of the differences if requested with updateGUI """ self.saveTraceManager() disk = util.convert_to_str(ConfigObjProj(infile=self.filename)) ram = util.convert_to_str(self.config) def hasDiffs(self): if self.dirty.value(): return True #added, removed, changed = self.checkDiff() if (len(added) + len(removed) + len(changed)) == 0: return False return True def consolidate(self, keepOriginals=True): for indx, t in enumerate(self._traceManager.traceSegments): destinationDir = os.path.normpath( os.path.join(self.datadirectory, "traces")) config = ConfigObj(t.config.configFilename()) prefix = config['Trace Config']['prefix'] tracePath = os.path.normpath( os.path.split(t.config.configFilename())[0]) if destinationDir == tracePath: continue for traceFile in os.listdir(tracePath): if traceFile.startswith(prefix): util.copyFile( os.path.normpath(os.path.join(tracePath, traceFile)), destinationDir, keepOriginals) util.copyFile(t.config.configFilename(), destinationDir, keepOriginals) t.config.setConfigFilename( os.path.normpath( os.path.join(destinationDir, os.path.split(t.config.configFilename())[1]))) self.sigStatusChanged.emit() def __del__(self): if __debug__: logging.debug('Deleted: ' + str(self)) @property def location(self): """The directory in which the project is located. Example:: print(project.location) '/path/to/the/directory/containing/this/project' :Getter: (str) Returns the file path of the projects parent directory. .. versionadded:: 5.1 Added **location** attribute to project. """ return os.path.dirname(os.path.abspath(self.get_filename())) def export(self, file_path, file_type='zip'): """Export a chipwhisperer project. Saves project before exporting. Supported export types: * zip (default) Returns: (str) Path to the exported file. .. versionadded:: 5.1 Add **export** method to active project. """ self.save() _, cwp_file = os.path.split(self.get_filename()) name, ext = os.path.splitext(cwp_file) data_folder = os.path.join(self.location, '_'.join([name, 'data'])) file_paths = list() file_paths.append(os.path.join(self.location, cwp_file)) for root, directories, files in os.walk(data_folder): for filename in files: file_paths.append(os.path.join(root, filename)) if file_type == 'zip': file_path = os.path.abspath(file_path) with zipfile.ZipFile(file_path, 'w') as zip: common_path = os.path.commonpath(file_paths) for file in file_paths: relative_path = os.path.relpath(file, common_path) zip.write(file, arcname=relative_path) export_file_path = os.path.abspath(zip.filename) else: raise ValueError('{} not supported'.format(file_type)) return export_file_path def close(self, save=True): """Closes the project cleanly. Saves by default. Then closes all claimed files. Args: save (bool): Saves the project before closing. """ if save: self.save() for seg in self.segments: seg.unloadAllTraces() def remove(self, i_am_sure=False): """Remove a project from disk. Args: i_am_sure (bool): Are you sure you want to remove the project? """ if not i_am_sure: raise RuntimeWarning( 'Project not removed... i_am_sure not set to True.') self.close(save=False) try: shutil.rmtree(self.datadirectory) except FileNotFoundError: pass _, cwp_file = os.path.split(self.get_filename()) try: os.remove(os.path.join(self.location, cwp_file)) except FileNotFoundError: pass @property def traces(self): """The interface to all traces contained in the project. Instance of :class:`.Traces`. """ return self._traces @property def segments(self): """The interface to all segments contained in the project. Instance of :class:`.Segments`. """ return self._segments @property def keys(self): """Iterable for working with only the known keys. Each item in the iterable is a byte array. Supports iterating, indexing, and slicing:: for key in my_project.keys: # do something """ return self._keys @property def textins(self): """Iterable for working with only the text in. Each item in the iterable is a byte array. Supports iterating, indexing, and slicing:: for textin in my_project.textins: # do something """ return self._textins @property def textouts(self): """Iterable for working with only the text out. Each item in the iterable is a byte array. Supports iterating, indexing, and slicing:: for textout in my_project.textouts: # do something """ return self._textouts @property def waves(self): """Iterable for working with only the trace data. Each item in the iterable is a numpy array. Supports iterating, indexing, and slicing:: for wave in my_project.waves: # do something """ return self._waves
class ChipWhispererSAD(object): """Communicates with the SAD module inside the CW Pro This submodule is only available on the ChipWhisperer1200 Pro Example:: trace, ret = cw.capture_trace(scope, data, text, key) cw.SAD.reference = trace[400:528] cw.SAD.threshold = 1000 cw.SAD.start() cw.trigger.module = 'SAD' #SAD trigger active trace, ret = cw.capture_trace(scope, data, text, key) """ _name = 'SAD Trigger Module' STATUS_RUNNING_MASK = 1 << 3 STATUS_RESET_MASK = 1 << 0 STATUS_START_MASK = 1 << 1 def __init__(self, oa): self.oldlow = None self.oldhigh = None self.oa = oa self.sadref = [0] def _dict_repr(self): dict = OrderedDict() dict['threshold'] = self.threshold dict['reference'] = self.reference return dict def __repr__(self): return util.dict_to_str(self._dict_repr()) def __str__(self): return self.__repr__() @property def threshold(self): """ The threshold for the SAD trigger. The threshold has a maximum value of 100 000. You should set the reference waveform via SAD.reference before setting this value. :Getter: Return the current threshold :Setter: Set the current threshold Raises: ValueError: The user attempted to set a threshold above 100 000 IOError: User attempted to set the threshold before the reference waveform. """ return self.getThreshold() @threshold.setter def threshold(self, value): self.setThreshold(value) @property def reference(self): """Set the reference data for the SAD Trigger. The reference must be 128 samples long. Through this interface, it is represented as a numpy array of floats between -0.5 and 0.5 (the same as trace data). :Getter: Gets the currently set SAD reference :Setter: Sets the SAD reference Raises: ValueError: Data not 128 samples long """ return np.array(self.sadref) @reference.setter def reference(self, data): self.set_reference(data) def set_reference(self, data): data = np.array(data) data = (data + 0.5) * 1024 self.sadref = data self.setRefWaveform(data) def reset(self): """ Reset the SAD hardware block. The ADC clock must be running! """ data = self.oa.sendMessage(CODE_READ, sadcfgaddr, maxResp=4) data[0] = 0x01 self.oa.sendMessage(CODE_WRITE, sadcfgaddr, data) if self.check_status(): raise IOError( "SAD Reset in progress, but SAD reports still running. Is ADC Clock stopped?" ) data[0] = 0x00 self.oa.sendMessage(CODE_WRITE, sadcfgaddr, data) def start(self): """ Start the SAD algorithm, which causes the reference data to be loaded from the FIFO """ data = self.oa.sendMessage(CODE_READ, sadcfgaddr, maxResp=4) data[0] = 0x02 self.oa.sendMessage(CODE_WRITE, sadcfgaddr, data, Validate=False) data[0] = 0x00 self.oa.sendMessage(CODE_WRITE, sadcfgaddr, data, Validate=False) def check_status(self): """ Check if the SAD module is running & outputting valid data """ data = self.oa.sendMessage(CODE_READ, sadcfgaddr, maxResp=4) if not (data[0] & self.STATUS_RUNNING_MASK): return False else: return True checkStatus = util.camel_case_deprecated(check_status) def getThreshold(self): """ Get the threshold. When the SAD output falls below this threshold the system triggers """ data = self.oa.sendMessage(CODE_READ, sadcfgaddr, maxResp=4) threshold = data[1] threshold |= data[2] << 8 threshold |= data[3] << 16 return threshold def setThreshold(self, threshold): """ Set the threshold. When the SAD output falls below this threshold the system triggers """ if (threshold > 100000) or (threshold < 0): raise ValueError( "Invalid threshold {}. Must be in range (0, 100000)".format( threshold)) data = self.oa.sendMessage(CODE_READ, sadcfgaddr, maxResp=4) data[1] = threshold & 0xff data[2] = (threshold >> 8) & 0xff data[3] = (threshold >> 16) & 0xff self.oa.sendMessage(CODE_WRITE, sadcfgaddr, data, Validate=False) if self.check_status() == False: raise IOError( "SAD Threshold set, but SAD compare not running. No valid trigger will be present. Did you load a reference waveform?" ) def setRefWaveform(self, dataRef): """ Download a reference waveform. Resets the SAD module & starts it again after loading the new data. ADC Clock must be running! """ dataRefInt = [int(i) for i in dataRef] self.reset() # print dataRefInt dataRefInt = dataRefInt[::-1] wavedata = [] for d in dataRefInt: wavedata.append((d >> 8) & 0xff) wavedata.append(d & 0xff) self.oa.sendMessage(CODE_WRITE, saddataaddr, wavedata, Validate=False) self.start()
prog.open() prog.find() prog.erase() prog.program(fw_path, memtype="flash", verify=True) prog.close() except: if isinstance(prog, programmers.XMEGAProgrammer) and isinstance( scope, scopes.OpenADC): scope.io.pdic = 0 time.sleep(0.05) scope.io.pdic = None time.sleep(0.05) raise programTarget = camel_case_deprecated(program_target) def open_project(filename): """Load an existing project from disk. Args: filename (str): Path to project file. Returns: A chipwhisperer project object. Raises: OSError: filename does not exist. """ filename = project.ensure_cwp_extension(filename)
#Add PGE row df_pge = pd.DataFrame(attack_results.pge).transpose().rename( index={0: "PGE="}, columns=int) df = pd.concat([df_pge, df], ignore_index=False) clear_output(wait=True) reporting_interval = attack.reporting_interval tstart = current_trace_iteration * reporting_interval tend = tstart + reporting_interval current_trace_iteration += 1 display( df.head(head).style.format(format_stat).apply( color_corr_key, axis=1).set_caption( "Finished traces {} to {}".format(tstart, tend))) def get_jupyter_callback(attack, head=6, fmt="{:02X}<br>{:.3f}"): """Get callback for use in Jupyter""" global current_trace_iteration current_trace_iteration = 0 return lambda: _default_jupyter_callback(attack, head, fmt) getJupyterCallback = camel_case_deprecated(get_jupyter_callback) def reset_iteration(): global current_trace_iteration current_trace_iteration = 0
class TraceManager(TraceSource): """ When using traces in ChipWhisperer, you may have remapped a bunch of trace files into one segment of traces. This class is used to handle the remapping and provide methods to save, load and manage the traces. Don't use anything that modifies the trace manager -> stick to stuff that gives you information about it instead (i.e. get_trace, get_known_key) """ def __init__(self, name="Trace Management"): TraceSource.__init__(self, name) self.name = name self.dirty = util.Observable(False) self._numTraces = 0 self._numPoints = 0 self._sampleRate = 0 self.lastUsedSegment = None self.traceSegments = [] self.saved = False if __debug__: logging.debug('Created: ' + str(self)) def new_project(self): """Create a new empty set of traces.""" self.traceSegments = [] self.dirty.setValue(False) self.sigTracesChanged.emit() newProject = new_project def save_project(self, config, configfilename): """Save the trace segments information to a project file.""" config[self.name].clear() for indx, t in enumerate(self.traceSegments): starttime = datetime.now() prefix = starttime.strftime('%Y.%m.%d-%H.%M.%S') + "_" t.config.setConfigFilename( os.path.splitext(configfilename)[0] + "_data" + "/traces/config_" + prefix + ".cfg") t.config.setAttr("prefix", prefix) t.config.setAttr("date", starttime.strftime('%Y-%m-%d %H:%M:%S')) config[self.name]['tracefile%d' % indx] = os.path.normpath( os.path.relpath(t.config.configFilename(), os.path.split(configfilename)[0])) config[self.name]['enabled%d' % indx] = str(t.enabled) t.saveAllTraces(os.path.dirname(t.config.configFilename()), prefix) self.dirty.setValue(False) self.saved = True saveProject = util.camel_case_deprecated(save_project) def loadProject(self, configfilename): """Load the trace segments information from a project file.""" config = configparser.RawConfigParser() config.read(configfilename) alltraces = config.items(self.name) self.newProject() path, name = os.path.split(configfilename) for t in alltraces: if t[0].startswith("tracefile"): fname = os.path.join(path, t[1]) fname = os.path.normpath(fname.replace("\\", "/")) # print "Opening %s"%fname ti = TraceContainerNative() try: ti.config.loadTrace(fname) ti.loadAllTraces() except Exception as e: logging.error(str(e)) self.traceSegments.append(ti) if t[0].startswith("enabled"): tnum = re.findall(r'[0-9]+', t[0]) self.traceSegments[int(tnum[0])].enabled = t[1] == "True" self._setModified() self.dirty.setValue(False) def removeTraceSegments(self, positions): """Remove a list of trace segments. Do not repeat numbers!!""" if not isinstance(positions, list): positions = [positions] else: positions.sort(reverse=True) for pos in positions: self.traceSegments.pop(pos) self._setModified() def setTraceSegmentStatus(self, pos, newStatus): """Set a trace segment as enabled/disabled.""" self.traceSegments[pos].enabled = newStatus self._setModified() def getSegmentList(self, start=0, end=-1): """Return a list of segments.""" tnum = start if end == -1: end = self._numTraces dataDict = {'offsetList': [], 'lengthList': []} while (tnum < end): t = self.get_segment(tnum) dataDict['offsetList'].append(t.mappedRange[0]) dataDict['lengthList'].append(t.mappedRange[1] - t.mappedRange[0] + 1) tnum = t.mappedRange[1] + 1 return dataDict def get_segment(self, traceIndex): """Return the trace segment with the specified trace in the list with all enabled segments.""" if self.lastUsedSegment is not None: if self.lastUsedSegment.mappedRange is not None and self.lastUsedSegment.mappedRange[0] <= traceIndex <= \ self.lastUsedSegment.mappedRange[1]: return self.lastUsedSegment else: # Only load one segment at a time for memory reasons # If the traces are actually saved :) if self.saved: self.lastUsedSegment.unloadAllTraces() self.lastUsedSegment = None for traceSegment in self.traceSegments: if traceSegment.mappedRange and traceSegment.mappedRange[ 0] <= traceIndex <= traceSegment.mappedRange[1]: if not traceSegment.isLoaded(): traceSegment.loadAllTraces(None, None) self.lastUsedSegment = traceSegment return traceSegment raise ValueError("Error: Trace %d is not in mapped range." % traceIndex) getSegment = util.camel_case_deprecated(get_segment) def getAuxData(self, n, auxDic): """Return data about a segment""" t = self.get_segment(n) cfg = t.getAuxDataConfig(auxDic) if cfg is not None: filedata = t.loadAuxData(cfg["filename"]) else: filedata = None return {'cfgdata': cfg, 'filedata': filedata} def get_trace(self, n): """Return the trace with index n in the list of enabled segments""" t = self.get_segment(n) return t.getTrace(n - t.mappedRange[0]) getTrace = util.camel_case_deprecated(get_trace) def get_textin(self, n): """Return the input text of trace with index n in the list of enabled segments""" t = self.get_segment(n) return t.getTextin(n - t.mappedRange[0]) getTextin = util.camel_case_deprecated(get_textin) def get_textout(self, n): """Return the output text of trace with index n in the list of enabled segments""" t = self.get_segment(n) return t.getTextout(n - t.mappedRange[0]) getTextout = util.camel_case_deprecated(get_textout) def get_known_key(self, n): """Return the known encryption key.""" try: t = self.get_segment(n) return t.getKnownKey(n - t.mappedRange[0]) except ValueError: return [] getKnownKey = util.camel_case_deprecated(get_known_key) def _updateRanges(self): """Update the trace range for each segments.""" startTrace = 0 self._sampleRate = 0 self._numPoints = 0 for t in self.traceSegments: if t.enabled: tlen = t.numTraces() t.mappedRange = [startTrace, startTrace + tlen - 1] startTrace = startTrace + tlen np = int(t.config.attr("numPoints")) if self._numPoints != np and np != 0: if self._numPoints == 0: self._numPoints = np else: logging.warning( "Selected trace segments have different number of points: %d!=%d" % (self._numPoints, np)) self._numPoints = min(self._numPoints, np) sr = int(float(t.config.attr("scopeSampleRate"))) if self._sampleRate != sr and sr != 0: if self._sampleRate == 0: self._sampleRate = sr else: logging.warning( "Selected trace segments have different sample rates: %d!=%d" % (self._sampleRate, sr)) else: t.mappedRange = None self._numTraces = startTrace def num_points(self): """Return the number of points in traces of the selected segments.""" return self._numPoints numPoints = util.camel_case_deprecated(num_points) def num_traces(self): """Return the number of traces in the current list of enabled segments.""" return self._numTraces numTraces = util.camel_case_deprecated(num_traces) def appendSegment(self, ti, enabled=True): """Append a new segment to the list of trace segments.""" ti.enabled = enabled ti._isloaded = True self.traceSegments.append(ti) self._setModified() def _setModified(self): """Notify passive and active observers to be updated.""" self.dirty.setValue(True) self._updateRanges() self.sigTracesChanged.emit() def getSampleRate(self, ): return self._sampleRate def changeSegmentAttribute(self, segmentNum, attribute, value): """Change the value of a segment attribute. Changes are saved instantly.""" self.traceSegments[segmentNum].config.setAttr(attribute, value) self.traceSegments[segmentNum].config.saveTrace() logging.info('Trace attribute "%s" of segment %d changed to: %s' % (attribute, segmentNum, value)) self._updateRanges() self.sigTracesChanged.emit() def __del__(self): if __debug__: logging.debug('Deleted: ' + str(self))
class ResyncDTW(PreprocessingBase): """Align traces using the Dynamic Time Warp algorithm. Doesn't play well with noisy traces, but can remove random per-trace delays and synchronize entire trace. """ _name = "Resync: Dynamic Time Warp" _description = "Aligns traces to match a reference trace using the Fast Dynamic Time Warp algorithm." def __init__(self, traceSource=None, name=None): PreprocessingBase.__init__(self, traceSource, name=name) self._rtrace = 0 self._radius = 3 def _setRefTrace(self, num): self._rtrace = num def _getRefTrace(self): return self._rtrace @property def ref_trace(self): """The trace being used as a reference. Setter raises TypeError unless value is an integer.""" return self._getRefTrace() @ref_trace.setter def ref_trace(self, num): if not isinstance(num, int): raise TypeError("Expected int; got %s" % type(num), num) self._setRefTrace(num) def _setRadius(self, radius): self._radius = radius def _getRadius(self): return self._radius @property def radius(self): """The radius used in the DTW calculation. This is an integer value that roughly describes how much the DTW algorithm is allowed to backtrack in its search. High values take longer but are better at dealing with many smaller delays. """ return self._getRadius() @radius.setter def radius(self, radius): if not isinstance(radius, int): raise TypeError("Expected int; got %s" % type(radius), radius) self._setRadius(radius) def get_trace(self, n): if not self.enabled: return self._traceSource.get_trace(n) trace = self._traceSource.get_trace(n) ref_trace = self._traceSource.get_trace(self._rtrace) if trace is None or ref_trace is None: return None aligned = self._align_traces(ref_trace, trace) return aligned getTrace = camel_case_deprecated(get_trace) def _align_traces(self, ref, trace): N = self._traceSource.num_points() r = self._radius #try: # cython fastdtw can't take numpy.memmap inputs, so we convert them to arrays: aref = np.array(list(ref)) atrace = np.array(list(trace)) dist, path = fastdtw(aref, atrace, radius=r, dist=None) #except: # return None px = [x for x, y in path] py = [y for x, y in path] n = [0] * N s = [0.0] * N for x, y in path: s[x] += trace[y] n[x] += 1 ret = [s[i] / n[i] for i in range(N)] return ret
class AcqKeyTextPattern_Base(object): _name = "Key/Text Pattern" def __init__(self): self._key_len = 16 self._text_len = 16 self._key = None self._textin = None def setTarget(self, target): pass @property def key_len(self): return self._key_len @key_len.setter def key_len(self, n): self._key_len = n @property def text_len(self): return self._text_len @text_len.setter def text_len(self, n): self._text_len = n @property def fixed_key(self): """Generate fixed key (True) or not (False). :Getter: Return True if using fixed key or False if not. :Setter: Set whether to use fixed key (True) or not (False). """ raise NotImplementedError( "Target does not allow key to be changed to fixed/unfixed") @fixed_key.setter def fixed_key(self, enabled): raise NotImplementedError( "Target does not allow key to be changed to fixed/unfixed") @property def fixed_text(self): """Generate fixed plaintext (True) or not (False). :Getter: Return True if using fixed plaintext or False if not. :Setter: Set whether to use fixed plaintext (True) or not (False). """ raise NotImplementedError( "Target does not allow plaintext to be changed to fixed/unfixed") @fixed_text.setter def fixed_text(self, enabled): raise NotImplementedError( "Target does not allow plaintext to be changed to fixed/unfixed") def keyLen(self): return self._key_len def textLen(self): return self._text_len def validateKey(self): if len(self._key) != self.key_len: raise IOError("Key Length Wrong for Given Target, %d != %d" % (self.key_len, len(self.key))) def validateText(self): if len(self._textin) != self.text_len: raise IOError("Plaintext Length Wrong for Given Target, %d != %d" % (self.text_len, len(self.textin))) def _initPattern(self): """Perform any extra init stuff required. Called at the end of main init() & when target changed.""" pass def setInitialKey(self, initialKey, binaryKey=False): pass def setInitialText(self, initialText, binaryText=False): pass def init(self, maxtraces): """Initialize ktp for trace runs Not required for all targets (i.e. a basic key text pair) """ raise NotImplementedError("This target does not implement init") init_pair = init initPair = camel_case_deprecated(init_pair) def new_pair(self): """Called when a new encryption pair is requested""" raise NotImplementedError("This needs to be reimplemented") newPair = camel_case_deprecated(new_pair) def __str__(self): return "key: {}\ntext: {}".format(list(hex(b) for b in self._key), list(hex(b) for b in self._textin)) def next(self): self.new_pair()