Skip to content

Commit a7a2c04

Browse files
implemented v4 key rotation behind feature flag
1 parent de1b73a commit a7a2c04

File tree

10 files changed

+1404
-43
lines changed

10 files changed

+1404
-43
lines changed

src/main/java/com/uid2/admin/AdminConst.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ private AdminConst() {
88
public static final String ROLE_OKTA_GROUP_MAP_MAINTAINER = "role_okta_group_map_maintainer";
99
public static final String ROLE_OKTA_GROUP_MAP_PRIVILEGED = "role_okta_group_map_privileged";
1010
public static final String ROLE_OKTA_GROUP_MAP_SUPER_USER = "role_okta_group_map_super_user";
11+
public static final String ENABLE_V4_RAW_UID = "enable_v4_raw_uid";
1112
}

src/main/java/com/uid2/admin/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ public void run() {
233233
WriteLock writeLock = new WriteLock();
234234
KeyHasher keyHasher = new KeyHasher();
235235
IKeypairGenerator keypairGenerator = new SecureKeypairGenerator();
236-
SaltRotation saltRotation = new SaltRotation(keyGenerator);
236+
SaltRotation saltRotation = new SaltRotation(keyGenerator, config);
237237
EncryptionKeyService encryptionKeyService = new EncryptionKeyService(
238238
config, auth, writeLock, encryptionKeyStoreWriter, keysetKeyStoreWriter, keyProvider, keysetKeysProvider, adminKeysetProvider, adminKeysetStoreWriter, keyGenerator, clock);
239239
KeysetManager keysetManager = new KeysetManager(
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.uid2.admin.salt;
2+
3+
import com.uid2.shared.model.SaltEntry;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
import java.util.Arrays;
8+
import java.util.Comparator;
9+
10+
public class KeyIdGenerator {
11+
private static final int MAX_KEY_ID = 16777215; // 3 bytes
12+
private int lastActiveKeyId;
13+
14+
public KeyIdGenerator(SaltEntry[] salts) {
15+
this.lastActiveKeyId = getLastActiveKeyId(salts);
16+
}
17+
18+
private int getLastActiveKeyId(SaltEntry[] salts) {
19+
SaltEntry[] sortedSaltsByLastUpdated = Arrays.stream(salts).sorted(Comparator.comparingLong(SaltEntry::lastUpdated)).toArray(SaltEntry[]::new);
20+
var lastUpdated = sortedSaltsByLastUpdated[sortedSaltsByLastUpdated.length - 1].lastUpdated();
21+
22+
var sortedKeyIds = Arrays.stream(sortedSaltsByLastUpdated)
23+
.filter(s -> s.lastUpdated() == lastUpdated)
24+
.filter(s -> s.currentKey() != null)
25+
.mapToInt(s -> s.currentKey().id())
26+
.sorted()
27+
.toArray();
28+
29+
if (sortedKeyIds.length == 0) return MAX_KEY_ID;
30+
31+
if (sortedKeyIds[sortedKeyIds.length - 1] == MAX_KEY_ID) {
32+
if (sortedKeyIds[0] == 0) {
33+
for (int i = 1; i < sortedKeyIds.length; i++) {
34+
if (sortedKeyIds[i] - sortedKeyIds[i - 1] > 1) {
35+
return sortedKeyIds[i - 1];
36+
}
37+
}
38+
return 0;
39+
}
40+
return MAX_KEY_ID;
41+
}
42+
return sortedKeyIds[sortedKeyIds.length - 1];
43+
}
44+
45+
public int getNextKeyId() {
46+
this.lastActiveKeyId += 1;
47+
if (this.lastActiveKeyId > MAX_KEY_ID) {
48+
this.lastActiveKeyId = 0;
49+
}
50+
return this.lastActiveKeyId;
51+
}
52+
}

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

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.uid2.admin.salt;
22

3+
import com.uid2.admin.AdminConst;
34
import com.uid2.shared.model.SaltEntry;
45
import com.uid2.shared.secret.IKeyGenerator;
56

67
import com.uid2.shared.store.salt.ISaltProvider.ISaltSnapshot;
78
import com.uid2.shared.store.salt.RotatingSaltProvider.SaltSnapshot;
9+
import io.vertx.core.json.JsonObject;
810
import lombok.Getter;
911
import org.slf4j.Logger;
1012
import org.slf4j.LoggerFactory;
@@ -17,12 +19,15 @@
1719
public class SaltRotation {
1820
private static final long THIRTY_DAYS_IN_MS = Duration.ofDays(30).toMillis();
1921
private static final double MAX_SALT_PERCENTAGE = 0.8;
22+
private final boolean ENABLE_V4_RAW_UID;
2023

2124
private final IKeyGenerator keyGenerator;
25+
2226
private static final Logger LOGGER = LoggerFactory.getLogger(SaltRotation.class);
2327

24-
public SaltRotation(IKeyGenerator keyGenerator) {
28+
public SaltRotation(IKeyGenerator keyGenerator, JsonObject config) {
2529
this.keyGenerator = keyGenerator;
30+
this.ENABLE_V4_RAW_UID = config.getBoolean(AdminConst.ENABLE_V4_RAW_UID, false);
2631
}
2732

2833
public Result rotateSalts(
@@ -99,21 +104,24 @@ private boolean isRefreshable(TargetDate targetDate, SaltEntry salt) {
99104
}
100105

101106
private SaltEntry[] rotateSalts(SaltEntry[] oldSalts, List<SaltEntry> saltsToRotate, TargetDate targetDate) throws Exception {
107+
var keyIdGenerator = new KeyIdGenerator(oldSalts);
102108
var saltIdsToRotate = saltsToRotate.stream().map(SaltEntry::id).collect(Collectors.toSet());
103109

104110
var updatedSalts = new SaltEntry[oldSalts.length];
105111
for (int i = 0; i < oldSalts.length; i++) {
106112
var shouldRotate = saltIdsToRotate.contains(oldSalts[i].id());
107-
updatedSalts[i] = updateSalt(oldSalts[i], targetDate, shouldRotate);
113+
updatedSalts[i] = updateSalt(oldSalts[i], targetDate, shouldRotate, keyIdGenerator);
108114
}
109115
return updatedSalts;
110116
}
111117

112-
private SaltEntry updateSalt(SaltEntry oldSalt, TargetDate targetDate, boolean shouldRotate) throws Exception {
113-
var currentSalt = shouldRotate ? this.keyGenerator.generateRandomKeyString(32) : oldSalt.currentSalt();
118+
private SaltEntry updateSalt(SaltEntry oldSalt, TargetDate targetDate, boolean shouldRotate, KeyIdGenerator keyIdGenerator) throws Exception {
114119
var lastUpdated = shouldRotate ? targetDate.asEpochMs() : oldSalt.lastUpdated();
115120
var refreshFrom = calculateRefreshFrom(oldSalt, targetDate);
121+
var currentSalt = calculateCurrentSalt(oldSalt, shouldRotate);
116122
var previousSalt = calculatePreviousSalt(oldSalt, shouldRotate, targetDate);
123+
var currentKey = calculateCurrentKey(oldSalt, shouldRotate, keyIdGenerator);
124+
var previousKey = calculatePreviousKey(oldSalt,shouldRotate, targetDate);
117125

118126
return new SaltEntry(
119127
oldSalt.id(),
@@ -122,8 +130,8 @@ private SaltEntry updateSalt(SaltEntry oldSalt, TargetDate targetDate, boolean s
122130
currentSalt,
123131
refreshFrom,
124132
previousSalt,
125-
null,
126-
null
133+
currentKey,
134+
previousKey
127135
);
128136
}
129137

@@ -132,6 +140,12 @@ private long calculateRefreshFrom(SaltEntry salt, TargetDate targetDate) {
132140
return Instant.ofEpochMilli(salt.lastUpdated()).truncatedTo(ChronoUnit.DAYS).toEpochMilli() + (multiplier * THIRTY_DAYS_IN_MS);
133141
}
134142

143+
private String calculateCurrentSalt(SaltEntry salt, boolean shouldRotate) throws Exception {
144+
return shouldRotate ?
145+
ENABLE_V4_RAW_UID ? null : this.keyGenerator.generateRandomKeyString(32)
146+
: salt.currentSalt();
147+
}
148+
135149
private String calculatePreviousSalt(SaltEntry salt, boolean shouldRotate, TargetDate targetDate) {
136150
if (shouldRotate) {
137151
return salt.currentSalt();
@@ -142,6 +156,31 @@ private String calculatePreviousSalt(SaltEntry salt, boolean shouldRotate, Targe
142156
return null;
143157
}
144158

159+
private SaltEntry.KeyMaterial calculateCurrentKey(SaltEntry salt, boolean shouldRotate, KeyIdGenerator keyIdGenerator) throws Exception {
160+
if (shouldRotate) {
161+
if (ENABLE_V4_RAW_UID) {
162+
return new SaltEntry.KeyMaterial(
163+
keyIdGenerator.getNextKeyId(),
164+
this.keyGenerator.generateRandomKeyString(32),
165+
this.keyGenerator.generateRandomKeyString(32)
166+
);
167+
} else {
168+
return null;
169+
}
170+
}
171+
return salt.currentKey();
172+
}
173+
174+
private SaltEntry.KeyMaterial calculatePreviousKey(SaltEntry salt, boolean shouldRotate, TargetDate targetDate) {
175+
if (shouldRotate) {
176+
return salt.currentKey();
177+
}
178+
if (targetDate.saltAgeInDays(salt) < 90) {
179+
return salt.previousKey();
180+
}
181+
return null;
182+
}
183+
145184
private List<SaltEntry> pickSaltsToRotate(
146185
Set<SaltEntry> refreshableSalts,
147186
TargetDate targetDate,

src/main/java/com/uid2/admin/store/writer/SaltSerializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ private static void addLine(SaltEntry entry, StringBuilder stringBuilder) {
2222
.append(",")
2323
.append(lastUpdated % 1000 == 0 ? lastUpdated + 1 : lastUpdated)
2424
.append(",")
25-
.append(entry.currentSalt());
25+
.append(serializeNullable(entry.currentSalt()));
2626

2727
stringBuilder.append(",");
2828
stringBuilder.append(serializeNullable(entry.refreshFrom()));

src/main/resources/localstack/s3/core/salts/metadata.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
"location" : "salts/salts.txt.1670796729291",
1212
"size" : 2
1313
},{
14-
"effective" : 1745907348982,
15-
"expires" : 1766720293000,
16-
"location" : "salts/salts.txt.1745907348982",
17-
"size" : 2
14+
"effective" : 1755648000000,
15+
"expires" : 1756252800000,
16+
"location" : "salts/salts.txt.1755648000000",
17+
"size" : 1001
1818
}
1919
]
2020
}

0 commit comments

Comments
 (0)