Skip to content

Commit cec55dc

Browse files
Merge remote-tracking branch 'origin' into abu-add-encryption-urls
2 parents a14e301 + 0d457bc commit cec55dc

16 files changed

+501
-252
lines changed

.trivyignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
# for more details
44

55
# https://thetradedesk.atlassian.net/browse/UID2-5186
6-
CVE-2024-8176 exp:2025-03-27
6+
CVE-2024-8176 exp:2025-04-03

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
import com.uid2.admin.auth.OktaAuthProvider;
66
import com.uid2.admin.auth.AuthProvider;
77
import com.uid2.admin.auth.TokenRefreshHandler;
8+
import com.uid2.admin.cloudencryption.CloudKeyRotationStrategy;
9+
import com.uid2.admin.cloudencryption.ExpiredKeyCountRetentionStrategy;
810
import com.uid2.admin.job.JobDispatcher;
911
import com.uid2.admin.job.jobsync.EncryptedFilesSyncJob;
1012
import com.uid2.admin.job.jobsync.PrivateSiteDataSyncJob;
1113
import com.uid2.admin.job.jobsync.keyset.ReplaceSharingTypesWithSitesJob;
1214
import com.uid2.admin.legacy.LegacyClientKeyStoreWriter;
1315
import com.uid2.admin.legacy.RotatingLegacyClientKeyProvider;
1416
import com.uid2.admin.managers.KeysetManager;
17+
import com.uid2.admin.cloudencryption.CloudSecretGenerator;
1518
import com.uid2.admin.monitoring.DataStoreMetrics;
16-
import com.uid2.admin.managers.CloudEncryptionKeyManager;
19+
import com.uid2.admin.cloudencryption.CloudEncryptionKeyManager;
1720
import com.uid2.admin.secret.*;
1821
import com.uid2.admin.store.*;
1922
import com.uid2.admin.store.reader.RotatingAdminKeysetStore;
@@ -29,7 +32,6 @@
2932
import com.uid2.admin.vertx.service.*;
3033
import com.uid2.shared.Const;
3134
import com.uid2.shared.Utils;
32-
import com.uid2.shared.secret.IKeyGenerator;
3335
import com.uid2.shared.secret.KeyHasher;
3436
import com.uid2.shared.secret.SecureKeyGenerator;
3537
import com.uid2.shared.auth.EnclaveIdentifierProvider;
@@ -74,7 +76,6 @@ public class Main {
7476

7577
private final Vertx vertx;
7678
private final JsonObject config;
77-
7879
public Main(Vertx vertx, JsonObject config) {
7980
this.vertx = vertx;
8081
this.config = config;
@@ -122,7 +123,7 @@ public void run() {
122123
try {
123124
adminKeysetProvider.loadContent();
124125
} catch (CloudStorageException e) {
125-
if(e.getMessage().contains("The specified key does not exist")){
126+
if (e.getMessage().contains("The specified key does not exist")) {
126127
adminKeysetStoreWriter.upload(new HashMap<>(), null);
127128
adminKeysetProvider.loadContent();
128129
} else {
@@ -134,7 +135,7 @@ public void run() {
134135
GlobalScope keysetKeysGlobalScope = new GlobalScope(keysetKeyMetadataPath);
135136
RotatingKeysetKeyStore keysetKeysProvider = new RotatingKeysetKeyStore(cloudStorage, keysetKeysGlobalScope);
136137
KeysetKeyStoreWriter keysetKeyStoreWriter = new KeysetKeyStoreWriter(keysetKeysProvider, fileManager, versionGenerator, clock, keysetKeysGlobalScope, enableKeysets);
137-
if(enableKeysets) {
138+
if (enableKeysets) {
138139
try {
139140
keysetKeysProvider.loadContent();
140141
} catch (CloudStorageException e) {
@@ -154,7 +155,7 @@ public void run() {
154155
try {
155156
clientSideKeypairProvider.loadContent();
156157
} catch (CloudStorageException e) {
157-
if(e.getMessage().contains("The specified key does not exist")) {
158+
if (e.getMessage().contains("The specified key does not exist")) {
158159
clientSideKeypairStoreWriter.upload(new HashSet<>(), null);
159160
clientSideKeypairProvider.loadContent();
160161
} else {
@@ -163,13 +164,13 @@ public void run() {
163164
}
164165

165166
CloudPath serviceMetadataPath = new CloudPath(config.getString(Const.Config.ServiceMetadataPathProp));
166-
GlobalScope serviceGlobalScope= new GlobalScope(serviceMetadataPath);
167+
GlobalScope serviceGlobalScope = new GlobalScope(serviceMetadataPath);
167168
RotatingServiceStore serviceProvider = new RotatingServiceStore(cloudStorage, serviceGlobalScope);
168169
ServiceStoreWriter serviceStoreWriter = new ServiceStoreWriter(serviceProvider, fileManager, jsonWriter, versionGenerator, clock, serviceGlobalScope);
169170
try {
170171
serviceProvider.loadContent();
171172
} catch (CloudStorageException e) {
172-
if(e.getMessage().contains("The specified key does not exist")) {
173+
if (e.getMessage().contains("The specified key does not exist")) {
173174
serviceStoreWriter.upload(new HashSet<>(), null);
174175
serviceProvider.loadContent();
175176
} else {
@@ -178,13 +179,13 @@ public void run() {
178179
}
179180

180181
CloudPath serviceLinkMetadataPath = new CloudPath(config.getString(Const.Config.ServiceLinkMetadataPathProp));
181-
GlobalScope serviceLinkGlobalScope= new GlobalScope(serviceLinkMetadataPath);
182+
GlobalScope serviceLinkGlobalScope = new GlobalScope(serviceLinkMetadataPath);
182183
RotatingServiceLinkStore serviceLinkProvider = new RotatingServiceLinkStore(cloudStorage, serviceLinkGlobalScope);
183184
ServiceLinkStoreWriter serviceLinkStoreWriter = new ServiceLinkStoreWriter(serviceLinkProvider, fileManager, jsonWriter, versionGenerator, clock, serviceLinkGlobalScope);
184185
try {
185186
serviceLinkProvider.loadContent();
186187
} catch (CloudStorageException e) {
187-
if(e.getMessage().contains("The specified key does not exist")) {
188+
if (e.getMessage().contains("The specified key does not exist")) {
188189
serviceLinkStoreWriter.upload(new HashSet<>(), null);
189190
serviceLinkProvider.loadContent();
190191
} else {
@@ -202,8 +203,7 @@ public void run() {
202203
GlobalScope cloudEncryptionKeyGlobalScope = new GlobalScope(cloudEncryptionKeyMetadataPath);
203204
RotatingCloudEncryptionKeyProvider rotatingCloudEncryptionKeyProvider = new RotatingCloudEncryptionKeyProvider(cloudStorage, cloudEncryptionKeyGlobalScope);
204205
CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter = new CloudEncryptionKeyStoreWriter(rotatingCloudEncryptionKeyProvider, fileManager, jsonWriter, versionGenerator, clock, cloudEncryptionKeyGlobalScope);
205-
IKeyGenerator keyGenerator = new SecureKeyGenerator();
206-
CloudEncryptionKeyManager cloudEncryptionKeyManager = new CloudEncryptionKeyManager(rotatingCloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter,keyGenerator);
206+
SecureKeyGenerator keyGenerator = new SecureKeyGenerator();
207207
try {
208208
rotatingCloudEncryptionKeyProvider.loadContent();
209209
} catch (CloudStorageException e) {
@@ -247,6 +247,11 @@ public void run() {
247247

248248
ClientSideKeypairService clientSideKeypairService = new ClientSideKeypairService(config, auth, writeLock, clientSideKeypairStoreWriter, clientSideKeypairProvider, siteProvider, keysetManager, keypairGenerator, clock);
249249

250+
var cloudEncryptionSecretGenerator = new CloudSecretGenerator(keyGenerator);
251+
var cloudEncryptionKeyManager = new CloudEncryptionKeyManager(rotatingCloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, cloudEncryptionSecretGenerator);
252+
var cloudEncryptionKeyRetentionStrategy = new ExpiredKeyCountRetentionStrategy(clock, 5);
253+
var cloudEncryptionKeyRotationStrategy = new CloudKeyRotationStrategy(cloudEncryptionSecretGenerator, clock, cloudEncryptionKeyRetentionStrategy);
254+
250255
IService[] services = {
251256
new ClientKeyService(config, auth, writeLock, clientKeyStoreWriter, clientKeyProvider, siteProvider, keysetManager, keyGenerator, keyHasher),
252257
new EnclaveIdService(auth, writeLock, enclaveStoreWriter, enclaveIdProvider, clock),
@@ -264,7 +269,7 @@ public void run() {
264269
new EncryptedFilesSyncService(auth, jobDispatcher, writeLock, config, rotatingCloudEncryptionKeyProvider),
265270
new JobDispatcherService(auth, jobDispatcher),
266271
new SearchService(auth, clientKeyProvider, operatorKeyProvider),
267-
new CloudEncryptionKeyService(auth, rotatingCloudEncryptionKeyProvider)
272+
new CloudEncryptionKeyService(auth, rotatingCloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, operatorKeyProvider, cloudEncryptionKeyRotationStrategy)
268273
};
269274

270275

@@ -280,7 +285,7 @@ public void run() {
280285
try {
281286
keysetProvider.loadContent();
282287
} catch (CloudStorageException e) {
283-
if(e.getMessage().contains("The specified key does not exist")){
288+
if (e.getMessage().contains("The specified key does not exist")) {
284289
keysetStoreWriter.upload(new HashMap<>(), null);
285290
keysetProvider.loadContent();
286291
} else {
@@ -306,7 +311,7 @@ public void run() {
306311
The jobs are executed after because they copy data from these files locations consumed by public and private operators.
307312
This caused an issue because the files were empty and the job started to fail so the operators got empty files.
308313
*/
309-
if(enableKeysets) {
314+
if (enableKeysets) {
310315
synchronized (writeLock) {
311316
//UID2-628 keep keys.json and keyset_keys.json in sync. This function syncs them on start up
312317
keysetProvider.loadContent();
@@ -343,7 +348,7 @@ public void run() {
343348
CompletableFuture<Boolean> privateSiteDataSyncJobFuture = jobDispatcher.executeNextJob();
344349
privateSiteDataSyncJobFuture.get();
345350

346-
EncryptedFilesSyncJob encryptedFilesSyncJob = new EncryptedFilesSyncJob(config, writeLock,rotatingCloudEncryptionKeyProvider);
351+
EncryptedFilesSyncJob encryptedFilesSyncJob = new EncryptedFilesSyncJob(config, writeLock, rotatingCloudEncryptionKeyProvider);
347352
jobDispatcher.enqueue(encryptedFilesSyncJob);
348353
CompletableFuture<Boolean> encryptedFilesSyncJobFuture = jobDispatcher.executeNextJob();
349354
encryptedFilesSyncJobFuture.get();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.uid2.admin.cloudencryption;
2+
3+
import com.uid2.admin.store.Clock;
4+
import com.uid2.shared.model.CloudEncryptionKey;
5+
6+
import java.util.Collection;
7+
8+
public class CloudEncryptionKeyGenerator {
9+
private final Clock clock;
10+
private final CloudSecretGenerator secretGenerator;
11+
private final KeyIdGenerator idGenerator;
12+
13+
public CloudEncryptionKeyGenerator(
14+
Clock clock,
15+
CloudSecretGenerator secretGenerator,
16+
Collection<CloudEncryptionKey> existingKeys) {
17+
this.clock = clock;
18+
this.secretGenerator = secretGenerator;
19+
this.idGenerator = new KeyIdGenerator(existingKeys);
20+
}
21+
22+
public CloudEncryptionKey makeNewKey(Integer siteId) {
23+
var nowSeconds = clock.getEpochSecond();
24+
var keyId = idGenerator.nextId();
25+
var secret = secretGenerator.generate();
26+
return new CloudEncryptionKey(keyId, siteId, nowSeconds, nowSeconds, secret);
27+
}
28+
29+
private static class KeyIdGenerator {
30+
private int lastId;
31+
32+
public KeyIdGenerator(Collection<CloudEncryptionKey> existingKeys) {
33+
this.lastId = existingKeys.stream().map(CloudEncryptionKey::getId).max(Integer::compareTo).orElse(0);
34+
}
35+
36+
public int nextId() {
37+
return ++lastId;
38+
}
39+
}
40+
}

src/main/java/com/uid2/admin/managers/CloudEncryptionKeyManager.java renamed to src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManager.java

Lines changed: 5 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
package com.uid2.admin.managers;
1+
package com.uid2.admin.cloudencryption;
22

33
import com.uid2.admin.store.writer.CloudEncryptionKeyStoreWriter;
44
import com.uid2.shared.auth.OperatorKey;
55
import com.uid2.shared.model.CloudEncryptionKey;
6-
import com.uid2.shared.secret.IKeyGenerator;
76
import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider;
87
import org.slf4j.Logger;
98
import org.slf4j.LoggerFactory;
109

11-
import java.util.List;
12-
import java.util.Optional;
13-
import java.util.stream.Collectors;
14-
1510
import java.time.Instant;
1611
import java.util.*;
1712

@@ -21,16 +16,16 @@ public class CloudEncryptionKeyManager {
2116

2217
private final RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider;
2318
private final CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter;
24-
private final IKeyGenerator keyGenerator;
19+
private final CloudSecretGenerator secretGenerator;
2520

2621
public CloudEncryptionKeyManager(
2722
RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider,
2823
CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter,
29-
IKeyGenerator keyGenerator
24+
CloudSecretGenerator keyGenerator
3025
) {
3126
this.RotatingCloudEncryptionKeyProvider = RotatingCloudEncryptionKeyProvider;
3227
this.cloudEncryptionKeyStoreWriter = cloudEncryptionKeyStoreWriter;
33-
this.keyGenerator = keyGenerator;
28+
this.secretGenerator = keyGenerator;
3429
}
3530

3631
// Ensures there are `keyCountPerSite` sites for each site corresponding of operatorKeys. If there are less - create new ones.
@@ -89,15 +84,10 @@ private static Set<Integer> uniqueSiteIdsForOperators(Collection<OperatorKey> op
8984

9085
CloudEncryptionKey generateCloudEncryptionKey(int siteId, long activates, long created) throws Exception {
9186
int newKeyId = getNextKeyId();
92-
String secret = generateSecret();
87+
String secret = secretGenerator.generate();
9388
return new CloudEncryptionKey(newKeyId, siteId, activates, created, secret);
9489
}
9590

96-
String generateSecret() throws Exception {
97-
//Generate a 32-byte key for AesGcm
98-
return keyGenerator.generateRandomKeyString(32);
99-
}
100-
10191
void addCloudEncryptionKey(CloudEncryptionKey cloudEncryptionKey) throws Exception {
10292
Map<Integer, CloudEncryptionKey> cloudEncryptionKeys = new HashMap<>(RotatingCloudEncryptionKeyProvider.getAll());
10393
cloudEncryptionKeys.put(cloudEncryptionKey.getId(), cloudEncryptionKey);
@@ -112,49 +102,6 @@ int getNextKeyId() {
112102
return cloudEncryptionKeys.keySet().stream().max(Integer::compareTo).orElse(0) + 1;
113103
}
114104

115-
// Used in test only
116-
// Creates and uploads a CloudEncryptionKey that activates immediately for a specific sites, for emergency rotation
117-
CloudEncryptionKey createAndAddImmediateCloudEncryptionKey(int siteId) throws Exception {
118-
int newKeyId = getNextKeyId();
119-
long created = Instant.now().getEpochSecond();
120-
CloudEncryptionKey newKey = new CloudEncryptionKey(newKeyId, siteId, created, created, generateSecret());
121-
addCloudEncryptionKey(newKey);
122-
return newKey;
123-
}
124-
125-
// Used in test only
126-
CloudEncryptionKey getCloudEncryptionKeyByKeyIdentifier(int keyIdentifier) {
127-
return RotatingCloudEncryptionKeyProvider.getAll().get(keyIdentifier);
128-
}
129-
130-
// Used in test only
131-
Optional<CloudEncryptionKey> getCloudEncryptionKeyBySiteId(int siteId) {
132-
return RotatingCloudEncryptionKeyProvider.getAll().values().stream()
133-
.filter(key -> key.getSiteId() == siteId)
134-
.findFirst();
135-
}
136-
137-
// Used in test only
138-
List<CloudEncryptionKey> getAllCloudEncryptionKeysBySiteId(int siteId) {
139-
return RotatingCloudEncryptionKeyProvider.getAll().values().stream()
140-
.filter(key -> key.getSiteId() == siteId)
141-
.collect(Collectors.toList());
142-
}
143-
144-
// Used in test only
145-
Map<Integer, CloudEncryptionKey> getAllCloudEncryptionKeys() {
146-
return RotatingCloudEncryptionKeyProvider.getAll();
147-
}
148-
149-
// Used in test only
150-
boolean doesSiteHaveKeys(int siteId) {
151-
Map<Integer, CloudEncryptionKey> allKeys = RotatingCloudEncryptionKeyProvider.getAll();
152-
if (allKeys == null) {
153-
return false;
154-
}
155-
return allKeys.values().stream().anyMatch(key -> key.getSiteId() == siteId);
156-
}
157-
158105
int countKeysForSite(int siteId) {
159106
Map<Integer, CloudEncryptionKey> allKeys = RotatingCloudEncryptionKeyProvider.getAll();
160107
return (int) allKeys.values().stream().filter(key -> key.getSiteId() == siteId).count();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.uid2.admin.cloudencryption;
2+
3+
import com.uid2.shared.model.CloudEncryptionKey;
4+
5+
import java.util.Set;
6+
7+
public interface CloudKeyRetentionStrategy {
8+
Set<CloudEncryptionKey> selectKeysToRetain(Set<CloudEncryptionKey> keysForSite);
9+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.uid2.admin.cloudencryption;
2+
3+
import com.google.common.collect.Streams;
4+
import com.uid2.admin.store.Clock;
5+
import com.uid2.shared.auth.OperatorKey;
6+
import com.uid2.shared.model.CloudEncryptionKey;
7+
8+
import java.util.Collection;
9+
import java.util.Map;
10+
import java.util.Set;
11+
import java.util.stream.Collectors;
12+
import java.util.stream.Stream;
13+
14+
public class CloudKeyRotationStrategy {
15+
private final CloudSecretGenerator secretGenerator;
16+
private final Clock clock;
17+
private final CloudKeyRetentionStrategy keyRetentionStrategy;
18+
19+
public CloudKeyRotationStrategy(
20+
CloudSecretGenerator secretGenerator,
21+
Clock clock,
22+
CloudKeyRetentionStrategy keyRetentionStrategy
23+
) {
24+
this.secretGenerator = secretGenerator;
25+
this.clock = clock;
26+
this.keyRetentionStrategy = keyRetentionStrategy;
27+
}
28+
29+
public Set<CloudEncryptionKey> computeDesiredKeys(
30+
Collection<CloudEncryptionKey> existingCloudKeys,
31+
Collection<OperatorKey> operatorKeys
32+
) {
33+
var keyGenerator = new CloudEncryptionKeyGenerator(clock, secretGenerator, existingCloudKeys);
34+
Map<Integer, Set<CloudEncryptionKey>> existingKeysBySite = existingCloudKeys
35+
.stream()
36+
.collect(Collectors.groupingBy(CloudEncryptionKey::getSiteId, Collectors.toSet()));
37+
38+
return operatorKeys
39+
.stream()
40+
.map(OperatorKey::getSiteId)
41+
.distinct()
42+
.flatMap(siteId -> desiredKeysForSite(siteId, keyGenerator, existingKeysBySite.getOrDefault(siteId, Set.of())))
43+
.collect(Collectors.toSet());
44+
}
45+
46+
private Stream<CloudEncryptionKey> desiredKeysForSite(
47+
Integer siteId,
48+
CloudEncryptionKeyGenerator keyGenerator,
49+
Set<CloudEncryptionKey> existingKeys
50+
) {
51+
var existingKeysToRetain = keyRetentionStrategy.selectKeysToRetain(existingKeys);
52+
var newKey = keyGenerator.makeNewKey(siteId);
53+
return Streams.concat(existingKeysToRetain.stream(), Stream.of(newKey));
54+
}
55+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.uid2.admin.cloudencryption;
2+
3+
import com.uid2.shared.secret.SecureKeyGenerator;
4+
5+
public class CloudSecretGenerator {
6+
private final SecureKeyGenerator keyGenerator;
7+
8+
// The SecureKeyGenerator is preferable to the IKeyGenerator interface as it doesn't throw Exception
9+
public CloudSecretGenerator(SecureKeyGenerator keyGenerator) {
10+
this.keyGenerator = keyGenerator;
11+
}
12+
13+
public String generate() {
14+
//Generate a 32-byte key for AesGcm
15+
return keyGenerator.generateRandomKeyString(32);
16+
}
17+
}

0 commit comments

Comments
 (0)