mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
Modified test_replay_attack.py, util_test_tools.py and test_util_test_tools.py.
This commit is contained in:
parent
e7ba0092e4
commit
c0d5d2cbb5
4 changed files with 242 additions and 292 deletions
|
|
@ -546,6 +546,6 @@ def wrapper( self, *args, **kwargs ):
|
|||
############################## GLOBAL VARIABLES ################################
|
||||
|
||||
|
||||
# We use False as a sentinal value.
|
||||
# We use False as a sentinel value.
|
||||
_previous_urllib_urlopener = False
|
||||
_previous_urllib2_opener = False
|
||||
|
|
|
|||
|
|
@ -15,10 +15,17 @@
|
|||
Simulate a replay attack. A simple client update vs. client update
|
||||
implementing TUF.
|
||||
|
||||
Note: The interposition provided by 'tuf.interposition' is used to intercept
|
||||
all calls made by urllib/urillib2 to certain network locations specified in
|
||||
the interposition configuration file. Look up interposition.py for more
|
||||
information and illustration of a sample contents of the interposition
|
||||
configuration file. Interposition was meant to make TUF integration with an
|
||||
existing software updater an easy process. This allows for more flexibility
|
||||
to the existing software updater. However, if you are planning to solely use
|
||||
TUF there should be no need for interposition, all necessary calls will be
|
||||
generated from within TUF.
|
||||
|
||||
Note: There is no difference between 'updates' and 'target' files.
|
||||
Note: If TUF is implemented - you would NOT use urllib like it's done here
|
||||
for the testing purposes. TUF handles the downloads.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -28,12 +35,20 @@
|
|||
import tempfile
|
||||
import util_test_tools
|
||||
|
||||
import tuf.interposition
|
||||
|
||||
|
||||
def test_replay_attack(tuf=False):
|
||||
class TestSetupError(Exception):
|
||||
pass
|
||||
|
||||
class ReplayAttackError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def test_replay_attack(TUF=False):
|
||||
"""
|
||||
<Arguments>
|
||||
tuf:
|
||||
TUF:
|
||||
If set to 'False' all directories that start with 'tuf_' are ignored,
|
||||
indicating that tuf is not implemented.
|
||||
|
||||
|
|
@ -42,112 +57,100 @@ def test_replay_attack(tuf=False):
|
|||
|
||||
"""
|
||||
|
||||
# Setup.
|
||||
temp_root, url = util_test_tools.init_repo(tuf=tuf)
|
||||
repo = os.path.join(temp_root, 'repo')
|
||||
tuf_repo = os.path.join(temp_root, 'tuf_repo')
|
||||
downloads =os.path.join(temp_root, 'downloads')
|
||||
try:
|
||||
# Setup.
|
||||
root_repo, url, server_proc, keyids, interpose_json = \
|
||||
util_test_tools.init_repo(tuf=TUF)
|
||||
reg_repo = os.path.join(root_repo, 'reg_repo')
|
||||
tuf_repo = os.path.join(root_repo, 'tuf_repo')
|
||||
downloads = os.path.join(root_repo, 'downloads')
|
||||
tuf_targets = os.path.join(tuf_repo, 'targets')
|
||||
|
||||
# Add file to 'repo' directory: {temp_root}
|
||||
filepath = util_test_tools.add_file_to_repository('Test A')
|
||||
file_basename = os.path.basename(filepath)
|
||||
url_to_repo = url+'repo/'+file_basename
|
||||
downloaded_file = os.path.join(downloads, file_basename)
|
||||
# Add file to 'repo' directory: {root_repo}
|
||||
filepath = util_test_tools.add_file_to_repository(reg_repo, 'Test A')
|
||||
file_basename = os.path.basename(filepath)
|
||||
url_to_repo = url+'reg_repo/'+file_basename
|
||||
downloaded_file = os.path.join(downloads, file_basename)
|
||||
|
||||
# Attacker saves the original file into 'evil_dir'.
|
||||
evil_dir = tempfile.mkdtemp(dir=root_repo)
|
||||
vulnerable_file = os.path.join(evil_dir, file_basename)
|
||||
shutil.copy(filepath, evil_dir)
|
||||
|
||||
# Client performs initial update.
|
||||
if tuf:
|
||||
util_test_tools.tuf_refresh_and_download()
|
||||
else:
|
||||
# Refresh the tuf repository and apply tuf interpose.
|
||||
if TUF:
|
||||
util_test_tools.tuf_refresh_repo(root_repo, keyids)
|
||||
tuf.interposition.configure(interpose_json)
|
||||
tuf.interposition.interpose()
|
||||
|
||||
# End Setup.
|
||||
|
||||
# Client performs initial update.
|
||||
urllib.urlretrieve(url_to_repo, downloaded_file)
|
||||
|
||||
# Content of the downloaded file.
|
||||
# Downloads are stored in the same directory '{temp_root}/downloads/'
|
||||
# independent of who stores there (tuf or regular client). See warning
|
||||
# in util_test_tools.init_repo().
|
||||
downloaded_content = util_test_tools.read_file_content(downloaded_file)
|
||||
msg = '[Initial Updata] Failed to download the file.'
|
||||
assert 'Test A' == downloaded_content, msg
|
||||
# Content of the downloaded file.
|
||||
# Downloads are stored in the same directory '{root_repo}/downloads/'
|
||||
# independent of who stores there (tuf or regular client). See warning
|
||||
# in util_test_tools.init_repo().
|
||||
downloaded_content = util_test_tools.read_file_content(downloaded_file)
|
||||
msg = '[Initial Updata] Failed to download the file.'
|
||||
if 'Test A' != downloaded_content:
|
||||
raise TestSetupError(msg)
|
||||
|
||||
# Developer patches the file and updates the repository.
|
||||
util_test_tools.modify_file_at_repository(filepath, 'Test NOT A')
|
||||
if TUF:
|
||||
util_test_tools.tuf_refresh_repo(root_repo, keyids)
|
||||
|
||||
# Attacker finds a vulnerability in the file.
|
||||
evil_dir = tempfile.mkdtemp(dir=temp_root)
|
||||
vulnerable_file = os.path.join(evil_dir, file_basename)
|
||||
urllib.urlretrieve(url_to_repo, vulnerable_file)
|
||||
|
||||
# Developer patches the file and updates the repository.
|
||||
util_test_tools.modify_file_at_repository(filepath, 'Test NOT A')
|
||||
|
||||
|
||||
# Client downloads the patched file.
|
||||
if tuf:
|
||||
util_test_tools.tuf_refresh_and_download()
|
||||
else:
|
||||
# Client downloads the patched file.
|
||||
urllib.urlretrieve(url_to_repo, downloaded_file)
|
||||
|
||||
# Content of the downloaded file.
|
||||
downloaded_content = util_test_tools.read_file_content(downloaded_file)
|
||||
msg = '[Updata] Failed to update the file.'
|
||||
assert 'Test NOT A' == downloaded_content, msg
|
||||
# Content of the downloaded file.
|
||||
downloaded_content = util_test_tools.read_file_content(downloaded_file)
|
||||
msg = '[Update] Failed to update the file.'
|
||||
if 'Test NOT A' != downloaded_content:
|
||||
raise TestSetupError(msg)
|
||||
|
||||
# Attacker tries to be clever, he manages to modifies regular and tuf
|
||||
# targets directory by replacing a patched file with an old one.
|
||||
if os.path.isdir(tuf_targets):
|
||||
target = os.path.join(tuf_targets, file_basename)
|
||||
util_test_tools.delete_file_at_repository(target)
|
||||
shutil.copy(vulnerable_file, tuf_targets)
|
||||
# Verify that 'target' is an old, un-patched file.
|
||||
target = os.path.join(tuf_targets, file_basename)
|
||||
target_content = util_test_tools.read_file_content(target)
|
||||
msg = "The 'target' file contains new data!"
|
||||
if 'Test A' != target_content:
|
||||
raise TestSetupError(msg)
|
||||
else:
|
||||
util_test_tools.delete_file_at_repository(filepath)
|
||||
shutil.copy(vulnerable_file, reg_repo)
|
||||
|
||||
# Attacker tries to be clever, he manages to modifies tuf targets directory
|
||||
# by replacing a patched file with an old one.
|
||||
#
|
||||
# Since we don't really have any restriction where regular download
|
||||
# retrieves the files from, this works fine. On the other hand, when
|
||||
# tuf is used this will guarantee that tuf-client will be retrieving the
|
||||
# attacker's file. This happens, because mirror's list is pointing to
|
||||
# the tuf repository.
|
||||
#
|
||||
# If tuf is False none of the tuf directories are created, but attacker
|
||||
# needs tuf targets directory in order to be able to attack both tuf and
|
||||
# non-tuf clients. For this purpose he creates an artificial tuf targets
|
||||
# directory (Remember: the tuf is not setup at this point!).
|
||||
targets_dir = os.path.join(tuf_repo, 'targets')
|
||||
if not os.path.isdir(targets_dir):
|
||||
os.makedirs(targets_dir)
|
||||
shutil.copy(vulnerable_file, targets_dir)
|
||||
url_to_tuf = url+'tuf_repo/targets/'+file_basename
|
||||
# Client downloads the file once time.
|
||||
urllib.urlretrieve(url_to_repo, downloaded_file)
|
||||
|
||||
# Verify that 'target' is an old, un-patched file.
|
||||
target = os.path.join(targets_dir, file_basename)
|
||||
target_content = util_test_tools.read_file_content(target)
|
||||
msg = 'The \'target\' file contains new data!'
|
||||
assert 'Test A' == target_content, msg
|
||||
# Check whether the attack succeeded by inspecting the content of the
|
||||
# update. The update should contain 'Test NOT A'.
|
||||
downloaded_content = util_test_tools.read_file_content(downloaded_file)
|
||||
msg = 'Replay attack was successful!\n'
|
||||
if 'Test NOT A' != downloaded_content:
|
||||
raise ReplayAttackError(msg)
|
||||
|
||||
|
||||
# Client downloads the file once time.
|
||||
if tuf:
|
||||
util_test_tools.tuf_refresh_and_download()
|
||||
else:
|
||||
urllib.urlretrieve(url_to_tuf, downloaded_file)
|
||||
|
||||
# Check whether the attack succeeded by inspecting the content of the
|
||||
# update. The update should contain 'Test NOT A'.
|
||||
downloaded_content = util_test_tools.read_file_content(downloaded_file)
|
||||
msg = 'Replay attack was successful!\n'
|
||||
assert 'Test NOT A' == downloaded_content, msg
|
||||
finally:
|
||||
tuf.interposition.go_away()
|
||||
util_test_tools.cleanup(root_repo, server_proc)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
try:
|
||||
test_replay_attack(tuf=False)
|
||||
except AssertionError, e:
|
||||
print 'Expected Failure: '+repr(e)
|
||||
else:
|
||||
print 'Unexpected Failure!'
|
||||
finally:
|
||||
util_test_tools.cleanup()
|
||||
|
||||
test_replay_attack(TUF=False)
|
||||
except ReplayAttackError, err:
|
||||
print err
|
||||
|
||||
try:
|
||||
test_replay_attack(tuf=True)
|
||||
except AssertionError, e:
|
||||
print 'Unexpected Failure: '+repr(e)
|
||||
else:
|
||||
print 'Expected Success!'
|
||||
finally:
|
||||
util_test_tools.cleanup()
|
||||
test_replay_attack(TUF=True)
|
||||
except ReplayAttackError, err:
|
||||
print err
|
||||
|
|
@ -25,11 +25,18 @@
|
|||
class test_UtilTestTools(unittest.TestCase):
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.temp_root, self.url = util_test_tools.init_repo(tuf=True)
|
||||
|
||||
# Unpacking necessary parameters returned from init_repo()
|
||||
essential_params = util_test_tools.init_repo(tuf=True)
|
||||
self.root_repo = essential_params[0]
|
||||
self.url = essential_params[1]
|
||||
self.server_proc = essential_params[2]
|
||||
self.keyids = essential_params[3]
|
||||
self.interpose_json = essential_params[4]
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
util_test_tools.cleanup()
|
||||
util_test_tools.cleanup(self.root_repo, self.server_proc)
|
||||
|
||||
|
||||
#================================================#
|
||||
|
|
@ -40,14 +47,15 @@ def tearDown(self):
|
|||
# A few quick internal tests to see if everything runs smoothly.
|
||||
def test_direct_download(self):
|
||||
# Setup.
|
||||
downloads = os.path.join(self.temp_root, 'downloads')
|
||||
filepath = util_test_tools.add_file_to_repository('Test')
|
||||
reg_repo = os.path.join(self.root_repo, 'reg_repo')
|
||||
downloads = os.path.join(self.root_repo, 'downloads')
|
||||
filepath = util_test_tools.add_file_to_repository(reg_repo, 'Test')
|
||||
file_basename = os.path.basename(filepath)
|
||||
url = self.url+'repo/'+file_basename
|
||||
url_to_reg_repo = self.url+'reg_repo/'+file_basename
|
||||
downloaded_file = os.path.join(downloads, file_basename)
|
||||
|
||||
# Test direct download using 'urllib.urlretrieve'.
|
||||
urllib.urlretrieve(url, downloaded_file)
|
||||
urllib.urlretrieve(url_to_reg_repo, downloaded_file)
|
||||
self.assertTrue(os.path.isfile(downloaded_file))
|
||||
|
||||
# Verify the content of the downloaded file.
|
||||
|
|
@ -59,18 +67,18 @@ def test_direct_download(self):
|
|||
|
||||
|
||||
def test_correct_directory_structure(self):
|
||||
# Verify following directories exists: '{temp_root}/repo/',
|
||||
# '{temp_root}/downloads/.
|
||||
self.assertTrue(os.path.isdir(os.path.join(self.temp_root, 'repo')))
|
||||
self.assertTrue(os.path.isdir(os.path.join(self.temp_root, 'downloads')))
|
||||
# Verify following directories exists: '{root_repo}/reg_repo/',
|
||||
# '{root_repo}/downloads/.
|
||||
self.assertTrue(os.path.isdir(os.path.join(self.root_repo, 'reg_repo')))
|
||||
self.assertTrue(os.path.isdir(os.path.join(self.root_repo, 'downloads')))
|
||||
|
||||
# Verify that all necessary TUF-paths exist.
|
||||
tuf_repo = os.path.join(self.temp_root, 'tuf_repo')
|
||||
tuf_client = os.path.join(self.temp_root, 'tuf_client')
|
||||
tuf_repo = os.path.join(self.root_repo, 'tuf_repo')
|
||||
tuf_client = os.path.join(self.root_repo, 'tuf_client')
|
||||
metadata_dir = os.path.join(tuf_repo, 'metadata')
|
||||
current_dir = os.path.join(tuf_client, 'metadata', 'current')
|
||||
|
||||
# Verify '{temp_root}/tuf_repo/metadata/role.txt' paths exists.
|
||||
# Verify '{root_repo}/tuf_repo/metadata/role.txt' paths exists.
|
||||
for role in ['root', 'targets', 'release', 'timestamp']:
|
||||
# Repository side.
|
||||
role_file = os.path.join(metadata_dir, role+'.txt')
|
||||
|
|
@ -80,11 +88,11 @@ def test_correct_directory_structure(self):
|
|||
role_file = os.path.join(current_dir, role+'.txt')
|
||||
self.assertTrue(os.path.isfile(role_file))
|
||||
|
||||
# Verify '{temp_root}/tuf_repo/keystore/keyid.key' exists.
|
||||
# Verify '{root_repo}/tuf_repo/keystore/keyid.key' exists.
|
||||
keys_list = os.listdir(os.path.join(tuf_repo, 'keystore'))
|
||||
self.assertEquals(len(keys_list), 1)
|
||||
|
||||
# Verify '{temp_root}/tuf_repo/targets/' directory exists.
|
||||
# Verify '{root_repo}/tuf_repo/targets/' directory exists.
|
||||
self.assertTrue(os.path.isdir(os.path.join(tuf_repo, 'targets')))
|
||||
|
||||
|
||||
|
|
@ -103,19 +111,19 @@ def test_methods(self):
|
|||
|
||||
Note: here file at the 'filepath' and the 'target' file at tuf-targets
|
||||
directory are identical files.
|
||||
Ex: filepath = '{temp_root}/repo/file.txt'
|
||||
target = '{temp_root}/tuf_repo/targets/file.txt'
|
||||
Ex: filepath = '{root_repo}/reg_repo/file.txt'
|
||||
target = '{root_repo}/tuf_repo/targets/file.txt'
|
||||
"""
|
||||
|
||||
repo = os.path.join(self.temp_root, 'repo')
|
||||
tuf_repo = os.path.join(self.temp_root, 'tuf_repo')
|
||||
downloads = os.path.join(self.temp_root, 'downloads')
|
||||
reg_repo = os.path.join(self.root_repo, 'reg_repo')
|
||||
tuf_repo = os.path.join(self.root_repo, 'tuf_repo')
|
||||
downloads = os.path.join(self.root_repo, 'downloads')
|
||||
|
||||
# Test 'add_file_to_repository(data)' and read_file_content(filepath)
|
||||
# methods
|
||||
filepath = util_test_tools.add_file_to_repository('Test')
|
||||
# Test 'add_file_to_repository(directory, data)' and
|
||||
# read_file_content(filepath) methods.
|
||||
filepath = util_test_tools.add_file_to_repository(reg_repo, 'Test')
|
||||
self.assertTrue(os.path.isfile(filepath))
|
||||
self.assertEquals(os.path.dirname(filepath), repo)
|
||||
self.assertEquals(os.path.dirname(filepath), reg_repo)
|
||||
filepath_content = util_test_tools.read_file_content(filepath)
|
||||
self.assertEquals('Test', filepath_content)
|
||||
|
||||
|
|
@ -126,22 +134,17 @@ def test_methods(self):
|
|||
self.assertEquals('Modify', filepath_content)
|
||||
|
||||
# Test 'tuf_refresh_repo' method.
|
||||
util_test_tools.tuf_refresh_repo()
|
||||
util_test_tools.tuf_refresh_repo(self.root_repo, self.keyids)
|
||||
file_basename = os.path.basename(filepath)
|
||||
target = os.path.join(tuf_repo, 'targets', file_basename)
|
||||
self.assertTrue(os.path.isfile(target))
|
||||
|
||||
# Test 'tuf_refresh_and_download()' method.
|
||||
util_test_tools.tuf_refresh_and_download()
|
||||
target = os.path.join(downloads, file_basename)
|
||||
self.assertTrue(os.path.isfile(target))
|
||||
|
||||
# Test 'delete_file_at_repository(filepath)' method.
|
||||
util_test_tools.delete_file_at_repository(filepath)
|
||||
self.assertFalse(os.path.exists(filepath))
|
||||
|
||||
# Test 'tuf_refresh_repo' method once more.
|
||||
util_test_tools.tuf_refresh_repo()
|
||||
util_test_tools.tuf_refresh_repo(self.root_repo, self.keyids)
|
||||
file_basename = os.path.basename(filepath)
|
||||
target = os.path.join(tuf_repo, 'targets', file_basename)
|
||||
self.assertFalse(os.path.isfile(target))
|
||||
|
|
|
|||
|
|
@ -24,45 +24,45 @@
|
|||
<Directories>
|
||||
Initialized by init_repo()
|
||||
|
||||
The server is pointing to 'temp_root' directory, including the '/'.
|
||||
The server is pointing to 'root_repo' directory, including the '/'.
|
||||
|
||||
temp_root
|
||||
root_repo
|
||||
|
|
||||
-------------------------------------------------------
|
||||
| | | | |
|
||||
repo tuf_repo tuf_client tuf_downloads downloads
|
||||
----------------------------------------
|
||||
| | | |
|
||||
reg_repo tuf_repo tuf_client downloads
|
||||
|
||||
'{temp_repo}/downloads/': stores all direct downloads made by the client.
|
||||
'{temp_repo}/tuf_downloads/': stores all downloads made by the client using
|
||||
'{root_repo}/downloads/': stores all direct downloads made by the client.
|
||||
'{root_repo}/tuf_downloads/': stores all downloads made by the client using
|
||||
tuf.
|
||||
|
||||
|
||||
repo
|
||||
reg_repo
|
||||
|
|
||||
-----------------------------
|
||||
| | ... |
|
||||
file(1) file(2) ... file(n)
|
||||
|
||||
'{temp_repo}/repo/': main developer's repository that contains files or
|
||||
'{root_repo}/reg_repo/': main developer's repository that contains files or
|
||||
updates that need to be distributed.
|
||||
|
||||
|
||||
tuf_repo
|
||||
|
|
||||
------------------------------------------
|
||||
--------------------------------------------
|
||||
| | |
|
||||
keystore metadata targets
|
||||
| | |
|
||||
key1.key ... role.txt ... file(1) ...
|
||||
|
||||
'{temp_repo}/tuf_repo/': developer's tuf-repository directory containing
|
||||
'{root_repo}/tuf_repo/': developer's tuf-repository directory containing
|
||||
following subdirectories:
|
||||
'{temp_repo}/tuf_repo/keystore/': directory where all signing keys are
|
||||
stored.
|
||||
'{temp_repo}/tuf_repo/metadata/': directory where all metadata signed
|
||||
metadata files are stored.
|
||||
'{temp_repo}/tuf_repo/targets/': directory where all tuf verified files
|
||||
are stored.
|
||||
'{root_repo}/tuf_repo/keystore/': directory where all signing keys are
|
||||
stored.
|
||||
'{root_repo}/tuf_repo/metadata/': directory where all metadata signed
|
||||
metadata files are stored.
|
||||
'{root_repo}/tuf_repo/targets/': directory where all tuf verified files
|
||||
are stored.
|
||||
|
||||
tuf_client
|
||||
|
|
||||
|
|
@ -74,10 +74,10 @@
|
|||
| |
|
||||
role.txt ... role.txt ...
|
||||
|
||||
'{temp_repo}/tuf_cleint/': client directory containing tuf metadata.
|
||||
'{temp_repo}/tuf_cleint/metadata/current': directory where client stores
|
||||
'{root_repo}/tuf_client/': client directory containing tuf metadata.
|
||||
'{root_repo}/tuf_client/metadata/current': directory where client stores
|
||||
latest metadata files.
|
||||
'{temp_repo}/tuf_cleint/metadata/current': directory where client stores
|
||||
'{root_repo}/tuf_client/metadata/current': directory where client stores
|
||||
previous metadata files.
|
||||
|
||||
<Methods>
|
||||
|
|
@ -85,25 +85,25 @@
|
|||
Initializes the repositories (depicted in the diagram above) and
|
||||
starts the server process. init_repo takes one boolean argument
|
||||
which when True sets-up tuf repository i.e. adds all of the
|
||||
directories that start with 'tuf_' in the temp_root (depicted above).
|
||||
Returns a tuple - full path of the 'temp_root' directory, and the url.
|
||||
directories that start with 'tuf_' in the root_repo (depicted above).
|
||||
Returns a tuple - full path of the 'root_repo' directory, and the url.
|
||||
This should be sufficient to construct the tests.
|
||||
|
||||
cleanup():
|
||||
Deletes all of the created repositories and shuts down the server.
|
||||
|
||||
add_file_to_repository(data):
|
||||
Adds a file to the 'repo' directory and writes 'data' into it.
|
||||
Adds a file to the 'reg_repo' directory and writes 'data' into it.
|
||||
Returns full file path of the new file.
|
||||
|
||||
modify_file_at_repository(filepath, data):
|
||||
Modifies a file at the 'repo' directory by writing 'data' into it.
|
||||
'filepath' has to be an existing file at the 'repo' directory.
|
||||
Modifies a file at the 'reg_repo' directory by writing 'data' into it.
|
||||
'filepath' has to be an existing file at the 'reg_repo' directory.
|
||||
Returns full file path of the modified file.
|
||||
|
||||
delete_file_at_repository(filepath):
|
||||
Deletes a file at the 'repo' directory.
|
||||
'filepath' has to be an existing file at the 'repo' directory.
|
||||
Deletes a file at the 'reg_repo' directory.
|
||||
'filepath' has to be an existing file at the 'reg_repo' directory.
|
||||
|
||||
read_file_content(filepath):
|
||||
Returns data string of the 'filepath' content.
|
||||
|
|
@ -114,24 +114,9 @@
|
|||
|
||||
tuf_refresh_repo():
|
||||
Refreshes metadata files at the 'tuf_repo' directory i.e. role.txt's at
|
||||
'{temp_root}/tuf_repo/metadata/'. Following roles are refreshed:
|
||||
targets, release and timestamp. Also, the whole 'repo' directory is
|
||||
copied to targets directory i.e. '{temp_root}/tuf_repo/targets/'.
|
||||
|
||||
tuf_refresh_client_metadata():
|
||||
Downloads latests metadata files from '{temp_root}/tuf_repo/metadata/'
|
||||
into '{temp_root}/tuf_client/metadata/current/'.
|
||||
|
||||
tuf_download_updates()
|
||||
Downloads files in the secure manner and then performs all tuf security
|
||||
checks i.e. length and hash comparisons based on the information in the
|
||||
metadata files.
|
||||
|
||||
tuf_refresh_and_download()
|
||||
Combines tuf_refresh_repo(), tuf_refresh_client_metadata(), and
|
||||
tuf_download_updates().
|
||||
Returns 'tuf_downloads' directory where all tuf downloaded files are
|
||||
located.
|
||||
'{root_repo}/tuf_repo/metadata/'. Following roles are refreshed:
|
||||
targets, release and timestamp. Also, the whole 'reg_repo' directory is
|
||||
copied to targets directory i.e. '{root_repo}/tuf_repo/targets/'.
|
||||
|
||||
Note: metadata files are root.txt, targets.txt, release.txt and
|
||||
timestamp.txt (denoted as 'role.txt in the diagrams'). There could be
|
||||
|
|
@ -151,6 +136,8 @@
|
|||
import tempfile
|
||||
import subprocess
|
||||
|
||||
import tuf.util
|
||||
import tuf.interposition
|
||||
import tuf.client.updater
|
||||
import tuf.repo.signerlib as signerlib
|
||||
|
||||
|
|
@ -161,69 +148,64 @@
|
|||
|
||||
|
||||
# 'setup_info' stores all important setup information like the path of the
|
||||
# 'temp_root' directory, etc.
|
||||
setup_info = {}
|
||||
# 'root_repo' directory, etc.
|
||||
|
||||
|
||||
def init_repo(tuf=False):
|
||||
setup_info['tuf'] = tuf
|
||||
|
||||
# Temp root directory for regular and tuf repositories.
|
||||
# WARNING: tuf client stores files in '{temp_root}/downloads/' directory!
|
||||
# WARNING: tuf client stores files in '{root_repo}/downloads/' directory!
|
||||
# Make sure regular download are NOT stored in the that directory when
|
||||
# tuf stores its downloads there. If regular download needs to happen at
|
||||
# the time when tuf has or will have tuf downloads stored there just create
|
||||
# a separate directory in {temp_root} to store regular downloads in.
|
||||
# Ex: mkdir(temp_root, 'reg_downloads').
|
||||
setup_info['temp_root'] = temp_root = tempfile.mkdtemp(dir=os.getcwd())
|
||||
setup_info['repo'] = os.path.join(temp_root, 'repo')
|
||||
setup_info['downloads'] = os.path.join(temp_root, 'downloads')
|
||||
os.mkdir(setup_info['repo'])
|
||||
os.mkdir(setup_info['downloads'])
|
||||
# the time when tuf has or will have tuf downloads stored there, create
|
||||
# a separate directory in {root_repo} to store regular downloads in.
|
||||
# Ex: mkdir(root_repo, 'reg_downloads').
|
||||
root_repo = tempfile.mkdtemp(dir=os.getcwd())
|
||||
os.mkdir(os.path.join(root_repo, 'reg_repo'))
|
||||
os.mkdir(os.path.join(root_repo, 'downloads'))
|
||||
|
||||
# Start a simple server pointing to the repository directory.
|
||||
port = random.randint(30000, 45000)
|
||||
command = ['python', '-m', 'SimpleHTTPServer', str(port)]
|
||||
setup_info['server_proc'] = subprocess.Popen(command, stderr=subprocess.PIPE)
|
||||
server_proc = subprocess.Popen(command, stderr=subprocess.PIPE)
|
||||
|
||||
# Tailor url for the repository. In order to download a 'file.txt'
|
||||
# from 'repo' do: url+'repo/file.txt'
|
||||
relpath = os.path.basename(temp_root)
|
||||
setup_info['url'] = url = 'http://localhost:'+str(port)+'/'+relpath+'/'
|
||||
# from 'reg_repo' do: url+'reg_repo/file.txt'
|
||||
relpath = os.path.basename(root_repo)
|
||||
url = 'http://localhost:'+str(port)+'/'+relpath+'/'
|
||||
|
||||
# NOTE: The delay is needed to make up for asynchronous subprocess.
|
||||
# Otherwise following error might be raised:
|
||||
# <urlopen error [Errno 111] Connection refused>
|
||||
time.sleep(.1)
|
||||
|
||||
keyids = None
|
||||
interpose_json = None
|
||||
if tuf:
|
||||
init_tuf()
|
||||
keyids, interpose_json = init_tuf(root_repo, url)
|
||||
|
||||
return temp_root, url
|
||||
return root_repo, url, server_proc, keyids, interpose_json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def cleanup():
|
||||
if not setup_info:
|
||||
msg = 'init_repo() must be called before cleanup().\n'
|
||||
sys.exit(msg)
|
||||
|
||||
if setup_info['server_proc'].returncode is None:
|
||||
setup_info['server_proc'].kill()
|
||||
def cleanup(root_repo, server_process):
|
||||
if server_process.returncode is None:
|
||||
server_process.kill()
|
||||
print 'Server terminated.\n'
|
||||
|
||||
# Removing repository directory.
|
||||
try:
|
||||
shutil.rmtree(setup_info['temp_root'])
|
||||
shutil.rmtree(root_repo)
|
||||
except OSError, e:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
def add_file_to_repository(data='Test String'):
|
||||
junk, filepath = tempfile.mkstemp(dir=setup_info['repo'])
|
||||
|
||||
def add_file_to_repository(directory, data='Test String'):
|
||||
junk, filepath = tempfile.mkstemp(dir=directory)
|
||||
fileobj = open(filepath, 'wb')
|
||||
fileobj.write(data)
|
||||
fileobj.close()
|
||||
|
|
@ -234,9 +216,8 @@ def add_file_to_repository(data='Test String'):
|
|||
|
||||
|
||||
def modify_file_at_repository(filepath, data='Modified String'):
|
||||
repo = os.path.dirname(filepath)
|
||||
if repo != setup_info['repo'] or not os.path.isfile(filepath):
|
||||
msg = 'Provide a valid file on the repository to modify.'
|
||||
if not os.path.isfile(filepath):
|
||||
msg = ('Cannot modify file path '+repr(filepath)+', it does not exist.')
|
||||
sys.exit(msg)
|
||||
|
||||
fileobj = open(filepath, 'wb')
|
||||
|
|
@ -255,9 +236,8 @@ def delete_file_at_repository(filepath):
|
|||
|
||||
"""
|
||||
|
||||
repo = os.path.dirname(filepath)
|
||||
if repo != setup_info['repo'] or not os.path.isfile(filepath):
|
||||
msg = 'Provide a valid file on the repository to delete.'
|
||||
if not os.path.isfile(filepath):
|
||||
msg = ('Cannot remove file path '+repr(filepath)+', it does not exist.')
|
||||
sys.exit(msg)
|
||||
|
||||
os.remove(filepath)
|
||||
|
|
@ -268,7 +248,8 @@ def delete_file_at_repository(filepath):
|
|||
|
||||
def read_file_content(filepath):
|
||||
if not os.path.isfile(filepath):
|
||||
msg = 'Provide a valid file to read.'
|
||||
msg = ('File path '+repr(filepath)+' does not exist. '+
|
||||
'Provide a valid file to read.')
|
||||
sys.exit(msg)
|
||||
|
||||
fileobj = open(filepath, 'rb')
|
||||
|
|
@ -280,7 +261,7 @@ def read_file_content(filepath):
|
|||
|
||||
|
||||
|
||||
def init_tuf():
|
||||
def init_tuf(root_repo, url):
|
||||
"""
|
||||
<Purpose>
|
||||
Setup TUF directory structure and populated it with TUF metadata and
|
||||
|
|
@ -292,8 +273,7 @@ def init_tuf():
|
|||
threshold = 1
|
||||
|
||||
# Setup TUF-repo directory structure.
|
||||
setup_info['tuf_repo'] = tuf_repo = \
|
||||
os.path.join(setup_info['temp_root'], 'tuf_repo')
|
||||
tuf_repo = os.path.join(root_repo, 'tuf_repo')
|
||||
keystore_dir = os.path.join(tuf_repo, 'keystore')
|
||||
metadata_dir = os.path.join(tuf_repo, 'metadata')
|
||||
targets_dir = os.path.join(tuf_repo, 'targets')
|
||||
|
|
@ -301,13 +281,12 @@ def init_tuf():
|
|||
os.mkdir(tuf_repo)
|
||||
os.mkdir(keystore_dir)
|
||||
os.mkdir(metadata_dir)
|
||||
shutil.copytree(setup_info['repo'], targets_dir)
|
||||
shutil.copytree(os.path.join(root_repo, 'reg_repo'), targets_dir)
|
||||
|
||||
# Setting TUF-client directory structure.
|
||||
# 'tuf.client.updater.py' expects the 'current' and 'previous'
|
||||
# directories to exist under client's 'metadata' directory.
|
||||
setup_info['tuf_client'] = tuf_client = \
|
||||
os.path.join(setup_info['temp_root'], 'tuf_client')
|
||||
tuf_client = os.path.join(root_repo, 'tuf_client')
|
||||
tuf_client_metadata_dir = os.path.join(tuf_client, 'metadata')
|
||||
current_dir = os.path.join(tuf_client_metadata_dir, 'current')
|
||||
previous_dir = os.path.join(tuf_client_metadata_dir, 'previous')
|
||||
|
|
@ -315,8 +294,7 @@ def init_tuf():
|
|||
|
||||
# Generate at least one rsa key.
|
||||
key = signerlib.generate_and_save_rsa_key(keystore_dir, passwd)
|
||||
keyid = [key['keyid']]
|
||||
setup_info['keyid'] = keyid
|
||||
keyids = [key['keyid']]
|
||||
|
||||
# Set role info.
|
||||
info = {'keyids': [key['keyid']], 'threshold': threshold}
|
||||
|
|
@ -337,16 +315,16 @@ def init_tuf():
|
|||
conf_path = signerlib.build_config_file(metadata_dir, 365, role_info)
|
||||
|
||||
# Generate the 'root.txt' metadata file.
|
||||
signerlib.build_root_file(conf_path, keyid, metadata_dir)
|
||||
signerlib.build_root_file(conf_path, keyids, metadata_dir)
|
||||
|
||||
# Generate the 'targets.txt' metadata file.
|
||||
signerlib.build_targets_file(targets_dir, keyid, metadata_dir)
|
||||
signerlib.build_targets_file(targets_dir, keyids, metadata_dir)
|
||||
|
||||
# Generate the 'release.txt' metadata file.
|
||||
signerlib.build_release_file(keyid, metadata_dir)
|
||||
signerlib.build_release_file(keyids, metadata_dir)
|
||||
|
||||
# Generate the 'timestamp.txt' metadata file.
|
||||
signerlib.build_timestamp_file(keyid, metadata_dir)
|
||||
signerlib.build_timestamp_file(keyids, metadata_dir)
|
||||
|
||||
# Move the metadata to the client's 'current' and 'previous' directories.
|
||||
shutil.copytree(metadata_dir, current_dir)
|
||||
|
|
@ -357,100 +335,66 @@ def init_tuf():
|
|||
# Here is a mirrors dictionary that will allow a client to seek out
|
||||
# places to download the metadata and targets from.
|
||||
tuf_repo_relpath = os.path.basename(tuf_repo)
|
||||
url_prefix = setup_info['url']+tuf_repo_relpath+'/'
|
||||
setup_info['mirrors'] = {'mirror1':
|
||||
{'url_prefix': url_prefix,
|
||||
'metadata_path': 'metadata',
|
||||
'targets_path': 'targets',
|
||||
'confined_target_dirs': ['']}}
|
||||
tuf_url = url+tuf_repo_relpath
|
||||
mirrors = {'mirror1': {'url_prefix': tuf_url,
|
||||
'metadata_path': 'metadata',
|
||||
'targets_path': 'targets',
|
||||
'confined_target_dirs': ['']}}
|
||||
|
||||
# Adjusting configuration file (tuf.conf.py).
|
||||
tuf.conf.repository_directory = setup_info['tuf_client']
|
||||
tuf.conf.repository_directory = tuf_client
|
||||
|
||||
# Instantiate an updater.
|
||||
setup_info['updater'] = \
|
||||
tuf.client.updater.Updater('updater', setup_info['mirrors'])
|
||||
# In order to implement interposition we need to have a config file with
|
||||
# the following dictionary JSON-serialized.
|
||||
# tuf_url: http://localhost:port/root_repo/tuf_repo/
|
||||
interposition_dict = {"network_locations":
|
||||
{"localhost":
|
||||
{"repository_directory": tuf_client+'/',
|
||||
"repository_mirrors" :
|
||||
{"mirror1":
|
||||
{"url_prefix": tuf_url,
|
||||
"metadata_path": "metadata",
|
||||
"targets_path": "targets",
|
||||
"confined_target_dirs": [ "" ]}},
|
||||
"target_paths": [ { "(.*\\.html)": "{0}" } ]}}}
|
||||
|
||||
junk, interpose_json = tempfile.mkstemp(dir=root_repo)
|
||||
with open(interpose_json, 'wb') as fileobj:
|
||||
tuf.util.json.dump(interposition_dict, fileobj)
|
||||
|
||||
return keyids, interpose_json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def tuf_refresh_repo():
|
||||
def tuf_refresh_repo(root_repo, keyids):
|
||||
"""
|
||||
<Purpose>
|
||||
Update TUF metadata files. Call this method whenever targets files have
|
||||
changed in the 'repo'.
|
||||
changed in the 'reg_repo'.
|
||||
|
||||
"""
|
||||
|
||||
if not setup_info['tuf']:
|
||||
msg = 'TUF needs to be initialized.\n'
|
||||
sys.exist(msg)
|
||||
reg_repo = os.path.join(root_repo, 'reg_repo')
|
||||
tuf_repo = os.path.join(root_repo, 'tuf_repo')
|
||||
targets_dir = os.path.join(tuf_repo, 'targets')
|
||||
metadata_dir = os.path.join(tuf_repo, 'metadata')
|
||||
|
||||
for directory in [reg_repo, tuf_repo, targets_dir, metadata_dir]:
|
||||
if not os.path.isdir(directory):
|
||||
msg = ('Directory '+repr(directory)+' does not exist. '+
|
||||
'Verify that all directories were setup properly.')
|
||||
raise OSError(msg)
|
||||
|
||||
keyid = setup_info['keyid']
|
||||
metadata_dir = os.path.join(setup_info['tuf_repo'], 'metadata')
|
||||
targets_dir = os.path.join(setup_info['tuf_repo'], 'targets')
|
||||
shutil.rmtree(targets_dir)
|
||||
shutil.copytree(setup_info['repo'], targets_dir)
|
||||
shutil.copytree(reg_repo, targets_dir)
|
||||
|
||||
# Regenerate the 'targets.txt' metadata file.
|
||||
signerlib.build_targets_file(targets_dir, keyid, metadata_dir)
|
||||
signerlib.build_targets_file(targets_dir, keyids, metadata_dir)
|
||||
|
||||
# Regenerate the 'release.txt' metadata file.
|
||||
signerlib.build_release_file(keyid, metadata_dir)
|
||||
signerlib.build_release_file(keyids, metadata_dir)
|
||||
|
||||
# Regenerate the 'timestamp.txt' metadata file.
|
||||
signerlib.build_timestamp_file(keyid, metadata_dir)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def tuf_refresh_client_metadata():
|
||||
if not setup_info['tuf']:
|
||||
msg = 'TUF needs to be initialized.\n'
|
||||
sys.exist(msg)
|
||||
|
||||
# Update all metadata.
|
||||
setup_info['updater'].refresh()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def tuf_download_updates():
|
||||
"""
|
||||
Here it is assumed that client has already downloaded latest metadata files.
|
||||
"""
|
||||
if not setup_info['tuf']:
|
||||
msg = 'TUF needs to be initialized.\n'
|
||||
sys.exist(msg)
|
||||
|
||||
# Get the latest information on targets.
|
||||
targets = setup_info['updater'].all_targets()
|
||||
|
||||
# Create destination directory for the tuf targets.
|
||||
dest = setup_info['downloads']
|
||||
|
||||
# Determine which targets have changed or are new.
|
||||
updated_targets = \
|
||||
setup_info['updater'].updated_targets(targets, dest)
|
||||
|
||||
# Download new/changed targets and store them in the 'tuf_downloads' dir.
|
||||
for target in updated_targets:
|
||||
setup_info['updater'].download_target(target, dest)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def tuf_refresh_and_download():
|
||||
"""
|
||||
Combines tuf_refresh_repo(), tuf_refresh_client_metadata(), and
|
||||
tuf_download_updates().
|
||||
Returns 'tuf_downloads' directory.
|
||||
"""
|
||||
tuf_refresh_repo()
|
||||
tuf_refresh_client_metadata()
|
||||
tuf_download_updates()
|
||||
return setup_info['downloads']
|
||||
signerlib.build_timestamp_file(keyids, metadata_dir)
|
||||
Loading…
Reference in a new issue