1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
import copy
import json
from unittest.mock import MagicMock, patch
import os
import pytest
import random
import tempfile
from UM.CentralFileStorage import CentralFileStorage
from UM.Trust import TrustBasics, Trust
from scripts.signfile import signFile
from scripts.signfolder import signFolder
_folder_names = ["signed", "unsigned", "large"]
_subfolder_names = ["sub", "."]
_file_names = ["x.txt", "y.txt", "z.txt"]
_passphrase = "swordfish" # For code coverage: Securely storing a private key without one is probably better.
class TestTrust:
# NOTE: Exhaustively testing trust is going to be difficult. We rely on audits (as well) in this matter.
@pytest.fixture()
def init_trust(self):
# Create a temporary directory and save a test key-pair to it:
temp_dir = tempfile.TemporaryDirectory()
temp_path = temp_dir.name
private_key, public_key = TrustBasics.generateNewKeyPair()
private_path = os.path.join(temp_path, "test_private_key.pem")
public_path = os.path.join(temp_path, "test_public_key.pem")
TrustBasics.saveKeyPair(private_key, private_path, public_path, _passphrase)
# Create random files:
all_paths = [os.path.abspath(os.path.join(temp_path, x, y, z))
for x in _folder_names for y in _subfolder_names for z in _file_names]
for path in all_paths:
folder_path = os.path.dirname(path)
if not os.path.exists(folder_path):
os.makedirs(folder_path)
with open(path, "w") as file:
file.write("".join(random.choice(['a', 'b', 'c', '0', '1', '2', '\n']) for _ in range(1024)))
# Set up mocked Central File Storage & plugin file (don't move the files yet, though):
CentralFileStorage.setIsEnterprise(True)
central_storage_dir = tempfile.TemporaryDirectory()
central_storage_path = central_storage_dir.name
large_plugin_path = os.path.join(temp_path, _folder_names[2])
store_folder = os.path.join(large_plugin_path, _subfolder_names[0])
store_file = os.path.join(large_plugin_path, _file_names[2])
central_storage_dict = [
[f"{_subfolder_names[0]}", f"{_subfolder_names[0]}", "1.0.0", CentralFileStorage._hashItem(store_folder)],
[f"{_file_names[2]}", f"{_file_names[2]}", "1.0.0", CentralFileStorage._hashItem(store_file)]
]
central_storage_file_path = os.path.join(large_plugin_path, TrustBasics.getCentralStorageFilename())
with open(central_storage_file_path, "w") as file:
json.dump(central_storage_dict, file, indent = 2)
# Instantiate a trust object with the public key that was just generated:
violation_callback = MagicMock()
trust = Trust(public_path) # No '.getInstance', since key & handler provided.
trust._violation_handler = violation_callback
yield temp_path, private_path, trust, violation_callback, central_storage_path
temp_dir.cleanup()
central_storage_dir.cleanup()
CentralFileStorage.setIsEnterprise(False)
def test_signFileAndVerify(self, init_trust):
temp_dir, private_path, trust_instance, violation_callback, _ = init_trust
filepath_signed = os.path.join(temp_dir, _folder_names[0], _subfolder_names[0], _file_names[0])
filepath_unsigned = os.path.join(temp_dir, _folder_names[1], _subfolder_names[0], _file_names[2])
# Attempt to sign a file.
assert signFile(private_path, filepath_signed, _passphrase)
# Check if we're able to verify the file we just signed.
assert trust_instance.signedFileCheck(filepath_signed)
assert violation_callback.call_count == 0 # No violation
# Check if the file we didn't sign notifies us about this.
assert not trust_instance.signedFileCheck(filepath_unsigned)
assert violation_callback.call_count == 1
# An unknown file is also seen as an invalid one.
assert not trust_instance.signedFileCheck("file-not-found-check")
assert violation_callback.call_count == 2
# The signing should fail if we disable the key (since we can't confirm anything)
public_key = copy.copy(trust_instance._public_key)
trust_instance._public_key = None
assert not trust_instance.signedFileCheck(filepath_signed)
assert violation_callback.call_count == 3
violation_callback.reset_mock()
trust_instance._public_key = public_key
# Oh noes! Someone changed the file!
with open(filepath_signed, "w") as file:
file.write("\nPay 10 Golden Talents To Get Your Data Back Or Else\n")
assert not trust_instance.signedFileCheck(filepath_signed)
assert violation_callback.call_count > 0
violation_callback.reset_mock()
def test_signFolderAndPreStorageCheck(self, init_trust):
temp_dir, private_path, trust_instance, violation_callback, central_storage_dir = init_trust
folderpath_signed = os.path.join(temp_dir, _folder_names[2])
folderpath_without_storage = os.path.join(temp_dir, _folder_names[0])
# Note that we don't do anything with the storage here yet, after all, this is a _pre_-storage check.
# Verify completely unsigned folder should fail:
assert not trust_instance.signedFolderPreStorageCheck(folderpath_signed)
assert violation_callback.call_count == 1
violation_callback.reset_mock()
# Signing it, then verify, should succeed:
assert signFolder(private_path, folderpath_signed, [], _passphrase)
assert trust_instance.signedFolderPreStorageCheck(folderpath_signed)
# A folder without a central storage file should just pass, no matter what:
assert trust_instance.signedFolderPreStorageCheck(folderpath_without_storage)
# From here on out, make sure we're testing the other part of that functionality (prevent early out):
trust_instance._verifyFile = MagicMock(return_value = True)
trust_instance._verifyManifestIntegrety = MagicMock(return_value=True)
# Overwrite the central storage dictionary with files moved to an arbitrary location (then fail the check):
central_storage_dict = [["/root/.importantfile", "/home/eve", "1.0.0", "dummy"]]
central_storage_file_path = os.path.join(folderpath_signed, TrustBasics.getCentralStorageFilename())
with open(central_storage_file_path, "w") as file:
json.dump(central_storage_dict, file, indent=2)
assert not trust_instance.signedFolderPreStorageCheck(folderpath_signed)
# Overwrite the central storage dictionary with files-to-move outside of the storage area (then fail the check):
central_storage_dict = [["signatures.json", "../../signatures.json", "1.0.0", "dummy"]]
central_storage_file_path = os.path.join(folderpath_signed, TrustBasics.getCentralStorageFilename())
with open(central_storage_file_path, "w") as file:
json.dump(central_storage_dict, file, indent=2)
assert not trust_instance.signedFolderPreStorageCheck(folderpath_signed)
def test_signFolderAndVerify(self, init_trust):
temp_dir, private_path, trust_instance, violation_callback, central_storage_dir = init_trust
folderpath_signed = os.path.join(temp_dir, _folder_names[0])
folderpath_unsigned = os.path.join(temp_dir, _folder_names[1])
folderpath_large = os.path.join(temp_dir, _folder_names[2])
# Attempt to sign a folder & validate it's signatures.
assert signFolder(private_path, folderpath_signed, [], _passphrase)
assert trust_instance.signedFolderCheck(folderpath_signed)
# A folder that is not signed should be seen as such
assert not trust_instance.signedFolderCheck(folderpath_unsigned)
assert violation_callback.call_count == 1
violation_callback.reset_mock()
# Unknown folders should also be seen as unsigned
assert not trust_instance.signedFileCheck("folder-not-found-check")
assert violation_callback.call_count == 1
violation_callback.reset_mock()
# After removing the key, the folder that was signed should be seen as unsigned.
public_key = copy.copy(trust_instance._public_key)
trust_instance._public_key = None
assert not trust_instance.signedFolderCheck(folderpath_signed)
assert violation_callback.call_count == 1
violation_callback.reset_mock()
trust_instance._public_key = public_key
# Hecking around with the signature file should also be discouraged.
signatures_file = os.path.join(folderpath_signed, TrustBasics.getSignaturesLocalFilename())
with open(signatures_file, "r", encoding = "utf-8") as sigfile:
sig_json = json.load(sigfile)
restore_json = copy.copy(sig_json)
sig_json[TrustBasics.getRootSignedManifestKey()] = "HAHAHAHA"
os.remove(signatures_file)
with open(signatures_file, "w", encoding = "utf-8") as sigfile:
json.dump(sig_json, sigfile, indent = 2)
assert not trust_instance.signedFolderCheck(folderpath_signed)
assert violation_callback.call_count > 0
violation_callback.reset_mock()
os.remove(signatures_file)
with open(signatures_file, "w", encoding = "utf-8") as sigfile:
json.dump(restore_json, sigfile, indent = 2)
# Any modification should also invalidate it.
filepath = os.path.join(folderpath_signed, _subfolder_names[0], _file_names[1])
with open(filepath, "w") as file:
file.write("\nAlice and Bob will never notice this! Hehehehe.\n")
assert not trust_instance.signedFolderCheck(folderpath_signed)
assert violation_callback.call_count > 0
violation_callback.reset_mock()
# Any missing files should also be registered.
os.remove(filepath)
assert not trust_instance.signedFolderCheck(folderpath_signed)
assert violation_callback.call_count == 1
violation_callback.reset_mock()
# * 'Central file storage'-enabled section *
with patch("UM.CentralFileStorage.CentralFileStorage.getCentralStorageLocation", MagicMock(return_value = central_storage_dir)):
# Do some set-up (signing, moving files around with the central file storage):
assert signFolder(private_path, folderpath_large, [], _passphrase)
assert violation_callback.call_count == 0
subfolder_path = os.path.join(folderpath_large, _subfolder_names[0])
file_path = os.path.join(folderpath_large, _file_names[2])
stored_file_path = os.path.join(central_storage_dir, _file_names[2] + ".1.0.0")
CentralFileStorage.store(subfolder_path, _subfolder_names[0], "1.0.0", True)
CentralFileStorage.store(file_path, _file_names[2], "1.0.0", True)
# Should pass signed folder check, even though files have moved to central storage:
assert trust_instance.signedFolderCheck(folderpath_large)
assert violation_callback.call_count == 0
# Should not pass the signed folder check if one of the files doesn't have the right hash in storage.
with open(stored_file_path, "w") as file:
file.write("\nWhoopsadoodle, the file just changed suddenly.\n")
assert not trust_instance.signedFolderCheck(folderpath_large)
assert violation_callback.call_count > 0
violation_callback.reset_mock()
# Should not pass the signed folder check if one of the files is missing, even in storage.
os.remove(stored_file_path)
assert not trust_instance.signedFolderCheck(folderpath_large)
assert violation_callback.call_count > 0
violation_callback.reset_mock()
def test_initTrustFail(self):
with pytest.raises(Exception):
Trust("key-not-found")
with pytest.raises(Exception):
Trust.getInstance()
assert Trust.getInstanceOrNone() is None
def test_keyIOFails(self):
private_key, public_key = TrustBasics.generateNewKeyPair()
assert not TrustBasics.saveKeyPair(private_key, public_key, "file-not-found", _passphrase)
assert TrustBasics.loadPrivateKey("key-not-found", _passphrase) is None
def test_signNonexisting(self):
private_key, public_key = TrustBasics.generateNewKeyPair()
assert TrustBasics.getFileSignature("file-not-found", private_key) is None
@pytest.mark.parametrize("location,subfolder", [
(r"/a/b/c", r"/a/b/c/d"),
(r"/a/b/c", r"/a/b/c/d/.."),
(r"/a/b/c", r"/a/b/../b/c/d/../e"),
(r"/a/b/../d/c", r"/a/d/c")
])
def test_isPathInLocation(self, location, subfolder):
assert TrustBasics.isPathInLocation(location, subfolder)
@pytest.mark.parametrize("location,subfolder", [
(r"/a/b/c", r"/a/b/c/d/../.."),
(r"/a/b/c", r"/a/b"),
(r"/a/b/c", r"/d/q/f"),
(r"/a/b/../d/c", r"/a/d/c.txt"),
(r"/a/b/../d/c", r"/a/b/../b/c/d/../e"),
(r"/a/b/../d/c.txt", r"/a/d/c")
])
def test_notIsPathInLocation(self, location, subfolder):
assert not TrustBasics.isPathInLocation(location, subfolder)
|