Exemplo n.º 1
0
def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument('hjson')
    parser.add_argument('infile', type=argparse.FileType('rb'))
    parser.add_argument('outfile', type=argparse.FileType('w'))

    args = parser.parse_args()
    scrambler = Scrambler.from_hjson_path(args.hjson, ROM_SIZE_WORDS)

    # Load the input ELF file
    clr_mem = MemFile.load_elf32(args.infile, 4 * ROM_BASE_WORD)

    # Flatten the file, padding with pseudo-random data and ensuring it's
    # exactly scrambler.rom_size_words words long.
    clr_flat = scrambler.flatten(clr_mem)

    # Extend from 32 bits to 39 by adding Hsiao (39,32) ECC bits.
    clr_flat.add_ecc32()

    # Zero-extend the cleartext memory by one more bit (this is the size we
    # actually use in the physical ROM)
    assert clr_flat.width == 39
    clr_flat.width = 40

    # Scramble the memory
    scr_mem = scrambler.scramble40(clr_flat)

    # TODO: Calculate and insert the expected hash here.

    scr_mem.write_vmem(args.outfile)
Exemplo n.º 2
0
    def scramble40(self, mem: MemFile) -> MemFile:
        assert len(mem.chunks) == 1
        assert len(mem.chunks[0].words) == self.rom_size_words

        word_width = 40

        # Write addr_sp, data_sp for the S&P networks for address and data,
        # respectively. Write clr[i] for unscrambled data word i and scr[i] for
        # scrambled data word i. We need to construct scr[0], scr[1], ...,
        # scr[self.rom_size_words].
        #
        # Then, for all i, we have:
        #
        #   clr[i] = PRINCE(i) ^ data_sp(scr[addr_sp(i)])
        #
        # Change coordinates by evaluating at addr_sp_inv(i):
        #
        #   clr[addr_sp_inv(i)] = PRINCE(addr_sp_inv(i)) ^ data_sp(scr[i])
        #
        # so
        #
        #   scr[i] = data_sp_inv(clr[addr_sp_inv(i)] ^ PRINCE(addr_sp_inv(i)))
        subst_perm_rounds = 2
        num_rounds_half = 2

        assert word_width <= 64
        word_mask = (1 << word_width) - 1

        data_nonce_width = 64 - self._addr_width

        data_scr_nonce = self.nonce & ((1 << data_nonce_width) - 1)
        addr_scr_nonce = self.nonce >> data_nonce_width

        scrambled = []
        for phy_addr in range(self.rom_size_words):
            log_addr = subst_perm_dec(phy_addr, addr_scr_nonce,
                                      self._addr_width, subst_perm_rounds)
            assert 0 <= log_addr < self.rom_size_words

            to_scramble = (data_scr_nonce << self._addr_width) | log_addr
            keystream = prince(to_scramble, self.key, num_rounds_half)

            keystream_trunc = keystream & word_mask
            clr_data = mem.chunks[0].words[log_addr]
            assert 0 <= clr_data < word_mask

            sp_scr_data = keystream_trunc ^ clr_data
            scr_data = subst_perm_enc(sp_scr_data, 0, word_width,
                                      subst_perm_rounds)

            assert 0 <= scr_data < word_mask

            scrambled.append(scr_data)

        return MemFile(mem.width, [MemChunk(0, scrambled)])
Exemplo n.º 3
0
def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument('hjson')
    parser.add_argument('infile', type=argparse.FileType('rb'))
    parser.add_argument('outfile', type=argparse.FileType('w'))

    args = parser.parse_args()
    scrambler = Scrambler.from_hjson_path(args.hjson, ROM_SIZE_WORDS)

    # Load the input ELF file
    clr_mem = MemFile.load_elf32(args.infile, 4 * ROM_BASE_WORD)

    # Flatten the file, padding with pseudo-random data and ensuring it's
    # exactly scrambler.rom_size_words words long.
    clr_flat = scrambler.flatten(clr_mem)

    # Extend from 32 bits to 39 by adding Hsiao (39,32) ECC bits.
    clr_flat.add_ecc32()

    # Zero-extend the cleartext memory by one more bit (this is the size we
    # actually use in the physical ROM)
    assert clr_flat.width == 39
    clr_flat.width = 40

    # Scramble the memory
    scr_mem = scrambler.scramble40(clr_flat)

    # Insert the expected hash here to the top 8 words
    scrambler.add_hash(scr_mem)

    # Check for collisions
    collisions = scr_mem.collisions()
    if collisions:
        print(
            'ERROR: This combination of ROM contents and scrambling\n'
            '       key results in one or more collisions where\n'
            '       different addresses have the same data.\n'
            '\n'
            '       Looks like we\'ve been (very) unlucky with the\n'
            '       birthday problem. As a work-around, try again after\n'
            '       generating some different RndCnst* parameters.\n',
            file=sys.stderr)
        print('{} colliding addresses:'.format(len(collisions)),
              file=sys.stderr)
        for addr0, addr1 in collisions:
            print('  {:#010x}, {:#010x}'.format(addr0, addr1), file=sys.stderr)
        return 1

    scr_mem.write_vmem(args.outfile)
    return 0
