Skip to content

Commit a2b8529

Browse files
authored
Merge pull request #132 from IABTechLab/wzh-uid2-3572-s3encryption-keys-distribution-endpoint
Wzh uid2 3572 s3encryption keys distribution endpoint
2 parents 91949ad + e9c0461 commit a2b8529

File tree

5 files changed

+213
-8
lines changed

5 files changed

+213
-8
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.uid2</groupId>
88
<artifactId>uid2-core</artifactId>
9-
<version>2.16.0</version>
9+
<version>2.16.2-alpha-33-SNAPSHOT</version>
1010

1111
<properties>
1212
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -24,7 +24,7 @@
2424
<vertx.verticle>com.uid2.core.vertx.CoreVerticle</vertx.verticle>
2525
<launcher.class>io.vertx.core.Launcher</launcher.class>
2626

27-
<uid2-shared.version>7.16.0</uid2-shared.version>
27+
<uid2-shared.version>7.17.0</uid2-shared.version>
2828
<image.version>${project.version}</image.version>
2929
</properties>
3030

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public static void main(String[] args) {
157157

158158
JwtService jwtService = new JwtService(config);
159159

160-
coreVerticle = new CoreVerticle(cloudStorage, operatorKeyProvider, attestationService, attestationTokenService, enclaveIdProvider, operatorJWTTokenProvider, jwtService);
160+
coreVerticle = new CoreVerticle(cloudStorage, operatorKeyProvider, attestationService, attestationTokenService, enclaveIdProvider, operatorJWTTokenProvider, jwtService, s3KeyProvider);
161161
} catch (Exception e) {
162162
System.out.println("failed to initialize core verticle: " + e.getMessage());
163163
System.exit(-1);

src/main/java/com/uid2/core/vertx/CoreVerticle.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import java.security.spec.X509EncodedKeySpec;
5151
import java.time.Instant;
5252
import java.util.*;
53+
import com.uid2.shared.store.reader.RotatingS3KeyProvider;
54+
import com.uid2.shared.model.S3Key;
5355

5456
import static com.uid2.shared.Const.Config.EnforceJwtProp;
5557

@@ -78,14 +80,16 @@ public class CoreVerticle extends AbstractVerticle {
7880
private final IPartnerMetadataProvider partnerMetadataProvider;
7981
private final OperatorJWTTokenProvider operatorJWTTokenProvider;
8082
private final JwtService jwtService;
83+
private final RotatingS3KeyProvider s3KeyProvider;
8184

8285
public CoreVerticle(ICloudStorage cloudStorage,
8386
IAuthorizableProvider authProvider,
8487
AttestationService attestationService,
8588
IAttestationTokenService attestationTokenService,
8689
IEnclaveIdentifierProvider enclaveIdentifierProvider,
8790
OperatorJWTTokenProvider operatorJWTTokenProvider,
88-
JwtService jwtService) throws Exception {
91+
JwtService jwtService,
92+
RotatingS3KeyProvider s3KeyProvider) throws Exception {
8993
this.operatorJWTTokenProvider = operatorJWTTokenProvider;
9094
this.healthComponent.setHealthStatus(false, "not started");
9195

@@ -96,6 +100,7 @@ public CoreVerticle(ICloudStorage cloudStorage,
96100
this.jwtService = jwtService;
97101
this.enclaveIdentifierProvider = enclaveIdentifierProvider;
98102
this.enclaveIdentifierProvider.addListener(this.attestationService);
103+
this.s3KeyProvider = s3KeyProvider;
99104

100105
final String jwtAudience = ConfigStore.Global.get(Const.Config.CorePublicUrlProp);
101106
final String jwtIssuer = ConfigStore.Global.get(Const.Config.CorePublicUrlProp);
@@ -122,6 +127,16 @@ public CoreVerticle(ICloudStorage cloudStorage,
122127
this.serviceLinkMetadataProvider = new ServiceLinkMetadataProvider(cloudStorage);
123128
}
124129

130+
public CoreVerticle(ICloudStorage cloudStorage,
131+
IAuthorizableProvider authorizableProvider,
132+
AttestationService attestationService,
133+
IAttestationTokenService attestationTokenService,
134+
IEnclaveIdentifierProvider enclaveIdentifierProvider,
135+
OperatorJWTTokenProvider jwtTokenProvider,
136+
JwtService jwtService) throws Exception {
137+
this(cloudStorage, authorizableProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, jwtTokenProvider, jwtService, null);
138+
}
139+
125140
@Override
126141
public void start(Promise<Void> startPromise) {
127142
this.healthComponent.setHealthStatus(false, "still starting");
@@ -165,6 +180,7 @@ private Router createRoutesSetup() {
165180
router.post("/attest")
166181
.handler(new AttestationFailureHandler())
167182
.handler(auth.handle(this::handleAttestAsync, Role.OPERATOR, Role.OPTOUT_SERVICE));
183+
router.get("/s3encryption_keys/retrieve").handler(auth.handle(attestationMiddleware.handle(this::handleS3EncryptionKeysRetrieval), Role.OPERATOR));
168184
router.get("/sites/refresh").handler(auth.handle(attestationMiddleware.handle(this::handleSiteRefresh), Role.OPERATOR));
169185
router.get("/key/refresh").handler(auth.handle(attestationMiddleware.handle(this::handleKeyRefresh), Role.OPERATOR));
170186
router.get("/key/acl/refresh").handler(auth.handle(attestationMiddleware.handle(this::handleKeyAclRefresh), Role.OPERATOR));
@@ -565,6 +581,28 @@ private void handleEnclaveUnregister(RoutingContext rc) {
565581
handleEnclaveChange(rc, true);
566582
}
567583

584+
void handleS3EncryptionKeysRetrieval(RoutingContext rc) {
585+
try {
586+
OperatorInfo info = OperatorInfo.getOperatorInfo(rc);
587+
int siteId = info.getSiteId();
588+
List<S3Key> s3Keys = s3KeyProvider.getKeys(siteId);
589+
590+
if (s3Keys == null || s3Keys.isEmpty()) {
591+
Error("No S3 keys found", 500, rc, "No S3 keys found for siteId: " + siteId);
592+
return;
593+
}
594+
595+
JsonObject response = new JsonObject()
596+
.put("s3Keys", new JsonArray(s3Keys));
597+
598+
rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
599+
.end(response.encode());
600+
} catch (Exception e) {
601+
logger.error("Error in handleRefreshS3Keys: ", e);
602+
Error("error", 500, rc, "error generating attestation token");
603+
}
604+
}
605+
568606
//region test endpoints
569607
private void handleTestGetAttestationToken(RoutingContext rc) {
570608
HttpMethod method = rc.request().method();

src/test/java/com/uid2/core/vertx/TestCoreVerticle.java

Lines changed: 169 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
import com.uid2.shared.secure.AttestationFailure;
1515
import com.uid2.shared.secure.AttestationResult;
1616
import com.uid2.shared.secure.ICoreAttestationService;
17+
import com.uid2.shared.store.reader.RotatingS3KeyProvider;
1718
import io.vertx.core.*;
1819
import io.vertx.core.buffer.Buffer;
20+
import io.vertx.core.json.JsonArray;
1921
import io.vertx.core.json.JsonObject;
2022
import io.vertx.ext.web.client.HttpResponse;
2123
import io.vertx.ext.web.client.WebClient;
@@ -25,7 +27,9 @@
2527
import static org.junit.jupiter.api.Assertions.*;
2628

2729
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Tag;
2831
import org.junit.jupiter.api.Test;
32+
import org.junit.jupiter.api.TestInfo;
2933
import org.junit.jupiter.api.extension.ExtendWith;
3034
import org.mockito.Mock;
3135
import org.mockito.MockitoAnnotations;
@@ -38,6 +42,9 @@
3842
import java.util.*;
3943
import java.util.concurrent.Callable;
4044

45+
import com.uid2.shared.model.S3Key;
46+
import java.util.Arrays;
47+
4148
import static org.mockito.Mockito.*;
4249

4350
@ExtendWith(VertxExtension.class)
@@ -56,23 +63,31 @@ public class TestCoreVerticle {
5663
private OperatorJWTTokenProvider operatorJWTTokenProvider;
5764
@Mock
5865
private JwtService jwtService;
66+
@Mock
67+
private RotatingS3KeyProvider s3KeyProvider;
5968

6069
private AttestationService attestationService;
6170

6271
private static final String attestationProtocol = "test-attestation-protocol";
72+
private static final String attestationProtocolPublic = "trusted";
6373

6474
@BeforeEach
65-
void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable {
75+
void deployVerticle(TestInfo info, Vertx vertx, VertxTestContext testContext) throws Throwable {
6676
JsonObject config = new JsonObject();
6777
config.put(Const.Config.OptOutUrlProp, "test_optout_url");
6878
config.put(Const.Config.CorePublicUrlProp, "test_core_url");
6979
config.put(Const.Config.AwsKmsJwtSigningKeyIdProp, "test_aws_kms_keyId");
70-
config.put(Const.Config.EnforceJwtProp, true);
80+
if (info.getTags().contains("dontForceJwt")) {
81+
config.put(Const.Config.EnforceJwtProp, false);
82+
} else {
83+
config.put(Const.Config.EnforceJwtProp, true);
84+
}
7185
ConfigStore.Global.load(config);
7286

7387
attestationService = new AttestationService();
7488
MockitoAnnotations.initMocks(this);
75-
CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService);
89+
90+
CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, s3KeyProvider);
7691
vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow()));
7792
}
7893

@@ -81,6 +96,10 @@ private String getUrlForEndpoint(String endpoint) {
8196
}
8297

8398
private void fakeAuth(Role... roles) {
99+
this.fakeAuth(attestationProtocol, roles);
100+
}
101+
102+
private void fakeAuth(String attestationProtocol, Role... roles) {
84103
OperatorKey operatorKey = new OperatorKey("test-key-hash", "test-key-salt", "test-name", "test-contact", attestationProtocol, 0, false, 88, new HashSet<>(Arrays.asList(roles)), OperatorType.PRIVATE, "test-key-id");
85104
when(authProvider.get(any())).thenReturn(operatorKey);
86105
}
@@ -108,6 +127,11 @@ private void get(Vertx vertx, String endpoint, MultiMap form, Handler<AsyncResul
108127
client.getAbs(getUrlForEndpoint(endpoint)).putHeader("content-type", "multipart/form-data").sendForm(form, handler);
109128
}
110129

130+
private void get(Vertx vertx, String endpoint, Handler<AsyncResult<HttpResponse<Buffer>>> handler) {
131+
WebClient client = WebClient.create(vertx);
132+
client.getAbs(getUrlForEndpoint(endpoint)).send(handler);
133+
}
134+
111135
private void addAttestationProvider(String protocol) {
112136
attestationService.with(protocol, attestationProvider);
113137
}
@@ -450,4 +474,146 @@ void wrongMethodForEndpoint(Vertx vertx, VertxTestContext testContext) {
450474
testContext.completeNow();
451475
});
452476
}
477+
478+
@Test
479+
void wrongMethodForEndpointS3(Vertx vertx, VertxTestContext testContext) {
480+
post(vertx, "/s3encryption_keys/retrieve", makeAttestationRequestJson(null, null), ar -> {
481+
HttpResponse response = ar.result();
482+
assertEquals(405, response.statusCode());
483+
assertEquals("Method Not Allowed", response.statusMessage());
484+
testContext.completeNow();
485+
});
486+
}
487+
488+
@Tag("dontForceJwt")
489+
@Test
490+
void s3encryptionKeyRetrieveSuccess(Vertx vertx, VertxTestContext testContext) {
491+
fakeAuth(attestationProtocolPublic, Role.OPERATOR);
492+
addAttestationProvider(attestationProtocolPublic);
493+
onHandleAttestationRequest(() -> {
494+
byte[] resultPublicKey = null;
495+
return Future.succeededFuture(new AttestationResult(resultPublicKey, "test"));
496+
});
497+
498+
S3Key key = new S3Key(1, 88, 1687635529, 1687808329, "newSecret");
499+
500+
List<S3Key> keys = Arrays.asList(key);
501+
when(s3KeyProvider.getKeys(88)).thenReturn(keys);
502+
503+
get(vertx, "s3encryption_keys/retrieve", ar -> {
504+
if (ar.succeeded()) {
505+
HttpResponse<Buffer> response = ar.result();
506+
assertEquals(200, response.statusCode());
507+
508+
JsonObject json = response.bodyAsJsonObject();
509+
JsonArray s3KeysArray = json.getJsonArray("s3Keys");
510+
511+
assertNotNull(s3KeysArray);
512+
assertEquals(1, s3KeysArray.size());
513+
514+
JsonObject s3KeyJson = s3KeysArray.getJsonObject(0);
515+
assertEquals(1, s3KeyJson.getInteger("id"));
516+
assertEquals(88, s3KeyJson.getInteger("siteId"));
517+
assertEquals(1687635529, s3KeyJson.getLong("activates"));
518+
assertEquals(1687808329, s3KeyJson.getLong("created"));
519+
assertEquals("newSecret", s3KeyJson.getString("secret"));
520+
521+
testContext.completeNow();
522+
} else {
523+
testContext.failNow(ar.cause());
524+
}
525+
});
526+
}
527+
528+
529+
@Tag("dontForceJwt")
530+
@Test
531+
void s3encryptionKeyRetrieveSuccessWithThreeKeys(Vertx vertx, VertxTestContext testContext) {
532+
fakeAuth(attestationProtocolPublic, Role.OPERATOR);
533+
addAttestationProvider(attestationProtocolPublic);
534+
onHandleAttestationRequest(() -> {
535+
byte[] resultPublicKey = null;
536+
return Future.succeededFuture(new AttestationResult(resultPublicKey, "test"));
537+
});
538+
539+
// Create 3 S3Key objects
540+
S3Key key1 = new S3Key(1, 88, 1687635529, 1687808329, "secret1");
541+
S3Key key2 = new S3Key(2, 88, 1687635530, 1687808330, "secret2");
542+
S3Key key3 = new S3Key(3, 88, 1687635531, 1687808331, "secret3");
543+
544+
List<S3Key> keys = Arrays.asList(key1, key2, key3);
545+
when(s3KeyProvider.getKeys(88)).thenReturn(keys);
546+
547+
get(vertx, "s3encryption_keys/retrieve", ar -> {
548+
if (ar.succeeded()) {
549+
HttpResponse<Buffer> response = ar.result();
550+
assertEquals(200, response.statusCode());
551+
552+
JsonObject json = response.bodyAsJsonObject();
553+
JsonArray s3KeysArray = json.getJsonArray("s3Keys");
554+
555+
assertNotNull(s3KeysArray);
556+
assertEquals(3, s3KeysArray.size());
557+
558+
for (int i = 0; i < 3; i++) {
559+
JsonObject s3KeyJson = s3KeysArray.getJsonObject(i);
560+
assertEquals(i + 1, s3KeyJson.getInteger("id"));
561+
assertEquals(88, s3KeyJson.getInteger("siteId"));
562+
assertEquals(1687635529 + i, s3KeyJson.getLong("activates"));
563+
assertEquals(1687808329 + i, s3KeyJson.getLong("created"));
564+
assertEquals("secret" + (i + 1), s3KeyJson.getString("secret"));
565+
}
566+
567+
testContext.completeNow();
568+
} else {
569+
testContext.failNow(ar.cause());
570+
}
571+
});
572+
}
573+
574+
@Tag("dontForceJwt")
575+
@Test
576+
void s3encryptionKeyRetrieveNoKeysOrError(Vertx vertx, VertxTestContext testContext) {
577+
fakeAuth(attestationProtocolPublic, Role.OPERATOR);
578+
addAttestationProvider(attestationProtocolPublic);
579+
onHandleAttestationRequest(() -> {
580+
byte[] resultPublicKey = null;
581+
return Future.succeededFuture(new AttestationResult(resultPublicKey, "test"));
582+
});
583+
584+
// Test case 1: No keys found
585+
when(s3KeyProvider.getKeys(anyInt())).thenReturn(Collections.emptyList());
586+
587+
get(vertx, "s3encryption_keys/retrieve", ar -> {
588+
if (ar.succeeded()) {
589+
HttpResponse<Buffer> response = ar.result();
590+
assertEquals(500, response.statusCode());
591+
592+
JsonObject json = response.bodyAsJsonObject();
593+
assertEquals("No S3 keys found", json.getString("status"));
594+
assertTrue(json.getString("message").contains("No S3 keys found for siteId:"));
595+
596+
// Test case 2: Exception thrown
597+
when(s3KeyProvider.getKeys(anyInt())).thenThrow(new RuntimeException("Test exception"));
598+
599+
get(vertx, "s3encryption_keys/retrieve", ar2 -> {
600+
if (ar2.succeeded()) {
601+
HttpResponse<Buffer> response2 = ar2.result();
602+
assertEquals(500, response2.statusCode());
603+
604+
JsonObject json2 = response2.bodyAsJsonObject();
605+
System.out.println(json2);
606+
assertEquals("error", json2.getString("status"));
607+
assertEquals("error generating attestation token", json2.getString("message"));
608+
609+
testContext.completeNow();
610+
} else {
611+
testContext.failNow(ar2.cause());
612+
}
613+
});
614+
} else {
615+
testContext.failNow(ar.cause());
616+
}
617+
});
618+
}
453619
}

src/test/resources/com.uid2.core/model/test-config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222
"att_token_enc_key": "<key-for-attestation-token>",
2323
"att_token_enc_salt": "<salt-for-attestation-token>",
2424
"att_token_lifetime_seconds": 120,
25-
"provide_private_site_data": true
25+
"provide_private_site_data": true,
26+
"s3_keys_metadata_path": "s3encryption_keys/metadata.json"
2627
}

0 commit comments

Comments
 (0)