Пример #1
0
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)
Пример #2
0
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)
Пример #3
0
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()
Пример #4
0
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):
Пример #5
0
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__()
Пример #6
0
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)
Пример #7
0
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
Пример #8
0
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)
Пример #9
0
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
Пример #10
0
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
Пример #11
0
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()
Пример #12
0
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)
Пример #13
0
                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
Пример #14
0
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__()
Пример #15
0
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
Пример #16
0
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
Пример #17
0
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()
Пример #18
0
        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)
Пример #19
0
    #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
Пример #20
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))
Пример #21
0
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
Пример #22
0
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()