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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ target/*
.idea/*
.idea/
*.iml
.DS_Store
6 changes: 5 additions & 1 deletion src/main/java/com/uid2/admin/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,11 @@ public void run() {
}

synchronized (writeLock) {
cloudEncryptionKeyManager.generateKeysForOperators(operatorKeyProvider.getAll(), config.getLong("cloud_encryption_key_activates_in_seconds"), config.getInteger("cloud_encryption_key_count_per_site"));
cloudEncryptionKeyManager.generateKeysForOperators(
operatorKeyProvider.getAll(),
config.getLong("cloud_encryption_key_activates_in_seconds"),
config.getInteger("cloud_encryption_key_count_per_site")
);
RotatingCloudEncryptionKeyProvider.loadContent();
}

Expand Down
118 changes: 73 additions & 45 deletions src/main/java/com/uid2/admin/managers/CloudEncryptionKeyManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand All @@ -22,12 +23,70 @@ public class CloudEncryptionKeyManager {
private final CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter;
private final IKeyGenerator keyGenerator;

public CloudEncryptionKeyManager(RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider, CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter, IKeyGenerator keyGenerator) {
public CloudEncryptionKeyManager(
RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider,
CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter,
IKeyGenerator keyGenerator
) {
this.RotatingCloudEncryptionKeyProvider = RotatingCloudEncryptionKeyProvider;
this.cloudEncryptionKeyStoreWriter = cloudEncryptionKeyStoreWriter;
this.keyGenerator = keyGenerator;
}

// Ensures there are `keyCountPerSite` sites for each site corresponding of operatorKeys. If there are less - create new ones.
// Give all new keys for each site `activationInterval` seconds between activations, starting now
public void generateKeysForOperators(
Collection<OperatorKey> operatorKeys,
long activationInterval,
int keyCountPerSite
) throws Exception {
this.RotatingCloudEncryptionKeyProvider.loadContent();

if (operatorKeys == null || operatorKeys.isEmpty()) {
throw new IllegalArgumentException("Operator keys collection must not be null or empty");
}
if (activationInterval <= 0) {
throw new IllegalArgumentException("Key activate interval must be greater than zero");
}
if (keyCountPerSite <= 0) {
throw new IllegalArgumentException("Key count per site must be greater than zero");
}

for (Integer siteId : uniqueSiteIdsForOperators(operatorKeys)) {
ensureEnoughKeysForSite(activationInterval, keyCountPerSite, siteId);
}
}

private void ensureEnoughKeysForSite(long activationInterval, int keyCountPerSite, Integer siteId) throws Exception {
// Check if the site ID already exists in the S3 key provider and has fewer than the required number of keys
int currentKeyCount = countKeysForSite(siteId);
if (currentKeyCount >= keyCountPerSite) {
LOGGER.info("Site ID {} already has the required number of keys. Skipping key generation.", siteId);
return;
}

int keysToGenerate = keyCountPerSite - currentKeyCount;
for (int i = 0; i < keysToGenerate; i++) {
addKey(activationInterval, siteId, i);
}
LOGGER.info("Generated {} keys for site ID {}", keysToGenerate, siteId);
}

private void addKey(long keyActivateInterval, Integer siteId, int keyIndex) throws Exception {
long created = Instant.now().getEpochSecond();
long activated = created + (keyIndex * keyActivateInterval);
CloudEncryptionKey cloudEncryptionKey = generateCloudEncryptionKey(siteId, activated, created);
addCloudEncryptionKey(cloudEncryptionKey);
}

private static Set<Integer> uniqueSiteIdsForOperators(Collection<OperatorKey> operatorKeys) {
Set<Integer> uniqueSiteIds = new HashSet<>();
for (OperatorKey operatorKey : operatorKeys) {
uniqueSiteIds.add(operatorKey.getSiteId());
}
return uniqueSiteIds;
}

CloudEncryptionKey generateCloudEncryptionKey(int siteId, long activates, long created) throws Exception {
int newKeyId = getNextKeyId();
String secret = generateSecret();
Expand All @@ -53,36 +112,42 @@ int getNextKeyId() {
return cloudEncryptionKeys.keySet().stream().max(Integer::compareTo).orElse(0) + 1;
}

// Method to create and upload an S3 key that activates immediately for a specific site, for emergency rotation
public CloudEncryptionKey createAndAddImmediate3Key(int siteId) throws Exception {
// Used in test only
// Creates and uploads a CloudEncryptionKey that activates immediately for a specific sites, for emergency rotation
CloudEncryptionKey createAndAddImmediateCloudEncryptionKey(int siteId) throws Exception {
int newKeyId = getNextKeyId();
long created = Instant.now().getEpochSecond();
CloudEncryptionKey newKey = new CloudEncryptionKey(newKeyId, siteId, created, created, generateSecret());
addCloudEncryptionKey(newKey);
return newKey;
}

public CloudEncryptionKey getCloudEncryptionKeyByKeyIdentifier(int keyIdentifier) {
// Used in test only
CloudEncryptionKey getCloudEncryptionKeyByKeyIdentifier(int keyIdentifier) {
return RotatingCloudEncryptionKeyProvider.getAll().get(keyIdentifier);
}

public Optional<CloudEncryptionKey> getCloudEncryptionKeyBySiteId(int siteId) {
// Used in test only
Optional<CloudEncryptionKey> getCloudEncryptionKeyBySiteId(int siteId) {
return RotatingCloudEncryptionKeyProvider.getAll().values().stream()
.filter(key -> key.getSiteId() == siteId)
.findFirst();
}

public List<CloudEncryptionKey> getAllCloudEncryptionKeysBySiteId(int siteId) {
// Used in test only
List<CloudEncryptionKey> getAllCloudEncryptionKeysBySiteId(int siteId) {
return RotatingCloudEncryptionKeyProvider.getAll().values().stream()
.filter(key -> key.getSiteId() == siteId)
.collect(Collectors.toList());
}

public Map<Integer, CloudEncryptionKey> getAllCloudEncryptionKeys() {
// Used in test only
Map<Integer, CloudEncryptionKey> getAllCloudEncryptionKeys() {
return RotatingCloudEncryptionKeyProvider.getAll();
}

public boolean doesSiteHaveKeys(int siteId) {
// Used in test only
boolean doesSiteHaveKeys(int siteId) {
Map<Integer, CloudEncryptionKey> allKeys = RotatingCloudEncryptionKeyProvider.getAll();
if (allKeys == null) {
return false;
Expand All @@ -94,41 +159,4 @@ int countKeysForSite(int siteId) {
Map<Integer, CloudEncryptionKey> allKeys = RotatingCloudEncryptionKeyProvider.getAll();
return (int) allKeys.values().stream().filter(key -> key.getSiteId() == siteId).count();
}

public void generateKeysForOperators(Collection<OperatorKey> operatorKeys, long keyActivateInterval, int keyCountPerSite) throws Exception {
this.RotatingCloudEncryptionKeyProvider.loadContent();

if (operatorKeys == null || operatorKeys.isEmpty()) {
throw new IllegalArgumentException("Operator keys collection must not be null or empty");
}
if (keyActivateInterval <= 0) {
throw new IllegalArgumentException("Key activate interval must be greater than zero");
}
if (keyCountPerSite <= 0) {
throw new IllegalArgumentException("Key count per site must be greater than zero");
}

// Extract all the unique site IDs from input operator keys collection
Set<Integer> uniqueSiteIds = new HashSet<>();
for (OperatorKey operatorKey : operatorKeys) {
uniqueSiteIds.add(operatorKey.getSiteId());
}

for (Integer siteId : uniqueSiteIds) {
// Check if the site ID already exists in the S3 key provider and has fewer than the required number of keys
int currentKeyCount = countKeysForSite(siteId);
if (currentKeyCount < keyCountPerSite) {
int keysToGenerate = keyCountPerSite - currentKeyCount;
for (int i = 0; i < keysToGenerate; i++) {
long created = Instant.now().getEpochSecond();
long activated = created + (i * keyActivateInterval);
CloudEncryptionKey cloudEncryptionKey = generateCloudEncryptionKey(siteId, activated, created);
addCloudEncryptionKey(cloudEncryptionKey);
}
LOGGER.info("Generated " + keysToGenerate + " keys for site ID " + siteId);
} else {
LOGGER.info("Site ID " + siteId + " already has the required number of keys. Skipping key generation.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,60 @@
import static org.mockito.Mockito.*;

class CloudEncryptionKeyManagerTest {

private RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider;
private CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter;
private IKeyGenerator keyGenerator;
private CloudEncryptionKeyManager cloudEncryptionKeyManager;

private final long keyActivateInterval = 3600; // 1 hour
private final int keyCountPerSite = 3;
private final int siteId = 1;

@BeforeEach
void setUp() {
cloudEncryptionKeyProvider = mock(RotatingCloudEncryptionKeyProvider.class);
cloudEncryptionKeyStoreWriter = mock(CloudEncryptionKeyStoreWriter.class);
keyGenerator = mock(IKeyGenerator.class);
cloudEncryptionKeyManager = new CloudEncryptionKeyManager(cloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter,keyGenerator);
cloudEncryptionKeyManager = new CloudEncryptionKeyManager(cloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, keyGenerator);
}

@Test
void testGenerateCloudEncryptionKey() throws Exception {
when(keyGenerator.generateRandomKeyString(32)).thenReturn("randomKeyString");

CloudEncryptionKey cloudEncryptionKey = cloudEncryptionKeyManager.generateCloudEncryptionKey(1, 1000L, 2000L);
CloudEncryptionKey cloudEncryptionKey = cloudEncryptionKeyManager.generateCloudEncryptionKey(siteId, 1000L, 2000L);

assertNotNull(cloudEncryptionKey);
assertEquals(1, cloudEncryptionKey.getSiteId());
assertEquals(siteId, cloudEncryptionKey.getSiteId());
assertEquals(1000L, cloudEncryptionKey.getActivates());
assertEquals(2000L, cloudEncryptionKey.getCreated());
assertEquals("randomKeyString", cloudEncryptionKey.getSecret());
}

@Test
void testAddCloudEncryptionKeyToEmpty() throws Exception {
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, 1, 1000L, 2000L, "randomKeyString");
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, siteId, 1000L, 2000L, "randomKeyString");

Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys);

cloudEncryptionKeyManager.addCloudEncryptionKey( cloudEncryptionKey);
cloudEncryptionKeyManager.addCloudEncryptionKey(cloudEncryptionKey);

ArgumentCaptor<Map> captor = ArgumentCaptor.forClass(Map.class);
verify(cloudEncryptionKeyStoreWriter).upload(captor.capture(), isNull());

Map<Integer, CloudEncryptionKey> capturedKeys = captor.getValue();
assertEquals(1, capturedKeys.size());
assertEquals( cloudEncryptionKey, capturedKeys.get(1));
assertEquals(cloudEncryptionKey, capturedKeys.get(1));
}

@Test
void testAddCloudEncryptionKeyToExisting() throws Exception {
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(3, 1, 1000L, 2000L, "randomKeyString");
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(3, siteId, 1000L, 2000L, "randomKeyString");

Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
CloudEncryptionKey existingKey1 = new CloudEncryptionKey(1, 1, 500L, 1500L, "existingSecret1");
CloudEncryptionKey existingKey2 = new CloudEncryptionKey(2, 1, 600L, 1600L, "existingSecret2");
CloudEncryptionKey existingKey1 = new CloudEncryptionKey(1, siteId, 500L, 1500L, "existingSecret1");
CloudEncryptionKey existingKey2 = new CloudEncryptionKey(2, siteId, 600L, 1600L, "existingSecret2");
existingKeys.put(1, existingKey1);
existingKeys.put(2, existingKey2);

Expand All @@ -88,7 +91,7 @@ void testAddCloudEncryptionKeyToExisting() throws Exception {
@Test
void testGetNextKeyId() {
Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
existingKeys.put(1, new CloudEncryptionKey(1, 1, 500L, 1500L, "existingSecret1"));
existingKeys.put(1, new CloudEncryptionKey(1, siteId, 500L, 1500L, "existingSecret1"));
when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys);

int nextKeyId = cloudEncryptionKeyManager.getNextKeyId();
Expand All @@ -98,7 +101,7 @@ void testGetNextKeyId() {

@Test
void testGetCloudEncryptionKey() {
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, 1, 500L, 1500L, "existingSecret1");
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, siteId, 500L, 1500L, "existingSecret1");
Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
existingKeys.put(1, cloudEncryptionKey);
when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys);
Expand All @@ -111,8 +114,8 @@ void testGetCloudEncryptionKey() {
@Test
void testGetAllCloudEncryptionKeys() {
Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
CloudEncryptionKey existingKey1 = new CloudEncryptionKey(1, 1, 500L, 1500L, "existingSecret1");
CloudEncryptionKey existingKey2 = new CloudEncryptionKey(2, 1, 600L, 1600L, "existingSecret2");
CloudEncryptionKey existingKey1 = new CloudEncryptionKey(1, siteId, 500L, 1500L, "existingSecret1");
CloudEncryptionKey existingKey2 = new CloudEncryptionKey(2, siteId, 600L, 1600L, "existingSecret2");
existingKeys.put(1, existingKey1);
existingKeys.put(2, existingKey2);

Expand All @@ -125,7 +128,7 @@ void testGetAllCloudEncryptionKeys() {

@Test
void testAddCloudEncryptionKey() throws Exception {
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, 1, 1000L, 2000L, "randomKeyString");
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, siteId, 1000L, 2000L, "randomKeyString");

Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys);
Expand Down Expand Up @@ -178,7 +181,7 @@ void testCreateAndAddImmediateCloudEncryptionKey() throws Exception {
when(cloudEncryptionKeyProvider.getAll()).thenReturn(new HashMap<>());
when(keyGenerator.generateRandomKeyString(32)).thenReturn("generatedSecret");

CloudEncryptionKey newKey = cloudEncryptionKeyManager.createAndAddImmediate3Key(100);
CloudEncryptionKey newKey = cloudEncryptionKeyManager.createAndAddImmediateCloudEncryptionKey(100);

assertNotNull(newKey);
assertEquals(100, newKey.getSiteId());
Expand All @@ -189,7 +192,6 @@ void testCreateAndAddImmediateCloudEncryptionKey() throws Exception {

@Test
public void testDoesSiteHaveKeys_SiteHasKeys() {
int siteId = 1;
CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(siteId, siteId, 0L, 0L, "key");
Map<Integer, CloudEncryptionKey> allKeys = new HashMap<>();
allKeys.put(1, cloudEncryptionKey);
Expand All @@ -199,9 +201,9 @@ public void testDoesSiteHaveKeys_SiteHasKeys() {
boolean result = cloudEncryptionKeyManager.doesSiteHaveKeys(siteId);
assertTrue(result);
}

@Test
public void testDoesSiteHaveKeys_SiteDoesNotHaveKeys() {
int siteId = 1;
Map<Integer, CloudEncryptionKey> allKeys = new HashMap<>();

when(cloudEncryptionKeyProvider.getAll()).thenReturn(allKeys);
Expand All @@ -212,8 +214,6 @@ public void testDoesSiteHaveKeys_SiteDoesNotHaveKeys() {

@Test
public void testDoesSiteHaveKeys_AllKeysNull() {
int siteId = 1;

when(cloudEncryptionKeyProvider.getAll()).thenReturn(null);

boolean result = cloudEncryptionKeyManager.doesSiteHaveKeys(siteId);
Expand All @@ -237,7 +237,6 @@ public void testDoesSiteHaveKeys_MultipleKeysDifferentSiteIds() {

@Test
public void testDoesSiteHaveKeys_SameSiteIdMultipleKeys() {
int siteId = 1;
CloudEncryptionKey cloudEncryptionKey1 = new CloudEncryptionKey(siteId, siteId, 0L, 0L, "key1");
CloudEncryptionKey cloudEncryptionKey2 = new CloudEncryptionKey(siteId, siteId, 0L, 0L, "key2");
Map<Integer, CloudEncryptionKey> allKeys = new HashMap<>();
Expand Down Expand Up @@ -299,8 +298,6 @@ void testGenerateKeysForOperators() throws Exception {
createOperatorKey("hash2", 100),
createOperatorKey("hash3", 200)
);
long keyActivateInterval = 3600; // 1 hour
int keyCountPerSite = 3;

Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
existingKeys.put(1, new CloudEncryptionKey(1, 100, 1000L, 900L, "existingKey1"));
Expand All @@ -322,8 +319,6 @@ void testGenerateKeysForOperators_NoNewKeysNeeded() throws Exception {
Collection<OperatorKey> operatorKeys = Collections.singletonList(
createOperatorKey("hash1", 100)
);
long keyActivateInterval = 3600;
int keyCountPerSite = 3;

Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
existingKeys.put(1, new CloudEncryptionKey(1, 100, 1000L, 900L, "existingKey1"));
Expand All @@ -339,8 +334,6 @@ void testGenerateKeysForOperators_NoNewKeysNeeded() throws Exception {
@Test
void testGenerateKeysForOperators_EmptyOperatorKeys() {
Collection<OperatorKey> operatorKeys = Collections.emptyList();
long keyActivateInterval = 3600;
int keyCountPerSite = 3;

assertThrows(IllegalArgumentException.class, () ->
cloudEncryptionKeyManager.generateKeysForOperators(operatorKeys, keyActivateInterval, keyCountPerSite)
Expand All @@ -353,7 +346,6 @@ void testGenerateKeysForOperators_InvalidKeyActivateInterval() {
createOperatorKey("hash1", 100)
);
long keyActivateInterval = 0;
int keyCountPerSite = 3;

assertThrows(IllegalArgumentException.class, () ->
cloudEncryptionKeyManager.generateKeysForOperators(operatorKeys, keyActivateInterval, keyCountPerSite)
Expand All @@ -365,7 +357,6 @@ void testGenerateKeysForOperators_InvalidKeyCountPerSite() {
Collection<OperatorKey> operatorKeys = Collections.singletonList(
createOperatorKey("hash1", 100)
);
long keyActivateInterval = 3600;
int keyCountPerSite = 0;

assertThrows(IllegalArgumentException.class, () ->
Expand All @@ -380,8 +371,6 @@ void testGenerateKeysForOperators_MultipleSitesWithVaryingExistingKeys() throws
createOperatorKey("hash2", 200),
createOperatorKey("hash3", 300)
);
long keyActivateInterval = 3600;
int keyCountPerSite = 3;

Map<Integer, CloudEncryptionKey> existingKeys = new HashMap<>();
existingKeys.put(1, new CloudEncryptionKey(1, 100, 1000L, 900L, "existingKey1"));
Expand Down