From 9ce98510bd4e64ce2dfd6485dad9fe0e49a381dc Mon Sep 17 00:00:00 2001 From: Thomas Manson Date: Fri, 11 Oct 2024 13:18:46 +1100 Subject: [PATCH 1/5] Return the correct codes on attestation failure --- pom.xml | 2 +- .../handler/AttestationFailureHandler.java | 6 +- .../core/vertx/AttestationFailureReason.java | 29 --------- .../com/uid2/core/vertx/CoreVerticle.java | 65 ++++++++++++------- .../com/uid2/core/vertx/TestCoreVerticle.java | 32 +++++++-- 5 files changed, 70 insertions(+), 64 deletions(-) delete mode 100644 src/main/java/com/uid2/core/vertx/AttestationFailureReason.java diff --git a/pom.xml b/pom.xml index 247d0a7..166ef70 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ com.uid2.core.vertx.CoreVerticle io.vertx.core.Launcher - 7.19.0 + 7.19.1-SNAPSHOT ${project.version} diff --git a/src/main/java/com/uid2/core/handler/AttestationFailureHandler.java b/src/main/java/com/uid2/core/handler/AttestationFailureHandler.java index 6d78c20..390f756 100644 --- a/src/main/java/com/uid2/core/handler/AttestationFailureHandler.java +++ b/src/main/java/com/uid2/core/handler/AttestationFailureHandler.java @@ -1,10 +1,10 @@ package com.uid2.core.handler; import com.uid2.core.Const; -import com.uid2.core.vertx.AttestationFailureReason; import com.uid2.shared.auth.IAuthorizable; import com.uid2.shared.auth.OperatorKey; import com.uid2.shared.middleware.AuthMiddleware; +import com.uid2.shared.secure.AttestationFailure; import io.vertx.core.Handler; import io.vertx.core.json.JsonObject; import io.vertx.core.net.SocketAddress; @@ -40,14 +40,14 @@ private void logAttestationFailure(RoutingContext context) { return; } - final AttestationFailureReason attestationFailureReason = context.get(Const.RoutingContextData.ATTESTATION_FAILURE_REASON_PROP); + final AttestationFailure attestationFailure = context.get(Const.RoutingContextData.ATTESTATION_FAILURE_REASON_PROP); final String attestationFailureDataJson = getAttestationFailureDataJson(context); final String originatingIpAddress = getOriginatingIpAddress(context); LOG.warn("Attestation failed. StatusCode={} Reason={} Data={} OperatorKeyHash={} OperatorKeyName={} SiteId={} Protocol={} OperatorType={} OriginatingIpAddress={}", context.response().getStatusCode(), - attestationFailureReason, + attestationFailure, attestationFailureDataJson, operatorKey.getKeyHash(), operatorKey.getName(), diff --git a/src/main/java/com/uid2/core/vertx/AttestationFailureReason.java b/src/main/java/com/uid2/core/vertx/AttestationFailureReason.java deleted file mode 100644 index a262098..0000000 --- a/src/main/java/com/uid2/core/vertx/AttestationFailureReason.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.uid2.core.vertx; - -public enum AttestationFailureReason { - /** - * Request body is not valid JSON. - */ - REQUEST_BODY_IS_NOT_VALID_JSON, - /** - * No attestation request in the request body. - */ - NO_ATTESTATION_REQUEST_ATTACHED, - /** - * Exception occurred encrypting the response. - */ - RESPONSE_ENCRYPTION_EXCEPTION, - /** - * Invalid protocol specified. - */ - INVALID_PROTOCOL, - /** - * Attestation was attempted, but failed. - */ - ATTESTATION_FAILURE, - - /** - * Internal server error - */ - INTERNAL_ERROR, -} diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index 1f4d2ba..3674a3c 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -49,6 +49,7 @@ import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.util.*; + import com.uid2.shared.store.reader.RotatingS3KeyProvider; import com.uid2.shared.model.S3Key; @@ -78,7 +79,7 @@ public class CoreVerticle extends AbstractVerticle { private final ISaltMetadataProvider saltMetadataProvider; private final IPartnerMetadataProvider partnerMetadataProvider; private final OperatorJWTTokenProvider operatorJWTTokenProvider; - private final JwtService jwtService; + //private final JwtService jwtService; private final RotatingS3KeyProvider s3KeyProvider; public CoreVerticle(ICloudStorage cloudStorage, @@ -96,19 +97,18 @@ public CoreVerticle(ICloudStorage cloudStorage, this.attestationService = attestationService; this.attestationTokenService = attestationTokenService; - this.jwtService = jwtService; this.enclaveIdentifierProvider = enclaveIdentifierProvider; this.enclaveIdentifierProvider.addListener(this.attestationService); this.s3KeyProvider = s3KeyProvider; final String jwtAudience = ConfigStore.Global.get(Const.Config.CorePublicUrlProp); final String jwtIssuer = ConfigStore.Global.get(Const.Config.CorePublicUrlProp); - Boolean enforceJwt = ConfigStore.Global.getBoolean(Const.Config.EnforceJwtProp); + Boolean enforceJwt = ConfigStore.Global.getBoolean(EnforceJwtProp); if (enforceJwt == null) { enforceJwt = false; } - this.attestationMiddleware = new AttestationMiddleware(this.attestationTokenService, this.jwtService, jwtAudience, jwtIssuer, enforceJwt); + this.attestationMiddleware = new AttestationMiddleware(this.attestationTokenService, jwtService, jwtAudience, jwtIssuer, enforceJwt); this.auth = new AuthMiddleware(authProvider); @@ -225,7 +225,7 @@ private void handleAttestAsync(RoutingContext rc) { try { json = rc.body().asJsonObject(); } catch (DecodeException e) { - setAttestationFailureReason(rc, AttestationFailureReason.REQUEST_BODY_IS_NOT_VALID_JSON); + setAttestationFailureReason(rc, AttestationFailure.BAD_PAYLOAD, Collections.singletonMap("cause", AttestationFailure.BAD_PAYLOAD.explain())); Error("request body is not a valid json", 400, rc, null); return; } @@ -233,7 +233,7 @@ private void handleAttestAsync(RoutingContext rc) { String request = json == null ? null : json.getString("attestation_request"); if (request == null || request.isEmpty()) { - setAttestationFailureReason(rc, AttestationFailureReason.NO_ATTESTATION_REQUEST_ATTACHED); + setAttestationFailureReason(rc, AttestationFailure.BAD_PAYLOAD, Collections.singletonMap("cause", AttestationFailure.BAD_PAYLOAD.explain())); Error("no attestation_request attached", 400, rc, null); return; } @@ -243,7 +243,8 @@ private void handleAttestAsync(RoutingContext rc) { try { attestationService.attest(protocol, request, clientPublicKey, ar -> { if (!ar.succeeded()) { - setAttestationFailureReason(rc, AttestationFailureReason.ATTESTATION_FAILURE, Collections.singletonMap("cause", ar.cause().getMessage())); + // 500 is only for unknown errors in the attestation processing + setAttestationFailureReason(rc, AttestationFailure.INTERNAL_ERROR, Collections.singletonMap("cause", ar.cause().getMessage())); logger.warn("attestation failure: ", ar.cause()); Error("attestation failure", 500, rc, null); return; @@ -251,14 +252,28 @@ private void handleAttestAsync(RoutingContext rc) { final AttestationResult attestationResult = ar.result(); if (!attestationResult.isSuccess()) { - setAttestationFailureReason(rc, AttestationFailureReason.ATTESTATION_FAILURE, Collections.singletonMap("reason", attestationResult.getReason())); - Error(attestationResult.getReason(), 401, rc, null); - return; + AttestationFailure failure = attestationResult.getFailure(); + switch (failure) { + case AttestationFailure.UNKNOWN_ATTESTATION_URL: + case AttestationFailure.FORBIDDEN_ENCLAVE: + case AttestationFailure.BAD_FORMAT: + case AttestationFailure.INVALID_PROTOCOL: + case AttestationFailure.BAD_CERTIFICATE: + case AttestationFailure.BAD_PAYLOAD: + setAttestationFailureReason(rc, failure, Collections.singletonMap("reason", attestationResult.getReason())); + Error(attestationResult.getReason(), 403, rc, failure.explain()); + return; + case AttestationFailure.UNKNOWN: + case AttestationFailure.INTERNAL_ERROR: + setAttestationFailureReason(rc, failure, Collections.singletonMap("reason", attestationResult.getReason())); + Error(attestationResult.getReason(), 500, rc, failure.explain()); + return; + } } if (json.containsKey("operator_type") && !operator.getOperatorType().name().equalsIgnoreCase(json.getString("operator_type"))) { - setAttestationFailureReason(rc, AttestationFailureReason.ATTESTATION_FAILURE); - Error("attestation failure; invalid operator type", 400, rc, null); + setAttestationFailureReason(rc, AttestationFailure.INVALID_TYPE, Collections.singletonMap("reason", AttestationFailure.INVALID_TYPE.explain())); + Error("attestation failure; invalid operator type", 403, rc, null); return; } @@ -294,7 +309,7 @@ private void handleAttestAsync(RoutingContext rc) { } } } catch (Exception e) { - Error("attestation failure", 500, rc, null); + Error("attestation failure", 500, rc, AttestationFailure.INTERNAL_ERROR.explain()); return; } @@ -302,8 +317,8 @@ private void handleAttestAsync(RoutingContext rc) { Success(rc, responseObj); }); } catch (AttestationService.NotFound e) { - setAttestationFailureReason(rc, AttestationFailureReason.INVALID_PROTOCOL); - Error("protocol not found", 500, rc, null); + setAttestationFailureReason(rc, AttestationFailure.INVALID_PROTOCOL, Collections.singletonMap("cause", AttestationFailure.INVALID_PROTOCOL.explain())); + Error("protocol not found", 403, rc, null); } } @@ -316,7 +331,7 @@ private static String encodeAttestationToken(RoutingContext rc, AttestationResul cipher.init(Cipher.ENCRYPT_MODE, publicKey); return Base64.getEncoder().encodeToString(cipher.doFinal(encodedAttestationToken.getBytes(StandardCharsets.UTF_8))); } catch (Exception e) { - setAttestationFailureReason(rc, AttestationFailureReason.RESPONSE_ENCRYPTION_EXCEPTION, Collections.singletonMap("exception", e.getMessage())); + setAttestationFailureReason(rc, AttestationFailure.RESPONSE_ENCRYPTION_ERROR, Collections.singletonMap("exception", e.getMessage())); logger.warn("attestation failure: exception while encrypting response", e); throw e; } @@ -333,10 +348,9 @@ private Map.Entry getJWTTokens(RoutingContext rc, IAuthorizable String optOutJwtToken = this.operatorJWTTokenProvider.getOptOutJWTToken(operator.getKeyHash(), operator.getName(), operator.getRoles(), operator.getSiteId(), enclaveId, operator.getProtocol(), clientVersion, expiresAt); String coreJwtToken = this.operatorJWTTokenProvider.getCoreJWTToken(operator.getKeyHash(), operator.getName(), operator.getRoles(), operator.getSiteId(), enclaveId, operator.getProtocol(), clientVersion, expiresAt); - Map.Entry tokens = new AbstractMap.SimpleEntry<>(optOutJwtToken, coreJwtToken); - return tokens; + return new AbstractMap.SimpleEntry<>(optOutJwtToken, coreJwtToken); } catch (JWTTokenProvider.JwtSigningException e) { - setAttestationFailureReason(rc, AttestationFailureReason.INTERNAL_ERROR, Collections.singletonMap("exception", e.getMessage())); + setAttestationFailureReason(rc, AttestationFailure.INTERNAL_ERROR, Collections.singletonMap("exception", e.getMessage())); logger.error("OptOut JWT token generation failed", e); throw e; } @@ -346,11 +360,11 @@ private Map.Entry getJWTTokens(RoutingContext rc, IAuthorizable return null; } - private static void setAttestationFailureReason(RoutingContext context, AttestationFailureReason reason) { + private static void setAttestationFailureReason(RoutingContext context, AttestationFailure reason) { setAttestationFailureReason(context, reason, null); } - private static void setAttestationFailureReason(RoutingContext context, AttestationFailureReason reason, Map data) { + private static void setAttestationFailureReason(RoutingContext context, AttestationFailure reason, Map data) { context.put(com.uid2.core.Const.RoutingContextData.ATTESTATION_FAILURE_REASON_PROP, reason); context.put(com.uid2.core.Const.RoutingContextData.ATTESTATION_FAILURE_DATA_PROP, data); } @@ -523,14 +537,13 @@ JsonObject make(String name, String failReason) { } Object enclavesObj = main.getValue("enclaves"); - if (!(enclavesObj instanceof JsonArray)) { + if (!(enclavesObj instanceof JsonArray enclaves)) { logger.info("enclave register has been called without .enclaves key"); Error("error", 400, rc, ".enclaves needs to be an array"); return; } JsonArray res = new JsonArray(); - JsonArray enclaves = (JsonArray) enclavesObj; for (int i = 0; i < enclaves.size(); i++) { Result result = new Result(); JsonObject item = enclaves.getJsonObject(i); @@ -573,7 +586,11 @@ private static String getClientVersionFromHeader(RoutingContext rc, IAuthorizabl String clientVersion = "unknown client version"; if (rc.request().headers().contains(Const.Http.AppVersionHeader)) { var client = VertxUtils.parseClientAppVersion(rc.request().headers().get(Const.Http.AppVersionHeader)); - clientVersion = profile.getContact() + "|" + client.getKey() + "|" + client.getValue(); + if (client != null) { + clientVersion = profile.getContact() + "|" + client.getKey() + "|" + client.getValue(); + } else { + clientVersion = profile.getContact() + "|null client key"; + } } return clientVersion; } diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index 6e7076d..8be006a 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -220,7 +221,7 @@ void attestUnknownAttestationProtocol(Vertx vertx, VertxTestContext testContext) post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy"), ar -> { assertTrue(ar.succeeded()); HttpResponse response = ar.result(); - assertEquals(500, response.statusCode()); + assertEquals(403, response.statusCode()); testContext.completeNow(); }); } @@ -240,17 +241,34 @@ void attestFailureWithAnException(Vertx vertx, VertxTestContext testContext) { }); } - @Test - void attestFailureWithResult(Vertx vertx, VertxTestContext testContext) { + @ParameterizedTest + @EnumSource(value = AttestationFailure.class, names = {"UNKNOWN_ATTESTATION_URL", "FORBIDDEN_ENCLAVE", "BAD_FORMAT", "INVALID_PROTOCOL", "BAD_CERTIFICATE", "BAD_PAYLOAD"}) + void attestFailureWithResultClientError(AttestationFailure failure, Vertx vertx, VertxTestContext testContext) { + fakeAuth(Role.OPERATOR); + addAttestationProvider(attestationProtocol); + onHandleAttestationRequest(() -> { + return Future.succeededFuture(new AttestationResult(failure)); + }); + post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy"), ar -> { + assertTrue(ar.succeeded()); + HttpResponse response = ar.result(); + assertEquals(403, response.statusCode()); + testContext.completeNow(); + }); + } + + @ParameterizedTest + @EnumSource(value = AttestationFailure.class, names = {"UNKNOWN", "INTERNAL_ERROR"}) + void attestFailureWithResultServerError(AttestationFailure failure, Vertx vertx, VertxTestContext testContext) { fakeAuth(Role.OPERATOR); addAttestationProvider(attestationProtocol); onHandleAttestationRequest(() -> { - return Future.succeededFuture(new AttestationResult(AttestationFailure.BAD_PAYLOAD)); + return Future.succeededFuture(new AttestationResult(failure)); }); post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy"), ar -> { assertTrue(ar.succeeded()); HttpResponse response = ar.result(); - assertEquals(401, response.statusCode()); + assertEquals(500, response.statusCode()); testContext.completeNow(); }); } @@ -293,7 +311,7 @@ void attestOperatorTypeMismatchNoEncryption(String operatorType, Vertx vertx, Ve post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy", operatorType.equalsIgnoreCase("public") ? "private" : "public"), ar -> { assertTrue(ar.succeeded()); HttpResponse response = ar.result(); - assertEquals(400, response.statusCode()); + assertEquals(403, response.statusCode()); JsonObject json = response.bodyAsJsonObject(); assertEquals("attestation failure; invalid operator type", json.getString("status")); testContext.completeNow(); @@ -356,7 +374,7 @@ void attestOperatorTypeMismatchWithEncryption(String operatorType, Vertx vertx, post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy", operatorType.equalsIgnoreCase("public") ? "private" : "public"), ar -> { assertTrue(ar.succeeded()); HttpResponse response = ar.result(); - assertEquals(400, response.statusCode()); + assertEquals(403, response.statusCode()); JsonObject json = response.bodyAsJsonObject(); assertEquals("attestation failure; invalid operator type", json.getString("status")); testContext.completeNow(); From 154a66a6d017517d88e2420eb64578616491d48e Mon Sep 17 00:00:00 2001 From: Thomas Manson Date: Mon, 14 Oct 2024 15:47:12 +1100 Subject: [PATCH 2/5] Return AttestionFailures or Retryable failures --- .../com/uid2/core/vertx/CoreVerticle.java | 11 +- .../com/uid2/core/vertx/TestCoreVerticle.java | 383 ++++++++++-------- 2 files changed, 231 insertions(+), 163 deletions(-) diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index 3674a3c..202c993 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -243,6 +243,13 @@ private void handleAttestAsync(RoutingContext rc) { try { attestationService.attest(protocol, request, clientPublicKey, ar -> { if (!ar.succeeded()) { + if (ar.cause() instanceof AttestationClientException ace && ace.IsClientError()) { + setAttestationFailureReason(rc, ace.getAttestationFailure(), Collections.singletonMap("reason", ace.getAttestationFailure().explain())); + logger.warn("attestation failure: ", ace); + Error("attestation failure", 400, rc, ace.getAttestationFailure().explain()); + return; + } + // 500 is only for unknown errors in the attestation processing setAttestationFailureReason(rc, AttestationFailure.INTERNAL_ERROR, Collections.singletonMap("cause", ar.cause().getMessage())); logger.warn("attestation failure: ", ar.cause()); @@ -254,12 +261,12 @@ private void handleAttestAsync(RoutingContext rc) { if (!attestationResult.isSuccess()) { AttestationFailure failure = attestationResult.getFailure(); switch (failure) { - case AttestationFailure.UNKNOWN_ATTESTATION_URL: - case AttestationFailure.FORBIDDEN_ENCLAVE: case AttestationFailure.BAD_FORMAT: case AttestationFailure.INVALID_PROTOCOL: case AttestationFailure.BAD_CERTIFICATE: case AttestationFailure.BAD_PAYLOAD: + case AttestationFailure.UNKNOWN_ATTESTATION_URL: + case AttestationFailure.FORBIDDEN_ENCLAVE: setAttestationFailureReason(rc, failure, Collections.singletonMap("reason", attestationResult.getReason())); Error(attestationResult.getReason(), 403, rc, failure.explain()); return; diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index 8be006a..cc1450b 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -103,10 +103,10 @@ private void fakeAuth(Role... roles) { } private void fakeAuth(String attestationProtocol, String operatorType, Role... roles) { - if(operatorType.isEmpty()) { + if (operatorType.isEmpty()) { operatorType = "PRIVATE"; } - OperatorKey operatorKey = new OperatorKey("test-key-hash", "test-key-salt", "test-name", "test-contact", attestationProtocol, 0, false, 88, new HashSet<>(Arrays.asList(roles)), OperatorType.valueOf(operatorType.toUpperCase()), "test-key-id"); + OperatorKey operatorKey = new OperatorKey("test-key-hash", "test-key-salt", "test-name", "test-contact", attestationProtocol, 0, false, 88, new HashSet<>(Arrays.asList(roles)), OperatorType.valueOf(operatorType.toUpperCase()), "test-key-id"); when(authProvider.get(any())).thenReturn(operatorKey); } @@ -157,7 +157,7 @@ private void onHandleAttestationRequest(Callable> f) { private static String makeAttestationRequestJson(String attestationRequest, String publicKey, String operatorType) { JsonObject json = new JsonObject(); - if(!operatorType.isEmpty()) { + if (!operatorType.isEmpty()) { json.put("operator_type", operatorType); } if (attestationRequest != null) { @@ -250,10 +250,14 @@ void attestFailureWithResultClientError(AttestationFailure failure, Vertx vertx, return Future.succeededFuture(new AttestationResult(failure)); }); post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy"), ar -> { - assertTrue(ar.succeeded()); - HttpResponse response = ar.result(); - assertEquals(403, response.statusCode()); - testContext.completeNow(); + try { + assertTrue(ar.succeeded()); + HttpResponse response = ar.result(); + assertEquals(403, response.statusCode()); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -266,10 +270,14 @@ void attestFailureWithResultServerError(AttestationFailure failure, Vertx vertx, return Future.succeededFuture(new AttestationResult(failure)); }); post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy"), ar -> { - assertTrue(ar.succeeded()); - HttpResponse response = ar.result(); - assertEquals(500, response.statusCode()); - testContext.completeNow(); + try { + assertTrue(ar.succeeded()); + HttpResponse response = ar.result(); + assertEquals(500, response.statusCode()); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -285,15 +293,19 @@ void attestSuccessNoEncryption(String operatorType, Vertx vertx, VertxTestContex EncryptedAttestationToken encryptedAttestationToken = new EncryptedAttestationToken("test-attestation-token", Instant.ofEpochMilli(111)); when(attestationTokenService.createToken(any())).thenReturn(encryptedAttestationToken); post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy"), ar -> { - assertTrue(ar.succeeded()); - HttpResponse response = ar.result(); - assertEquals(200, response.statusCode()); - JsonObject json = response.bodyAsJsonObject(); - String attestationToken = json.getJsonObject("body").getString("attestation_token"); - String expiresAt = json.getJsonObject("body").getString("expiresAt"); - assertEquals("test-attestation-token", attestationToken); - assertEquals("1970-01-01T00:00:00.111Z", expiresAt); - testContext.completeNow(); + try { + assertTrue(ar.succeeded()); + HttpResponse response = ar.result(); + assertEquals(200, response.statusCode()); + JsonObject json = response.bodyAsJsonObject(); + String attestationToken = json.getJsonObject("body").getString("attestation_token"); + String expiresAt = json.getJsonObject("body").getString("expiresAt"); + assertEquals("test-attestation-token", attestationToken); + assertEquals("1970-01-01T00:00:00.111Z", expiresAt); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -309,12 +321,16 @@ void attestOperatorTypeMismatchNoEncryption(String operatorType, Vertx vertx, Ve EncryptedAttestationToken encryptedAttestationToken = new EncryptedAttestationToken("test-attestation-token", Instant.ofEpochMilli(111)); when(attestationTokenService.createToken(any())).thenReturn(encryptedAttestationToken); post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy", operatorType.equalsIgnoreCase("public") ? "private" : "public"), ar -> { - assertTrue(ar.succeeded()); - HttpResponse response = ar.result(); - assertEquals(403, response.statusCode()); - JsonObject json = response.bodyAsJsonObject(); - assertEquals("attestation failure; invalid operator type", json.getString("status")); - testContext.completeNow(); + try { + assertTrue(ar.succeeded()); + HttpResponse response = ar.result(); + assertEquals(403, response.statusCode()); + JsonObject json = response.bodyAsJsonObject(); + assertEquals("attestation failure; invalid operator type", json.getString("status")); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -334,24 +350,28 @@ void attestSuccessWithEncryption(String operatorType, Vertx vertx, VertxTestCont EncryptedAttestationToken encryptedAttestationToken = new EncryptedAttestationToken("test-attestation-token", Instant.ofEpochMilli(111)); when(attestationTokenService.createToken(any())).thenReturn(encryptedAttestationToken); post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy", operatorType), ar -> { - assertTrue(ar.succeeded()); - HttpResponse response = ar.result(); - assertEquals(200, response.statusCode()); - JsonObject json = response.bodyAsJsonObject(); - String attestationToken = json.getJsonObject("body").getString("attestation_token"); - assertNotEquals("", attestationToken); - String expiresAt = json.getJsonObject("body").getString("expiresAt"); - assertEquals("1970-01-01T00:00:00.111Z", expiresAt); - String[] decryptedAttestationToken = {""}; - assertDoesNotThrow(() -> { - Cipher cipher = Cipher.getInstance(Const.Name.AsymetricEncryptionCipherClass); - cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); - decryptedAttestationToken[0] = new String(cipher.doFinal(Base64.getDecoder().decode(attestationToken))); - }); - - assertEquals("test-attestation-token", decryptedAttestationToken[0]); + try { + assertTrue(ar.succeeded()); + HttpResponse response = ar.result(); + assertEquals(200, response.statusCode()); + JsonObject json = response.bodyAsJsonObject(); + String attestationToken = json.getJsonObject("body").getString("attestation_token"); + assertNotEquals("", attestationToken); + String expiresAt = json.getJsonObject("body").getString("expiresAt"); + assertEquals("1970-01-01T00:00:00.111Z", expiresAt); + String[] decryptedAttestationToken = {""}; + assertDoesNotThrow(() -> { + Cipher cipher = Cipher.getInstance(Const.Name.AsymetricEncryptionCipherClass); + cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); + decryptedAttestationToken[0] = new String(cipher.doFinal(Base64.getDecoder().decode(attestationToken))); + }); - testContext.completeNow(); + assertEquals("test-attestation-token", decryptedAttestationToken[0]); + + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -372,12 +392,16 @@ void attestOperatorTypeMismatchWithEncryption(String operatorType, Vertx vertx, EncryptedAttestationToken encryptedAttestationToken = new EncryptedAttestationToken("test-attestation-token", Instant.ofEpochMilli(111)); when(attestationTokenService.createToken(any())).thenReturn(encryptedAttestationToken); post(vertx, "attest", makeAttestationRequestJson("xxx", "yyy", operatorType.equalsIgnoreCase("public") ? "private" : "public"), ar -> { - assertTrue(ar.succeeded()); - HttpResponse response = ar.result(); - assertEquals(403, response.statusCode()); - JsonObject json = response.bodyAsJsonObject(); - assertEquals("attestation failure; invalid operator type", json.getString("status")); - testContext.completeNow(); + try { + assertTrue(ar.succeeded()); + HttpResponse response = ar.result(); + assertEquals(403, response.statusCode()); + JsonObject json = response.bodyAsJsonObject(); + assertEquals("attestation failure; invalid operator type", json.getString("status")); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -396,25 +420,30 @@ void attestSuccessWithEncryptionNoPublicKeyOnRequest(Vertx vertx, VertxTestConte EncryptedAttestationToken encryptedAttestationToken = new EncryptedAttestationToken("test-attestation-token", Instant.ofEpochMilli(111)); when(attestationTokenService.createToken(any())).thenReturn(encryptedAttestationToken); post(vertx, "attest", makeAttestationRequestJson("xxx", null), ar -> { - assertTrue(ar.succeeded()); - HttpResponse response = ar.result(); - assertEquals(200, response.statusCode()); - JsonObject json = response.bodyAsJsonObject(); - String attestationToken = json.getJsonObject("body").getString("attestation_token"); - assertNotEquals("", attestationToken); - String expiresAt = json.getJsonObject("body").getString("expiresAt"); - assertEquals("1970-01-01T00:00:00.111Z", expiresAt); - - String[] decryptedAttestationToken = {""}; - assertDoesNotThrow(() -> { - Cipher cipher = Cipher.getInstance(Const.Name.AsymetricEncryptionCipherClass); - cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); - decryptedAttestationToken[0] = new String(cipher.doFinal(Base64.getDecoder().decode(attestationToken))); - }); - - assertEquals("test-attestation-token", decryptedAttestationToken[0]); + try { + assertTrue(ar.succeeded()); - testContext.completeNow(); + HttpResponse response = ar.result(); + assertEquals(200, response.statusCode()); + JsonObject json = response.bodyAsJsonObject(); + String attestationToken = json.getJsonObject("body").getString("attestation_token"); + assertNotEquals("", attestationToken); + String expiresAt = json.getJsonObject("body").getString("expiresAt"); + assertEquals("1970-01-01T00:00:00.111Z", expiresAt); + + String[] decryptedAttestationToken = {""}; + assertDoesNotThrow(() -> { + Cipher cipher = Cipher.getInstance(Const.Name.AsymetricEncryptionCipherClass); + cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); + decryptedAttestationToken[0] = new String(cipher.doFinal(Base64.getDecoder().decode(attestationToken))); + }); + + assertEquals("test-attestation-token", decryptedAttestationToken[0]); + + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -435,22 +464,26 @@ void attestOptOutJWTCalledUnknownClient(Vertx vertx, VertxTestContext testContex when(operatorJWTTokenProvider.getOptOutJWTToken("test-key-hash", "test-name", Set.of(Role.OPERATOR, Role.OPTOUT), 88, "test-enclaveId", attestationProtocol, "unknown client version", Instant.ofEpochMilli(111))).thenReturn("dummy_token_optout"); when(operatorJWTTokenProvider.getCoreJWTToken("test-key-hash", "test-name", Set.of(Role.OPERATOR, Role.OPTOUT), 88, "test-enclaveId", attestationProtocol, "unknown client version", Instant.ofEpochMilli(111))).thenReturn("dummy_token_core"); post(vertx, "attest", makeAttestationRequestJson("xxx", null), ar -> { - assertTrue(ar.succeeded()); - HttpResponse response = ar.result(); - try { - verify(operatorJWTTokenProvider, times(1)).getCoreJWTToken("test-key-hash", "test-name", Set.of(Role.OPERATOR, Role.OPTOUT), 88, "test-enclaveId", attestationProtocol, "unknown client version", Instant.ofEpochMilli(111)); - verify(operatorJWTTokenProvider, times(1)).getOptOutJWTToken("test-key-hash", "test-name", Set.of(Role.OPERATOR, Role.OPTOUT), 88, "test-enclaveId", attestationProtocol, "unknown client version", Instant.ofEpochMilli(111)); - JsonObject json = response.bodyAsJsonObject(); - String jwtOptout = json.getJsonObject("body").getString("attestation_jwt_optout"); - String jwtCore = json.getJsonObject("body").getString("attestation_jwt_core"); - assertEquals("dummy_token_optout", jwtOptout); - assertEquals("dummy_token_core", jwtCore); - } catch (Exception e) { - testContext.failNow(e); - } + assertTrue(ar.succeeded()); + HttpResponse response = ar.result(); + + try { + verify(operatorJWTTokenProvider, times(1)).getCoreJWTToken("test-key-hash", "test-name", Set.of(Role.OPERATOR, Role.OPTOUT), 88, "test-enclaveId", attestationProtocol, "unknown client version", Instant.ofEpochMilli(111)); + verify(operatorJWTTokenProvider, times(1)).getOptOutJWTToken("test-key-hash", "test-name", Set.of(Role.OPERATOR, Role.OPTOUT), 88, "test-enclaveId", attestationProtocol, "unknown client version", Instant.ofEpochMilli(111)); + JsonObject json = response.bodyAsJsonObject(); + String jwtOptout = json.getJsonObject("body").getString("attestation_jwt_optout"); + String jwtCore = json.getJsonObject("body").getString("attestation_jwt_core"); + assertEquals("dummy_token_optout", jwtOptout); + assertEquals("dummy_token_core", jwtCore); + } catch (Exception e) { + testContext.failNow(e); + } - testContext.completeNow(); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -527,10 +560,14 @@ void multipartRequestWrongMethodForMultipart(Vertx vertx, VertxTestContext testC form.set("secondValue", "value2"); get(vertx, "/sites/refresh", form, (ar) -> { - HttpResponse response = ar.result(); - assertEquals(400, response.statusCode()); - assertEquals("Content-Type \"multipart/*\" Not Allowed\"", response.bodyAsString()); - testContext.completeNow(); + try { + HttpResponse response = ar.result(); + assertEquals(400, response.statusCode()); + assertEquals("Content-Type \"multipart/*\" Not Allowed\"", response.bodyAsString()); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -541,30 +578,42 @@ void multipartRequestWrongMethodForEndpoint(Vertx vertx, VertxTestContext testCo form.set("secondValue", "value2"); post(vertx, "/sites/refresh", form, (ar) -> { - HttpResponse response = ar.result(); - assertEquals(405, response.statusCode()); - assertEquals("Method Not Allowed", response.statusMessage()); - testContext.completeNow(); + try { + HttpResponse response = ar.result(); + assertEquals(405, response.statusCode()); + assertEquals("Method Not Allowed", response.statusMessage()); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @Test void wrongMethodForEndpoint(Vertx vertx, VertxTestContext testContext) { post(vertx, "/sites/refresh", makeAttestationRequestJson(null, null), ar -> { - HttpResponse response = ar.result(); - assertEquals(405, response.statusCode()); - assertEquals("Method Not Allowed", response.statusMessage()); - testContext.completeNow(); + try { + HttpResponse response = ar.result(); + assertEquals(405, response.statusCode()); + assertEquals("Method Not Allowed", response.statusMessage()); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @Test void wrongMethodForEndpointS3(Vertx vertx, VertxTestContext testContext) { post(vertx, "/s3encryption_keys/retrieve", makeAttestationRequestJson(null, null), ar -> { - HttpResponse response = ar.result(); - assertEquals(405, response.statusCode()); - assertEquals("Method Not Allowed", response.statusMessage()); - testContext.completeNow(); + try { + HttpResponse response = ar.result(); + assertEquals(405, response.statusCode()); + assertEquals("Method Not Allowed", response.statusMessage()); + testContext.completeNow(); + } catch (Throwable ex) { + testContext.failNow(ex); + } }); } @@ -584,26 +633,30 @@ void s3encryptionKeyRetrieveSuccess(Vertx vertx, VertxTestContext testContext) { when(s3KeyProvider.getKeys(88)).thenReturn(keys); get(vertx, "s3encryption_keys/retrieve", ar -> { - if (ar.succeeded()) { - HttpResponse response = ar.result(); - assertEquals(200, response.statusCode()); + try { + if (ar.succeeded()) { + HttpResponse response = ar.result(); + assertEquals(200, response.statusCode()); - JsonObject json = response.bodyAsJsonObject(); - JsonArray s3KeysArray = json.getJsonArray("s3Keys"); + JsonObject json = response.bodyAsJsonObject(); + JsonArray s3KeysArray = json.getJsonArray("s3Keys"); - assertNotNull(s3KeysArray); - assertEquals(1, s3KeysArray.size()); + assertNotNull(s3KeysArray); + assertEquals(1, s3KeysArray.size()); - JsonObject s3KeyJson = s3KeysArray.getJsonObject(0); - assertEquals(1, s3KeyJson.getInteger("id")); - assertEquals(88, s3KeyJson.getInteger("siteId")); - assertEquals(1687635529, s3KeyJson.getLong("activates")); - assertEquals(1687808329, s3KeyJson.getLong("created")); - assertEquals("newSecret", s3KeyJson.getString("secret")); + JsonObject s3KeyJson = s3KeysArray.getJsonObject(0); + assertEquals(1, s3KeyJson.getInteger("id")); + assertEquals(88, s3KeyJson.getInteger("siteId")); + assertEquals(1687635529, s3KeyJson.getLong("activates")); + assertEquals(1687808329, s3KeyJson.getLong("created")); + assertEquals("newSecret", s3KeyJson.getString("secret")); - testContext.completeNow(); - } else { - testContext.failNow(ar.cause()); + testContext.completeNow(); + } else { + testContext.failNow(ar.cause()); + } + } catch (Throwable ex) { + testContext.failNow(ex); } }); } @@ -628,28 +681,32 @@ void s3encryptionKeyRetrieveSuccessWithThreeKeys(Vertx vertx, VertxTestContext t when(s3KeyProvider.getKeys(88)).thenReturn(keys); get(vertx, "s3encryption_keys/retrieve", ar -> { - if (ar.succeeded()) { - HttpResponse response = ar.result(); - assertEquals(200, response.statusCode()); - - JsonObject json = response.bodyAsJsonObject(); - JsonArray s3KeysArray = json.getJsonArray("s3Keys"); - - assertNotNull(s3KeysArray); - assertEquals(3, s3KeysArray.size()); + try { + if (ar.succeeded()) { + HttpResponse response = ar.result(); + assertEquals(200, response.statusCode()); + + JsonObject json = response.bodyAsJsonObject(); + JsonArray s3KeysArray = json.getJsonArray("s3Keys"); + + assertNotNull(s3KeysArray); + assertEquals(3, s3KeysArray.size()); + + for (int i = 0; i < 3; i++) { + JsonObject s3KeyJson = s3KeysArray.getJsonObject(i); + assertEquals(i + 1, s3KeyJson.getInteger("id")); + assertEquals(88, s3KeyJson.getInteger("siteId")); + assertEquals(1687635529 + i, s3KeyJson.getLong("activates")); + assertEquals(1687808329 + i, s3KeyJson.getLong("created")); + assertEquals("secret" + (i + 1), s3KeyJson.getString("secret")); + } - for (int i = 0; i < 3; i++) { - JsonObject s3KeyJson = s3KeysArray.getJsonObject(i); - assertEquals(i + 1, s3KeyJson.getInteger("id")); - assertEquals(88, s3KeyJson.getInteger("siteId")); - assertEquals(1687635529 + i, s3KeyJson.getLong("activates")); - assertEquals(1687808329 + i, s3KeyJson.getLong("created")); - assertEquals("secret" + (i + 1), s3KeyJson.getString("secret")); + testContext.completeNow(); + } else { + testContext.failNow(ar.cause()); } - - testContext.completeNow(); - } else { - testContext.failNow(ar.cause()); + } catch (Throwable ex) { + testContext.failNow(ex); } }); } @@ -668,34 +725,38 @@ void s3encryptionKeyRetrieveNoKeysOrError(Vertx vertx, VertxTestContext testCont when(s3KeyProvider.getKeys(anyInt())).thenReturn(Collections.emptyList()); get(vertx, "s3encryption_keys/retrieve", ar -> { - if (ar.succeeded()) { - HttpResponse response = ar.result(); - assertEquals(500, response.statusCode()); - - JsonObject json = response.bodyAsJsonObject(); - assertEquals("No S3 keys found", json.getString("status")); - assertTrue(json.getString("message").contains("No S3 keys found for siteId:")); - - // Test case 2: Exception thrown - when(s3KeyProvider.getKeys(anyInt())).thenThrow(new RuntimeException("Test exception")); - - get(vertx, "s3encryption_keys/retrieve", ar2 -> { - if (ar2.succeeded()) { - HttpResponse response2 = ar2.result(); - 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")); - - testContext.completeNow(); - } else { - testContext.failNow(ar2.cause()); - } - }); - } else { - testContext.failNow(ar.cause()); + try { + if (ar.succeeded()) { + HttpResponse response = ar.result(); + assertEquals(500, response.statusCode()); + + JsonObject json = response.bodyAsJsonObject(); + assertEquals("No S3 keys found", json.getString("status")); + assertTrue(json.getString("message").contains("No S3 keys found for siteId:")); + + // Test case 2: Exception thrown + when(s3KeyProvider.getKeys(anyInt())).thenThrow(new RuntimeException("Test exception")); + + get(vertx, "s3encryption_keys/retrieve", ar2 -> { + if (ar2.succeeded()) { + HttpResponse response2 = ar2.result(); + 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")); + + testContext.completeNow(); + } else { + testContext.failNow(ar2.cause()); + } + }); + } else { + testContext.failNow(ar.cause()); + } + } catch (Throwable ex) { + testContext.failNow(ex); } }); } From 93a5745d3e18a96aae16d1365f28c96b72f6d18f Mon Sep 17 00:00:00 2001 From: Thomas Manson Date: Mon, 14 Oct 2024 16:42:01 +1100 Subject: [PATCH 3/5] Update version of shared --- pom.xml | 2 +- src/main/java/com/uid2/core/vertx/CoreVerticle.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 166ef70..037e0c7 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ com.uid2.core.vertx.CoreVerticle io.vertx.core.Launcher - 7.19.1-SNAPSHOT + 7.19.2-alpha-151-SNAPSHOT ${project.version} diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index 202c993..00eccda 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -79,7 +79,6 @@ public class CoreVerticle extends AbstractVerticle { private final ISaltMetadataProvider saltMetadataProvider; private final IPartnerMetadataProvider partnerMetadataProvider; private final OperatorJWTTokenProvider operatorJWTTokenProvider; - //private final JwtService jwtService; private final RotatingS3KeyProvider s3KeyProvider; public CoreVerticle(ICloudStorage cloudStorage, @@ -103,7 +102,7 @@ public CoreVerticle(ICloudStorage cloudStorage, final String jwtAudience = ConfigStore.Global.get(Const.Config.CorePublicUrlProp); final String jwtIssuer = ConfigStore.Global.get(Const.Config.CorePublicUrlProp); - Boolean enforceJwt = ConfigStore.Global.getBoolean(EnforceJwtProp); + Boolean enforceJwt = ConfigStore.Global.getBoolean(Const.Config.EnforceJwtProp); if (enforceJwt == null) { enforceJwt = false; } From 9ab318ad5813fadfec6b6024ae2785c0e01301b8 Mon Sep 17 00:00:00 2001 From: Release Workflow Date: Mon, 14 Oct 2024 05:48:13 +0000 Subject: [PATCH 4/5] [CI Pipeline] Released Snapshot version: 2.20.1-alpha-50-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 037e0c7..a91304e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-core - 2.20.0 + 2.20.1-alpha-50-SNAPSHOT UTF-8 From 0dbd2cd900db1595e379d8bd17ed91eed8783f1f Mon Sep 17 00:00:00 2001 From: Thomas Manson Date: Mon, 21 Oct 2024 11:20:13 +1100 Subject: [PATCH 5/5] Update version of shared --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a91304e..c8042b0 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ com.uid2.core.vertx.CoreVerticle io.vertx.core.Launcher - 7.19.2-alpha-151-SNAPSHOT + 7.20.0 ${project.version}