36
36
import java .util .List ;
37
37
import java .util .Locale ;
38
38
import java .util .Map ;
39
+ import java .util .Objects ;
39
40
import java .util .Optional ;
40
41
import java .util .logging .Level ;
41
42
import java .util .logging .Logger ;
@@ -57,13 +58,17 @@ public class Updater {
57
58
58
59
private static final Logger log = Logger .getLogger (Updater .class .getName ());
59
60
60
- private Clock clock ;
61
- private Verifiers .Supplier verifiers ;
62
- private MetaFetcher metaFetcher ;
63
- private Fetcher targetFetcher ;
61
+ private final Clock clock ;
62
+ private final Verifiers .Supplier verifiers ;
63
+ private final MetaFetcher metaFetcher ;
64
+ private final Fetcher targetFetcher ;
65
+ private final RootProvider trustedRootPath ;
66
+ // TODO: this should be replaced by a dedicated target store
67
+ private final MutableTufStore localStore ;
68
+
69
+ // Mutable State
64
70
private ZonedDateTime updateStartTime ;
65
- private RootProvider trustedRootPath ;
66
- private MutableTufStore localStore ;
71
+ private TrustedMeta trustedMeta ;
67
72
68
73
Updater (
69
74
Clock clock ,
@@ -78,6 +83,7 @@ public class Updater {
78
83
this .localStore = localStore ;
79
84
this .metaFetcher = metaFetcher ;
80
85
this .targetFetcher = targetFetcher ;
86
+ this .trustedMeta = TrustedMeta .newTrustedMeta (localStore );
81
87
}
82
88
83
89
public static Builder builder () {
@@ -86,28 +92,36 @@ public static Builder builder() {
86
92
87
93
public void update ()
88
94
throws IOException , NoSuchAlgorithmException , InvalidKeySpecException , InvalidKeyException {
89
- var root = updateRoot ();
90
- // only returns a timestamp value if a more recent timestamp file has been found.
91
- var timestampMaybe = updateTimestamp (root );
92
- if (timestampMaybe .isPresent ()) {
93
- var snapshot = updateSnapshot (root , timestampMaybe .get ());
94
- var targets = updateTargets (root , snapshot );
95
- downloadTargets (targets );
95
+ updateMeta ();
96
+ downloadTargets (trustedMeta .getTargets ());
97
+ }
98
+
99
+ void updateMeta () throws IOException , NoSuchAlgorithmException , InvalidKeySpecException {
100
+ updateRoot ();
101
+ var oldTimestamp = trustedMeta .findTimestamp ();
102
+ updateTimestamp ();
103
+ if (Objects .equals (oldTimestamp .orElse (null ), trustedMeta .getTimestamp ())
104
+ && trustedMeta .findSnapshot ().isPresent ()
105
+ && trustedMeta .findTargets ().isPresent ()) {
106
+ return ;
96
107
}
108
+ // if we need to update or we can't find targets/timestamps locally then grab new snapshot and
109
+ // targets from remote
110
+ updateSnapshot ();
111
+ updateTargets ();
97
112
}
98
113
99
114
// https://theupdateframework.github.io/specification/latest/#detailed-client-workflow
100
- Root updateRoot ()
115
+ void updateRoot ()
101
116
throws IOException , RoleExpiredException , NoSuchAlgorithmException , InvalidKeySpecException ,
102
- InvalidKeyException , FileExceedsMaxLengthException , RollbackVersionException ,
103
- SignatureVerificationException {
117
+ FileExceedsMaxLengthException , RollbackVersionException , SignatureVerificationException {
104
118
// 5.3.1) record the time at start and use for expiration checks consistently throughout the
105
119
// update.
106
120
updateStartTime = ZonedDateTime .now (clock );
107
121
108
122
// 5.3.2) load the trust metadata file (root.json), get version of root.json and the role
109
123
// signature threshold value
110
- Optional <Root > localRoot = localStore . loadTrustedRoot ();
124
+ Optional <Root > localRoot = trustedMeta . findRoot ();
111
125
Root trustedRoot ;
112
126
if (localRoot .isPresent ()) {
113
127
trustedRoot = localRoot .get ();
@@ -148,7 +162,7 @@ Root updateRoot()
148
162
// 5.3.7) set the trusted root metadata to the new root
149
163
trustedRoot = newRoot ;
150
164
// 5.3.8) persist to repo
151
- localStore . storeTrustedRoot (trustedRoot );
165
+ trustedMeta . setRoot (trustedRoot );
152
166
// 5.3.9) see if there are more versions go back 5.3.3
153
167
nextVersion ++;
154
168
}
@@ -164,9 +178,9 @@ Root updateRoot()
164
178
|| hasNewKeys (
165
179
preUpdateTimestampRole ,
166
180
trustedRoot .getSignedMeta ().getRoles ().get (RootRole .TIMESTAMP ))) {
167
- localStore .clearMetaDueToKeyRotation ();
181
+ trustedMeta .clearMetaDueToKeyRotation ();
168
182
}
169
- return trustedRoot ;
183
+ trustedMeta . setRoot ( trustedRoot ) ;
170
184
}
171
185
172
186
private void throwIfExpired (ZonedDateTime expires ) {
@@ -265,9 +279,9 @@ void verifyDelegate(
265
279
}
266
280
}
267
281
268
- Optional < Timestamp > updateTimestamp (Root root )
269
- throws IOException , NoSuchAlgorithmException , InvalidKeySpecException , InvalidKeyException ,
270
- FileNotFoundException , SignatureVerificationException {
282
+ void updateTimestamp ()
283
+ throws IOException , NoSuchAlgorithmException , InvalidKeySpecException , FileNotFoundException ,
284
+ SignatureVerificationException {
271
285
// 1) download the timestamp.json bytes.
272
286
var timestamp =
273
287
metaFetcher
@@ -276,56 +290,56 @@ Optional<Timestamp> updateTimestamp(Root root)
276
290
.getMetaResource ();
277
291
278
292
// 2) verify against threshold of keys as specified in trusted root.json
279
- verifyDelegate (root , timestamp );
293
+ verifyDelegate (trustedMeta . getRoot () , timestamp );
280
294
281
295
// 3) If the new timestamp file has a lesser version than our current trusted timestamp file
282
- // report a rollback attack. If it is equal abort the update as there should be no changes. If
283
- // it is higher than continue update.
284
- Optional <Timestamp > localTimestampMaybe = localStore . loadTimestamp ();
296
+ // report a rollback attack. If it is equal, just return the original timestamp there should
297
+ // be no changes. If it is higher than continue update.
298
+ Optional <Timestamp > localTimestampMaybe = trustedMeta . findTimestamp ();
285
299
if (localTimestampMaybe .isPresent ()) {
286
300
Timestamp localTimestamp = localTimestampMaybe .get ();
287
301
if (localTimestamp .getSignedMeta ().getVersion () > timestamp .getSignedMeta ().getVersion ()) {
288
302
throw new RollbackVersionException (
289
303
localTimestamp .getSignedMeta ().getVersion (), timestamp .getSignedMeta ().getVersion ());
290
304
}
291
305
if (localTimestamp .getSignedMeta ().getVersion () == timestamp .getSignedMeta ().getVersion ()) {
292
- return Optional .empty ();
306
+ trustedMeta .setTimestamp (localTimestamp );
307
+ return ;
293
308
}
294
309
}
295
310
// 4) check expiration timestamp is after tuf update start time, else fail.
296
311
throwIfExpired (timestamp .getSignedMeta ().getExpiresAsDate ());
297
312
// 5) persist timestamp.json
298
- localStore .storeMeta (RootRole .TIMESTAMP , timestamp );
299
- return Optional .of (timestamp );
313
+ trustedMeta .setTimestamp (timestamp );
300
314
}
301
315
302
- Snapshot updateSnapshot (Root root , Timestamp timestamp )
316
+ void updateSnapshot ()
303
317
throws IOException , FileNotFoundException , InvalidHashesException ,
304
- SignatureVerificationException , NoSuchAlgorithmException , InvalidKeySpecException ,
305
- InvalidKeyException {
318
+ SignatureVerificationException , NoSuchAlgorithmException , InvalidKeySpecException {
306
319
// 1) download the snapshot.json bytes up to timestamp's snapshot length.
307
- int timestampSnapshotVersion = timestamp .getSignedMeta ().getSnapshotMeta ().getVersion ();
320
+ int timestampSnapshotVersion =
321
+ trustedMeta .getTimestamp ().getSignedMeta ().getSnapshotMeta ().getVersion ();
308
322
var snapshotResult =
309
323
metaFetcher .getMeta (
310
324
RootRole .SNAPSHOT ,
311
325
timestampSnapshotVersion ,
312
326
Snapshot .class ,
313
- timestamp .getSignedMeta ().getSnapshotMeta ().getLengthOrDefault ());
327
+ trustedMeta . getTimestamp () .getSignedMeta ().getSnapshotMeta ().getLengthOrDefault ());
314
328
if (snapshotResult .isEmpty ()) {
315
329
throw new FileNotFoundException (
316
330
timestampSnapshotVersion + ".snapshot.json" , metaFetcher .getSource ());
317
331
}
318
332
// 2) check against timestamp.snapshot.hash, this is optional, the fallback is
319
333
// that the version must match, which is handled in (4).
320
334
var snapshot = snapshotResult .get ();
321
- if (timestamp .getSignedMeta ().getSnapshotMeta ().getHashes ().isPresent ()) {
335
+ if (trustedMeta . getTimestamp () .getSignedMeta ().getSnapshotMeta ().getHashes ().isPresent ()) {
322
336
verifyHashes (
323
337
"snapshot" ,
324
338
snapshot .getRawBytes (),
325
- timestamp .getSignedMeta ().getSnapshotMeta ().getHashes ().get ());
339
+ trustedMeta . getTimestamp () .getSignedMeta ().getSnapshotMeta ().getHashes ().get ());
326
340
}
327
341
// 3) Check against threshold of root signing keys, else fail
328
- verifyDelegate (root , snapshot .getMetaResource ());
342
+ verifyDelegate (trustedMeta . getRoot () , snapshot .getMetaResource ());
329
343
// 4) Check snapshot.version matches timestamp.snapshot.version, else fail.
330
344
int snapshotVersion = snapshot .getMetaResource ().getSignedMeta ().getVersion ();
331
345
if (snapshotVersion != timestampSnapshotVersion ) {
@@ -334,7 +348,7 @@ Snapshot updateSnapshot(Root root, Timestamp timestamp)
334
348
// 5) Ensure all targets and delegated targets in the trusted (old) snapshots file have versions
335
349
// which are less than or equal to the equivalent target in the new file. Check that no targets
336
350
// are missing in new file. Else fail.
337
- var trustedSnapshotMaybe = localStore . loadSnapshot ();
351
+ var trustedSnapshotMaybe = trustedMeta . findSnapshot ();
338
352
if (trustedSnapshotMaybe .isPresent ()) {
339
353
var trustedSnapshot = trustedSnapshotMaybe .get ();
340
354
for (Map .Entry <String , SnapshotMeta .SnapshotTarget > trustedTargetEntry :
@@ -356,8 +370,7 @@ Snapshot updateSnapshot(Root root, Timestamp timestamp)
356
370
// 6) Ensure expiration timestamp of snapshot is later than tuf update start time.
357
371
throwIfExpired (snapshot .getMetaResource ().getSignedMeta ().getExpiresAsDate ());
358
372
// 7) persist snapshot.
359
- localStore .storeMeta (RootRole .SNAPSHOT , snapshot .getMetaResource ());
360
- return snapshot .getMetaResource ();
373
+ trustedMeta .setSnapshot (snapshot .getMetaResource ());
361
374
}
362
375
363
376
// this method feels very wrong. I would not show it to a friend.
@@ -389,12 +402,13 @@ static void verifyHashes(String name, byte[] data, Hashes hashes) throws Invalid
389
402
}
390
403
}
391
404
392
- Targets updateTargets (Root root , Snapshot snapshot )
405
+ void updateTargets ()
393
406
throws IOException , FileNotFoundException , InvalidHashesException ,
394
407
SignatureVerificationException , NoSuchAlgorithmException , InvalidKeySpecException ,
395
- InvalidKeyException , FileExceedsMaxLengthException {
408
+ FileExceedsMaxLengthException {
396
409
// 1) download the targets.json up to targets.json length in bytes.
397
- SnapshotMeta .SnapshotTarget targetMeta = snapshot .getSignedMeta ().getTargetMeta ("targets.json" );
410
+ SnapshotMeta .SnapshotTarget targetMeta =
411
+ trustedMeta .getSnapshot ().getSignedMeta ().getTargetMeta ("targets.json" );
398
412
var targetsResultMaybe =
399
413
metaFetcher .getMeta (
400
414
RootRole .TARGETS ,
@@ -415,7 +429,7 @@ Targets updateTargets(Root root, Snapshot snapshot)
415
429
targetMeta .getHashes ().get ());
416
430
}
417
431
// 3) check against threshold of keys as specified by trusted root.json
418
- verifyDelegate (root , targetsResult .getMetaResource ());
432
+ verifyDelegate (trustedMeta . getRoot () , targetsResult .getMetaResource ());
419
433
// 4) check targets.version == snapshot.targets.version, else fail.
420
434
int targetsVersion = targetsResult .getMetaResource ().getSignedMeta ().getVersion ();
421
435
int snapshotTargetsVersion = targetMeta .getVersion ();
@@ -426,8 +440,7 @@ Targets updateTargets(Root root, Snapshot snapshot)
426
440
throwIfExpired (targetsResult .getMetaResource ().getSignedMeta ().getExpiresAsDate ());
427
441
// 6) persist targets metadata
428
442
// why do we persist the
429
- localStore .storeMeta (RootRole .TARGETS , targetsResult .getMetaResource ());
430
- return targetsResult .getMetaResource ();
443
+ trustedMeta .setTargets (targetsResult .getMetaResource ());
431
444
}
432
445
433
446
void downloadTargets (Targets targets )
@@ -470,6 +483,11 @@ MutableTufStore getLocalStore() {
470
483
return localStore ;
471
484
}
472
485
486
+ @ VisibleForTesting
487
+ TrustedMeta getTrustedMeta () {
488
+ return trustedMeta ;
489
+ }
490
+
473
491
public static class Builder {
474
492
private Clock clock = Clock .systemUTC ();
475
493
private Verifiers .Supplier verifiers = Verifiers ::newVerifier ;
0 commit comments