Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/main/java/com/uid2/admin/secret/SaltRotation.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
public class SaltRotation {
private final IKeyGenerator keyGenerator;
private final long THIRTY_DAYS_IN_MS = Duration.ofDays(30).toMillis();
private final long DAY_IN_MS = Duration.ofDays(1).toMillis();

public SaltRotation(IKeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
Expand Down Expand Up @@ -63,14 +64,15 @@ private SaltEntry updateSalt(SaltEntry oldSalt, boolean shouldRotate, long nextE
var currentSalt = shouldRotate ? this.keyGenerator.generateRandomKeyString(32) : oldSalt.currentSalt();
var lastUpdated = shouldRotate ? nextEffective : oldSalt.lastUpdated();
var refreshFrom = calculateRefreshFrom(oldSalt.lastUpdated(), nextEffective);
var previousSalt = calculatePreviousSalt(oldSalt, shouldRotate, nextEffective);

return new SaltEntry(
oldSalt.id(),
oldSalt.hashedId(),
lastUpdated,
currentSalt,
refreshFrom,
null,
previousSalt,
null,
null
);
Expand All @@ -82,6 +84,17 @@ private long calculateRefreshFrom(long lastUpdated, long nextEffective) {
return lastUpdated + (multiplier * THIRTY_DAYS_IN_MS);
}

private String calculatePreviousSalt(SaltEntry salt, boolean shouldRotate, long nextEffective) throws Exception {
if (shouldRotate) {
return salt.currentSalt();
}
long age = nextEffective - salt.lastUpdated();
if ( age / DAY_IN_MS < 90) {
return salt.previousSalt();
}
return null;
}

private List<Integer> pickSaltIndexesToRotate(
SaltSnapshot lastSnapshot,
Instant nextEffective,
Expand Down
79 changes: 79 additions & 0 deletions src/test/java/com/uid2/admin/secret/SaltRotationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,83 @@ void testRefreshFromCalculation(int lastRotationDaysAgo, int refreshFromDaysFrom

assertThat(actual.refreshFrom()).isEqualTo(expected);
}

@Test
void rotateSaltsPopulatePreviousSaltsOnRotation() throws Exception {
final Duration[] minAges = {
Duration.ofDays(90),
Duration.ofDays(60),
Duration.ofDays(30)
};

var lessThan90Days = daysEarlier(60).toEpochMilli();
var exactly90Days = daysEarlier(90).toEpochMilli();
var over90Days = daysEarlier(120).toEpochMilli();
var lastSnapshot = SnapshotBuilder.start()
.withEntries(
new SaltEntry(1, "1", lessThan90Days, "salt1", null, null, null, null),
new SaltEntry(3, "2", exactly90Days, "salt2", null, null, null, null),
new SaltEntry(5, "3", over90Days, "salt3", null, null, null, null)
)
.build(daysEarlier(1), daysLater(6));

var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate);
assertTrue(result.hasSnapshot());

var salts = result.getSnapshot().getAllRotatingSalts();
assertEquals("salt1", salts[0].previousSalt());
assertEquals("salt2", salts[1].previousSalt());
assertEquals("salt3", salts[2].previousSalt());
}

@Test
void rotateSaltsPreservePreviousSaltsLessThan90DaysOld() throws Exception {
final Duration[] minAges = {
Duration.ofDays(60),
};

var notValidForRotation1 = daysEarlier(40).toEpochMilli();
var notValidForRotation2 = daysEarlier(50).toEpochMilli();
var validForRotation = daysEarlier(70);
var lastSnapshot = SnapshotBuilder.start()
.withEntries(
new SaltEntry(1, "1", notValidForRotation1, "salt1", null, "previousSalt1", null, null),
new SaltEntry(2, "2", notValidForRotation2, "salt2", null, null, null, null)
)
.withEntries(1, validForRotation)
.build(daysEarlier(1), daysLater(6));

var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate);
assertTrue(result.hasSnapshot());

var salts = result.getSnapshot().getAllRotatingSalts();
assertEquals("previousSalt1", salts[0].previousSalt());
assertNull(salts[1].previousSalt());
}

@Test
void rotateSaltsRemovePreviousSaltsOver90DaysOld() throws Exception {
final Duration[] minAges = {
Duration.ofDays(100),
};

var exactly90Days = daysEarlier(90).toEpochMilli();
var over90Days = daysEarlier(100).toEpochMilli();
var validForRotation = daysEarlier(120);
var lastSnapshot = SnapshotBuilder.start()
.withEntries(
new SaltEntry(1, "1", exactly90Days, "salt1", null, "90DaysOld", null, null),
new SaltEntry(2, "2", over90Days, "salt2", null, "over90DaysOld", null, null)
)
.withEntries(1, validForRotation)
.build(daysEarlier(1), daysLater(6));

var result = saltRotation.rotateSalts(lastSnapshot, minAges, 0.5, targetDate);
assertTrue(result.hasSnapshot());

var salts = result.getSnapshot().getAllRotatingSalts();
assertNull(salts[0].previousSalt());
assertNull(salts[1].previousSalt());
}

}