Skip to content

Commit f279482

Browse files
Merge pull request #460 from IABTechLab/sch-UID2-5339-salt-rotation-previous-salt
sch-UID2-5339 salt rotation populate previous salt
2 parents 35fc2aa + 150c2d9 commit f279482

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

src/main/java/com/uid2/admin/secret/SaltRotation.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
public class SaltRotation {
2020
private final IKeyGenerator keyGenerator;
2121
private final long THIRTY_DAYS_IN_MS = Duration.ofDays(30).toMillis();
22+
private final long DAY_IN_MS = Duration.ofDays(1).toMillis();
2223

2324
public SaltRotation(IKeyGenerator keyGenerator) {
2425
this.keyGenerator = keyGenerator;
@@ -63,14 +64,15 @@ private SaltEntry updateSalt(SaltEntry oldSalt, boolean shouldRotate, long nextE
6364
var currentSalt = shouldRotate ? this.keyGenerator.generateRandomKeyString(32) : oldSalt.currentSalt();
6465
var lastUpdated = shouldRotate ? nextEffective : oldSalt.lastUpdated();
6566
var refreshFrom = calculateRefreshFrom(oldSalt.lastUpdated(), nextEffective);
67+
var previousSalt = calculatePreviousSalt(oldSalt, shouldRotate, nextEffective);
6668

6769
return new SaltEntry(
6870
oldSalt.id(),
6971
oldSalt.hashedId(),
7072
lastUpdated,
7173
currentSalt,
7274
refreshFrom,
73-
null,
75+
previousSalt,
7476
null,
7577
null
7678
);
@@ -82,6 +84,17 @@ private long calculateRefreshFrom(long lastUpdated, long nextEffective) {
8284
return lastUpdated + (multiplier * THIRTY_DAYS_IN_MS);
8385
}
8486

87+
private String calculatePreviousSalt(SaltEntry salt, boolean shouldRotate, long nextEffective) throws Exception {
88+
if (shouldRotate) {
89+
return salt.currentSalt();
90+
}
91+
long age = nextEffective - salt.lastUpdated();
92+
if ( age / DAY_IN_MS < 90) {
93+
return salt.previousSalt();
94+
}
95+
return null;
96+
}
97+
8598
private List<Integer> pickSaltIndexesToRotate(
8699
SaltSnapshot lastSnapshot,
87100
Instant nextEffective,

src/test/java/com/uid2/admin/secret/SaltRotationTest.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,83 @@ void testRefreshFromCalculation(int lastRotationDaysAgo, int refreshFromDaysFrom
239239

240240
assertThat(actual.refreshFrom()).isEqualTo(expected);
241241
}
242+
243+
@Test
244+
void rotateSaltsPopulatePreviousSaltsOnRotation() throws Exception {
245+
final Duration[] minAges = {
246+
Duration.ofDays(90),
247+
Duration.ofDays(60),
248+
Duration.ofDays(30)
249+
};
250+
251+
var lessThan90Days = daysEarlier(60).toEpochMilli();
252+
var exactly90Days = daysEarlier(90).toEpochMilli();
253+
var over90Days = daysEarlier(120).toEpochMilli();
254+
var lastSnapshot = SnapshotBuilder.start()
255+
.withEntries(
256+
new SaltEntry(1, "1", lessThan90Days, "salt1", null, null, null, null),
257+
new SaltEntry(3, "2", exactly90Days, "salt2", null, null, null, null),
258+
new SaltEntry(5, "3", over90Days, "salt3", null, null, null, null)
259+
)
260+
.build(daysEarlier(1), daysLater(6));
261+
262+
var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate);
263+
assertTrue(result.hasSnapshot());
264+
265+
var salts = result.getSnapshot().getAllRotatingSalts();
266+
assertEquals("salt1", salts[0].previousSalt());
267+
assertEquals("salt2", salts[1].previousSalt());
268+
assertEquals("salt3", salts[2].previousSalt());
269+
}
270+
271+
@Test
272+
void rotateSaltsPreservePreviousSaltsLessThan90DaysOld() throws Exception {
273+
final Duration[] minAges = {
274+
Duration.ofDays(60),
275+
};
276+
277+
var notValidForRotation1 = daysEarlier(40).toEpochMilli();
278+
var notValidForRotation2 = daysEarlier(50).toEpochMilli();
279+
var validForRotation = daysEarlier(70);
280+
var lastSnapshot = SnapshotBuilder.start()
281+
.withEntries(
282+
new SaltEntry(1, "1", notValidForRotation1, "salt1", null, "previousSalt1", null, null),
283+
new SaltEntry(2, "2", notValidForRotation2, "salt2", null, null, null, null)
284+
)
285+
.withEntries(1, validForRotation)
286+
.build(daysEarlier(1), daysLater(6));
287+
288+
var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate);
289+
assertTrue(result.hasSnapshot());
290+
291+
var salts = result.getSnapshot().getAllRotatingSalts();
292+
assertEquals("previousSalt1", salts[0].previousSalt());
293+
assertNull(salts[1].previousSalt());
294+
}
295+
296+
@Test
297+
void rotateSaltsRemovePreviousSaltsOver90DaysOld() throws Exception {
298+
final Duration[] minAges = {
299+
Duration.ofDays(100),
300+
};
301+
302+
var exactly90Days = daysEarlier(90).toEpochMilli();
303+
var over90Days = daysEarlier(100).toEpochMilli();
304+
var validForRotation = daysEarlier(120);
305+
var lastSnapshot = SnapshotBuilder.start()
306+
.withEntries(
307+
new SaltEntry(1, "1", exactly90Days, "salt1", null, "90DaysOld", null, null),
308+
new SaltEntry(2, "2", over90Days, "salt2", null, "over90DaysOld", null, null)
309+
)
310+
.withEntries(1, validForRotation)
311+
.build(daysEarlier(1), daysLater(6));
312+
313+
var result = saltRotation.rotateSalts(lastSnapshot, minAges, 0.5, targetDate);
314+
assertTrue(result.hasSnapshot());
315+
316+
var salts = result.getSnapshot().getAllRotatingSalts();
317+
assertNull(salts[0].previousSalt());
318+
assertNull(salts[1].previousSalt());
319+
}
320+
242321
}

0 commit comments

Comments
 (0)