diff --git a/conf/default-config.json b/conf/default-config.json index 2ae0c63..08caf73 100644 --- a/conf/default-config.json +++ b/conf/default-config.json @@ -17,5 +17,6 @@ "att_token_enc_key": null, "att_token_enc_salt": null, "enforceJwt": false, - "s3_keys_metadata_path": null + "s3_keys_metadata_path": null, + "encryption_support_version": "5" } diff --git a/conf/integ-config.json b/conf/integ-config.json index 136c60f..a9c09ab 100644 --- a/conf/integ-config.json +++ b/conf/integ-config.json @@ -18,5 +18,6 @@ "keyset_keys_metadata_path": "uid2/keyset_keys/metadata.json", "salts_metadata_path": "uid2/salts/metadata.json", "enforceJwt": false, - "s3_keys_metadata_path": "uid2/s3encryption_keys/metadata.json" + "s3_keys_metadata_path": "uid2/s3encryption_keys/metadata.json", + "encryption_support_version": "5" } \ No newline at end of file diff --git a/conf/local-config.json b/conf/local-config.json index 33d3c2b..c02cf07 100644 --- a/conf/local-config.json +++ b/conf/local-config.json @@ -19,5 +19,6 @@ "att_token_enc_salt": "", "provide_private_site_data": true, "enforceJwt": false, - "s3_keys_metadata_path": "/com.uid2.core/test/s3encryption_keys/metadata.json" + "s3_keys_metadata_path": "/com.uid2.core/test/s3encryption_keys/metadata.json", + "encryption_support_version": "5" } diff --git a/conf/local-e2e-config.json b/conf/local-e2e-config.json index eca0c84..6f86d1a 100644 --- a/conf/local-e2e-config.json +++ b/conf/local-e2e-config.json @@ -33,5 +33,6 @@ "aws_kms_jwt_signing_public_keys": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmvwB41qI5Fe41PDbXqcX5uOvSvfKh8l9QV0O3M+NsB4lKqQEP0t1hfoiXTpOgKz1ArYxHsQ2LeXifX4uwEbYJFlpVM+tyQkTWQjBOw6fsLYK2Xk4X2ylNXUUf7x3SDiOVxyvTh3OZW9kqrDBN9JxSoraNLyfw0hhW0SHpfs699SehgbQ7QWep/gVlKRLIz0XAXaZNw24s79ORcQlrCE6YD0PgQmpI/dK5xMML82n6y3qcTlywlGaU7OGIMdD+CTXA3BcOkgXeqZTXNaX1u6jCTa1lvAczun6avp5VZ4TFiuPo+y4rJ3GU+14cyT5NckEcaTKSvd86UdwK5Id9tl3bQIDAQAB", "core_public_url": "http://localhost:8088", "optout_url": "http://localhost:8081", - "s3_keys_metadata_path": "s3encryption_keys/metadata.json" + "s3_keys_metadata_path": "s3encryption_keys/metadata.json", + "encryption_support_version": "5" } diff --git a/conf/local-e2e-docker-config.json b/conf/local-e2e-docker-config.json index d86306e..dcf60e2 100644 --- a/conf/local-e2e-docker-config.json +++ b/conf/local-e2e-docker-config.json @@ -32,5 +32,6 @@ "aws_kms_jwt_signing_public_keys": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmvwB41qI5Fe41PDbXqcX5uOvSvfKh8l9QV0O3M+NsB4lKqQEP0t1hfoiXTpOgKz1ArYxHsQ2LeXifX4uwEbYJFlpVM+tyQkTWQjBOw6fsLYK2Xk4X2ylNXUUf7x3SDiOVxyvTh3OZW9kqrDBN9JxSoraNLyfw0hhW0SHpfs699SehgbQ7QWep/gVlKRLIz0XAXaZNw24s79ORcQlrCE6YD0PgQmpI/dK5xMML82n6y3qcTlywlGaU7OGIMdD+CTXA3BcOkgXeqZTXNaX1u6jCTa1lvAczun6avp5VZ4TFiuPo+y4rJ3GU+14cyT5NckEcaTKSvd86UdwK5Id9tl3bQIDAQAB", "core_public_url": "http://core:8088", "optout_url": "http://optout:8081", - "s3_keys_metadata_path": "s3encryption_keys/metadata.json" + "s3_keys_metadata_path": "s3encryption_keys/metadata.json", + "encryption_support_version": "5" } diff --git a/pom.xml b/pom.xml index e7c468d..2fe1cba 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-core - 2.17.0 + 2.17.8-alpha-44-SNAPSHOT UTF-8 @@ -24,7 +24,7 @@ com.uid2.core.vertx.CoreVerticle io.vertx.core.Launcher - 7.17.0 + 7.17.8-alpha-140-SNAPSHOT ${project.version} diff --git a/src/main/java/com/uid2/core/service/ClientMetadataProvider.java b/src/main/java/com/uid2/core/service/ClientMetadataProvider.java index 1ad85f5..3693527 100644 --- a/src/main/java/com/uid2/core/service/ClientMetadataProvider.java +++ b/src/main/java/com/uid2/core/service/ClientMetadataProvider.java @@ -23,7 +23,7 @@ public class ClientMetadataProvider implements IClientMetadataProvider { @Override public String getMetadata(OperatorInfo info) throws Exception { - String pathname = getMetadataPathName(info.getOperatorType(), info.getSiteId(), SecretStore.Global.get(ClientsMetadataPathName)); + String pathname = getMetadataPathName(info, SecretStore.Global.get(ClientsMetadataPathName)); String original = readToEndAsString(metadataStreamProvider.download(pathname)); JsonObject main = (JsonObject) Json.decodeValue(original); JsonObject obj = main.getJsonObject("client_keys"); diff --git a/src/main/java/com/uid2/core/service/ISiteMetadataProvider.java b/src/main/java/com/uid2/core/service/ISiteMetadataProvider.java index 6e18229..993f55f 100644 --- a/src/main/java/com/uid2/core/service/ISiteMetadataProvider.java +++ b/src/main/java/com/uid2/core/service/ISiteMetadataProvider.java @@ -1,5 +1,7 @@ package com.uid2.core.service; +import com.uid2.core.util.OperatorInfo; + public interface ISiteMetadataProvider { - String getMetadata() throws Exception; + String getMetadata(OperatorInfo info) throws Exception; } diff --git a/src/main/java/com/uid2/core/service/KeysetKeysMetadataProvider.java b/src/main/java/com/uid2/core/service/KeysetKeysMetadataProvider.java index 93534fa..f013ee2 100644 --- a/src/main/java/com/uid2/core/service/KeysetKeysMetadataProvider.java +++ b/src/main/java/com/uid2/core/service/KeysetKeysMetadataProvider.java @@ -21,7 +21,7 @@ public KeysetKeysMetadataProvider(ICloudStorage cloudStorage) { @Override public String getMetadata(OperatorInfo info) throws Exception { - String pathname = getMetadataPathName(info.getOperatorType(), info.getSiteId(), SecretStore.Global.get(Const.Config.KeysetKeysMetadataPathProp)); + String pathname = getMetadataPathName(info, SecretStore.Global.get(Const.Config.KeysetKeysMetadataPathProp)); String original = readToEndAsString(metadataStreamProvider.download(pathname)); JsonObject main = (JsonObject) Json.decodeValue(original); JsonObject obj = main.getJsonObject("keyset_keys"); diff --git a/src/main/java/com/uid2/core/service/KeysetMetadataProvider.java b/src/main/java/com/uid2/core/service/KeysetMetadataProvider.java index 4068730..208eea8 100644 --- a/src/main/java/com/uid2/core/service/KeysetMetadataProvider.java +++ b/src/main/java/com/uid2/core/service/KeysetMetadataProvider.java @@ -20,7 +20,7 @@ public KeysetMetadataProvider(ICloudStorage cloudStorage) { @Override public String getMetadata(OperatorInfo info) throws Exception { - String pathname = getMetadataPathName(info.getOperatorType(), info.getSiteId(), SecretStore.Global.get(Const.Config.KeysetsMetadataPathProp)); + String pathname = getMetadataPathName(info, SecretStore.Global.get(Const.Config.KeysetsMetadataPathProp)); String original = readToEndAsString(metadataStreamProvider.download(pathname)); JsonObject main = (JsonObject) Json.decodeValue(original); JsonObject obj = main.getJsonObject("keysets"); diff --git a/src/main/java/com/uid2/core/service/SiteMetadataProvider.java b/src/main/java/com/uid2/core/service/SiteMetadataProvider.java index 4d4bae1..4f95ab8 100644 --- a/src/main/java/com/uid2/core/service/SiteMetadataProvider.java +++ b/src/main/java/com/uid2/core/service/SiteMetadataProvider.java @@ -1,6 +1,8 @@ package com.uid2.core.service; import com.uid2.core.model.SecretStore; +import com.uid2.core.util.OperatorInfo; +import com.uid2.shared.Const; import com.uid2.shared.cloud.ICloudStorage; import com.uid2.shared.store.CloudPath; import com.uid2.shared.store.scope.GlobalScope; @@ -11,6 +13,7 @@ import java.io.InputStream; import java.io.InputStreamReader; +import static com.uid2.core.util.MetadataHelper.getMetadataPathName; import static com.uid2.core.util.MetadataHelper.readToEndAsString; public class SiteMetadataProvider implements ISiteMetadataProvider { @@ -22,8 +25,8 @@ public SiteMetadataProvider(ICloudStorage cloudStorage) { this.metadataStreamProvider = this.downloadUrlGenerator = cloudStorage; } @Override - public String getMetadata() throws Exception { - String pathname = new GlobalScope(new CloudPath(SecretStore.Global.get(SiteMetadataPathName))).getMetadataPath().toString(); + public String getMetadata(OperatorInfo info) throws Exception { + String pathname = getMetadataPathName(info, SecretStore.Global.get(SiteMetadataPathName)); String original = readToEndAsString(metadataStreamProvider.download(pathname)); JsonObject main = (JsonObject) Json.decodeValue(original); JsonObject obj = main.getJsonObject("sites"); diff --git a/src/main/java/com/uid2/core/util/MetadataHelper.java b/src/main/java/com/uid2/core/util/MetadataHelper.java index 1e519af..a6e4837 100644 --- a/src/main/java/com/uid2/core/util/MetadataHelper.java +++ b/src/main/java/com/uid2/core/util/MetadataHelper.java @@ -4,6 +4,7 @@ import com.uid2.shared.auth.OperatorType; import com.uid2.shared.auth.Role; import com.uid2.shared.store.CloudPath; +import com.uid2.shared.store.scope.EncryptedScope; import com.uid2.shared.store.scope.GlobalScope; import com.uid2.shared.store.scope.SiteScope; import com.uid2.shared.store.scope.StoreScope; @@ -22,17 +23,34 @@ public static String getSiteSpecificMetadataPathName(int siteId, String metadata return SiteSpecificDataSubDirPath +siteId + metadataPathName; } - public static String getMetadataPathName(OperatorType operatorType, int siteId, String metadataPathName) - { + public static String getMetadataPathName(OperatorType operatorType, int siteId, String metadataPathName) { + return getMetadataPathName(operatorType, siteId, metadataPathName, false); + } + + public static String getMetadataPathName(OperatorInfo info, String metadataPathName) { + return getMetadataPathName(info.getOperatorType(), info.getSiteId(), metadataPathName, info.getSupportsEncryption()); + } + + + public static String getMetadataPathName(OperatorType operatorType, int siteId, String metadataPathName, Boolean canDecrypt) { StoreScope store; Boolean providePrivateSiteData = ConfigStore.Global.getBoolean("provide_private_site_data"); - if (operatorType == OperatorType.PUBLIC || (providePrivateSiteData == null || !providePrivateSiteData.booleanValue())) - { - store = new GlobalScope(new CloudPath(metadataPathName)); - } - else //PRIVATE - { - store = new SiteScope(new CloudPath(metadataPathName), siteId); + if (canDecrypt) { // Check if decryption is possible + if (operatorType == OperatorType.PUBLIC ) //siteId_public folder + { + store = new EncryptedScope(new CloudPath(metadataPathName), siteId, true); + } else //siteId_private folder + { + store = new EncryptedScope(new CloudPath(metadataPathName), siteId, false); + } + } else { + if (operatorType == OperatorType.PUBLIC || (providePrivateSiteData == null || !providePrivateSiteData.booleanValue())) + { + store = new GlobalScope(new CloudPath(metadataPathName)); + } else //PRIVATE + { + store = new SiteScope(new CloudPath(metadataPathName), siteId); + } } return store.getMetadataPath().toString(); } diff --git a/src/main/java/com/uid2/core/util/OperatorInfo.java b/src/main/java/com/uid2/core/util/OperatorInfo.java index 2ec9702..333ae2b 100644 --- a/src/main/java/com/uid2/core/util/OperatorInfo.java +++ b/src/main/java/com/uid2/core/util/OperatorInfo.java @@ -1,9 +1,19 @@ package com.uid2.core.util; +import com.uid2.core.Const; import com.uid2.shared.auth.IAuthorizable; import com.uid2.shared.auth.OperatorKey; import com.uid2.shared.auth.OperatorType; +import io.vertx.core.http.HttpServerRequest; import io.vertx.ext.web.RoutingContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.uid2.core.model.ConfigStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.uid2.shared.Const.Config.encryptionSupportVersion; +import static com.uid2.shared.Const.Http.AppVersionHeader; import static com.uid2.shared.middleware.AuthMiddleware.API_CLIENT_PROP; /** @@ -13,6 +23,9 @@ public class OperatorInfo { private final OperatorType operatorType; private final int siteId; + private final boolean supportsEncryption; + + static Logger logger = LoggerFactory.getLogger(OperatorInfo.class); public OperatorType getOperatorType() { return operatorType; @@ -22,17 +35,70 @@ public int getSiteId() { return siteId; } - public OperatorInfo(OperatorType operatorType, int siteId) { + public boolean getSupportsEncryption() {return supportsEncryption;} + + public OperatorInfo(OperatorType operatorType, int siteId, boolean supportsEncryption) { this.operatorType = operatorType; this.siteId = siteId; + this.supportsEncryption = supportsEncryption; } public static OperatorInfo getOperatorInfo(RoutingContext rc) throws Exception { IAuthorizable profile = (IAuthorizable) rc.data().get(API_CLIENT_PROP); if (profile instanceof OperatorKey) { OperatorKey operatorKey = (OperatorKey) profile; - return new OperatorInfo(operatorKey.getOperatorType(), operatorKey.getSiteId()); + return new OperatorInfo(operatorKey.getOperatorType(), operatorKey.getSiteId(), supportsEncryption(rc)); } throw new Exception("Cannot determine the operator type and site id from the profile"); } + + static boolean supportsEncryption(RoutingContext rc) { + String appVersion = rc.request().getHeader(AppVersionHeader); + if (appVersion == null) { + logger.warn("AppVersion header is missing."); + return false; + } + String[] versions = appVersion.split(";"); + for (String version : versions) { + if (version.startsWith("uid2-operator=")) { + String operatorVersion = version.substring("uid2-operator=".length()); + boolean isSupported = isVersionGreaterOrEqual(operatorVersion, ConfigStore.Global.getOrDefault(encryptionSupportVersion, "9999")); + logger.debug("Operator version: {}, {}", + operatorVersion, isSupported ? "Supports encryption" : "Does not support encryption"); + return isSupported; + } + } + logger.warn("No operator version found in AppVersion header."); + return false; + } + + static boolean isVersionGreaterOrEqual(String v1, String v2) { + Pattern pattern = Pattern.compile("(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?"); + Matcher m1 = pattern.matcher(v1); + Matcher m2 = pattern.matcher(v2); + + int[] parts1 = extractParts(m1); + int[] parts2 = extractParts(m2); + + for (int i = 0; i < Math.max(parts1.length, parts2.length); i++) { + int p1 = i < parts1.length ? parts1[i] : 0; + int p2 = i < parts2.length ? parts2[i] : 0; + if (p1 != p2) { + return p1 > p2; + } + } + + return true; + } + + private static int[] extractParts(Matcher matcher) { + int[] parts = new int[3]; + if (matcher.find()) { + for (int i = 1; i <= 3; i++) { + String group = matcher.group(i); + parts[i - 1] = group != null ? Integer.parseInt(group) : 0; + } + } + return parts; + } } \ No newline at end of file diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index 8477293..e81e0a6 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -358,7 +358,7 @@ private void handleSiteRefresh(RoutingContext rc) { return; } rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json") - .end(siteMetadataProvider.getMetadata()); + .end(siteMetadataProvider.getMetadata(info)); } catch (Exception e) { logger.warn("exception in handleSiteRefresh: " + e.getMessage(), e); Error("error", 500, rc, "error processing sites refresh"); diff --git a/src/main/resources/com.uid2.core/test/operators/operators.json b/src/main/resources/com.uid2.core/test/operators/operators.json index 8da78f3..795783c 100644 --- a/src/main/resources/com.uid2.core/test/operators/operators.json +++ b/src/main/resources/com.uid2.core/test/operators/operators.json @@ -1,4 +1,21 @@ [ + { + "key": "UID2-O-L-999-dp9Dt0.JVoGpynN4J8nMA7FxmzsavxJa8B9H74y9xdEE=", + "name": "Special", + "contact": "Special", + "protocol": "trusted", + "created": 1701210253, + "disabled": false, + "roles": [ + "OPERATOR", + "OPTOUT" + ], + "site_id": 999, + "operator_type": "PUBLIC", + "key_hash": "rTD7MpJn5/j4G6N+Ph659F4FGtiJy7MLNtfVA7XUdu6cYC9ok6EwGeI2upyDOvxvPkOCUn7HBKay8ubPQmRc0A==", + "key_salt": "ZpqdDFksFeWx/ouPAoWi39TVuGrSGwijfCN4f0pAl2Y=", + "key_id": "UID2-O-L-999-dp9Dt" + }, { "key": "test-partner-key", "name": "partner@uid2.com", diff --git a/src/test/java/com/uid2/core/util/TestOperatorInfo.java b/src/test/java/com/uid2/core/util/TestOperatorInfo.java new file mode 100644 index 0000000..a1fcf6c --- /dev/null +++ b/src/test/java/com/uid2/core/util/TestOperatorInfo.java @@ -0,0 +1,100 @@ +package com.uid2.core.util; + +import com.uid2.core.model.ConfigStore; +import com.uid2.shared.auth.OperatorKey; +import com.uid2.shared.auth.OperatorType; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.RoutingContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; +import java.util.Map; + +import static com.uid2.shared.Const.Http.AppVersionHeader; +import static com.uid2.shared.middleware.AuthMiddleware.API_CLIENT_PROP; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class OperatorInfoTest { + + @Mock + private RoutingContext mockRoutingContext; + + @Mock + private HttpServerRequest mockRequest; + private static final String encryptionSupportVersion = "encryption_support_version"; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ConfigStore.Global.load(new JsonObject().put(encryptionSupportVersion, "2.6")); + when(mockRoutingContext.request()).thenReturn(mockRequest); + } + + @Test + void testConstructor() { + OperatorInfo operatorInfo = new OperatorInfo(OperatorType.PRIVATE, 123, true); + assertEquals(OperatorType.PRIVATE, operatorInfo.getOperatorType()); + assertEquals(123, operatorInfo.getSiteId()); + assertTrue(operatorInfo.getSupportsEncryption()); + } + + @Test + void testGetOperatorInfo() throws Exception { + OperatorKey mockOperatorKey = mock(OperatorKey.class); + when(mockOperatorKey.getOperatorType()).thenReturn(OperatorType.PUBLIC); + when(mockOperatorKey.getSiteId()).thenReturn(456); + + Map data = new HashMap<>(); + data.put(API_CLIENT_PROP, mockOperatorKey); + when(mockRoutingContext.data()).thenReturn(data); + when(mockRequest.getHeader(AppVersionHeader)).thenReturn("uid2-operator=3.0.0"); + + OperatorInfo result = OperatorInfo.getOperatorInfo(mockRoutingContext); + + assertNotNull(result); + assertEquals(OperatorType.PUBLIC, result.getOperatorType()); + assertEquals(456, result.getSiteId()); + assertTrue(result.getSupportsEncryption()); + } + + @Test + void testGetOperatorInfoThrowsException() { + Map data = new HashMap<>(); + data.put("api_client", "Invalid Object"); + when(mockRoutingContext.data()).thenReturn(data); + + assertThrows(Exception.class, () -> OperatorInfo.getOperatorInfo(mockRoutingContext)); + } + + @Test + void testSupportsEncryptionTrue() { + when(mockRequest.getHeader(AppVersionHeader)).thenReturn("uid2-operator=3.0.0"); + assertTrue(OperatorInfo.supportsEncryption(mockRoutingContext)); + } + + @Test + void testSupportsEncryptionFalse() { + when(mockRequest.getHeader(AppVersionHeader)).thenReturn("uid2-operator=1.0.0"); + assertFalse(OperatorInfo.supportsEncryption(mockRoutingContext)); + } + + @Test + void testSupportsEncryptionMissingHeader() { + when(mockRequest.getHeader(AppVersionHeader)).thenReturn(null); + assertFalse(OperatorInfo.supportsEncryption(mockRoutingContext)); + } + + @Test + void testIsVersionGreaterOrEqual() { + assertTrue(OperatorInfo.isVersionGreaterOrEqual("2.0.0", "1.0.0")); + assertTrue(OperatorInfo.isVersionGreaterOrEqual("2.0.0", "2.0.0")); + assertFalse(OperatorInfo.isVersionGreaterOrEqual("1.0.0", "2.0.0")); + assertTrue(OperatorInfo.isVersionGreaterOrEqual("2.1.0", "2.0.0")); + assertFalse(OperatorInfo.isVersionGreaterOrEqual("2.0.1", "2.1.0")); + } +} \ No newline at end of file diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index 39d43d8..0f416bf 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -1,7 +1,7 @@ package com.uid2.core.vertx; - import com.uid2.core.model.ConfigStore; -import com.uid2.core.service.AttestationService; +import com.uid2.core.model.SecretStore; +import com.uid2.core.service.*; import com.uid2.core.service.JWTTokenProvider; import com.uid2.core.service.OperatorJWTTokenProvider; import com.uid2.shared.Const; @@ -24,6 +24,8 @@ import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; +import static com.uid2.core.service.KeyMetadataProvider.KeysMetadataPathName; +import static com.uid2.shared.Const.Config.KeysetsMetadataPathProp; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeEach; @@ -35,6 +37,8 @@ import org.mockito.MockitoAnnotations; import javax.crypto.Cipher; +import java.io.ByteArrayInputStream; +import java.net.URL; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; @@ -77,18 +81,43 @@ void deployVerticle(TestInfo info, Vertx vertx, VertxTestContext testContext) th config.put(Const.Config.OptOutUrlProp, "test_optout_url"); config.put(Const.Config.CorePublicUrlProp, "test_core_url"); config.put(Const.Config.AwsKmsJwtSigningKeyIdProp, "test_aws_kms_keyId"); + config.put(Const.Config.KeysetsMetadataPathProp, "keysets/metadata.json"); + config.put(Const.Config.encryptionSupportVersion, "2.6"); if (info.getTags().contains("dontForceJwt")) { config.put(Const.Config.EnforceJwtProp, false); } else { config.put(Const.Config.EnforceJwtProp, true); } ConfigStore.Global.load(config); + SecretStore.Global.load(config); attestationService = new AttestationService(); MockitoAnnotations.initMocks(this); + // Mock download method for different paths + when(cloudStorage.download(anyString())).thenAnswer(invocation -> { + String path = invocation.getArgument(0); + System.out.println(path); + if (path.contains("encrypted")) { + return new ByteArrayInputStream("{ \"keysets\": { \"location\": \"encrypted-location\" } }".getBytes()); + } else { + return new ByteArrayInputStream("{ \"keysets\": { \"location\": \"default-location\" } }".getBytes()); + } + }); + + // Mock preSignUrl method for different paths + when(cloudStorage.preSignUrl(anyString())).thenAnswer(invocation -> { + String path = invocation.getArgument(0); + if (path.contains("encrypted")) { + return new URL("http://encrypted_url"); + }else { + return new URL("http://default_url"); + } + }); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, s3KeyProvider); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); + } private String getUrlForEndpoint(String endpoint) { @@ -132,6 +161,13 @@ private void get(Vertx vertx, String endpoint, Handler>> handler) { + WebClient client = WebClient.create(vertx); + client.getAbs(getUrlForEndpoint(endpoint)) + .putHeaders(headers) + .send(handler); + } + private void addAttestationProvider(String protocol) { attestationService.with(protocol, attestationProvider); } @@ -602,7 +638,6 @@ void s3encryptionKeyRetrieveNoKeysOrError(Vertx vertx, VertxTestContext testCont assertEquals(500, response2.statusCode()); JsonObject json2 = response2.bodyAsJsonObject(); - System.out.println(json2); assertEquals("error", json2.getString("status")); assertEquals("error generating attestation token", json2.getString("message")); @@ -616,4 +651,60 @@ void s3encryptionKeyRetrieveNoKeysOrError(Vertx vertx, VertxTestContext testCont } }); } + + @Tag("dontForceJwt") + @Test + void keysetRefreshSuccessHigherVersion(Vertx vertx, VertxTestContext testContext) throws Exception { + fakeAuth(attestationProtocolPublic, Role.OPERATOR); + addAttestationProvider(attestationProtocolPublic); + onHandleAttestationRequest(() -> { + byte[] resultPublicKey = null; + return Future.succeededFuture(new AttestationResult(resultPublicKey, "test")); + }); + + MultiMap headers = MultiMap.caseInsensitiveMultiMap(); + headers.add(Const.Http.AppVersionHeader, "uid2-operator=3.7.16-SNAPSHOT;uid2-attestation-api=1.1.0;uid2-shared=2.7.0-3e279acefa"); + + getWithVersion(vertx, "key/keyset/refresh", headers, ar -> { + assertTrue(ar.succeeded()); + if (ar.succeeded()) { + HttpResponse response = ar.result(); + assertEquals(200, response.statusCode()); + String responseBody = response.bodyAsString(); + System.out.println(responseBody); + assertEquals("{\"keysets\":{\"location\":\"http://encrypted_url\"}}", responseBody); + testContext.completeNow(); + } else { + testContext.failNow(ar.cause()); + } + }); + } + + @Tag("dontForceJwt") + @Test + void keysRefreshSuccessLowerVersion(Vertx vertx, VertxTestContext testContext) throws Exception { + fakeAuth(attestationProtocolPublic, Role.OPERATOR); + addAttestationProvider(attestationProtocolPublic); + onHandleAttestationRequest(() -> { + byte[] resultPublicKey = null; + return Future.succeededFuture(new AttestationResult(resultPublicKey, "test")); + }); + + MultiMap headers = MultiMap.caseInsensitiveMultiMap(); + headers.add(Const.Http.AppVersionHeader, "uid2-operator=2.1.16-SNAPSHOT;uid2-attestation-api=1.1.0;uid2-shared=2.7.0-3e279acefa"); + + getWithVersion(vertx, "key/keyset/refresh", headers, ar -> { + if (ar.succeeded()) { + HttpResponse response = ar.result(); + System.out.println(response.bodyAsString()); + assertEquals(200, response.statusCode()); + String responseBody = response.bodyAsString(); + assertEquals("{\"keysets\":{\"location\":\"http://default_url\"}}", responseBody); + testContext.completeNow(); + } else { + testContext.failNow(ar.cause()); + } + }); + } + }