Exemplo n.º 4
0
def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument('infile', type=argparse.FileType('rb'))
    parser.add_argument('outfile', type=argparse.FileType('w'))
    parser.add_argument('--swap-nibbles',
                        dest='swap_nibbles',
                        action='store_true')

    args = parser.parse_args()

    # Extract width from ROM file name.
    match = re.search(r'([0-9]+).vmem', args.infile.name)
    if not match:
        raise ValueError('Cannot extract ROM word width from file name ' +
                         args.infile.name)
    else:
        width = int(match.group(1))

    # Load the input vmem file.
    vmem = MemFile.load_vmem(width, args.infile)

    # OpenTitan vmem files should always contain one single contiguous chunk.
    assert len(vmem.chunks) == 1

    # Loop over all words, and:
    # 1) Generate the address,
    # 2) convert the endianness, and
    # 3) write this to the output file.
    addr_chars = 8
    word_chars = math.ceil(width / 4)
    for idx, word in enumerate(vmem.chunks[0].words):
        # Generate the address.
        addr = idx * math.ceil(width / 8)
        # Convert endianness.
        data = swap_bytes(width, word, args.swap_nibbles)
        # Write to file.
        toks = [f'@{addr:0{addr_chars}X}']
        toks.append(f'{data:0{word_chars}X}')
        args.outfile.write(' '.join(toks) + '\n')

    return 0
Exemplo n.º 5
0
    def scramble(self, mem: MemFile) -> MemFile:
        assert len(mem.chunks) == 1
        assert len(mem.chunks[0].words) == self.rom_size_words

        width = mem.width

        # Write addr_sp, data_sp for the S&P networks for address and data,
        # respectively. Write clr[i] for unscrambled data word i and scr[i] for
        # scrambled data word i. We need to construct scr[0], scr[1], ...,
        # scr[self.rom_size_words].
        #
        # Then, for all i, we have:
        #
        #   clr[i] = PRINCE(i) ^ data_sp_dec(scr[addr_sp_enc(i)])
        #
        # Change coordinates by evaluating at addr_sp_dec(i):
        #
        #   clr[addr_sp_dec(i)] = PRINCE(addr_sp_dec(i)) ^ data_sp_dec(scr[i])
        #
        # so
        #
        #   scr[i] = data_sp_enc(clr[addr_sp_dec(i)] ^ PRINCE(addr_sp_dec(i)))
        #
        # Using the scramble_word helper function, this is:
        #
        #   scr[i] = scramble_word(width, addr_sp_dec(i), clr[addr_sp_dec(i)])

        assert width <= 64

        scrambled = []
        for phy_addr in range(self.rom_size_words):
            log_addr = self.addr_sp_dec(phy_addr)
            assert 0 <= log_addr < self.rom_size_words

            clr_data = mem.chunks[0].words[log_addr]
            assert 0 <= clr_data < (1 << width)

            scrambled.append(self.scramble_word(width, log_addr, clr_data))

        return MemFile(mem.width, [MemChunk(0, scrambled)])
Exemplo n.º 6
0
    def flatten(self, mem: MemFile) -> MemFile:
        '''Flatten and pad mem up to the correct size

        This adds 8 trailing zero words as space to store the expected hash.
        These are (obviously!) not the right hash: we inject them properly
        later.

        '''
        digest_size_words = 8
        initial_len = self.rom_size_words - digest_size_words
        seed = self.key + self.nonce

        flattened = mem.flatten(initial_len, seed)
        assert len(flattened.chunks) == 1
        assert len(flattened.chunks[0].words) == initial_len

        # Add the 8 trailing zero words. We do it here, rather than passing
        # rom_size_words to mem.flatten, to make sure that we see the error if
        # mem is too big.
        flattened.chunks[0].words += [0] * digest_size_words

        return flattened