@@ -1126,6 +1126,183 @@ def test_6_get_one_valid_targetinfo(self):
1126
1126
1127
1127
1128
1128
1129
+ def test_6_get_one_valid_targetinfo__promiscuous (self ):
1130
+ """
1131
+ Test behavior when multiple roles have delegated to the same role,
1132
+ in two scenarios.
1133
+
1134
+ In all cases, the delegation structure is:
1135
+ Targets delegates foo/* to roleA
1136
+ Targets delegates foo/* to roleB
1137
+ roleA delegates foo/bar/* to roleC
1138
+ roleB delegates foo/baz/* to roleC
1139
+ """
1140
+
1141
+ # Prepare for the two test scenarios:
1142
+
1143
+ # Modify delegations on the remote repository to test behavior in cases
1144
+ # where multiple roles delegate to one role.
1145
+ repo = repo_tool .load_repository (self .repository_directory )
1146
+
1147
+ targets_directory = os .path .join (self .repository_directory , 'targets' )
1148
+
1149
+ # Patterns for delegating the directories
1150
+ foo_pattern = '/foo/*'
1151
+ bar_pattern = '/foo/bar/*'
1152
+ baz_pattern = '/foo/baz/*'
1153
+
1154
+ os .makedirs (os .path .join (targets_directory , 'foo' , 'bar' ))
1155
+ os .makedirs (os .path .join (targets_directory , 'foo' , 'baz' ))
1156
+
1157
+ # Filepaths within repo namespace (for retrieving from repo)
1158
+ bar_target_fname = os .path .join ('foo' , 'bar' , 'a.txt' )
1159
+ baz_target_fname = os .path .join ('foo' , 'baz' , 'b.txt' )
1160
+
1161
+ # Full filepaths on-disk (for creating files and adding to repo)
1162
+ bar_target_full_fname = os .path .join (targets_directory , bar_target_fname )
1163
+ baz_target_full_fname = os .path .join (targets_directory , baz_target_fname )
1164
+
1165
+ # Create the target files that roleC will list.
1166
+ with open (bar_target_full_fname , 'w' ) as fobj :
1167
+ fobj .write ('bar' )
1168
+ with open (baz_target_full_fname , 'w' ) as fobj :
1169
+ fobj .write ('baz' )
1170
+
1171
+ # Pick some sample keys to use for the roles.
1172
+ # We'll have A expect one key from C and B expect a different key from C.
1173
+ # (pubkey_C_A being the key A expects C to be signed with, and
1174
+ # pubkey_C_B being the key B expects C to be signed with)
1175
+ # Be sure to use keys for C_A and C_B that would not be currently loaded by
1176
+ # some other role, due to a quirk in current repository_tool behavior.
1177
+ # (See Issue #646)
1178
+ pubkey_A = self .role_keys ['targets' ]['public' ]
1179
+ prikey_A = self .role_keys ['targets' ]['private' ]
1180
+ pubkey_B = self .role_keys ['targets' ]['public' ]
1181
+ prikey_B = self .role_keys ['targets' ]['private' ]
1182
+ pubkey_C_A = self .role_keys ['root' ]['public' ]
1183
+ prikey_C_A = self .role_keys ['root' ]['private' ]
1184
+ pubkey_C_B = self .role_keys ['role1' ]['public' ]
1185
+ prikey_C_B = self .role_keys ['role1' ]['private' ]
1186
+
1187
+ # Delegate
1188
+ repo .targets .delegate ('roleA' , [pubkey_A ], [foo_pattern ])
1189
+ repo .targets .delegate ('roleB' , [pubkey_B ], [foo_pattern ])
1190
+ repo .targets ('roleA' ).delegate ('roleC' , [pubkey_C_A ], [bar_pattern ])
1191
+ repo .targets ('roleB' ).delegate ('roleC' , [pubkey_C_B ], [baz_pattern ])
1192
+
1193
+ # Have C specify two targets: /foo/bar/a.txt, /foo/baz/b.txt
1194
+ repo .targets ('roleC' ).add_targets (
1195
+ [bar_target_full_fname , baz_target_full_fname ])
1196
+
1197
+
1198
+
1199
+ # SCENARIO 2
1200
+ # roleC is signed by the key that B expects of it, but not the key that
1201
+ # A expects of it.
1202
+ # We should still be able to traverse Targets->roleB->roleC and retrieve
1203
+ # target info for targets delegated through that path, but not through
1204
+ # Targets->roleA->roleC.
1205
+ # Further, the inability to validate roleC through the roleA path should
1206
+ # not prevent us from validating roleC through the roleB path.
1207
+ # See TUF Specification repo's Issue #16 for more about the latter:
1208
+ # https://github.com/theupdateframework/specification/issues/15
1209
+
1210
+ # IN PROGRESS - WIP
1211
+
1212
+ # Unload signing key that role A expects of role C.
1213
+ # Load signing keys.
1214
+ repo .targets ('roleA' ).load_signing_key (prikey_A )
1215
+ repo .targets ('roleB' ).load_signing_key (prikey_B )
1216
+ #repo.targets('roleC').load_signing_key(prikey_C_A)
1217
+ repo .targets ('roleC' ).load_signing_key (prikey_C_B )
1218
+
1219
+ # Updating these delegated role files doesn't mark timestamp or snapshot
1220
+ # dirty, so we'll have to manually write them all (or mark them all dirty).
1221
+ repo .targets .load_signing_key (self .role_keys ['targets' ]['private' ])
1222
+ repo .snapshot .load_signing_key (self .role_keys ['snapshot' ]['private' ])
1223
+ repo .timestamp .load_signing_key (self .role_keys ['timestamp' ]['private' ])
1224
+ for role in ['roleC' , 'roleB' , 'roleA' , 'targets' ,'snapshot' , 'timestamp' ]:
1225
+ repo .write (role )
1226
+
1227
+ # Move the freshly written metadata to the hosted metadata directory.
1228
+ shutil .rmtree (os .path .join (self .repository_directory , 'metadata' ))
1229
+ shutil .copytree (os .path .join (self .repository_directory , 'metadata.staged' ),
1230
+ os .path .join (self .repository_directory , 'metadata' ))
1231
+
1232
+ # Update top-level metadata on the client.
1233
+ self .repository_updater .refresh ()
1234
+
1235
+ # We should be able to find target info for the file listed by roleC in the
1236
+ # Targets->RoleB->RoleC traversal, but not the Targets->RoleA->RoleC,
1237
+ # since we're missing the signature on roleC that roleA expects, but we
1238
+ # have the signature on roleC that roleB expects.
1239
+ with self .assertRaises (tuf .exceptions .NoWorkingMirrorError ):
1240
+ self .repository_updater .get_one_valid_targetinfo (bar_target_fname )
1241
+ # TODO: Consider checking inside NoWorkingMirrorError to make sure that
1242
+ # there is a BadSignatureError('roleC') in it.
1243
+
1244
+ with self .assertRaises (tuf .exceptions .NoWorkingMirrorError ):
1245
+ self .repository_updater .get_one_valid_targetinfo ('/' + bar_target_fname )
1246
+ # TODO: Consider checking inside NoWorkingMirrorError to make sure that
1247
+ # there is a BadSignatureError('roleC') in it.
1248
+
1249
+ import ipdb ; ipdb .set_trace ()
1250
+
1251
+ self .repository_updater .get_one_valid_targetinfo (baz_target_fname )
1252
+ self .repository_updater .get_one_valid_targetinfo ('/' + baz_target_fname )
1253
+
1254
+
1255
+
1256
+
1257
+
1258
+
1259
+
1260
+ # SCENARIO 1:
1261
+ # All roles are signed by all keys expected of them.
1262
+ # (e.g. C is signed by a threshold of all keys that A and B expect of
1263
+ # it for the delegations listed)
1264
+
1265
+ # Load signing keys.
1266
+ repo .targets ('roleA' ).load_signing_key (prikey_A )
1267
+ repo .targets ('roleB' ).load_signing_key (prikey_B )
1268
+ repo .targets ('roleC' ).load_signing_key (prikey_C_A )
1269
+ repo .targets ('roleC' ).load_signing_key (prikey_C_B )
1270
+
1271
+ # Updating these delegated role files doesn't mark timestamp or snapshot
1272
+ # dirty, so we'll have to manually write them all (or mark them all dirty).
1273
+ repo .targets .load_signing_key (self .role_keys ['targets' ]['private' ])
1274
+ repo .snapshot .load_signing_key (self .role_keys ['snapshot' ]['private' ])
1275
+ repo .timestamp .load_signing_key (self .role_keys ['timestamp' ]['private' ])
1276
+ for role in ['roleC' , 'roleB' , 'roleA' , 'targets' ,'snapshot' , 'timestamp' ]:
1277
+ repo .write (role )
1278
+
1279
+ # Move the freshly written metadata to the hosted metadata directory.
1280
+ shutil .rmtree (os .path .join (self .repository_directory , 'metadata' ))
1281
+ shutil .copytree (os .path .join (self .repository_directory , 'metadata.staged' ),
1282
+ os .path .join (self .repository_directory , 'metadata' ))
1283
+
1284
+
1285
+ # Update top-level metadata on the client.
1286
+ self .repository_updater .refresh ()
1287
+
1288
+ # We should be able to find target info for the two files listed by roleC,
1289
+ # in one case traversing Targets->RoleA->RoleC, and in the other case
1290
+ # Targets->RoleB->RoleC. Both traversals should find the appropriate
1291
+ # signatures for Scenario 1.
1292
+ self .repository_updater .get_one_valid_targetinfo (bar_target_fname )
1293
+ self .repository_updater .get_one_valid_targetinfo ('/' + bar_target_fname )
1294
+ self .repository_updater .get_one_valid_targetinfo (baz_target_fname )
1295
+ self .repository_updater .get_one_valid_targetinfo ('/' + baz_target_fname )
1296
+
1297
+
1298
+
1299
+
1300
+
1301
+
1302
+
1303
+
1304
+
1305
+
1129
1306
def test_6_download_target (self ):
1130
1307
# Create temporary directory (destination directory of downloaded targets)
1131
1308
# that will be passed as an argument to 'download_target()'.
0 commit comments