@@ -217,6 +217,68 @@ def test_root_rotation(self):
217217
218218
219219
220+ def test_verify_root_with_current_keyids_and_threshold (self ):
221+ """
222+ Each root file is signed by the current root threshold of keys as well
223+ as the previous root threshold of keys. Test that a root file which is
224+ not 'self-signed' with the current root threshold of keys causes the
225+ update to fail
226+ """
227+ # Load repository with root.json == 1.root.json (available on client)
228+ # Signing key: "root", Threshold: 1
229+ repository = repo_tool .load_repository (self .repository_directory )
230+
231+ # Rotate keys and update root: 1.root.json --> 2.root.json
232+ # Signing key: "root" (previous) and "root2" (current)
233+ # Threshold (for both): 1
234+ repository .root .load_signing_key (self .role_keys ['root' ]['private' ])
235+ repository .root .add_verification_key (self .role_keys ['root2' ]['public' ])
236+ repository .root .load_signing_key (self .role_keys ['root2' ]['private' ])
237+ # Remove the previous "root" key from the list of current
238+ # verification keys
239+ repository .root .remove_verification_key (self .role_keys ['root' ]['public' ])
240+ repository .writeall ()
241+
242+ # Move staged metadata to "live" metadata
243+ shutil .rmtree (os .path .join (self .repository_directory , 'metadata' ))
244+ shutil .copytree (os .path .join (self .repository_directory , 'metadata.staged' ),
245+ os .path .join (self .repository_directory , 'metadata' ))
246+
247+ # Intercept 2.root.json and tamper with "root2" (current) key signature
248+ root2_path_live = os .path .join (
249+ self .repository_directory , 'metadata' , '2.root.json' )
250+ root2 = securesystemslib .util .load_json_file (root2_path_live )
251+
252+ for idx , sig in enumerate (root2 ['signatures' ]):
253+ if sig ['keyid' ] == self .role_keys ['root2' ]['public' ]['keyid' ]:
254+ sig_len = len (root2 ['signatures' ][idx ]['sig' ])
255+ root2 ['signatures' ][idx ]['sig' ] = "deadbeef" .ljust (sig_len , '0' )
256+
257+ roo2_fobj = tempfile .TemporaryFile ()
258+ roo2_fobj .write (tuf .repository_lib ._get_written_metadata (root2 ))
259+ securesystemslib .util .persist_temp_file (roo2_fobj , root2_path_live )
260+
261+ # Update 1.root.json -> 2.root.json
262+ # Signature verification with current keys should fail because we replaced
263+ with self .assertRaises (tuf .exceptions .NoWorkingMirrorError ) as cm :
264+ self .repository_updater .refresh ()
265+
266+ for mirror_url , mirror_error in six .iteritems (cm .exception .mirror_errors ):
267+ self .assertTrue (mirror_url .endswith ('/2.root.json' ))
268+ self .assertTrue (isinstance (mirror_error ,
269+ securesystemslib .exceptions .BadSignatureError ))
270+
271+ # Assert that the current 'root.json' on the client side is the verified one
272+ self .assertTrue (filecmp .cmp (
273+ os .path .join (self .repository_directory , 'metadata' , '1.root.json' ),
274+ os .path .join (self .client_metadata_current , 'root.json' )))
275+
276+
277+
278+
279+
280+
281+
220282 def test_root_rotation_full (self ):
221283 """Test that a client whose root is outdated by multiple versions and who
222284 has none of the latest nor next-to-latest root keys can still update and
@@ -340,22 +402,26 @@ def test_root_rotation_missing_keys(self):
340402 shutil .copytree (os .path .join (self .repository_directory , 'metadata.staged' ),
341403 os .path .join (self .repository_directory , 'metadata' ))
342404
343- try :
405+ with self . assertRaises ( tuf . exceptions . NoWorkingMirrorError ) as cm :
344406 self .repository_updater .refresh ()
345407
346- except tuf .exceptions .NoWorkingMirrorError as exception :
347- for mirror_url , mirror_error in six .iteritems (exception .mirror_errors ):
348- url_prefix = self .repository_mirrors ['mirror1' ]['url_prefix' ]
349- url_file = os .path .join (url_prefix , 'metadata' , '2.root.json' )
350-
351- # Verify that '2.root.json' is the culprit.
352- self .assertEqual (url_file .replace ('\\ ' , '/' ), mirror_url )
353- self .assertTrue (isinstance (mirror_error ,
408+ for mirror_url , mirror_error in six .iteritems (cm .exception .mirror_errors ):
409+ self .assertTrue (mirror_url .endswith ('/2.root.json' ))
410+ self .assertTrue (isinstance (mirror_error ,
354411 securesystemslib .exceptions .BadSignatureError ))
355412
413+ # Assert that the current 'root.json' on the client side is the verified one
414+ self .assertTrue (filecmp .cmp (
415+ os .path .join (self .repository_directory , 'metadata' , '1.root.json' ),
416+ os .path .join (self .client_metadata_current , 'root.json' )))
417+
418+
356419
357420
358- def test_root_rotation_unmet_threshold (self ):
421+ def test_root_rotation_unmet_last_version_threshold (self ):
422+ """Test that client detects a root.json version that is not signed
423+ by a previous threshold of signatures """
424+
359425 repository = repo_tool .load_repository (self .repository_directory )
360426
361427 # Add verification keys
@@ -411,8 +477,100 @@ def test_root_rotation_unmet_threshold(self):
411477
412478 # The following refresh should fail because root must be signed by the
413479 # previous self.role_keys['root'] key, which wasn't loaded.
414- self .assertRaises (tuf .exceptions .NoWorkingMirrorError ,
415- self .repository_updater .refresh )
480+ with self .assertRaises (tuf .exceptions .NoWorkingMirrorError ) as cm :
481+ self .repository_updater .refresh ()
482+
483+ for mirror_url , mirror_error in six .iteritems (cm .exception .mirror_errors ):
484+ self .assertTrue (mirror_url .endswith ('/3.root.json' ))
485+ self .assertTrue (isinstance (mirror_error ,
486+ securesystemslib .exceptions .BadSignatureError ))
487+
488+ # Assert that the current 'root.json' on the client side is the verified one
489+ self .assertTrue (filecmp .cmp (
490+ os .path .join (self .repository_directory , 'metadata' , '2.root.json' ),
491+ os .path .join (self .client_metadata_current , 'root.json' )))
492+
493+
494+
495+ def test_root_rotation_unmet_new_threshold (self ):
496+ """Test that client detects a root.json version that is not signed
497+ by a current threshold of signatures """
498+ repository = repo_tool .load_repository (self .repository_directory )
499+
500+ # Create a new, valid root.json.
501+ repository .root .threshold = 2
502+ repository .root .load_signing_key (self .role_keys ['root' ]['private' ])
503+ repository .root .add_verification_key (self .role_keys ['root2' ]['public' ])
504+ repository .root .load_signing_key (self .role_keys ['root2' ]['private' ])
505+
506+ repository .writeall ()
507+
508+ # Increase the threshold and add a new verification key without
509+ # actually loading the signing key
510+ repository .root .threshold = 3
511+ repository .root .add_verification_key (self .role_keys ['root3' ]['public' ])
512+
513+ # writeall fails as expected since the third signature is missing
514+ self .assertRaises (tuf .exceptions .UnsignedMetadataError , repository .writeall )
515+ # write an invalid '3.root.json' as partially signed
516+ repository .write ('root' )
517+
518+ # Move the staged metadata to the "live" metadata.
519+ shutil .rmtree (os .path .join (self .repository_directory , 'metadata' ))
520+ shutil .copytree (os .path .join (self .repository_directory , 'metadata.staged' ),
521+ os .path .join (self .repository_directory , 'metadata' ))
522+
523+
524+ # The following refresh should fail because root must be signed by the
525+ # current self.role_keys['root3'] key, which wasn't loaded.
526+ with self .assertRaises (tuf .exceptions .NoWorkingMirrorError ) as cm :
527+ self .repository_updater .refresh ()
528+
529+ for mirror_url , mirror_error in six .iteritems (cm .exception .mirror_errors ):
530+ self .assertTrue (mirror_url .endswith ('/3.root.json' ))
531+ self .assertTrue (isinstance (mirror_error ,
532+ securesystemslib .exceptions .BadSignatureError ))
533+
534+ # Assert that the current 'root.json' on the client side is the verified one
535+ self .assertTrue (filecmp .cmp (
536+ os .path .join (self .repository_directory , 'metadata' , '2.root.json' ),
537+ os .path .join (self .client_metadata_current , 'root.json' )))
538+
539+
540+
541+ def test_root_rotation_discard_untrusted_version (self ):
542+ """Test that client discards root.json version that failed the
543+ signature verification """
544+ repository = repo_tool .load_repository (self .repository_directory )
545+
546+ # Rotate the root key without signing with the previous version key 'root'
547+ repository .root .remove_verification_key (self .role_keys ['root' ]['public' ])
548+ repository .root .add_verification_key (self .role_keys ['root2' ]['public' ])
549+ repository .root .load_signing_key (self .role_keys ['root2' ]['private' ])
550+
551+ # 2.root.json
552+ repository .writeall ()
553+
554+ # Move the staged metadata to the "live" metadata.
555+ shutil .rmtree (os .path .join (self .repository_directory , 'metadata' ))
556+ shutil .copytree (os .path .join (self .repository_directory , 'metadata.staged' ),
557+ os .path .join (self .repository_directory , 'metadata' ))
558+
559+ # Refresh on the client side should fail because 2.root.json is not signed
560+ # with a threshold of prevous keys
561+ with self .assertRaises (tuf .exceptions .NoWorkingMirrorError ) as cm :
562+ self .repository_updater .refresh ()
563+
564+ for mirror_url , mirror_error in six .iteritems (cm .exception .mirror_errors ):
565+ self .assertTrue (mirror_url .endswith ('/2.root.json' ))
566+ self .assertTrue (isinstance (mirror_error ,
567+ securesystemslib .exceptions .BadSignatureError ))
568+
569+ # Assert that the current 'root.json' on the client side is the trusted one
570+ # and 2.root.json is discarded
571+ self .assertTrue (filecmp .cmp (
572+ os .path .join (self .repository_directory , 'metadata' , '1.root.json' ),
573+ os .path .join (self .client_metadata_current , 'root.json' )))
416574
417575
418576
0 commit comments