From 41b5f5ec7a99dcba0b288a93fe8d0065100ac725 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 26 Jun 2024 14:58:43 -0600 Subject: [PATCH 01/25] Added unit test for `Application` class --- pom.xml | 26 +++++++++++++++++-- .../us/dot/its/jpo/sec/ApplicationTest.java | 25 ++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/test/java/us/dot/its/jpo/sec/ApplicationTest.java diff --git a/pom.xml b/pom.xml index a517083..5aabfae 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ usdot-jpo-ode-1 https://sonarcloud.io - 0.8.8 + 0.8.11 jacoco reuseReports ${project.basedir}/target/site/jacoco/jacoco.xml @@ -95,7 +95,29 @@ org.springframework.boot spring-boot-maven-plugin - + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M7 + + + -javaagent:${user.home}/.m2/repository/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar + + all + exit + + ${loader.path} + ${project.build.directory} + + + + + org.apache.maven.surefire + surefire-junit4 + 3.1.2 + + + org.jacoco jacoco-maven-plugin diff --git a/src/test/java/us/dot/its/jpo/sec/ApplicationTest.java b/src/test/java/us/dot/its/jpo/sec/ApplicationTest.java new file mode 100644 index 0000000..c73b2d0 --- /dev/null +++ b/src/test/java/us/dot/its/jpo/sec/ApplicationTest.java @@ -0,0 +1,25 @@ +package us.dot.its.jpo.sec; + +import org.junit.Test; +import org.springframework.boot.SpringApplication; + +import mockit.Capturing; +import mockit.Expectations; + +public class ApplicationTest { + + @Capturing + SpringApplication capturingSpringApplication; + + @Test + public void test() { + new Expectations() { + { + SpringApplication.run((Class) any, (String[]) any); + times = 1; + } + }; + Application.main(new String[] { "testArg" }); + } + +} From 402669de32326a6282689fa6d8f338b7b2efe1c1 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 26 Jun 2024 21:43:56 +0000 Subject: [PATCH 02/25] Added unit tests for base URI & endpoint path trimming --- .../sec/controllers/SignatureController.java | 29 +++++---- .../controllers/SignatureControllerTest.java | 59 +++++++++++++++++++ 2 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index 6323760..c9d5236 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -87,7 +87,6 @@ public static class Message { @RequestMapping(value = "/sign", method = RequestMethod.POST, produces = "application/json") @ResponseBody public ResponseEntity> sign(@RequestBody Message message) throws URISyntaxException { - logger.info("Received message: {}", message.msg); logger.info("Received sigValidityOverride: {}", message.sigValidityOverride); @@ -97,20 +96,7 @@ public ResponseEntity> sign(@RequestBody Message message) th logger.info("Signing using HSM"); response = signWithHsm(message); } else { - logger.debug("Before trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", - cryptoServiceBaseUri, cryptoServiceEndpointSignPath); - // Remove all slashes from the end of the URI, if any - while (cryptoServiceBaseUri != null && cryptoServiceBaseUri.endsWith("/")) { - cryptoServiceBaseUri = cryptoServiceBaseUri.substring(0, cryptoServiceBaseUri.lastIndexOf('/')); - } - - // Remove all slashes from the beginning of the path string, if any - while (cryptoServiceEndpointSignPath != null && cryptoServiceEndpointSignPath.startsWith("/")) { - cryptoServiceEndpointSignPath = cryptoServiceEndpointSignPath.substring(1); - } - - logger.debug("After Trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, - cryptoServiceEndpointSignPath); + trimBaseUriAndEndpointPath(); String resultString = message.msg; if (!StringUtils.isEmpty(cryptoServiceBaseUri) && !StringUtils.isEmpty(cryptoServiceEndpointSignPath)) { @@ -151,6 +137,19 @@ public ResponseEntity> sign(@RequestBody Message message) th } + public void trimBaseUriAndEndpointPath() { + logger.debug("Before trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, cryptoServiceEndpointSignPath); + // Remove all slashes from the end of the URI, if any + while (cryptoServiceBaseUri != null && cryptoServiceBaseUri.endsWith("/")) { + cryptoServiceBaseUri = cryptoServiceBaseUri.substring(0, cryptoServiceBaseUri.lastIndexOf('/')); + } + // Remove all slashes from the beginning of the path string, if any + while (cryptoServiceEndpointSignPath != null && cryptoServiceEndpointSignPath.startsWith("/")) { + cryptoServiceEndpointSignPath = cryptoServiceEndpointSignPath.substring(1); + } + logger.debug("After Trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, cryptoServiceEndpointSignPath); + } + private JSONObject forwardMessageToExternalService(Message message) throws URISyntaxException { HttpHeaders headers = new HttpHeaders(); diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java new file mode 100644 index 0000000..bd8d574 --- /dev/null +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -0,0 +1,59 @@ +package us.dot.its.jpo.sec.controllers; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.core.env.Environment; + +import mockit.Injectable; +import mockit.Tested; + +public class SignatureControllerTest { + @Tested + SignatureController testSignatureController; + + @Injectable + Environment environment; + + @BeforeEach + public void setUp() { + testSignatureController = new SignatureController(); + } + + @Test + public void testTrimBaseUriAndEndpointPath_TrailingSlashInUri() { + // prepare + setUp(); + String baseUri = "http://example.com/"; + String endpointPath = "endpoint"; + String expected = "http://example.com/endpoint"; + + testSignatureController.setCryptoServiceBaseUri(baseUri); + testSignatureController.setCryptoServiceEndpointSignPath(endpointPath); + + // execute + testSignatureController.trimBaseUriAndEndpointPath(); + + // verify + assertEquals(expected, testSignatureController.getCryptoServiceBaseUri() + "/" + testSignatureController.getCryptoServiceEndpointSignPath()); + } + + @Test + public void testTrimBaseUriAndEndpointPath_PrecedingSlashInPath() { + // prepare + setUp(); + String baseUri = "http://example.com"; + String endpointPath = "/endpoint"; + String expected = "http://example.com/endpoint"; + + testSignatureController.setCryptoServiceBaseUri(baseUri); + testSignatureController.setCryptoServiceEndpointSignPath(endpointPath); + + // execute + testSignatureController.trimBaseUriAndEndpointPath(); + + // verify + assertEquals(expected, testSignatureController.getCryptoServiceBaseUri() + "/" + testSignatureController.getCryptoServiceEndpointSignPath()); + } +} From f27691ae253b81c306997dfbcc111dc0c1d77759 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Mon, 1 Jul 2024 16:57:27 -0600 Subject: [PATCH 03/25] Added unit test for signing using HSM --- .../sec/controllers/SignatureController.java | 4 +++- .../controllers/SignatureControllerTest.java | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index c9d5236..5a18f08 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -117,10 +117,12 @@ public ResponseEntity> sign(@RequestBody Message message) th response = ResponseEntity.status(HttpStatus.OK) .body(Collections.singletonMap("result", new JSONObject(mapResult).toString())); } else { + // no response from external service response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Collections.singletonMap("error", "Error communicating with external service")); } } else { + // base URI or endpoint path not set, return the message unchanged String msg = "Properties sec.cryptoServiceBaseUri=" + cryptoServiceBaseUri + ", sec.cryptoServiceEndpointSignPath=" + cryptoServiceEndpointSignPath + " Not defined. Returning the message unchanged."; @@ -222,7 +224,7 @@ private KeyStore readStore() throws Exception { private ResponseEntity> signWithHsm(Message message) { return ResponseEntity.status(HttpStatus.OK).body( - Collections.singletonMap("result", message + "NOT IMPLEMENTED")); + Collections.singletonMap("result", message.msg + "NOT IMPLEMENTED")); } @Override diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index bd8d574..8b992e4 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -2,12 +2,18 @@ import static org.junit.Assert.assertEquals; +import java.net.URISyntaxException; +import java.util.Map; + +import org.json.JSONException; import org.junit.Test; import org.junit.jupiter.api.BeforeEach; import org.springframework.core.env.Environment; +import org.springframework.http.ResponseEntity; import mockit.Injectable; import mockit.Tested; +import us.dot.its.jpo.sec.controllers.SignatureController.Message; public class SignatureControllerTest { @Tested @@ -21,6 +27,22 @@ public void setUp() { testSignatureController = new SignatureController(); } + @Test + public void testSign_useHsm() throws URISyntaxException, JSONException { + // prepare + setUp(); + testSignatureController.setUseHsm(true); + Message message = new Message(); + message.msg = "test message"; + + // execute + ResponseEntity> response = testSignatureController.sign(message); + + // verify + assertEquals(1, response.getBody().size()); + assertEquals("test messageNOT IMPLEMENTED", response.getBody().get("result")); + } + @Test public void testTrimBaseUriAndEndpointPath_TrailingSlashInUri() { // prepare @@ -56,4 +78,5 @@ public void testTrimBaseUriAndEndpointPath_PrecedingSlashInPath() { // verify assertEquals(expected, testSignatureController.getCryptoServiceBaseUri() + "/" + testSignatureController.getCryptoServiceEndpointSignPath()); } + } From faac2e07da111d2b0cb7e91d86824acd369adbbe Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Thu, 11 Jul 2024 20:45:40 +0000 Subject: [PATCH 04/25] Implemented unit tests for `Message` class --- .../sec/controllers/SignatureController.java | 28 ++++++---------- .../us/dot/its/jpo/sec/models/Message.java | 31 +++++++++++++++++ .../controllers/SignatureControllerTest.java | 4 +-- .../dot/its/jpo/sec/models/MessageTest.java | 33 +++++++++++++++++++ 4 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 src/main/java/us/dot/its/jpo/sec/models/Message.java create mode 100644 src/test/java/us/dot/its/jpo/sec/models/MessageTest.java diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index 5a18f08..2e2c253 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -15,6 +15,8 @@ ******************************************************************************/ package us.dot.its.jpo.sec.controllers; +import us.dot.its.jpo.sec.models.Message; + import java.io.File; import java.io.FileInputStream; import java.io.InputStream; @@ -55,8 +57,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import com.fasterxml.jackson.annotation.JsonProperty; - @Configuration @ConfigurationProperties("sec") @PropertySource("classpath:application.properties") @@ -74,21 +74,13 @@ public class SignatureController implements EnvironmentAware { private String keyStorePath; private String keyStorePassword; - public static class Message { - @JsonProperty("message") - public String msg; - - @JsonProperty("sigValidityOverride") - public int sigValidityOverride = 0; - } - private static final Logger logger = LoggerFactory.getLogger(SignatureController.class); @RequestMapping(value = "/sign", method = RequestMethod.POST, produces = "application/json") @ResponseBody public ResponseEntity> sign(@RequestBody Message message) throws URISyntaxException { - logger.info("Received message: {}", message.msg); - logger.info("Received sigValidityOverride: {}", message.sigValidityOverride); + logger.info("Received message: {}", message.getMsg()); + logger.info("Received sigValidityOverride: {}", message.getSigValidityOverride()); ResponseEntity> response; @@ -98,7 +90,7 @@ public ResponseEntity> sign(@RequestBody Message message) th } else { trimBaseUriAndEndpointPath(); - String resultString = message.msg; + String resultString = message.getMsg(); if (!StringUtils.isEmpty(cryptoServiceBaseUri) && !StringUtils.isEmpty(cryptoServiceEndpointSignPath)) { logger.info("Sending signature request to external service"); JSONObject json = forwardMessageToExternalService(message); @@ -158,12 +150,12 @@ private JSONObject forwardMessageToExternalService(Message message) throws URISy headers.setContentType(MediaType.APPLICATION_JSON); Map map; - if (message.sigValidityOverride > 0) { + if (message.getSigValidityOverride() > 0) { map = new HashMap<>(); - map.put("message", message.msg); - map.put("sigValidityOverride", Integer.toString(message.sigValidityOverride)); + map.put("message", message.getMsg()); + map.put("sigValidityOverride", Integer.toString(message.getSigValidityOverride())); } else { - map = Collections.singletonMap("message", message.msg); + map = Collections.singletonMap("message", message.getMsg()); } HttpEntity> entity = new HttpEntity<>(map, headers); RestTemplate template = new RestTemplate(); @@ -224,7 +216,7 @@ private KeyStore readStore() throws Exception { private ResponseEntity> signWithHsm(Message message) { return ResponseEntity.status(HttpStatus.OK).body( - Collections.singletonMap("result", message.msg + "NOT IMPLEMENTED")); + Collections.singletonMap("result", message.getMsg() + "NOT IMPLEMENTED")); } @Override diff --git a/src/main/java/us/dot/its/jpo/sec/models/Message.java b/src/main/java/us/dot/its/jpo/sec/models/Message.java new file mode 100644 index 0000000..3a4a69a --- /dev/null +++ b/src/main/java/us/dot/its/jpo/sec/models/Message.java @@ -0,0 +1,31 @@ +package us.dot.its.jpo.sec.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Message { + @JsonProperty("message") + private String msg; + + @JsonProperty("sigValidityOverride") + private int sigValidityOverride = 0; + + public Message() { + super(); + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public int getSigValidityOverride() { + return sigValidityOverride; + } + + public void setSigValidityOverride(int sigValidityOverride) { + this.sigValidityOverride = sigValidityOverride; + } + } \ No newline at end of file diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index 8b992e4..8feac93 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -13,7 +13,7 @@ import mockit.Injectable; import mockit.Tested; -import us.dot.its.jpo.sec.controllers.SignatureController.Message; +import us.dot.its.jpo.sec.models.Message; public class SignatureControllerTest { @Tested @@ -33,7 +33,7 @@ public void testSign_useHsm() throws URISyntaxException, JSONException { setUp(); testSignatureController.setUseHsm(true); Message message = new Message(); - message.msg = "test message"; + message.setMsg("test message"); // execute ResponseEntity> response = testSignatureController.sign(message); diff --git a/src/test/java/us/dot/its/jpo/sec/models/MessageTest.java b/src/test/java/us/dot/its/jpo/sec/models/MessageTest.java new file mode 100644 index 0000000..7d7fca4 --- /dev/null +++ b/src/test/java/us/dot/its/jpo/sec/models/MessageTest.java @@ -0,0 +1,33 @@ +package us.dot.its.jpo.sec.models; + +import org.junit.Test; + +public class MessageTest { + + @Test + public void testInitialization() { + // execute + Message testMessage = new Message(); + + // verify + assert(testMessage.getMsg() == null); + assert(testMessage.getSigValidityOverride() == 0); + } + + @Test + public void testSettersAndGetters() { + // prepare + Message testMessage = new Message(); + String testString = "test string"; + int testInt = 42; + + // execute + testMessage.setMsg(testString); + testMessage.setSigValidityOverride(testInt); + + // verify + assert(testMessage.getMsg().equals(testString)); + assert(testMessage.getSigValidityOverride() == testInt); + } + +} From f22b456b3d6e2f51a5fe0eb1888339569e3564ac Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Thu, 11 Jul 2024 20:47:39 +0000 Subject: [PATCH 05/25] Removed unimplemented `signWithHsm()` method from SignatureController class --- .../sec/controllers/SignatureController.java | 75 ++++++++----------- .../controllers/SignatureControllerTest.java | 23 ------ 2 files changed, 32 insertions(+), 66 deletions(-) diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index 2e2c253..9324cd6 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -84,47 +84,41 @@ public ResponseEntity> sign(@RequestBody Message message) th ResponseEntity> response; - if (useHsm) { - logger.info("Signing using HSM"); - response = signWithHsm(message); - } else { - trimBaseUriAndEndpointPath(); - - String resultString = message.getMsg(); - if (!StringUtils.isEmpty(cryptoServiceBaseUri) && !StringUtils.isEmpty(cryptoServiceEndpointSignPath)) { - logger.info("Sending signature request to external service"); - JSONObject json = forwardMessageToExternalService(message); - - if (json != null) { - resultString = json.getString("message-signed"); - Map mapResult = new HashMap<>(); - try { - - mapResult.put("message-expiry", String.valueOf(json.getLong("message-expiry"))); - - } catch (Exception e) { - mapResult.put("message-expiry", "null"); - } - mapResult.put("message-signed", resultString); - response = ResponseEntity.status(HttpStatus.OK) - .body(Collections.singletonMap("result", new JSONObject(mapResult).toString())); - } else { - // no response from external service - response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Collections.singletonMap("error", "Error communicating with external service")); + trimBaseUriAndEndpointPath(); + + String resultString = message.getMsg(); + if (!StringUtils.isEmpty(cryptoServiceBaseUri) && !StringUtils.isEmpty(cryptoServiceEndpointSignPath)) { + logger.info("Sending signature request to external service"); + JSONObject json = forwardMessageToExternalService(message); + + if (json != null) { + resultString = json.getString("message-signed"); + Map mapResult = new HashMap<>(); + try { + + mapResult.put("message-expiry", String.valueOf(json.getLong("message-expiry"))); + + } catch (Exception e) { + mapResult.put("message-expiry", "null"); } + mapResult.put("message-signed", resultString); + response = ResponseEntity.status(HttpStatus.OK) + .body(Collections.singletonMap("result", new JSONObject(mapResult).toString())); } else { - // base URI or endpoint path not set, return the message unchanged - String msg = "Properties sec.cryptoServiceBaseUri=" + cryptoServiceBaseUri - + ", sec.cryptoServiceEndpointSignPath=" + cryptoServiceEndpointSignPath - + " Not defined. Returning the message unchanged."; - logger.warn(msg); - Map result = new HashMap(); - result.put("result", resultString); - result.put("warn", msg); - response = ResponseEntity.status(HttpStatus.NOT_FOUND).body(result); + // no response from external service + response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Collections.singletonMap("error", "Error communicating with external service")); } - + } else { + // base URI or endpoint path not set, return the message unchanged + String msg = "Properties sec.cryptoServiceBaseUri=" + cryptoServiceBaseUri + + ", sec.cryptoServiceEndpointSignPath=" + cryptoServiceEndpointSignPath + + " Not defined. Returning the message unchanged."; + logger.warn(msg); + Map result = new HashMap(); + result.put("result", resultString); + result.put("warn", msg); + response = ResponseEntity.status(HttpStatus.NOT_FOUND).body(result); } return response; @@ -214,11 +208,6 @@ private KeyStore readStore() throws Exception { } } - private ResponseEntity> signWithHsm(Message message) { - return ResponseEntity.status(HttpStatus.OK).body( - Collections.singletonMap("result", message.getMsg() + "NOT IMPLEMENTED")); - } - @Override public void setEnvironment(Environment env) { this.env = env; diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index 8feac93..1425b20 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -2,18 +2,11 @@ import static org.junit.Assert.assertEquals; -import java.net.URISyntaxException; -import java.util.Map; - -import org.json.JSONException; import org.junit.Test; import org.junit.jupiter.api.BeforeEach; import org.springframework.core.env.Environment; -import org.springframework.http.ResponseEntity; - import mockit.Injectable; import mockit.Tested; -import us.dot.its.jpo.sec.models.Message; public class SignatureControllerTest { @Tested @@ -27,22 +20,6 @@ public void setUp() { testSignatureController = new SignatureController(); } - @Test - public void testSign_useHsm() throws URISyntaxException, JSONException { - // prepare - setUp(); - testSignatureController.setUseHsm(true); - Message message = new Message(); - message.setMsg("test message"); - - // execute - ResponseEntity> response = testSignatureController.sign(message); - - // verify - assertEquals(1, response.getBody().size()); - assertEquals("test messageNOT IMPLEMENTED", response.getBody().get("result")); - } - @Test public void testTrimBaseUriAndEndpointPath_TrailingSlashInUri() { // prepare From d5b15c937a21042e203eef4bdff822b501602be8 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Thu, 11 Jul 2024 21:06:14 +0000 Subject: [PATCH 06/25] Added unit tests for the case where 'base uri' and 'endpoint sign path' are not set --- .../sec/controllers/SignatureController.java | 47 ++++++++++--------- .../controllers/SignatureControllerTest.java | 44 +++++++++++++++++ 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index 9324cd6..d753d0f 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -87,29 +87,7 @@ public ResponseEntity> sign(@RequestBody Message message) th trimBaseUriAndEndpointPath(); String resultString = message.getMsg(); - if (!StringUtils.isEmpty(cryptoServiceBaseUri) && !StringUtils.isEmpty(cryptoServiceEndpointSignPath)) { - logger.info("Sending signature request to external service"); - JSONObject json = forwardMessageToExternalService(message); - - if (json != null) { - resultString = json.getString("message-signed"); - Map mapResult = new HashMap<>(); - try { - - mapResult.put("message-expiry", String.valueOf(json.getLong("message-expiry"))); - - } catch (Exception e) { - mapResult.put("message-expiry", "null"); - } - mapResult.put("message-signed", resultString); - response = ResponseEntity.status(HttpStatus.OK) - .body(Collections.singletonMap("result", new JSONObject(mapResult).toString())); - } else { - // no response from external service - response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Collections.singletonMap("error", "Error communicating with external service")); - } - } else { + if (StringUtils.isEmpty(cryptoServiceBaseUri) || StringUtils.isEmpty(cryptoServiceEndpointSignPath)) { // base URI or endpoint path not set, return the message unchanged String msg = "Properties sec.cryptoServiceBaseUri=" + cryptoServiceBaseUri + ", sec.cryptoServiceEndpointSignPath=" + cryptoServiceEndpointSignPath @@ -119,6 +97,29 @@ public ResponseEntity> sign(@RequestBody Message message) th result.put("result", resultString); result.put("warn", msg); response = ResponseEntity.status(HttpStatus.NOT_FOUND).body(result); + return response; + } + + logger.info("Sending signature request to external service"); + JSONObject json = forwardMessageToExternalService(message); + + if (json != null) { + resultString = json.getString("message-signed"); + Map mapResult = new HashMap<>(); + try { + + mapResult.put("message-expiry", String.valueOf(json.getLong("message-expiry"))); + + } catch (Exception e) { + mapResult.put("message-expiry", "null"); + } + mapResult.put("message-signed", resultString); + response = ResponseEntity.status(HttpStatus.OK) + .body(Collections.singletonMap("result", new JSONObject(mapResult).toString())); + } else { + // no response from external service + response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Collections.singletonMap("error", "Error communicating with external service")); } return response; diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index 1425b20..83b4dd6 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -2,11 +2,17 @@ import static org.junit.Assert.assertEquals; +import java.net.URISyntaxException; +import java.util.Map; + import org.junit.Test; import org.junit.jupiter.api.BeforeEach; import org.springframework.core.env.Environment; +import org.springframework.http.ResponseEntity; + import mockit.Injectable; import mockit.Tested; +import us.dot.its.jpo.sec.models.Message; public class SignatureControllerTest { @Tested @@ -20,6 +26,44 @@ public void setUp() { testSignatureController = new SignatureController(); } + @Test + public void testSign_CryptoServiceBaseUriNotSet() throws URISyntaxException { + // prepare + setUp(); + testSignatureController.setCryptoServiceBaseUri(null); + testSignatureController.setCryptoServiceEndpointSignPath("endpoint"); + Message message = new Message(); + message.setMsg("test"); + String expectedWarnString = "Properties sec.cryptoServiceBaseUri=null, sec.cryptoServiceEndpointSignPath=endpoint Not defined. Returning the message unchanged."; + + // execute + ResponseEntity> response = testSignatureController.sign(message); + + // verify + assertEquals(org.springframework.http.HttpStatus.NOT_FOUND, response.getStatusCode()); + assertEquals(message.getMsg(), response.getBody().get("result")); + assertEquals(expectedWarnString, response.getBody().get("warn")); + } + + @Test + public void testSign_CryptoServiceEndpointSignPathNotSet() throws URISyntaxException { + // prepare + setUp(); + testSignatureController.setCryptoServiceBaseUri("http://example.com/"); + testSignatureController.setCryptoServiceEndpointSignPath(null); + Message message = new Message(); + message.setMsg("test"); + String expectedWarnString = "Properties sec.cryptoServiceBaseUri=http://example.com, sec.cryptoServiceEndpointSignPath=null Not defined. Returning the message unchanged."; + + // execute + ResponseEntity> response = testSignatureController.sign(message); + + // verify + assertEquals(org.springframework.http.HttpStatus.NOT_FOUND, response.getStatusCode()); + assertEquals(message.getMsg(), response.getBody().get("result")); + assertEquals(expectedWarnString, response.getBody().get("warn")); + } + @Test public void testTrimBaseUriAndEndpointPath_TrailingSlashInUri() { // prepare From 56371b9c1169fb89f952fb051b73e04ce0985072 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Thu, 11 Jul 2024 21:23:05 +0000 Subject: [PATCH 07/25] Removed unused variable, renamed variable & modified accessors in SignatureController class --- .../sec/controllers/SignatureController.java | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index d753d0f..251e586 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -68,9 +68,8 @@ public class SignatureController implements EnvironmentAware { public String cryptoServiceBaseUri; private String cryptoServiceEndpointSignPath; - public boolean useHsm; - private boolean useCertficates; + private boolean useCertificates; private String keyStorePath; private String keyStorePassword; @@ -126,7 +125,7 @@ public ResponseEntity> sign(@RequestBody Message message) th } - public void trimBaseUriAndEndpointPath() { + protected void trimBaseUriAndEndpointPath() { logger.debug("Before trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, cryptoServiceEndpointSignPath); // Remove all slashes from the end of the URI, if any while (cryptoServiceBaseUri != null && cryptoServiceBaseUri.endsWith("/")) { @@ -139,7 +138,7 @@ public void trimBaseUriAndEndpointPath() { logger.debug("After Trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, cryptoServiceEndpointSignPath); } - private JSONObject forwardMessageToExternalService(Message message) throws URISyntaxException { + protected JSONObject forwardMessageToExternalService(Message message) throws URISyntaxException { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); @@ -161,7 +160,7 @@ private JSONObject forwardMessageToExternalService(Message message) throws URISy logger.debug("Sending request to: {}", uri); - if (useCertficates) { + if (useCertificates) { try { SSLContext sslContext = SSLContexts.custom() .loadKeyMaterial(readStore(), keyStorePassword.toCharArray()) @@ -199,7 +198,7 @@ private JSONObject forwardMessageToExternalService(Message message) throws URISy } } - private KeyStore readStore() throws Exception { + protected KeyStore readStore() throws Exception { try (InputStream keyStoreStream = new FileInputStream(new File(keyStorePath))) { KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(keyStoreStream, keyStorePassword.toCharArray()); @@ -238,20 +237,12 @@ public void setCryptoServiceEndpointSignPath(String cryptoServiceEndpointSignPat this.cryptoServiceEndpointSignPath = cryptoServiceEndpointSignPath; } - public boolean isUseHsm() { - return useHsm; + public boolean isUseCertificates() { + return useCertificates; } - public void setUseHsm(boolean useHsm) { - this.useHsm = useHsm; - } - - public boolean isUseCertficates() { - return useCertficates; - } - - public void setUseCertficates(boolean useCertficates) { - this.useCertficates = useCertficates; + public void setUseCertificates(boolean useCertficates) { + this.useCertificates = useCertficates; } public String getKeyStorePath() { From 63b866502e832be37cf6e620fc375944f5590081 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Thu, 11 Jul 2024 21:27:38 +0000 Subject: [PATCH 08/25] Moved away from deprecated `StringUtils.isEmpty()` method in SignatureController class --- .../us/dot/its/jpo/sec/controllers/SignatureController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index 251e586..3d4e7ea 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -49,7 +49,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -86,7 +85,7 @@ public ResponseEntity> sign(@RequestBody Message message) th trimBaseUriAndEndpointPath(); String resultString = message.getMsg(); - if (StringUtils.isEmpty(cryptoServiceBaseUri) || StringUtils.isEmpty(cryptoServiceEndpointSignPath)) { + if ((cryptoServiceBaseUri == null || cryptoServiceBaseUri.length() == 0) || (cryptoServiceEndpointSignPath == null || cryptoServiceEndpointSignPath.length() == 0)) { // base URI or endpoint path not set, return the message unchanged String msg = "Properties sec.cryptoServiceBaseUri=" + cryptoServiceBaseUri + ", sec.cryptoServiceEndpointSignPath=" + cryptoServiceEndpointSignPath From 3ef2239517c11b2af60dfacc279e45734bffb865 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 12 Jul 2024 15:52:00 +0000 Subject: [PATCH 09/25] Created RestTemplateFactory class to make REST calls testable in SignatureController class --- .../sec/controllers/SignatureController.java | 12 ++- .../jpo/sec/helpers/RestTemplateFactory.java | 12 +++ .../controllers/SignatureControllerTest.java | 102 ++++++++++++++---- .../sec/helpers/RestTemplateFactoryTest.java | 24 +++++ 4 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 src/main/java/us/dot/its/jpo/sec/helpers/RestTemplateFactory.java create mode 100644 src/test/java/us/dot/its/jpo/sec/helpers/RestTemplateFactoryTest.java diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index 3d4e7ea..2e14155 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -15,6 +15,7 @@ ******************************************************************************/ package us.dot.its.jpo.sec.controllers; +import us.dot.its.jpo.sec.helpers.RestTemplateFactory; import us.dot.its.jpo.sec.models.Message; import java.io.File; @@ -65,6 +66,9 @@ public class SignatureController implements EnvironmentAware { @Autowired private Environment env; + @Autowired + private RestTemplateFactory restTemplateFactory; + public String cryptoServiceBaseUri; private String cryptoServiceEndpointSignPath; @@ -74,6 +78,12 @@ public class SignatureController implements EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(SignatureController.class); + @Autowired + public void injectBaseDependencies(Environment env, RestTemplateFactory restTemplateFactory) { + this.env = env; + this.restTemplateFactory = restTemplateFactory; + } + @RequestMapping(value = "/sign", method = RequestMethod.POST, produces = "application/json") @ResponseBody public ResponseEntity> sign(@RequestBody Message message) throws URISyntaxException { @@ -151,7 +161,7 @@ protected JSONObject forwardMessageToExternalService(Message message) throws URI map = Collections.singletonMap("message", message.getMsg()); } HttpEntity> entity = new HttpEntity<>(map, headers); - RestTemplate template = new RestTemplate(); + RestTemplate template = restTemplateFactory.getRestTemplate(); logger.debug("Received request: {}", entity); diff --git a/src/main/java/us/dot/its/jpo/sec/helpers/RestTemplateFactory.java b/src/main/java/us/dot/its/jpo/sec/helpers/RestTemplateFactory.java new file mode 100644 index 0000000..9cf003f --- /dev/null +++ b/src/main/java/us/dot/its/jpo/sec/helpers/RestTemplateFactory.java @@ -0,0 +1,12 @@ +package us.dot.its.jpo.sec.helpers; + +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +public class RestTemplateFactory { + + public RestTemplate getRestTemplate() { + return new RestTemplate(); + } +} diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index 83b4dd6..3786eee 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -1,43 +1,68 @@ package us.dot.its.jpo.sec.controllers; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import java.net.URISyntaxException; import java.util.Map; +import org.json.JSONException; +import org.json.JSONObject; import org.junit.Test; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.env.Environment; import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; import mockit.Injectable; -import mockit.Tested; +import us.dot.its.jpo.sec.helpers.RestTemplateFactory; import us.dot.its.jpo.sec.models.Message; +@ExtendWith(MockitoExtension.class) public class SignatureControllerTest { - @Tested - SignatureController testSignatureController; + @Mock + protected RestTemplate mockRestTemplate; + + @Mock + protected RestTemplateFactory mockRestTemplateFactory; @Injectable Environment environment; + @InjectMocks + SignatureController uut = new SignatureController(); + @BeforeEach public void setUp() { - testSignatureController = new SignatureController(); + mockRestTemplateFactory = mock(RestTemplateFactory.class); + mockRestTemplate = mock(RestTemplate.class); + doReturn(mockRestTemplate).when(mockRestTemplateFactory).getRestTemplate(); + uut.injectBaseDependencies(environment, mockRestTemplateFactory); } + // @Test + // public void testSign_SUCCESS() throws URISyntaxException { + // // TODO: implement + // } + @Test public void testSign_CryptoServiceBaseUriNotSet() throws URISyntaxException { // prepare setUp(); - testSignatureController.setCryptoServiceBaseUri(null); - testSignatureController.setCryptoServiceEndpointSignPath("endpoint"); + uut.setCryptoServiceBaseUri(null); + uut.setCryptoServiceEndpointSignPath("endpoint"); Message message = new Message(); message.setMsg("test"); String expectedWarnString = "Properties sec.cryptoServiceBaseUri=null, sec.cryptoServiceEndpointSignPath=endpoint Not defined. Returning the message unchanged."; // execute - ResponseEntity> response = testSignatureController.sign(message); + ResponseEntity> response = uut.sign(message); // verify assertEquals(org.springframework.http.HttpStatus.NOT_FOUND, response.getStatusCode()); @@ -49,14 +74,14 @@ public void testSign_CryptoServiceBaseUriNotSet() throws URISyntaxException { public void testSign_CryptoServiceEndpointSignPathNotSet() throws URISyntaxException { // prepare setUp(); - testSignatureController.setCryptoServiceBaseUri("http://example.com/"); - testSignatureController.setCryptoServiceEndpointSignPath(null); + uut.setCryptoServiceBaseUri("http://example.com/"); + uut.setCryptoServiceEndpointSignPath(null); Message message = new Message(); message.setMsg("test"); String expectedWarnString = "Properties sec.cryptoServiceBaseUri=http://example.com, sec.cryptoServiceEndpointSignPath=null Not defined. Returning the message unchanged."; // execute - ResponseEntity> response = testSignatureController.sign(message); + ResponseEntity> response = uut.sign(message); // verify assertEquals(org.springframework.http.HttpStatus.NOT_FOUND, response.getStatusCode()); @@ -72,14 +97,14 @@ public void testTrimBaseUriAndEndpointPath_TrailingSlashInUri() { String endpointPath = "endpoint"; String expected = "http://example.com/endpoint"; - testSignatureController.setCryptoServiceBaseUri(baseUri); - testSignatureController.setCryptoServiceEndpointSignPath(endpointPath); + uut.setCryptoServiceBaseUri(baseUri); + uut.setCryptoServiceEndpointSignPath(endpointPath); // execute - testSignatureController.trimBaseUriAndEndpointPath(); + uut.trimBaseUriAndEndpointPath(); // verify - assertEquals(expected, testSignatureController.getCryptoServiceBaseUri() + "/" + testSignatureController.getCryptoServiceEndpointSignPath()); + assertEquals(expected, uut.getCryptoServiceBaseUri() + "/" + uut.getCryptoServiceEndpointSignPath()); } @Test @@ -90,14 +115,55 @@ public void testTrimBaseUriAndEndpointPath_PrecedingSlashInPath() { String endpointPath = "/endpoint"; String expected = "http://example.com/endpoint"; - testSignatureController.setCryptoServiceBaseUri(baseUri); - testSignatureController.setCryptoServiceEndpointSignPath(endpointPath); + uut.setCryptoServiceBaseUri(baseUri); + uut.setCryptoServiceEndpointSignPath(endpointPath); // execute - testSignatureController.trimBaseUriAndEndpointPath(); + uut.trimBaseUriAndEndpointPath(); // verify - assertEquals(expected, testSignatureController.getCryptoServiceBaseUri() + "/" + testSignatureController.getCryptoServiceEndpointSignPath()); + assertEquals(expected, uut.getCryptoServiceBaseUri() + "/" + uut.getCryptoServiceEndpointSignPath()); } + @SuppressWarnings("unchecked") + @Test + public void testForwardMessageToExternalService_useCertificates_False() throws URISyntaxException, JSONException { + // prepare + setUp(); + uut.setUseCertificates(false); + uut.setCryptoServiceBaseUri("http://example.com/"); + uut.setCryptoServiceEndpointSignPath("endpoint"); + ResponseEntity> mockResponseEntity = mock(ResponseEntity.class); + doReturn("{\"result\":\"test\"}").when(mockResponseEntity).getBody(); + doReturn(mockResponseEntity).when(mockRestTemplate).postForEntity(any(), any(), any()); + Message message = new Message(); + message.setMsg("test"); + + // execute + JSONObject response = uut.forwardMessageToExternalService(message); + + // verify + assertEquals("test", response.get("result")); + } + + // @Test + // public void testForwardMessageToExternalService_useCertificates_True_SUCCESS() { + // // TODO: implement + // } + + // @Test + // public void testForwardMessageToExternalService_useCertificates_True_ERROR() { + // // TODO: implement + // } + + // @Test + // public void testReadStore_SUCCESS() { + // // TODO: implement + // } + + // @Test + // public void testReadStore_ERROR() { + // // TODO: implement + // } + } diff --git a/src/test/java/us/dot/its/jpo/sec/helpers/RestTemplateFactoryTest.java b/src/test/java/us/dot/its/jpo/sec/helpers/RestTemplateFactoryTest.java new file mode 100644 index 0000000..ea99ea8 --- /dev/null +++ b/src/test/java/us/dot/its/jpo/sec/helpers/RestTemplateFactoryTest.java @@ -0,0 +1,24 @@ +package us.dot.its.jpo.sec.helpers; + +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.client.RestTemplate; + +import mockit.Tested; + +@ExtendWith(MockitoExtension.class) +public class RestTemplateFactoryTest { + + @Tested + RestTemplateFactory restTemplateFactory = new RestTemplateFactory(); + + @Test + public void testGetRestTemplate() { + // execute + RestTemplate restTemplate = restTemplateFactory.getRestTemplate(); + + // verify + assert(restTemplate != null); + } +} From 0616ce2ac3025c504a9b96921e5825c24b2a0134 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 12 Jul 2024 16:30:14 +0000 Subject: [PATCH 10/25] Created more helpers & added unit test for successfully forwarding message to external service using certificates --- .../sec/controllers/SignatureController.java | 56 ++++++++-------- .../jpo/sec/helpers/HttpClientFactory.java | 17 +++++ .../sec/helpers/HttpEntityStringifier.java | 15 +++++ .../its/jpo/sec/helpers/KeyStoreReader.java | 23 +++++++ .../jpo/sec/helpers/SSLContextFactory.java | 22 +++++++ .../controllers/SignatureControllerTest.java | 65 +++++++++++++++++-- 6 files changed, 164 insertions(+), 34 deletions(-) create mode 100644 src/main/java/us/dot/its/jpo/sec/helpers/HttpClientFactory.java create mode 100644 src/main/java/us/dot/its/jpo/sec/helpers/HttpEntityStringifier.java create mode 100644 src/main/java/us/dot/its/jpo/sec/helpers/KeyStoreReader.java create mode 100644 src/main/java/us/dot/its/jpo/sec/helpers/SSLContextFactory.java diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index 2e14155..33bf463 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -15,12 +15,13 @@ ******************************************************************************/ package us.dot.its.jpo.sec.controllers; +import us.dot.its.jpo.sec.helpers.HttpClientFactory; +import us.dot.its.jpo.sec.helpers.HttpEntityStringifier; +import us.dot.its.jpo.sec.helpers.KeyStoreReader; import us.dot.its.jpo.sec.helpers.RestTemplateFactory; +import us.dot.its.jpo.sec.helpers.SSLContextFactory; import us.dot.its.jpo.sec.models.Message; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.security.KeyStore; @@ -33,8 +34,6 @@ import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import org.json.JSONObject; import org.slf4j.Logger; @@ -69,6 +68,18 @@ public class SignatureController implements EnvironmentAware { @Autowired private RestTemplateFactory restTemplateFactory; + @Autowired + private KeyStoreReader keyStoreReader; + + @Autowired + private SSLContextFactory sslContextFactory; + + @Autowired + private HttpClientFactory httpClientFactory; + + @Autowired + private HttpEntityStringifier httpEntityStringifier; + public String cryptoServiceBaseUri; private String cryptoServiceEndpointSignPath; @@ -79,9 +90,14 @@ public class SignatureController implements EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(SignatureController.class); @Autowired - public void injectBaseDependencies(Environment env, RestTemplateFactory restTemplateFactory) { + public void injectBaseDependencies(Environment env, RestTemplateFactory restTemplateFactory, KeyStoreReader keyStoreReader, + SSLContextFactory sslContextFactory, HttpClientFactory httpClientFactory, HttpEntityStringifier httpEntityStringifier) { this.env = env; this.restTemplateFactory = restTemplateFactory; + this.keyStoreReader = keyStoreReader; + this.sslContextFactory = sslContextFactory; + this.httpClientFactory = httpClientFactory; + this.httpEntityStringifier = httpEntityStringifier; } @RequestMapping(value = "/sign", method = RequestMethod.POST, produces = "application/json") @@ -171,24 +187,18 @@ protected JSONObject forwardMessageToExternalService(Message message) throws URI if (useCertificates) { try { - SSLContext sslContext = SSLContexts.custom() - .loadKeyMaterial(readStore(), keyStorePassword.toCharArray()) - .build(); - - HttpClient httpClient = HttpClients.custom() - .setSSLContext(sslContext) - .build(); + KeyStore keyStore = keyStoreReader.readStore(keyStorePath, keyStorePassword); + SSLContext sslContext = sslContextFactory.getSSLContext(keyStore, keyStorePassword); + HttpClient httpClient = httpClientFactory.getHttpClient(sslContext); HttpPost httpPost = new HttpPost(uri); httpPost.setHeader("Content-Type", "application/json"); - org.apache.http.HttpEntity entity2 = new org.apache.http.entity.StringEntity( - new JSONObject(map).toString()); + org.apache.http.HttpEntity entity2 = new org.apache.http.entity.StringEntity(new JSONObject(map).toString()); httpPost.setEntity(entity2); - HttpResponse response = httpClient - .execute(httpPost); + HttpResponse response = httpClient.execute(httpPost); org.apache.http.HttpEntity apache_entity = response.getEntity(); - String result = EntityUtils.toString(apache_entity); + String result = httpEntityStringifier.stringifyHttpEntity(apache_entity); logger.debug("Returned signature object: {}", result); JSONObject jObj = new JSONObject(result); EntityUtils.consume(apache_entity); @@ -207,16 +217,6 @@ protected JSONObject forwardMessageToExternalService(Message message) throws URI } } - protected KeyStore readStore() throws Exception { - try (InputStream keyStoreStream = new FileInputStream(new File(keyStorePath))) { - KeyStore keyStore = KeyStore.getInstance("JKS"); - keyStore.load(keyStoreStream, keyStorePassword.toCharArray()); - return keyStore; - } catch (Exception e) { - throw new Exception("Error reading keystore", e); - } - } - @Override public void setEnvironment(Environment env) { this.env = env; diff --git a/src/main/java/us/dot/its/jpo/sec/helpers/HttpClientFactory.java b/src/main/java/us/dot/its/jpo/sec/helpers/HttpClientFactory.java new file mode 100644 index 0000000..27cd705 --- /dev/null +++ b/src/main/java/us/dot/its/jpo/sec/helpers/HttpClientFactory.java @@ -0,0 +1,17 @@ +package us.dot.its.jpo.sec.helpers; + +import javax.net.ssl.SSLContext; + +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.stereotype.Component; + +@Component +public class HttpClientFactory { + + public HttpClient getHttpClient(SSLContext sslContext) { + return HttpClients.custom() + .setSSLContext(sslContext) + .build(); + } +} diff --git a/src/main/java/us/dot/its/jpo/sec/helpers/HttpEntityStringifier.java b/src/main/java/us/dot/its/jpo/sec/helpers/HttpEntityStringifier.java new file mode 100644 index 0000000..f8b7f8f --- /dev/null +++ b/src/main/java/us/dot/its/jpo/sec/helpers/HttpEntityStringifier.java @@ -0,0 +1,15 @@ +package us.dot.its.jpo.sec.helpers; + +import java.io.IOException; + +import org.apache.http.ParseException; +import org.apache.http.util.EntityUtils; +import org.springframework.stereotype.Component; + +@Component +public class HttpEntityStringifier { + + public String stringifyHttpEntity(org.apache.http.HttpEntity apache_entity) throws ParseException, IOException { + return EntityUtils.toString(apache_entity); + } +} diff --git a/src/main/java/us/dot/its/jpo/sec/helpers/KeyStoreReader.java b/src/main/java/us/dot/its/jpo/sec/helpers/KeyStoreReader.java new file mode 100644 index 0000000..d64ae23 --- /dev/null +++ b/src/main/java/us/dot/its/jpo/sec/helpers/KeyStoreReader.java @@ -0,0 +1,23 @@ +package us.dot.its.jpo.sec.helpers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.KeyStore; + +import org.springframework.stereotype.Component; + +@Component +public class KeyStoreReader { + + public KeyStore readStore(String keyStorePath, String keyStorePassword) throws Exception { + try (InputStream keyStoreStream = new FileInputStream(new File(keyStorePath))) { + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(keyStoreStream, keyStorePassword.toCharArray()); + return keyStore; + } catch (Exception e) { + throw new Exception("Error reading keystore", e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/us/dot/its/jpo/sec/helpers/SSLContextFactory.java b/src/main/java/us/dot/its/jpo/sec/helpers/SSLContextFactory.java new file mode 100644 index 0000000..23de680 --- /dev/null +++ b/src/main/java/us/dot/its/jpo/sec/helpers/SSLContextFactory.java @@ -0,0 +1,22 @@ +package us.dot.its.jpo.sec.helpers; + +import org.springframework.stereotype.Component; + +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +import javax.net.ssl.SSLContext; +import org.apache.http.ssl.SSLContexts; + +@Component +public class SSLContextFactory { + + public SSLContext getSSLContext(KeyStore keyStore, String keyStorePassword) throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { + return SSLContexts.custom() + .loadKeyMaterial(keyStore, keyStorePassword.toCharArray()) + .build(); + } +} diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index 3786eee..bd306f7 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -5,9 +5,20 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import java.io.IOException; import java.net.URISyntaxException; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; import java.util.Map; +import javax.net.ssl.SSLContext; + import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; @@ -21,7 +32,11 @@ import org.springframework.web.client.RestTemplate; import mockit.Injectable; +import us.dot.its.jpo.sec.helpers.HttpClientFactory; +import us.dot.its.jpo.sec.helpers.HttpEntityStringifier; +import us.dot.its.jpo.sec.helpers.KeyStoreReader; import us.dot.its.jpo.sec.helpers.RestTemplateFactory; +import us.dot.its.jpo.sec.helpers.SSLContextFactory; import us.dot.its.jpo.sec.models.Message; @ExtendWith(MockitoExtension.class) @@ -32,6 +47,18 @@ public class SignatureControllerTest { @Mock protected RestTemplateFactory mockRestTemplateFactory; + @Mock + protected KeyStoreReader mockKeyStoreReader; + + @Mock + protected SSLContextFactory mockSSLContextFactory; + + @Mock + protected HttpClientFactory mockHttpClientFactory; + + @Mock + HttpEntityStringifier mockHttpEntityStringifier; + @Injectable Environment environment; @@ -40,10 +67,15 @@ public class SignatureControllerTest { @BeforeEach public void setUp() { - mockRestTemplateFactory = mock(RestTemplateFactory.class); mockRestTemplate = mock(RestTemplate.class); + mockRestTemplateFactory = mock(RestTemplateFactory.class); doReturn(mockRestTemplate).when(mockRestTemplateFactory).getRestTemplate(); - uut.injectBaseDependencies(environment, mockRestTemplateFactory); + mockKeyStoreReader = mock(KeyStoreReader.class); + mockSSLContextFactory = mock(SSLContextFactory.class); + mockHttpClientFactory = mock(HttpClientFactory.class); + mockHttpEntityStringifier = mock(HttpEntityStringifier.class); + uut.injectBaseDependencies(environment, mockRestTemplateFactory, mockKeyStoreReader, + mockSSLContextFactory, mockHttpClientFactory, mockHttpEntityStringifier); } // @Test @@ -146,10 +178,31 @@ public void testForwardMessageToExternalService_useCertificates_False() throws U assertEquals("test", response.get("result")); } - // @Test - // public void testForwardMessageToExternalService_useCertificates_True_SUCCESS() { - // // TODO: implement - // } + @Test + public void testForwardMessageToExternalService_useCertificates_True_SUCCESS() throws URISyntaxException, KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, ClientProtocolException, IOException, JSONException { + // prepare + setUp(); + uut.setUseCertificates(true); + uut.setCryptoServiceBaseUri("http://example.com/"); + uut.setCryptoServiceEndpointSignPath("endpoint"); + SSLContext mockSSLContext = mock(SSLContext.class); + doReturn(mockSSLContext).when(mockSSLContextFactory).getSSLContext(any(), any()); + HttpClient mockHttpClient = mock(HttpClient.class); + doReturn(mockHttpClient).when(mockHttpClientFactory).getHttpClient(mockSSLContext); + HttpResponse mockHttpResponse = mock(HttpResponse.class); + doReturn(mockHttpResponse).when(mockHttpClient).execute(any()); + org.apache.http.HttpEntity mockHttpEntity = mock(org.apache.http.HttpEntity.class); + doReturn(mockHttpEntity).when(mockHttpResponse).getEntity(); + doReturn("{\"result\":\"test\"}").when(mockHttpEntityStringifier).stringifyHttpEntity(mockHttpEntity); + Message message = new Message(); + message.setMsg("test"); + + // execute + JSONObject response = uut.forwardMessageToExternalService(message); + + // verify + assertEquals("test", response.get("result")); + } // @Test // public void testForwardMessageToExternalService_useCertificates_True_ERROR() { From c92f155cbcd4953cd7383abe4d47f7cc9a81a39a Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 12 Jul 2024 16:36:21 +0000 Subject: [PATCH 11/25] Added unit test for error case during forwarding message to external service using certificates --- .../sec/controllers/SignatureController.java | 4 +-- .../controllers/SignatureControllerTest.java | 29 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index 33bf463..cca42aa 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -250,8 +250,8 @@ public boolean isUseCertificates() { return useCertificates; } - public void setUseCertificates(boolean useCertficates) { - this.useCertificates = useCertficates; + public void setUseCertificates(boolean useCertificates) { + this.useCertificates = useCertificates; } public String getKeyStorePath() { diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index bd306f7..9b5190e 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -1,8 +1,10 @@ package us.dot.its.jpo.sec.controllers; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import java.io.IOException; @@ -204,19 +206,22 @@ public void testForwardMessageToExternalService_useCertificates_True_SUCCESS() t assertEquals("test", response.get("result")); } - // @Test - // public void testForwardMessageToExternalService_useCertificates_True_ERROR() { - // // TODO: implement - // } + @Test + public void testForwardMessageToExternalService_useCertificates_True_ERROR() throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, URISyntaxException { + // prepare + setUp(); + uut.setUseCertificates(true); + uut.setCryptoServiceBaseUri("http://example.com/"); + uut.setCryptoServiceEndpointSignPath("endpoint"); + doThrow(new KeyManagementException()).when(mockSSLContextFactory).getSSLContext(any(), any()); + Message message = new Message(); + message.setMsg("test"); - // @Test - // public void testReadStore_SUCCESS() { - // // TODO: implement - // } + // execute + JSONObject response = uut.forwardMessageToExternalService(message); - // @Test - // public void testReadStore_ERROR() { - // // TODO: implement - // } + // verify + assertNull(response); + } } From 1f9ba912895063c8e38234173313849f0b0a1056 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 12 Jul 2024 16:48:25 +0000 Subject: [PATCH 12/25] Added success/failure unit tests for sign() method --- .../sec/controllers/SignatureController.java | 26 ++-- .../controllers/SignatureControllerTest.java | 111 ++++++++++++------ 2 files changed, 89 insertions(+), 48 deletions(-) diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index cca42aa..ada7e94 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -150,19 +150,6 @@ public ResponseEntity> sign(@RequestBody Message message) th } - protected void trimBaseUriAndEndpointPath() { - logger.debug("Before trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, cryptoServiceEndpointSignPath); - // Remove all slashes from the end of the URI, if any - while (cryptoServiceBaseUri != null && cryptoServiceBaseUri.endsWith("/")) { - cryptoServiceBaseUri = cryptoServiceBaseUri.substring(0, cryptoServiceBaseUri.lastIndexOf('/')); - } - // Remove all slashes from the beginning of the path string, if any - while (cryptoServiceEndpointSignPath != null && cryptoServiceEndpointSignPath.startsWith("/")) { - cryptoServiceEndpointSignPath = cryptoServiceEndpointSignPath.substring(1); - } - logger.debug("After Trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, cryptoServiceEndpointSignPath); - } - protected JSONObject forwardMessageToExternalService(Message message) throws URISyntaxException { HttpHeaders headers = new HttpHeaders(); @@ -217,6 +204,19 @@ protected JSONObject forwardMessageToExternalService(Message message) throws URI } } + protected void trimBaseUriAndEndpointPath() { + logger.debug("Before trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, cryptoServiceEndpointSignPath); + // Remove all slashes from the end of the URI, if any + while (cryptoServiceBaseUri != null && cryptoServiceBaseUri.endsWith("/")) { + cryptoServiceBaseUri = cryptoServiceBaseUri.substring(0, cryptoServiceBaseUri.lastIndexOf('/')); + } + // Remove all slashes from the beginning of the path string, if any + while (cryptoServiceEndpointSignPath != null && cryptoServiceEndpointSignPath.startsWith("/")) { + cryptoServiceEndpointSignPath = cryptoServiceEndpointSignPath.substring(1); + } + logger.debug("After Trimming: cryptoServiceBaseUri={}, cryptoServiceEndpointSignPath={}", cryptoServiceBaseUri, cryptoServiceEndpointSignPath); + } + @Override public void setEnvironment(Environment env) { this.env = env; diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index 9b5190e..8ad3daa 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -80,83 +80,88 @@ public void setUp() { mockSSLContextFactory, mockHttpClientFactory, mockHttpEntityStringifier); } - // @Test - // public void testSign_SUCCESS() throws URISyntaxException { - // // TODO: implement - // } - @Test - public void testSign_CryptoServiceBaseUriNotSet() throws URISyntaxException { + public void testSign_SUCCESS() throws URISyntaxException, KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, ClientProtocolException, IOException { // prepare setUp(); - uut.setCryptoServiceBaseUri(null); + uut.setUseCertificates(true); + uut.setCryptoServiceBaseUri("http://example.com/"); uut.setCryptoServiceEndpointSignPath("endpoint"); + SSLContext mockSSLContext = mock(SSLContext.class); + doReturn(mockSSLContext).when(mockSSLContextFactory).getSSLContext(any(), any()); + HttpClient mockHttpClient = mock(HttpClient.class); + doReturn(mockHttpClient).when(mockHttpClientFactory).getHttpClient(mockSSLContext); + HttpResponse mockHttpResponse = mock(HttpResponse.class); + doReturn(mockHttpResponse).when(mockHttpClient).execute(any()); + org.apache.http.HttpEntity mockHttpEntity = mock(org.apache.http.HttpEntity.class); + doReturn(mockHttpEntity).when(mockHttpResponse).getEntity(); + doReturn("{\"message-signed\":\"test12345\",\"message-expiry\":1}").when(mockHttpEntityStringifier).stringifyHttpEntity(mockHttpEntity); Message message = new Message(); message.setMsg("test"); - String expectedWarnString = "Properties sec.cryptoServiceBaseUri=null, sec.cryptoServiceEndpointSignPath=endpoint Not defined. Returning the message unchanged."; // execute ResponseEntity> response = uut.sign(message); // verify - assertEquals(org.springframework.http.HttpStatus.NOT_FOUND, response.getStatusCode()); - assertEquals(message.getMsg(), response.getBody().get("result")); - assertEquals(expectedWarnString, response.getBody().get("warn")); + assertEquals(org.springframework.http.HttpStatus.OK, response.getStatusCode()); + assertEquals("{\"message-expiry\":\"1\",\"message-signed\":\"test12345\"}", response.getBody().get("result")); } @Test - public void testSign_CryptoServiceEndpointSignPathNotSet() throws URISyntaxException { + public void testSign_ERROR_NoResponse() throws URISyntaxException, KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { // prepare setUp(); + uut.setUseCertificates(true); uut.setCryptoServiceBaseUri("http://example.com/"); - uut.setCryptoServiceEndpointSignPath(null); + uut.setCryptoServiceEndpointSignPath("endpoint"); + doThrow(new KeyManagementException()).when(mockSSLContextFactory).getSSLContext(any(), any()); Message message = new Message(); message.setMsg("test"); - String expectedWarnString = "Properties sec.cryptoServiceBaseUri=http://example.com, sec.cryptoServiceEndpointSignPath=null Not defined. Returning the message unchanged."; // execute ResponseEntity> response = uut.sign(message); // verify - assertEquals(org.springframework.http.HttpStatus.NOT_FOUND, response.getStatusCode()); - assertEquals(message.getMsg(), response.getBody().get("result")); - assertEquals(expectedWarnString, response.getBody().get("warn")); + assertEquals(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertEquals("Error communicating with external service", response.getBody().get("error")); } @Test - public void testTrimBaseUriAndEndpointPath_TrailingSlashInUri() { + public void testSign_CryptoServiceBaseUriNotSet() throws URISyntaxException { // prepare setUp(); - String baseUri = "http://example.com/"; - String endpointPath = "endpoint"; - String expected = "http://example.com/endpoint"; - - uut.setCryptoServiceBaseUri(baseUri); - uut.setCryptoServiceEndpointSignPath(endpointPath); + uut.setCryptoServiceBaseUri(null); + uut.setCryptoServiceEndpointSignPath("endpoint"); + Message message = new Message(); + message.setMsg("test"); + String expectedWarnString = "Properties sec.cryptoServiceBaseUri=null, sec.cryptoServiceEndpointSignPath=endpoint Not defined. Returning the message unchanged."; // execute - uut.trimBaseUriAndEndpointPath(); + ResponseEntity> response = uut.sign(message); // verify - assertEquals(expected, uut.getCryptoServiceBaseUri() + "/" + uut.getCryptoServiceEndpointSignPath()); + assertEquals(org.springframework.http.HttpStatus.NOT_FOUND, response.getStatusCode()); + assertEquals(message.getMsg(), response.getBody().get("result")); + assertEquals(expectedWarnString, response.getBody().get("warn")); } @Test - public void testTrimBaseUriAndEndpointPath_PrecedingSlashInPath() { + public void testSign_CryptoServiceEndpointSignPathNotSet() throws URISyntaxException { // prepare setUp(); - String baseUri = "http://example.com"; - String endpointPath = "/endpoint"; - String expected = "http://example.com/endpoint"; - - uut.setCryptoServiceBaseUri(baseUri); - uut.setCryptoServiceEndpointSignPath(endpointPath); + uut.setCryptoServiceBaseUri("http://example.com/"); + uut.setCryptoServiceEndpointSignPath(null); + Message message = new Message(); + message.setMsg("test"); + String expectedWarnString = "Properties sec.cryptoServiceBaseUri=http://example.com, sec.cryptoServiceEndpointSignPath=null Not defined. Returning the message unchanged."; // execute - uut.trimBaseUriAndEndpointPath(); + ResponseEntity> response = uut.sign(message); // verify - assertEquals(expected, uut.getCryptoServiceBaseUri() + "/" + uut.getCryptoServiceEndpointSignPath()); + assertEquals(org.springframework.http.HttpStatus.NOT_FOUND, response.getStatusCode()); + assertEquals(message.getMsg(), response.getBody().get("result")); + assertEquals(expectedWarnString, response.getBody().get("warn")); } @SuppressWarnings("unchecked") @@ -224,4 +229,40 @@ public void testForwardMessageToExternalService_useCertificates_True_ERROR() thr assertNull(response); } + @Test + public void testTrimBaseUriAndEndpointPath_TrailingSlashInUri() { + // prepare + setUp(); + String baseUri = "http://example.com/"; + String endpointPath = "endpoint"; + String expected = "http://example.com/endpoint"; + + uut.setCryptoServiceBaseUri(baseUri); + uut.setCryptoServiceEndpointSignPath(endpointPath); + + // execute + uut.trimBaseUriAndEndpointPath(); + + // verify + assertEquals(expected, uut.getCryptoServiceBaseUri() + "/" + uut.getCryptoServiceEndpointSignPath()); + } + + @Test + public void testTrimBaseUriAndEndpointPath_PrecedingSlashInPath() { + // prepare + setUp(); + String baseUri = "http://example.com"; + String endpointPath = "/endpoint"; + String expected = "http://example.com/endpoint"; + + uut.setCryptoServiceBaseUri(baseUri); + uut.setCryptoServiceEndpointSignPath(endpointPath); + + // execute + uut.trimBaseUriAndEndpointPath(); + + // verify + assertEquals(expected, uut.getCryptoServiceBaseUri() + "/" + uut.getCryptoServiceEndpointSignPath()); + } + } From ee81b1a548cc31bf9f2fc0690d2ae9c3201b541f Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 12 Jul 2024 11:18:50 -0600 Subject: [PATCH 13/25] Corrected 'certficates' typo to 'certificates' --- README.md | 2 +- docker-compose.yml | 2 +- docs/dockerhub.md | 2 +- sample.env | 4 ++-- src/main/resources/application.properties | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7498012..c190872 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,6 @@ In `./src/main/resources/application.properties` you'll find the following prope | sec.useHsm | Whether to use an HSM or not. | false | SEC_USE_HSM | | sec.cryptoServiceBaseUri | Cryptographic service endpoint URI excluding path. For example, `http://:` OR `http://server.dns.name` including the port number, if any. | - |SEC_CRYPTO_SERVICE_BASE_URI| | sec.cryptoServiceEndpointSignPath | The REST endpoint path of the external service. | /tmc/signtim |SEC_CRYPTO_SERVICE_ENDPOINT_SIGN_PATH| -| sec.useCertficates | Whether to use certificates or not. | true | SEC_USE_CERTIFICATES | +| sec.useCertificates | Whether to use certificates or not. | true | SEC_USE_CERTIFICATES | | sec.keyStorePath | The path to the keystore file. | /home/cert.jks | SEC_KEY_STORE_PATH | | sec.keyStorePassword | The password for the keystore file. | password | SEC_KEY_STORE_PASSWORD | diff --git a/docker-compose.yml b/docker-compose.yml index e64d62f..a2d9e75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: environment: SEC_CRYPTO_SERVICE_BASE_URI: ${SEC_CRYPTO_SERVICE_BASE_URI} SEC_CRYPTO_SERVICE_ENDPOINT_SIGN_PATH: ${SEC_CRYPTO_SERVICE_ENDPOINT_SIGN_PATH} - SEC_USE_CERTFICATES: ${SEC_USE_CERTFICATES} + SEC_USE_CERTIFICATES: ${SEC_USE_CERTIFICATES} #SEC_KEY_STORE_PATH: ${SEC_KEY_STORE_PATH} SEC_KEY_STORE_PASSWORD: ${SEC_KEY_STORE_PASSWORD} volumes: diff --git a/docs/dockerhub.md b/docs/dockerhub.md index 08000c5..3bd55ba 100644 --- a/docs/dockerhub.md +++ b/docs/dockerhub.md @@ -36,7 +36,7 @@ services: environment: SEC_CRYPTO_SERVICE_BASE_URI: ${SEC_CRYPTO_SERVICE_BASE_URI} SEC_CRYPTO_SERVICE_ENDPOINT_SIGN_PATH: ${SEC_CRYPTO_SERVICE_ENDPOINT_SIGN_PATH} - SEC_USE_CERTFICATES: ${SEC_USE_CERTFICATES} + SEC_USE_CERTIFICATES: ${SEC_USE_CERTIFICATES} SEC_KEY_STORE_PASSWORD: ${SEC_KEY_STORE_PASSWORD} volumes: - ./creds:/usr/local/share/ca-certificates diff --git a/sample.env b/sample.env index 8830272..781dffa 100644 --- a/sample.env +++ b/sample.env @@ -2,7 +2,7 @@ SEC_CRYPTO_SERVICE_BASE_URI= #The REST endpoint path of the external service if `sec.useHsm=false` SEC_CRYPTO_SERVICE_ENDPOINT_SIGN_PATH=/tmc/signtim -SEC_USE_CERTFICATES=true -#The following properties are valid only if sec.useCertficates=true +SEC_USE_CERTIFICATES=true +#The following properties are valid only if sec.useCertificates=true SEC_KEY_STORE_PATH=/home/cert.jks SEC_KEY_STORE_PASSWORD=password \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fc40680..ba27404 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,7 +8,7 @@ sec.cryptoServiceBaseUri= sec.cryptoServiceEndpointSignPath=/tmc/signtim # Set to true to enable the use of off-site signatory endpoints with MTLS enabled -sec.useCertficates=true -# The following properties are valid only if sec.useCertficates=true +sec.useCertificates=true +# The following properties are valid only if sec.useCertificates=true sec.keyStorePath=/home/cert.jks sec.keyStorePassword=password \ No newline at end of file From 9b822bb64afa3728a46a488ecca5a68fb4bb0c38 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 12 Jul 2024 14:14:54 -0600 Subject: [PATCH 14/25] Added dev container & test script for convenience --- .devcontainer/devcontainer.json | 27 +++++++++++++++++++++++++++ test-scripts/hit_endpoint.sh | 3 +++ 2 files changed, 30 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 test-scripts/hit_endpoint.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..8becf2e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,27 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/java +{ + "name": "Java", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/java:1-21-bullseye", + + "features": { + "ghcr.io/devcontainers/features/java:1": { + "version": "none", + "installMaven": "true", + "installGradle": "false" + } + } + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "java -version", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/test-scripts/hit_endpoint.sh b/test-scripts/hit_endpoint.sh new file mode 100644 index 0000000..f43b40e --- /dev/null +++ b/test-scripts/hit_endpoint.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +curl -X POST localhost:8090/sign -H "Content-Type: application/json" -d "{\"message\": \"test\", \"sigValidityOverride\": 0}" \ No newline at end of file From e69a7eec53106d0cc8da58450c5264f897337ec9 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 26 Jul 2024 14:12:34 -0400 Subject: [PATCH 15/25] Revised README --- README.md | 135 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index c190872..b03dc9e 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,29 @@ # jpo-security-svcs -This module exposes a RESTful API for performing cryptographic functions. The following paths identify the functions: -|Verb|path|Content Type|Functionality|Request Body Format|Response Body Format| -|--|--|--|--|--|--| -|POST|/sign|application/json|signs data provided in the body of the request|{"message":"Base64 encoded unsigned data"}|{"result": "Base64 Encoded Signed Data"} +This module exposes a RESTful API for performing cryptographic signing of messages. +Cryptographic signing is carried out against a remote instance, which requires mutual TLS authentication to secure the communications. This requires using private and public keys to secure the communications between this instance and the remote signing instance. + +## Table of Contents +- [Release notes](#release-notes) +- [Usage](#usage) +- [Installation](#installation) +- [Configuration](#configuration) +- [Mutual TLS Authentication](#mutual-tls-authentication) +- [Debugging](#debugging) +- [Testing](#testing) -Note that the cryptographic functions may be carried out against an on-prem instance, or against a remote instance. A local instance is a simplified case and does not require mutual TLS authentication. For the remote signing use case, MTLS will be used to secure the communications. This requires some additional work on the part of configuration, and uses private and public keys to secure the communications between this instance and the remote signing instance. -## Mutual TLS Authentication -For enhanced security, MTLS is used to communicate with a remote signing system. This requires properly configured certificates from the remote system to property perform - work with your remote signing authority to obtain necessary certificates. ## Release Notes The current version and release history of the Jpo-security-svcs: [Jpo-security-svcs Release Notes]() -### Certificate Conversion -Certificates can be in several different formats, including the widely used PEM format. Because this is a Java application, the signing certificates must be in the Java Keystore format to be used. The following commands using [OpenSSL](https://www.openssl.org/) and the Java keytool will convert a PEM file to a Java Keystore file. - -1. Convert the PEM file to a PKCS12 file: -``` -openssl pkcs12 -export -in -inkey -out -name -``` -2. Convert the PKCS12 file to a Java Keystore file: -``` -keytool -importkeystore -srckeystore -srcstoretype pkcs12 -destkeystore -``` - -### Certificate Authority Issues -If your remote signing authority is using their own certificates as a CA, you may need to import those certificates into your Java truststore to allow the handshake to go through. Note that when running the dockerized application, these CA certificates are to be one certificate per file (chain certificates are not supported). - -## Install - -`mvn clean install` - - -## Debug -If running in VS Code, a launch.json has been included to allow for ease of debugging. A .env file can be created using the sample.env file as a starting point. Once this settings file is in place, simply click the green arrow in the debug tab to run the application. At this point all breakpoints will function as expected. - -## Run - -### Java JAR: - -`java -jar target/jpo-security-svcs-0.0.1-SNAPSHOT.jar` - -### Docker: - -`docker build .` - -(Take note of image reported by docker build) - -`docker run -p 8090:8090 ` - -### Docker Compose -A docker-compose.yml file has been included as an example for running the application under Docker Compose. Additionally a sample.env has been included to show which values are expected. - -The docker-compose.yml file is configured for using certificates, and mounts a volume to the container pointing to the local `./src/main/resources/creds` directory. This directory (or another you chose to point to) should contain your own JKS used to sign messages for MTLS. It should also contain a subdirectory, "caCerts", containing any CA certificates (one per file) from the remote system that need to be installed in the Java truststore on boot. - -To use, copy the sample.env file to a new '.env' file and replace with your settings. Then, simply run the following command: -``` -docker-compose up --build -d -``` -This will spin up a new container and run it listening on port 8090. To stop the container, run the following command: -``` -docker-compose down -``` - -## Test +## Usage +### RESTful API +The following table depicts the RESTful API exposed by the jpo-security-svcs module: +| Verb | path | Content Type | Functionality | Request Body Format | Response Body Format | +| ---- | ---- | ------------ | ------------- | -------------------- | --------------------- | +| POST | /sign|application/json|signs data provided in the body of the request | {"message":"Base64 encoded unsigned data"} | {"result": "Base64 Encoded Signed Data"} +### Example Send a POST request to `localhost:8090/sign` with a body of the form: ``` @@ -81,16 +40,62 @@ Expected output: } ``` -## Configuration +## Installation +### Docker +1. Install Docker +1. Set up a directory to contain your own JKS file. +1. Set up a subdirectory in the above directory called "caCerts" to contain any CA certificates (one per file) from the remote system that need to be installed in the Java truststore on boot. +1. Copy the sample.env file to a new '.env' file and populate with your settings. See [Configuration](#configuration) for more information. +1. Build & run the application using `docker compose up --build -d` +1. To spin down the container, run `docker compose down` + +### Manual +1. Install Maven +1. Install Java +1. Configure the properties file as needed (see [Configuration](#configuration)) +1. Package the application using `mvn clean install` +1. Run the application using `java -jar target/jpo-security-svcs-0.0.1-SNAPSHOT.jar` +## Configuration In `./src/main/resources/application.properties` you'll find the following properties which can be defined whether on the command line or by environment variable. To define the property on the command line, insert `--` to the front of the Property name, for example, `--server.port=8091`: | Property | Meaning | Default Value | Environment Variable Substitute | -| -----------|------------|-----------------|-----------| -| server.port | The port number to which this service will be listening.| 8090 |SERVER_PORT| -| sec.useHsm | Whether to use an HSM or not. | false | SEC_USE_HSM | -| sec.cryptoServiceBaseUri | Cryptographic service endpoint URI excluding path. For example, `http://:` OR `http://server.dns.name` including the port number, if any. | - |SEC_CRYPTO_SERVICE_BASE_URI| -| sec.cryptoServiceEndpointSignPath | The REST endpoint path of the external service. | /tmc/signtim |SEC_CRYPTO_SERVICE_ENDPOINT_SIGN_PATH| +| -------- | ------- | ------------- | ------------------------------- | +| server.port | The port number to which this service will be listening. | 8090 | SERVER_PORT | +| sec.cryptoServiceBaseUri | Cryptographic service endpoint URI excluding path. For example, `http://:` OR `http://server.dns.name` including the port number, if any. | - | SEC_CRYPTO_SERVICE_BASE_URI| +| sec.cryptoServiceEndpointSignPath | The REST endpoint path of the external service. | /tmc/signtim |SEC_CRYPTO_SERVICE_ENDPOINT_SIGN_PATH | | sec.useCertificates | Whether to use certificates or not. | true | SEC_USE_CERTIFICATES | | sec.keyStorePath | The path to the keystore file. | /home/cert.jks | SEC_KEY_STORE_PATH | | sec.keyStorePassword | The password for the keystore file. | password | SEC_KEY_STORE_PASSWORD | + +## Mutual TLS Authentication +For enhanced security, MTLS is used to communicate with a remote signing system. This requires properly configured certificates from the remote system to property perform - work with your remote signing authority to obtain necessary certificates. + +### Certificate Conversion +Certificates can be in several different formats, including the widely used PEM format. Because this is a Java application, the signing certificates must be in the Java Keystore format to be used. The following commands using [OpenSSL](https://www.openssl.org/) and the Java keytool will convert a PEM file to a Java Keystore file. + +1. Convert the PEM file to a PKCS12 file: +``` +openssl pkcs12 -export -in -inkey -out -name +``` +2. Convert the PKCS12 file to a Java Keystore file: +``` +keytool -importkeystore -srckeystore -srcstoretype pkcs12 -destkeystore +``` + +### Certificate Authority Issues +If your remote signing authority is using their own certificates as a CA, you may need to import those certificates into your Java truststore to allow the handshake to go through. Note that when running the dockerized application, these CA certificates are to be one certificate per file (chain certificates are not supported). + +## Debugging +If running in VS Code, a launch.json has been included to allow for ease of debugging. A .env file can be created using the sample.env file as a starting point. Once this settings file is in place, simply click the green arrow in the debug tab to run the application. At this point all breakpoints will function as expected. + +## Testing +### Unit Tests +To run the unit tests, reopen the project in the provided dev container and run the following command: +`mvn test` + +### hit_endpoint.sh Script +A script has been provided to test the endpoint. To use this script, run the following command: +`./hit_endpoint.sh ` + +This script will send a POST request to the endpoint with the provided data to sign. The output will be the signed data returned from the endpoint. See [Usage](#usage) for more information on the expected input and output. \ No newline at end of file From 29d8c5ade7065054469a71902e652b4b94e4925a Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:57:43 -0600 Subject: [PATCH 16/25] testing github artifact publishing --- .github/workflows/artifact-publish.yml | 33 ++++++++++++++++++++++++++ pom.xml | 9 ++++++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/artifact-publish.yml diff --git a/.github/workflows/artifact-publish.yml b/.github/workflows/artifact-publish.yml new file mode 100644 index 0000000..9dbf8cf --- /dev/null +++ b/.github/workflows/artifact-publish.yml @@ -0,0 +1,33 @@ +name: Publish Java Package + +on: + # push: + # tags: + # - 'jpo-sdw-depositor-*' + pull_request: + types: [opened, reopened, synchronize] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'adopt' + + - name: Remove snapshot from version + run: mvn versions:set -DremoveSnapshot + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Publish to GitHub Packages + run: mvn --batch-mode -Dgithub_organization=${{ github.repository_owner }} deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/pom.xml b/pom.xml index a517083..fb3aafe 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ reuseReports ${project.basedir}/target/site/jacoco/jacoco.xml java + usdot-jpo-ode @@ -118,5 +119,11 @@ - + + + github + GitHub Packages + https://maven.pkg.github.com/${github_organization}/jpo-security-svcs + + From 93e91fb0cd4b5117af55535625540d677624fb03 Mon Sep 17 00:00:00 2001 From: Michael7371 <40476797+Michael7371@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:00:41 -0600 Subject: [PATCH 17/25] updating trigger to only be on tag creation --- .github/workflows/artifact-publish.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/artifact-publish.yml b/.github/workflows/artifact-publish.yml index 9dbf8cf..d64951d 100644 --- a/.github/workflows/artifact-publish.yml +++ b/.github/workflows/artifact-publish.yml @@ -1,11 +1,9 @@ name: Publish Java Package on: - # push: - # tags: - # - 'jpo-sdw-depositor-*' - pull_request: - types: [opened, reopened, synchronize] + push: + tags: + - 'jpo-security-svcs-*' jobs: build: From e81af575b98213100a704796fed9045b7fc0fd80 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 30 Jul 2024 10:45:33 -0600 Subject: [PATCH 18/25] Added a test for the HttpClientFactory class --- .../sec/controllers/SignatureController.java | 4 ++++ .../jpo/sec/helpers/HttpClientFactory.java | 10 +++++--- .../controllers/SignatureControllerTest.java | 20 ++++++++++++++++ .../sec/helpers/HttpClientFactoryTest.java | 24 +++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/test/java/us/dot/its/jpo/sec/helpers/HttpClientFactoryTest.java diff --git a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java index ada7e94..71ebf5f 100644 --- a/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java +++ b/src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java @@ -177,6 +177,10 @@ protected JSONObject forwardMessageToExternalService(Message message) throws URI KeyStore keyStore = keyStoreReader.readStore(keyStorePath, keyStorePassword); SSLContext sslContext = sslContextFactory.getSSLContext(keyStore, keyStorePassword); HttpClient httpClient = httpClientFactory.getHttpClient(sslContext); + if (httpClient == null) { + logger.error("Error creating HttpClient"); + return null; + } HttpPost httpPost = new HttpPost(uri); httpPost.setHeader("Content-Type", "application/json"); diff --git a/src/main/java/us/dot/its/jpo/sec/helpers/HttpClientFactory.java b/src/main/java/us/dot/its/jpo/sec/helpers/HttpClientFactory.java index 27cd705..5b0abcb 100644 --- a/src/main/java/us/dot/its/jpo/sec/helpers/HttpClientFactory.java +++ b/src/main/java/us/dot/its/jpo/sec/helpers/HttpClientFactory.java @@ -3,6 +3,7 @@ import javax.net.ssl.SSLContext; import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.springframework.stereotype.Component; @@ -10,8 +11,11 @@ public class HttpClientFactory { public HttpClient getHttpClient(SSLContext sslContext) { - return HttpClients.custom() - .setSSLContext(sslContext) - .build(); + HttpClientBuilder httpClientBuilder = HttpClients.custom(); + if (sslContext == null) { + return null; + } + httpClientBuilder.setSSLContext(sslContext); + return httpClientBuilder.build(); } } diff --git a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java index 8ad3daa..b436077 100644 --- a/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java +++ b/src/test/java/us/dot/its/jpo/sec/controllers/SignatureControllerTest.java @@ -229,6 +229,26 @@ public void testForwardMessageToExternalService_useCertificates_True_ERROR() thr assertNull(response); } + @Test + public void testForwardMessageToExternalService_useCertificates_True_FailureToCreateHttpContext() throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, ClientProtocolException, IOException, URISyntaxException { + // prepare + setUp(); + uut.setUseCertificates(true); + uut.setCryptoServiceBaseUri("http://example.com/"); + uut.setCryptoServiceEndpointSignPath("endpoint"); + SSLContext mockSSLContext = mock(SSLContext.class); + doReturn(mockSSLContext).when(mockSSLContextFactory).getSSLContext(any(), any()); + doReturn(null).when(mockHttpClientFactory).getHttpClient(mockSSLContext); + Message message = new Message(); + message.setMsg("test"); + + // execute + JSONObject response = uut.forwardMessageToExternalService(message); + + // verify + assertNull(response); + } + @Test public void testTrimBaseUriAndEndpointPath_TrailingSlashInUri() { // prepare diff --git a/src/test/java/us/dot/its/jpo/sec/helpers/HttpClientFactoryTest.java b/src/test/java/us/dot/its/jpo/sec/helpers/HttpClientFactoryTest.java new file mode 100644 index 0000000..0f6c25c --- /dev/null +++ b/src/test/java/us/dot/its/jpo/sec/helpers/HttpClientFactoryTest.java @@ -0,0 +1,24 @@ +package us.dot.its.jpo.sec.helpers; + +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.apache.http.client.HttpClient; + +import mockit.Tested; + +@ExtendWith(MockitoExtension.class) +public class HttpClientFactoryTest { + + @Tested + HttpClientFactory httpClientFactory = new HttpClientFactory(); + + @Test + public void testGetHttpClient_nullSslContext() { + // execute + HttpClient httpClient = httpClientFactory.getHttpClient(null); + + // verify + assert(httpClient == null); + } +} From 856ee470f70ba6d5a6fb1a213f33cb8312ef7036 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 30 Jul 2024 10:54:34 -0600 Subject: [PATCH 19/25] Added test for HttpEntityStringifier class --- .../helpers/HttpEntityStringifierTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/test/java/us/dot/its/jpo/sec/helpers/HttpEntityStringifierTest.java diff --git a/src/test/java/us/dot/its/jpo/sec/helpers/HttpEntityStringifierTest.java b/src/test/java/us/dot/its/jpo/sec/helpers/HttpEntityStringifierTest.java new file mode 100644 index 0000000..0d425bf --- /dev/null +++ b/src/test/java/us/dot/its/jpo/sec/helpers/HttpEntityStringifierTest.java @@ -0,0 +1,29 @@ +package us.dot.its.jpo.sec.helpers; + +import java.io.IOException; + +import org.apache.http.ParseException; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import mockit.Tested; + +@ExtendWith(MockitoExtension.class) +public class HttpEntityStringifierTest { + + @Tested + HttpEntityStringifier httpEntityStringifier = new HttpEntityStringifier(); + + @Test + public void testStringifyHttpEntity() throws ParseException, IOException { + // prepare + org.apache.http.HttpEntity apache_entity = new org.apache.http.entity.StringEntity("test"); + + // execute + String stringified = httpEntityStringifier.stringifyHttpEntity(apache_entity); + + // verify + assert(stringified.equals("test")); + } +} From 9b62b055128c895f6617f14145bda46b7dc089f7 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 30 Jul 2024 11:24:28 -0600 Subject: [PATCH 20/25] Added tests for KeyStoreReader class --- .../jpo/sec/helpers/KeyStoreReaderTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/test/java/us/dot/its/jpo/sec/helpers/KeyStoreReaderTest.java diff --git a/src/test/java/us/dot/its/jpo/sec/helpers/KeyStoreReaderTest.java b/src/test/java/us/dot/its/jpo/sec/helpers/KeyStoreReaderTest.java new file mode 100644 index 0000000..0c24f1a --- /dev/null +++ b/src/test/java/us/dot/its/jpo/sec/helpers/KeyStoreReaderTest.java @@ -0,0 +1,84 @@ +package us.dot.its.jpo.sec.helpers; + +import static org.junit.Assert.assertThrows; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import mockit.Tested; + +@ExtendWith(MockitoExtension.class) +public class KeyStoreReaderTest { + + @Tested + KeyStoreReader keyStoreReader = new KeyStoreReader(); + + private void createKeyStoreForTesting() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { + KeyStore testKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + char[] password = "password".toCharArray(); + testKeyStore.load(null, password); + testKeyStore.store(new FileOutputStream("src/test/resources/test.jks"), password); + } + + private void setUp() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { + // create resources directory if it doesn't exist + File resourcesDir = new File("src/test/resources"); + if (!resourcesDir.exists()) { + resourcesDir.mkdir(); + } + File file = new File("src/test/resources/test.jks"); + if (file.exists()) { + file.delete(); + } + createKeyStoreForTesting(); + } + + @Test + public void testReadStore_Success() throws Exception { + // prepare + setUp(); + String keyStorePath = "src/test/resources/test.jks"; + String keyStorePassword = "password"; + + // execute + KeyStore keyStore = keyStoreReader.readStore(keyStorePath, keyStorePassword); + + // verify + assert(keyStore != null); + } + + @Test + public void testReadStore_Failure_WrongPassword() throws Exception { + // prepare + setUp(); + String keyStorePath = "src/test/resources/test.jks"; + String keyStorePassword = "wrongpassword"; + + // execute + assertThrows(Exception.class, () -> { + KeyStore keyStore = keyStoreReader.readStore(keyStorePath, keyStorePassword); + }); + } + + @Test + public void testReadStore_Failure_WrongPath() throws Exception { + // prepare + setUp(); + String keyStorePath = "src/test/resources/wrong.jks"; + String keyStorePassword = "password"; + + // execute + assertThrows(Exception.class, () -> { + KeyStore keyStore = keyStoreReader.readStore(keyStorePath, keyStorePassword); + }); + } +} From 49afc12deb6cf13e7f2d78a17694023def21ca80 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 30 Jul 2024 11:52:36 -0600 Subject: [PATCH 21/25] Added a test for SSLContextFactory class --- .../sec/helpers/SSLContextFactoryTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/test/java/us/dot/its/jpo/sec/helpers/SSLContextFactoryTest.java diff --git a/src/test/java/us/dot/its/jpo/sec/helpers/SSLContextFactoryTest.java b/src/test/java/us/dot/its/jpo/sec/helpers/SSLContextFactoryTest.java new file mode 100644 index 0000000..92a29cf --- /dev/null +++ b/src/test/java/us/dot/its/jpo/sec/helpers/SSLContextFactoryTest.java @@ -0,0 +1,41 @@ +package us.dot.its.jpo.sec.helpers; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.SSLContext; + +import org.apache.http.ParseException; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import mockit.Tested; + +@ExtendWith(MockitoExtension.class) +public class SSLContextFactoryTest { + + @Tested + SSLContextFactory sslContextFactory = new SSLContextFactory(); + + @Test + public void testGetSSLContext() throws ParseException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException { + // prepare + KeyStore testKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + char[] password = "password".toCharArray(); + testKeyStore.load(null, password); + + // execute + SSLContext sslContext = sslContextFactory.getSSLContext(testKeyStore, "password"); + + // verify + assert(sslContext != null); + } +} From 43dc01085efd75f799a8c8db857003748b13d186 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 30 Jul 2024 11:56:53 -0600 Subject: [PATCH 22/25] Added test-scripts README --- test-scripts/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test-scripts/README.md diff --git a/test-scripts/README.md b/test-scripts/README.md new file mode 100644 index 0000000..1cd9193 --- /dev/null +++ b/test-scripts/README.md @@ -0,0 +1,9 @@ +# Test Scripts + +## hit_endpoint.sh +The `hit_endpoint.sh` script is a simple bash script that sends a GET request to localhost:8090/sign and prints the response. It is used to test the `/sign` endpoint of the server, the code for which can be found in [SignatureController.java](../src/main/java/us/dot/its/jpo/sec/controllers/SignatureController.java). + +To run the script, simply execute the following command in the terminal: +```bash +./hit_endpoint.sh +``` From 79b8e8032667849fefce2219d886bd4d7dbcb259 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 3 Sep 2024 10:52:36 -0600 Subject: [PATCH 23/25] Fixed typo in `Mutual TLS Authentication` section of README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b03dc9e..576da9d 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ In `./src/main/resources/application.properties` you'll find the following prope | sec.keyStorePassword | The password for the keystore file. | password | SEC_KEY_STORE_PASSWORD | ## Mutual TLS Authentication -For enhanced security, MTLS is used to communicate with a remote signing system. This requires properly configured certificates from the remote system to property perform - work with your remote signing authority to obtain necessary certificates. +For enhanced security, MTLS is used to communicate with a remote signing system. This requires properly configured certificates from the remote system to properly perform - work with your remote signing authority to obtain necessary certificates. ### Certificate Conversion Certificates can be in several different formats, including the widely used PEM format. Because this is a Java application, the signing certificates must be in the Java Keystore format to be used. The following commands using [OpenSSL](https://www.openssl.org/) and the Java keytool will convert a PEM file to a Java Keystore file. From 626fc77e697dfb47bccaa6a8b55a9891ae3d701b Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 6 Sep 2024 16:47:00 -0600 Subject: [PATCH 24/25] Changed version to 1.5.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 670f373..b6666ca 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ usdot.jpo.ode jpo-security-svcs - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT jar jpo-security-svcs From d268434bc9dac89a06d2ca473675d228ec279e90 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Fri, 6 Sep 2024 16:49:11 -0600 Subject: [PATCH 25/25] Added release notes for version 1.5.0 --- docs/Release_notes.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/Release_notes.md b/docs/Release_notes.md index 91ddefc..91d2b4d 100644 --- a/docs/Release_notes.md +++ b/docs/Release_notes.md @@ -1,6 +1,17 @@ Jpo-security-svcs Release Notes ---------------------------- +Version 1.5.0, released September 2024 +---------------------------------------- +### **Summary** +The changes for the jpo-security-svcs v1.5.0 release include unit tests, documentation updates, and a GitHub action to publish java artifacts to GitHub's hosted Maven Central. + +Enhancements in this release: +- CDOT PR 10: Added unit tests to the project +- CDOT PR 11: Revised documentation for accuracy +- CDOT PR 12: Added GitHub action to publish java artifacts to GitHub's hosted Maven Central + + Version 1.4.0, released February 2024 ----------------------------------------