@@ -336,6 +336,7 @@ impl Repository {
336336 let expiration_enforcement = loader. expiration_enforcement . unwrap_or_default ( ) ;
337337 let metadata_base_url = parse_url ( loader. metadata_base_url ) ?;
338338 let targets_base_url = parse_url ( loader. targets_base_url ) ?;
339+ let update_start = datastore. system_time ( ) . await ?;
339340
340341 // 0. Load the trusted root metadata file + 1. Update the root metadata file
341342 let root = load_root (
@@ -346,6 +347,7 @@ impl Repository {
346347 limits. max_root_updates ,
347348 & metadata_base_url,
348349 expiration_enforcement,
350+ & update_start,
349351 )
350352 . await ?;
351353
@@ -357,6 +359,7 @@ impl Repository {
357359 limits. max_timestamp_size ,
358360 & metadata_base_url,
359361 expiration_enforcement,
362+ & update_start,
360363 )
361364 . await ?;
362365
@@ -369,6 +372,7 @@ impl Repository {
369372 & datastore,
370373 & metadata_base_url,
371374 expiration_enforcement,
375+ & update_start,
372376 )
373377 . await ?;
374378
@@ -381,6 +385,7 @@ impl Repository {
381385 limits. max_targets_size ,
382386 & metadata_base_url,
383387 expiration_enforcement,
388+ & update_start,
384389 )
385390 . await ?;
386391
@@ -633,9 +638,9 @@ pub(crate) fn encode_filename<S: AsRef<str>>(name: S) -> String {
633638
634639/// TUF v1.0.16, 5.2.9, 5.3.3, 5.4.5, 5.5.4, The expiration timestamp in the `[metadata]` file MUST
635640/// be higher than the fixed update start time.
636- async fn check_expired < T : Role > ( datastore : & Datastore , role : & T ) -> Result < ( ) > {
641+ fn check_expired < T : Role > ( update_start : & DateTime < Utc > , role : & T ) -> Result < ( ) > {
637642 ensure ! (
638- datastore . system_time ( ) . await ? <= role. expires( ) ,
643+ * update_start <= role. expires( ) ,
639644 error:: ExpiredMetadataSnafu { role: T :: TYPE }
640645 ) ;
641646 Ok ( ( ) )
@@ -656,6 +661,7 @@ fn parse_url(url: Url) -> Result<Url> {
656661
657662/// Steps 0 and 1 of the client application, which load the current root metadata file based on a
658663/// trusted root metadata file.
664+ #[ allow( clippy:: too_many_arguments) ]
659665async fn load_root < R : AsRef < [ u8 ] > > (
660666 transport : & dyn Transport ,
661667 root : R ,
@@ -664,8 +670,9 @@ async fn load_root<R: AsRef<[u8]>>(
664670 max_root_updates : u64 ,
665671 metadata_base_url : & Url ,
666672 expiration_enforcement : ExpirationEnforcement ,
673+ update_start : & DateTime < Utc > ,
667674) -> Result < Signed < Root > > {
668- // 0 . Load the trusted root metadata file. We assume that a good, trusted copy of this file was
675+ // 5.2 . Load the trusted root metadata file. We assume that a good, trusted copy of this file was
669676 // shipped with the package manager or software updater using an out-of-band process. Note
670677 // that the expiration of the trusted root metadata file does not matter, because we will
671678 // attempt to update it in the next step.
@@ -675,7 +682,7 @@ async fn load_root<R: AsRef<[u8]>>(
675682 . verify_role ( & root)
676683 . context ( error:: VerifyTrustedMetadataSnafu ) ?;
677684
678- // Used in step 1.2
685+ // Used in step 5.3
679686 let original_root_version = root. signed . version . get ( ) ;
680687
681688 // Used in step 1.9
@@ -690,15 +697,15 @@ async fn load_root<R: AsRef<[u8]>>(
690697 . cloned ( )
691698 . collect :: < Vec < _ > > ( ) ;
692699
693- // 1 . Update the root metadata file. Since it may now be signed using entirely different keys,
700+ // 5.3 . Update the root metadata file. Since it may now be signed using entirely different keys,
694701 // the client must somehow be able to establish a trusted line of continuity to the latest
695702 // set of keys. To do so, the client MUST download intermediate root metadata files, until
696703 // the latest available one is reached. Therefore, it MUST temporarily turn on consistent
697704 // snapshots in order to download versioned root metadata files as described next.
698705 loop {
699- // 1.1 . Let N denote the version number of the trusted root metadata file.
706+ // 5.3.2 . Let N denote the version number of the trusted root metadata file.
700707 //
701- // 1.2 . Try downloading version N+1 of the root metadata file, up to some X number of bytes
708+ // 5.3.3 . Try downloading version N+1 of the root metadata file, up to some X number of bytes
702709 // (because the size is unknown). The value for X is set by the authors of the
703710 // application using TUF. For example, X may be tens of kilobytes. The filename used to
704711 // download the root metadata file is of the fixed form VERSION_NUMBER.FILENAME.EXT
@@ -725,7 +732,7 @@ async fn load_root<R: AsRef<[u8]>>(
725732 )
726733 . await
727734 {
728- Err ( _) => break , // If this file is not available, then go to step 1.8 .
735+ Err ( _) => break , // If this file is not available, then go to step 5.3.10 .
729736 Ok ( stream) => {
730737 let data = match stream. into_vec ( ) . await {
731738 Ok ( d) => d,
@@ -737,7 +744,7 @@ async fn load_root<R: AsRef<[u8]>>(
737744 role : RoleType :: Root ,
738745 } ) ?;
739746
740- // 1.3 . Check signatures. Version N+1 of the root metadata file MUST have been
747+ // 5.3.4 . Check signatures. Version N+1 of the root metadata file MUST have been
741748 // signed by: (1) a threshold of keys specified in the trusted root metadata file
742749 // (version N), and (2) a threshold of keys specified in the new root metadata
743750 // file being validated (version N+1). If version N+1 is not signed as required,
@@ -755,56 +762,49 @@ async fn load_root<R: AsRef<[u8]>>(
755762 role : RoleType :: Root ,
756763 } ) ?;
757764
758- // 1.4. Check for a rollback attack. The version number of the trusted root
759- // metadata file (version N) must be less than or equal to the version number of
760- // the new root metadata file (version N+1). Effectively, this means checking
761- // that the version number signed in the new root metadata file is indeed N+1. If
762- // the version of the new root metadata file is less than the trusted metadata
763- // file, discard it, abort the update cycle, and report the rollback attack. On
764- // the next update cycle, begin at step 0 and version N of the root metadata
765- // file.
765+ // 5.3.5. Check for a rollback attack. The version number of the new root
766+ // metadata (version N+1) MUST be exactly the version in the trusted root
767+ // metadata (version N) incremented by one, that is precisely N+1.
768+ // off-spec: protect the comparison against u64 overflow (if N < new value,
769+ // N+1 will not overflow).
766770 ensure ! (
767- root. signed. version <= new_root. signed. version,
771+ root. signed. version < new_root. signed. version
772+ && root. signed. version. get( ) + 1 == new_root. signed. version. get( ) ,
768773 error:: OlderMetadataSnafu {
769774 role: RoleType :: Root ,
770775 current_version: root. signed. version,
771776 new_version: new_root. signed. version
772777 }
773778 ) ;
774779
775- // Off-spec: 1.4 specifies that the version number of the trusted root metadata
776- // file must be less than or equal to the version number of the new root metadata
777- // file. If they are equal, this will create an infinite loop, so we ignore the new
778- // root metadata file but do not report an error. This could only happen if the
779- // path we built above, referencing N+1, has a filename that doesn't match its
780- // contents, which would have to list version N.
781- if root. signed . version == new_root. signed . version {
782- break ;
783- }
784-
785- // 1.5. Note that the expiration of the new (intermediate) root metadata file does
780+ // 5.3.6. Note that the expiration of the new (intermediate) root metadata file does
786781 // not matter yet, because we will check for it in step 1.8.
787782 //
788- // 1.6 . Set the trusted root metadata file to the new root metadata file.
783+ // 5.3.7 . Set the trusted root metadata file to the new root metadata file.
789784 //
790785 // (This is where version N+1 becomes version N.)
791786 root = new_root;
792787
793- // 1.7. Repeat steps 1.1 to 1.7.
788+ // 5.3.8. Persist root metadata. The client MUST write the file to non-volatile storage
789+ // as FILENAME.EXT (e.g. root.json).
790+ datastore. remove ( "root.json" ) . await ?;
791+ datastore. create ( "root.json" , & root) . await ?;
792+
793+ // 5.3.9. Repeat 5.3.2 through 5.3.9.
794794 continue ;
795795 }
796796 }
797797 }
798798
799- datastore. remove ( "root.json" ) ;
799+ datastore. remove ( "root.json" ) . await ? ;
800800 datastore. create ( "root.json" , & root) . await ?;
801801
802802 // TUF v1.0.16, 5.2.9. Check for a freeze attack. The expiration timestamp in the trusted root
803803 // metadata file MUST be higher than the fixed update start time. If the trusted root metadata
804804 // file has expired, abort the update cycle, report the potential freeze attack. On the next
805805 // update cycle, begin at step 5.1 and version N of the root metadata file.
806806 if expiration_enforcement == ExpirationEnforcement :: Safe {
807- check_expired ( datastore , & root. signed ) . await ?;
807+ check_expired ( update_start , & root. signed ) ?;
808808 }
809809
810810 // 1.9. If the timestamp and / or snapshot keys have been rotated, then delete the trusted
@@ -842,6 +842,7 @@ async fn load_timestamp(
842842 max_timestamp_size : u64 ,
843843 metadata_base_url : & Url ,
844844 expiration_enforcement : ExpirationEnforcement ,
845+ update_start : & DateTime < Utc > ,
845846) -> Result < Signed < Timestamp > > {
846847 // 2. Download the timestamp metadata file, up to Y number of bytes (because the size is
847848 // unknown.) The value for Y is set by the authors of the application using TUF. For
@@ -905,7 +906,7 @@ async fn load_timestamp(
905906 // metadata file becomes the trusted timestamp metadata file. If the new timestamp metadata file
906907 // has expired, discard it, abort the update cycle, and report the potential freeze attack.
907908 if expiration_enforcement == ExpirationEnforcement :: Safe {
908- check_expired ( datastore , & timestamp. signed ) . await ?;
909+ check_expired ( update_start , & timestamp. signed ) ?;
909910 }
910911
911912 // Now that everything seems okay, write the timestamp file to the datastore.
@@ -915,7 +916,7 @@ async fn load_timestamp(
915916}
916917
917918/// Step 3 of the client application, which loads the snapshot metadata file.
918- #[ allow( clippy:: too_many_lines) ]
919+ #[ allow( clippy:: too_many_arguments , clippy :: too_many_lines) ]
919920async fn load_snapshot (
920921 transport : & dyn Transport ,
921922 root : & Signed < Root > ,
@@ -924,6 +925,7 @@ async fn load_snapshot(
924925 datastore : & Datastore ,
925926 metadata_base_url : & Url ,
926927 expiration_enforcement : ExpirationEnforcement ,
928+ update_start : & DateTime < Utc > ,
927929) -> Result < Signed < Snapshot > > {
928930 // 3. Download snapshot metadata file, up to the number of bytes specified in the timestamp
929931 // metadata file. If consistent snapshots are not used (see Section 7), then the filename
@@ -1062,7 +1064,7 @@ async fn load_snapshot(
10621064 // metadata file becomes the trusted snapshot metadata file. If the new snapshot metadata file
10631065 // is expired, discard it, abort the update cycle, and report the potential freeze attack.
10641066 if expiration_enforcement == ExpirationEnforcement :: Safe {
1065- check_expired ( datastore , & snapshot. signed ) . await ?;
1067+ check_expired ( update_start , & snapshot. signed ) ?;
10661068 }
10671069
10681070 // Now that everything seems okay, write the snapshot file to the datastore.
@@ -1072,6 +1074,7 @@ async fn load_snapshot(
10721074}
10731075
10741076/// Step 4 of the client application, which loads the targets metadata file.
1077+ #[ allow( clippy:: too_many_arguments) ]
10751078async fn load_targets (
10761079 transport : & dyn Transport ,
10771080 root : & Signed < Root > ,
@@ -1080,6 +1083,7 @@ async fn load_targets(
10801083 max_targets_size : u64 ,
10811084 metadata_base_url : & Url ,
10821085 expiration_enforcement : ExpirationEnforcement ,
1086+ update_start : & DateTime < Utc > ,
10831087) -> Result < Signed < crate :: schema:: Targets > > {
10841088 // 4. Download the top-level targets metadata file, up to either the number of bytes specified
10851089 // in the snapshot metadata file, or some Z number of bytes. The value for Z is set by the
@@ -1186,7 +1190,7 @@ async fn load_targets(
11861190 // metadata file becomes the trusted targets metadata file. If the new targets metadata file is
11871191 // expired, discard it, abort the update cycle, and report the potential freeze attack.
11881192 if expiration_enforcement == ExpirationEnforcement :: Safe {
1189- check_expired ( datastore , & targets. signed ) . await ?;
1193+ check_expired ( update_start , & targets. signed ) ?;
11901194 }
11911195
11921196 // Now that everything seems okay, write the targets file to the datastore.
0 commit comments