@@ -336,6 +336,7 @@ impl Repository {
336
336
let expiration_enforcement = loader. expiration_enforcement . unwrap_or_default ( ) ;
337
337
let metadata_base_url = parse_url ( loader. metadata_base_url ) ?;
338
338
let targets_base_url = parse_url ( loader. targets_base_url ) ?;
339
+ let update_start = datastore. system_time ( ) . await ?;
339
340
340
341
// 0. Load the trusted root metadata file + 1. Update the root metadata file
341
342
let root = load_root (
@@ -346,6 +347,7 @@ impl Repository {
346
347
limits. max_root_updates ,
347
348
& metadata_base_url,
348
349
expiration_enforcement,
350
+ & update_start,
349
351
)
350
352
. await ?;
351
353
@@ -357,6 +359,7 @@ impl Repository {
357
359
limits. max_timestamp_size ,
358
360
& metadata_base_url,
359
361
expiration_enforcement,
362
+ & update_start,
360
363
)
361
364
. await ?;
362
365
@@ -369,6 +372,7 @@ impl Repository {
369
372
& datastore,
370
373
& metadata_base_url,
371
374
expiration_enforcement,
375
+ & update_start,
372
376
)
373
377
. await ?;
374
378
@@ -381,6 +385,7 @@ impl Repository {
381
385
limits. max_targets_size ,
382
386
& metadata_base_url,
383
387
expiration_enforcement,
388
+ & update_start,
384
389
)
385
390
. await ?;
386
391
@@ -633,9 +638,9 @@ pub(crate) fn encode_filename<S: AsRef<str>>(name: S) -> String {
633
638
634
639
/// TUF v1.0.16, 5.2.9, 5.3.3, 5.4.5, 5.5.4, The expiration timestamp in the `[metadata]` file MUST
635
640
/// 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 < ( ) > {
637
642
ensure ! (
638
- datastore . system_time ( ) . await ? <= role. expires( ) ,
643
+ * update_start <= role. expires( ) ,
639
644
error:: ExpiredMetadataSnafu { role: T :: TYPE }
640
645
) ;
641
646
Ok ( ( ) )
@@ -656,6 +661,7 @@ fn parse_url(url: Url) -> Result<Url> {
656
661
657
662
/// Steps 0 and 1 of the client application, which load the current root metadata file based on a
658
663
/// trusted root metadata file.
664
+ #[ allow( clippy:: too_many_arguments) ]
659
665
async fn load_root < R : AsRef < [ u8 ] > > (
660
666
transport : & dyn Transport ,
661
667
root : R ,
@@ -664,8 +670,9 @@ async fn load_root<R: AsRef<[u8]>>(
664
670
max_root_updates : u64 ,
665
671
metadata_base_url : & Url ,
666
672
expiration_enforcement : ExpirationEnforcement ,
673
+ update_start : & DateTime < Utc > ,
667
674
) -> 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
669
676
// shipped with the package manager or software updater using an out-of-band process. Note
670
677
// that the expiration of the trusted root metadata file does not matter, because we will
671
678
// attempt to update it in the next step.
@@ -675,7 +682,7 @@ async fn load_root<R: AsRef<[u8]>>(
675
682
. verify_role ( & root)
676
683
. context ( error:: VerifyTrustedMetadataSnafu ) ?;
677
684
678
- // Used in step 1.2
685
+ // Used in step 5.3
679
686
let original_root_version = root. signed . version . get ( ) ;
680
687
681
688
// Used in step 1.9
@@ -690,15 +697,15 @@ async fn load_root<R: AsRef<[u8]>>(
690
697
. cloned ( )
691
698
. collect :: < Vec < _ > > ( ) ;
692
699
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,
694
701
// the client must somehow be able to establish a trusted line of continuity to the latest
695
702
// set of keys. To do so, the client MUST download intermediate root metadata files, until
696
703
// the latest available one is reached. Therefore, it MUST temporarily turn on consistent
697
704
// snapshots in order to download versioned root metadata files as described next.
698
705
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.
700
707
//
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
702
709
// (because the size is unknown). The value for X is set by the authors of the
703
710
// application using TUF. For example, X may be tens of kilobytes. The filename used to
704
711
// 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]>>(
725
732
)
726
733
. await
727
734
{
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 .
729
736
Ok ( stream) => {
730
737
let data = match stream. into_vec ( ) . await {
731
738
Ok ( d) => d,
@@ -737,7 +744,7 @@ async fn load_root<R: AsRef<[u8]>>(
737
744
role : RoleType :: Root ,
738
745
} ) ?;
739
746
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
741
748
// signed by: (1) a threshold of keys specified in the trusted root metadata file
742
749
// (version N), and (2) a threshold of keys specified in the new root metadata
743
750
// 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]>>(
755
762
role : RoleType :: Root ,
756
763
} ) ?;
757
764
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).
766
770
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( ) ,
768
773
error:: OlderMetadataSnafu {
769
774
role: RoleType :: Root ,
770
775
current_version: root. signed. version,
771
776
new_version: new_root. signed. version
772
777
}
773
778
) ;
774
779
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
786
781
// not matter yet, because we will check for it in step 1.8.
787
782
//
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.
789
784
//
790
785
// (This is where version N+1 becomes version N.)
791
786
root = new_root;
792
787
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.
794
794
continue ;
795
795
}
796
796
}
797
797
}
798
798
799
- datastore. remove ( "root.json" ) ;
799
+ datastore. remove ( "root.json" ) . await ? ;
800
800
datastore. create ( "root.json" , & root) . await ?;
801
801
802
802
// TUF v1.0.16, 5.2.9. Check for a freeze attack. The expiration timestamp in the trusted root
803
803
// metadata file MUST be higher than the fixed update start time. If the trusted root metadata
804
804
// file has expired, abort the update cycle, report the potential freeze attack. On the next
805
805
// update cycle, begin at step 5.1 and version N of the root metadata file.
806
806
if expiration_enforcement == ExpirationEnforcement :: Safe {
807
- check_expired ( datastore , & root. signed ) . await ?;
807
+ check_expired ( update_start , & root. signed ) ?;
808
808
}
809
809
810
810
// 1.9. If the timestamp and / or snapshot keys have been rotated, then delete the trusted
@@ -842,6 +842,7 @@ async fn load_timestamp(
842
842
max_timestamp_size : u64 ,
843
843
metadata_base_url : & Url ,
844
844
expiration_enforcement : ExpirationEnforcement ,
845
+ update_start : & DateTime < Utc > ,
845
846
) -> Result < Signed < Timestamp > > {
846
847
// 2. Download the timestamp metadata file, up to Y number of bytes (because the size is
847
848
// unknown.) The value for Y is set by the authors of the application using TUF. For
@@ -905,7 +906,7 @@ async fn load_timestamp(
905
906
// metadata file becomes the trusted timestamp metadata file. If the new timestamp metadata file
906
907
// has expired, discard it, abort the update cycle, and report the potential freeze attack.
907
908
if expiration_enforcement == ExpirationEnforcement :: Safe {
908
- check_expired ( datastore , & timestamp. signed ) . await ?;
909
+ check_expired ( update_start , & timestamp. signed ) ?;
909
910
}
910
911
911
912
// Now that everything seems okay, write the timestamp file to the datastore.
@@ -915,7 +916,7 @@ async fn load_timestamp(
915
916
}
916
917
917
918
/// 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) ]
919
920
async fn load_snapshot (
920
921
transport : & dyn Transport ,
921
922
root : & Signed < Root > ,
@@ -924,6 +925,7 @@ async fn load_snapshot(
924
925
datastore : & Datastore ,
925
926
metadata_base_url : & Url ,
926
927
expiration_enforcement : ExpirationEnforcement ,
928
+ update_start : & DateTime < Utc > ,
927
929
) -> Result < Signed < Snapshot > > {
928
930
// 3. Download snapshot metadata file, up to the number of bytes specified in the timestamp
929
931
// metadata file. If consistent snapshots are not used (see Section 7), then the filename
@@ -1062,7 +1064,7 @@ async fn load_snapshot(
1062
1064
// metadata file becomes the trusted snapshot metadata file. If the new snapshot metadata file
1063
1065
// is expired, discard it, abort the update cycle, and report the potential freeze attack.
1064
1066
if expiration_enforcement == ExpirationEnforcement :: Safe {
1065
- check_expired ( datastore , & snapshot. signed ) . await ?;
1067
+ check_expired ( update_start , & snapshot. signed ) ?;
1066
1068
}
1067
1069
1068
1070
// Now that everything seems okay, write the snapshot file to the datastore.
@@ -1072,6 +1074,7 @@ async fn load_snapshot(
1072
1074
}
1073
1075
1074
1076
/// Step 4 of the client application, which loads the targets metadata file.
1077
+ #[ allow( clippy:: too_many_arguments) ]
1075
1078
async fn load_targets (
1076
1079
transport : & dyn Transport ,
1077
1080
root : & Signed < Root > ,
@@ -1080,6 +1083,7 @@ async fn load_targets(
1080
1083
max_targets_size : u64 ,
1081
1084
metadata_base_url : & Url ,
1082
1085
expiration_enforcement : ExpirationEnforcement ,
1086
+ update_start : & DateTime < Utc > ,
1083
1087
) -> Result < Signed < crate :: schema:: Targets > > {
1084
1088
// 4. Download the top-level targets metadata file, up to either the number of bytes specified
1085
1089
// 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(
1186
1190
// metadata file becomes the trusted targets metadata file. If the new targets metadata file is
1187
1191
// expired, discard it, abort the update cycle, and report the potential freeze attack.
1188
1192
if expiration_enforcement == ExpirationEnforcement :: Safe {
1189
- check_expired ( datastore , & targets. signed ) . await ?;
1193
+ check_expired ( update_start , & targets. signed ) ?;
1190
1194
}
1191
1195
1192
1196
// Now that everything seems okay, write the targets file to the datastore.
0 commit comments