From d6d626de2370ab956167f12b6ae028ee7a4d127b Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Thu, 21 Sep 2023 15:39:04 +0530 Subject: [PATCH 01/10] fixed encryption and decryption process --- .../registry/helper/RegistryHelper.java | 2 + .../registry/service/DecryptionHelper.java | 8 ++- .../registry/service/EncryptionHelper.java | 9 ++- .../service/impl/RegistryServiceImpl.java | 6 +- .../sunbirdrc/registry/util/PrivateField.java | 72 ++++++++++++------- 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java index d95f59584..386749c7e 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java @@ -309,6 +309,8 @@ public JsonNode readEntity(String userId, String entityType, String label, boole resultNode = includePrivateFields ? decryptionHelper.getDecryptedJson(resultNode) : resultNode; } resultNode = vTransformer.transform(viewTemplate, resultNode); + } else if (encryptionEnabled) { + decryptionHelper.getDecryptedJson(resultNode); } logger.debug("readEntity ends"); if(isEventsEnabled) { diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/service/DecryptionHelper.java b/java/registry/src/main/java/dev/sunbirdrc/registry/service/DecryptionHelper.java index 8d99c5bfe..0324ebc75 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/service/DecryptionHelper.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/service/DecryptionHelper.java @@ -1,6 +1,8 @@ package dev.sunbirdrc.registry.service; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import dev.sunbirdrc.registry.exception.EncryptionException; import dev.sunbirdrc.registry.util.PrivateField; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -14,8 +16,10 @@ public class DecryptionHelper extends PrivateField { public JsonNode getDecryptedJson(JsonNode rootNode) throws EncryptionException { String rootFieldName = rootNode.fieldNames().next(); - process(rootNode.get(rootFieldName), rootFieldName, null); - return rootNode; + JsonNode updatedNode = process(rootNode.get(rootFieldName), rootFieldName, null); + ObjectNode objectNode = JsonNodeFactory.instance.objectNode(); + objectNode.set(rootFieldName, updatedNode); + return objectNode; } protected Map performOperation(Map plainMap) throws EncryptionException { diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/service/EncryptionHelper.java b/java/registry/src/main/java/dev/sunbirdrc/registry/service/EncryptionHelper.java index 361eb46e7..2ca78c9a3 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/service/EncryptionHelper.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/service/EncryptionHelper.java @@ -1,6 +1,8 @@ package dev.sunbirdrc.registry.service; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import dev.sunbirdrc.registry.exception.EncryptionException; import dev.sunbirdrc.registry.util.PrivateField; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -17,8 +19,9 @@ protected Map performOperation(Map plainMap) thr public JsonNode getEncryptedJson(JsonNode rootNode) throws EncryptionException { String rootFieldName = rootNode.fieldNames().next(); - process(rootNode.get(rootFieldName), rootFieldName, null); - - return rootNode; + JsonNode updatedNode = process(rootNode.get(rootFieldName), rootFieldName, null); + ObjectNode objectNode = JsonNodeFactory.instance.objectNode(); + objectNode.set(rootFieldName, updatedNode); + return objectNode; } } diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/RegistryServiceImpl.java b/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/RegistryServiceImpl.java index 8652ab8c8..267fd5bf1 100755 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/RegistryServiceImpl.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/RegistryServiceImpl.java @@ -212,12 +212,12 @@ public String addEntity(Shard shard, String userId, JsonNode rootNode, boolean s String vertexLabel = rootNode.fieldNames().next(); Definition definition = null; - systemFieldsHelper.ensureCreateAuditFields(vertexLabel, rootNode.get(vertexLabel), userId); - if (encryptionEnabled) { rootNode = encryptionHelper.getEncryptedJson(rootNode); } + systemFieldsHelper.ensureCreateAuditFields(vertexLabel, rootNode.get(vertexLabel), userId); + if (!skipSignature) { generateCredentials(rootNode, vertexLabel); } @@ -287,10 +287,10 @@ private void generateCredentials(JsonNode rootNode, String vertexLabel) throws S public void updateEntity(Shard shard, String userId, String id, String jsonString, boolean skipSignature) throws Exception { JsonNode inputNode = objectMapper.readTree(jsonString); String entityType = inputNode.fields().next().getKey(); - systemFieldsHelper.ensureUpdateAuditFields(entityType, inputNode.get(entityType), userId); if (encryptionEnabled) { inputNode = encryptionHelper.getEncryptedJson(inputNode); } + systemFieldsHelper.ensureUpdateAuditFields(entityType, inputNode.get(entityType), userId); DatabaseProvider databaseProvider = shard.getDatabaseProvider(); IRegistryDao registryDao = new RegistryDaoImpl(databaseProvider, definitionsManager, uuidPropertyName); diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/util/PrivateField.java b/java/registry/src/main/java/dev/sunbirdrc/registry/util/PrivateField.java index a97f80790..29d386454 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/util/PrivateField.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/util/PrivateField.java @@ -3,18 +3,23 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; import dev.sunbirdrc.registry.exception.EncryptionException; import dev.sunbirdrc.registry.middleware.util.Constants; +import dev.sunbirdrc.registry.middleware.util.JSONUtil; import dev.sunbirdrc.registry.service.EncryptionService; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class PrivateField { @Autowired @@ -31,13 +36,13 @@ public class PrivateField { */ public Map getPrivateFields(JsonNode rootNode, List privatePropertyLst) { Map plainKeyValues = new HashMap<>(); - rootNode.fields().forEachRemaining(entry -> { - JsonNode entryValue = entry.getValue(); - if (entryValue.isValueNode()) { - if (privatePropertyLst.contains(entry.getKey())) - plainKeyValues.put(entry.getKey(), entryValue.asText()); - } - }); + if(privatePropertyLst != null) { + DocumentContext documentContext = JsonPath.parse(rootNode.toString()); + privatePropertyLst.forEach(path -> { + Object read = documentContext.read(path); + plainKeyValues.put(path, read); + }); + } return plainKeyValues; } @@ -49,17 +54,15 @@ public Map getPrivateFields(JsonNode rootNode, List priv * @param privatePropertyLst * @param privateFieldMap Contains the values encrypted/decrypted based on base call */ - public JsonNode replacePrivateFields(JsonNode rootNode, List privatePropertyLst, Map privateFieldMap) { - - rootNode.fields().forEachRemaining(entry -> { - JsonNode entryValue = entry.getValue(); - - if (entryValue.isValueNode() && privatePropertyLst.contains(entry.getKey())) { - String privateFieldValue = privateFieldMap.get(entry.getKey()).toString(); - JsonNode encryptedValNode = JsonNodeFactory.instance.textNode(privateFieldValue); - entry.setValue(encryptedValNode); - } - }); + public JsonNode replacePrivateFields(JsonNode rootNode, List privatePropertyLst, Map privateFieldMap) throws IOException { + if (privatePropertyLst != null) { + DocumentContext documentContext = JsonPath.parse(rootNode.toString()); + privateFieldMap.forEach((key, value) -> { + String privateFieldValue = value.toString(); + documentContext.set(key, privateFieldValue); + }); + return JSONUtil.convertStringJsonNode(documentContext.jsonString()); + } return rootNode; } @@ -70,35 +73,50 @@ protected Map performOperation(Map plainMap) thr protected JsonNode processPrivateFields(JsonNode element, String rootDefinitionName, String childFieldName) throws EncryptionException { JsonNode tempElement = element; Definition definition = definitionsManager.getDefinition(rootDefinitionName);; - if (null != childFieldName) { + if (null != childFieldName && definition != null) { String defnName = definition.getDefinitionNameForField(childFieldName); Definition childDefinition = definitionsManager.getDefinition(defnName); if (null == childDefinition) { - logger.error("Cannot get child name definition {}", childFieldName); + logger.info("Cannot get child name definition {}", childFieldName); return element; } definition = childDefinition; + } else if (definition == null) { + return element; } - List privatePropertyLst = definition.getOsSchemaConfiguration().getPrivateFields(); + List privatePropertyLst = definition.getOsSchemaConfiguration().getPrivateFields() + .stream().map(d -> { + if (!d.startsWith("$.")) + return String.format("$.%s", d.replaceAll("/", ".")); + return d; + }).collect(Collectors.toList()); Map plainMap = getPrivateFields(element, privatePropertyLst); if (null != plainMap && !plainMap.isEmpty()) { Map encodedMap = performOperation(plainMap); - tempElement = replacePrivateFields(element, privatePropertyLst, encodedMap); + try { + tempElement = replacePrivateFields(element, privatePropertyLst, encodedMap); + } catch (IOException e) { + throw new RuntimeException(e); + } } return tempElement; } - private void processArray(ArrayNode arrayNode, String rootFieldName, String fieldName) throws EncryptionException { + private ArrayNode processArray(ArrayNode arrayNode, String rootFieldName, String fieldName) throws EncryptionException { + ArrayNode updatedArrayNode = JsonNodeFactory.instance.arrayNode(); for (JsonNode jsonNode : arrayNode) { + JsonNode updatedNode = jsonNode; if (jsonNode.isObject()) { - process(jsonNode, rootFieldName, fieldName); + updatedNode = process(jsonNode, rootFieldName, fieldName); } + updatedArrayNode.add(updatedNode); } + return updatedArrayNode; } protected JsonNode process(JsonNode jsonNode, String rootFieldName, String fieldName) throws EncryptionException { - processPrivateFields(jsonNode, rootFieldName, fieldName); + jsonNode = processPrivateFields(jsonNode, rootFieldName, fieldName); String tempFieldName = fieldName; if (null == tempFieldName) { @@ -121,9 +139,9 @@ protected JsonNode process(JsonNode jsonNode, String rootFieldName, String field if (isNotSignatures && entryValue.isObject()) { // Recursive calls - process(entryValue, tempFieldName, entry.getKey()); + entry.setValue(process(entryValue, tempFieldName, entry.getKey())); } else if (isNotSignatures && entryValue.isArray()) { - processArray((ArrayNode) entryValue, tempFieldName, entry.getKey()); + entry.setValue(processArray((ArrayNode) entryValue, tempFieldName, entry.getKey())); } } catch (EncryptionException e) { logger.error("Exception occurred in PrivateField: {}", ExceptionUtils.getStackTrace(e)); From f241619a3c58bb962051742c0bf48635d9b93b3b Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Fri, 22 Sep 2023 17:46:31 +0530 Subject: [PATCH 02/10] fixed encryption issues --- .../registry/helper/RegistryHelper.java | 2 +- .../service/impl/EncryptionServiceImpl.java | 80 ++++++++----------- .../sunbirdrc/registry/util/PrivateField.java | 5 +- .../src/main/resources/application.yml | 11 ++- 4 files changed, 43 insertions(+), 55 deletions(-) diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java index 386749c7e..da782e16b 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java @@ -310,7 +310,7 @@ public JsonNode readEntity(String userId, String entityType, String label, boole } resultNode = vTransformer.transform(viewTemplate, resultNode); } else if (encryptionEnabled) { - decryptionHelper.getDecryptedJson(resultNode); + resultNode = decryptionHelper.getDecryptedJson(resultNode); } logger.debug("readEntity ends"); if(isEventsEnabled) { diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/EncryptionServiceImpl.java b/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/EncryptionServiceImpl.java index 9c5d21323..b1968f799 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/EncryptionServiceImpl.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/EncryptionServiceImpl.java @@ -23,8 +23,7 @@ import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.RestClientException; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static dev.sunbirdrc.registry.middleware.util.Constants.CONNECTION_FAILURE; import static dev.sunbirdrc.registry.middleware.util.Constants.SUNBIRD_ENCRYPTION_SERVICE_NAME; @@ -36,6 +35,10 @@ public class EncryptionServiceImpl implements EncryptionService { private static Logger logger = LoggerFactory.getLogger(EncryptionServiceImpl.class); @Value("${encryption.enabled}") private boolean encryptionEnabled; + @Value("${encryption.tenant.id}") + private String encryptionTenantId; + @Value("${encryption.method}") + private String encryptionMethod; @Value("${encryption.uri}") private String encryptionUri; @Value("${decryption.uri}") @@ -62,20 +65,7 @@ public class EncryptionServiceImpl implements EncryptionService { */ @Override public String encrypt(Object propertyValue) throws EncryptionException { - logger.debug("encrypt starts with value"); - MultiValueMap map = new LinkedMultiValueMap<>(); - map.add("value", propertyValue); - HttpEntity> request = new HttpEntity<>(map); - try { - ResponseEntity response = retryRestTemplate.postForEntity(encryptionUri, request); - return response.getBody(); - } catch (ResourceAccessException e) { - logger.error("ResourceAccessException while connecting encryption service : {}", ExceptionUtils.getStackTrace(e)); - throw new EncryptionException("Exception while connecting encryption service! "); - } catch (Exception e) { - logger.error("Exception in encryption service !: {}", ExceptionUtils.getStackTrace(e)); - throw new EncryptionException("Exception in encryption service"); - } + return this.doEncrypt(propertyValue, encryptionUri).toString(); } /** decrypts the input @@ -85,21 +75,7 @@ public String encrypt(Object propertyValue) throws EncryptionException { */ @Override public String decrypt(Object propertyValue) throws EncryptionException { - logger.debug("decrypt starts with value {}", propertyValue); - MultiValueMap map = new LinkedMultiValueMap<>(); - map.add("value", propertyValue); - HttpEntity> request = new HttpEntity<>(map); - try { - ResponseEntity response = retryRestTemplate.postForEntity(decryptionUri,request); - logger.info("Property decrypted successfully !"); - return response.getBody(); - } catch (ResourceAccessException e) { - logger.error("ResourceAccessException while connecting decryption service : {}", ExceptionUtils.getStackTrace(e)); - throw new EncryptionException("Exception while connecting encryption service ! "); - } catch (Exception e) { - logger.error("Exception in decryption service !: {}", ExceptionUtils.getStackTrace(e)); - throw new EncryptionException("Exception in encryption service ! "); - } + return this.doDecrypt(propertyValue, decryptionUri).toString(); } /** encrypts the input which is in Map format @@ -109,19 +85,39 @@ public String decrypt(Object propertyValue) throws EncryptionException { */ @Override public Map encrypt(Map propertyValue) throws EncryptionException { + return this.doEncrypt(propertyValue, encryptionBatchUri); + } + + /** decrypts the input which is in Map format + * @param propertyValue - input is in format Map + * @return Map + * @throws EncryptionException + */ + @Override + public Map decrypt(Map propertyValue) throws EncryptionException { + return this.doDecrypt(propertyValue, decryptionBatchUri); + } + + private T doEncrypt(T propertyValue, String uri) throws EncryptionException { logger.debug("encrypt starts with value {}", propertyValue); Map map = new HashMap<>(); - map.put("value", propertyValue); + Map encReqObj = new HashMap<>(); + encReqObj.put("tenantId", encryptionTenantId); + encReqObj.put("value", propertyValue); + encReqObj.put("type", encryptionMethod); + map.put("encryptionRequests", Collections.singletonList(encReqObj)); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity entity = new HttpEntity<>(gson.toJson(map), headers); try { watch.start("EncryptionServiceImpl.encryptBatch"); - ResponseEntity response = retryRestTemplate.postForEntity(encryptionBatchUri,entity); + ResponseEntity response = retryRestTemplate.postForEntity(uri, entity); watch.stop("EncryptionServiceImpl.encryptBatch"); - return gson.fromJson(response.getBody(), new TypeToken>() { + List results = gson.fromJson(response.getBody(), new TypeToken>() { }.getType()); + assert results != null; + return results.get(0); } catch (ResourceAccessException e) { logger.error("Exception while connecting encryption service : {}", ExceptionUtils.getStackTrace(e)); throw new EncryptionException("Exception while connecting encryption service! "); @@ -131,26 +127,18 @@ public Map encrypt(Map propertyValue) throws Enc } } - /** decrypts the input which is in Map format - * @param propertyValue - input is in format Map - * @return Map - * @throws EncryptionException - */ - @Override - public Map decrypt(Map propertyValue) throws EncryptionException { + private T doDecrypt(T propertyValue, String uri) throws EncryptionException { logger.debug("decrypt starts with value {}", propertyValue); - Map map = new HashMap<>(); - map.put("value", propertyValue); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity entity = new HttpEntity<>(gson.toJson(map), headers); + HttpEntity entity = new HttpEntity<>(gson.toJson(propertyValue), headers); try { watch.start("EncryptionServiceImpl.decryptBatch"); - ResponseEntity response = retryRestTemplate.postForEntity(decryptionBatchUri,entity); + ResponseEntity response = retryRestTemplate.postForEntity(uri, entity); watch.stop("EncryptionServiceImpl.decryptBatch"); - return gson.fromJson(response.getBody(), new TypeToken>() { + return gson.fromJson(response.getBody(), new TypeToken() { }.getType()); } catch (ResourceAccessException e) { logger.error("Exception while connecting decryption service : {}", ExceptionUtils.getStackTrace(e)); diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/util/PrivateField.java b/java/registry/src/main/java/dev/sunbirdrc/registry/util/PrivateField.java index 29d386454..9b2b8f534 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/util/PrivateField.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/util/PrivateField.java @@ -57,10 +57,7 @@ public Map getPrivateFields(JsonNode rootNode, List priv public JsonNode replacePrivateFields(JsonNode rootNode, List privatePropertyLst, Map privateFieldMap) throws IOException { if (privatePropertyLst != null) { DocumentContext documentContext = JsonPath.parse(rootNode.toString()); - privateFieldMap.forEach((key, value) -> { - String privateFieldValue = value.toString(); - documentContext.set(key, privateFieldValue); - }); + privateFieldMap.forEach(documentContext::set); return JSONUtil.convertStringJsonNode(documentContext.jsonString()); } return rootNode; diff --git a/java/registry/src/main/resources/application.yml b/java/registry/src/main/resources/application.yml index 4ff75516a..ef3803471 100644 --- a/java/registry/src/main/resources/application.yml +++ b/java/registry/src/main/resources/application.yml @@ -190,14 +190,17 @@ frame: encryption: enabled: ${encryption_enabled:false} healthCheckURL: ${encryption_health_check_url:http://localhost:8013} - uri: ${encryption_uri:http://localhost:8013/encrypt} + uri: ${encryption_uri:http://localhost:8013/crypto/v1/_encrypt} batch: - uri: ${encryption_batch_uri:http://localhost:8013/encrypt/obj} + uri: ${encryption_batch_uri:http://localhost:8013/crypto/v1/_encrypt} + method: ${encryption_method:Normal} + tenant: + id: ${encryption_tenant_id:sunbird} decryption: - uri: ${decryption_uri:http://localhost:8013/decrypt} + uri: ${decryption_uri:http://localhost:8013/crypto/v1/_decrypt} batch: - uri: ${decryption_batch_uri:http://localhost:8013/decrypt/obj} + uri: ${decryption_batch_uri:http://localhost:8013/crypto/v1/_decrypt} signature: enabled: ${signature_enabled:true} From c51dc545354b5c0aa3df040e03b145a3a97810f1 Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Fri, 22 Sep 2023 17:47:44 +0530 Subject: [PATCH 03/10] added encryption service --- docker-compose.yml | 10 + services/encryption-service/.gitignore | 1 + services/encryption-service/CHANGELOG.md | 32 +++ services/encryption-service/Dockerfile | 6 + services/encryption-service/LOCALSETUP.md | 21 ++ services/encryption-service/Makefile | 15 ++ services/encryption-service/README.md | 63 ++++++ .../egov-enc-service.postman_collection.json | 203 +++++++++++++++++ services/encryption-service/enc-service.yaml | 181 +++++++++++++++ services/encryption-service/pom.xml | 162 ++++++++++++++ .../egov-enc-service.postman_collection.json | 203 +++++++++++++++++ .../src/main/java/org/egov/enc/Main.java | 15 ++ .../org/egov/enc/config/AppProperties.java | 46 ++++ .../egov/enc/config/MainConfiguration.java | 40 ++++ .../egov/enc/keymanagement/KeyGenerator.java | 101 +++++++++ .../enc/keymanagement/KeyIdGenerator.java | 59 +++++ .../org/egov/enc/keymanagement/KeyStore.java | 208 ++++++++++++++++++ .../masterkey/MasterKeyProvider.java | 9 + .../masterkey/providers/AwsKmsMasterKey.java | 65 ++++++ .../providers/SoftwareBasedMasterKey.java | 81 +++++++ .../org/egov/enc/models/AsymmetricKey.java | 27 +++ .../java/org/egov/enc/models/Ciphertext.java | 30 +++ .../java/org/egov/enc/models/MethodEnum.java | 33 +++ .../java/org/egov/enc/models/ModeEnum.java | 34 +++ .../java/org/egov/enc/models/Plaintext.java | 22 ++ .../java/org/egov/enc/models/Signature.java | 31 +++ .../org/egov/enc/models/SymmetricKey.java | 27 +++ .../egov/enc/repository/KeyRepository.java | 89 ++++++++ .../services/AsymmetricEncryptionService.java | 50 +++++ .../egov/enc/services/EncryptionService.java | 49 +++++ .../services/EncryptionServiceInterface.java | 20 ++ .../enc/services/KeyManagementService.java | 143 ++++++++++++ .../egov/enc/services/SignatureService.java | 46 ++++ .../services/SymmetricEncryptionService.java | 56 +++++ .../enc/utils/AsymmetricEncryptionUtil.java | 47 ++++ .../java/org/egov/enc/utils/Constants.java | 9 + .../org/egov/enc/utils/ProcessJSONUtil.java | 120 ++++++++++ .../org/egov/enc/utils/SignatureUtil.java | 43 ++++ .../enc/utils/SymmetricEncryptionUtil.java | 46 ++++ .../web/controllers/CryptoApiController.java | 73 ++++++ .../org/egov/enc/web/models/EncReqObject.java | 39 ++++ .../enc/web/models/EncryptionRequest.java | 20 ++ .../egov/enc/web/models/RotateKeyRequest.java | 19 ++ .../enc/web/models/RotateKeyResponse.java | 16 ++ .../org/egov/enc/web/models/SignRequest.java | 67 ++++++ .../org/egov/enc/web/models/SignResponse.java | 19 ++ .../egov/enc/web/models/VerifyRequest.java | 68 ++++++ .../egov/enc/web/models/VerifyResponse.java | 17 ++ .../src/main/resources/application.properties | 51 +++++ .../src/main/resources/db/Dockerfile | 9 + .../src/main/resources/db/migrate.sh | 3 + .../main/V20180607185601__eg_enc.sql | 31 +++ .../java/org/egov/enc/TestConfiguration.java | 16 ++ .../controllers/CryptoApiControllerTest.java | 86 ++++++++ 54 files changed, 2977 insertions(+) create mode 100644 services/encryption-service/.gitignore create mode 100644 services/encryption-service/CHANGELOG.md create mode 100644 services/encryption-service/Dockerfile create mode 100644 services/encryption-service/LOCALSETUP.md create mode 100644 services/encryption-service/Makefile create mode 100644 services/encryption-service/README.md create mode 100644 services/encryption-service/egov-enc-service.postman_collection.json create mode 100644 services/encryption-service/enc-service.yaml create mode 100644 services/encryption-service/pom.xml create mode 100644 services/encryption-service/postman/egov-enc-service.postman_collection.json create mode 100644 services/encryption-service/src/main/java/org/egov/enc/Main.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/config/AppProperties.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/config/MainConfiguration.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyGenerator.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyIdGenerator.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyStore.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/MasterKeyProvider.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/providers/AwsKmsMasterKey.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/providers/SoftwareBasedMasterKey.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/models/AsymmetricKey.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/models/Ciphertext.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/models/MethodEnum.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/models/ModeEnum.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/models/Plaintext.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/models/Signature.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/models/SymmetricKey.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/repository/KeyRepository.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/AsymmetricEncryptionService.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/EncryptionService.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/EncryptionServiceInterface.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/SignatureService.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/SymmetricEncryptionService.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/utils/AsymmetricEncryptionUtil.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/utils/Constants.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/utils/ProcessJSONUtil.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/utils/SignatureUtil.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/utils/SymmetricEncryptionUtil.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/models/EncReqObject.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/models/EncryptionRequest.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/models/RotateKeyRequest.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/models/RotateKeyResponse.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/models/SignRequest.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/models/SignResponse.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/models/VerifyRequest.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/web/models/VerifyResponse.java create mode 100644 services/encryption-service/src/main/resources/application.properties create mode 100644 services/encryption-service/src/main/resources/db/Dockerfile create mode 100644 services/encryption-service/src/main/resources/db/migrate.sh create mode 100644 services/encryption-service/src/main/resources/db/migration/main/V20180607185601__eg_enc.sql create mode 100644 services/encryption-service/src/test/java/org/egov/enc/TestConfiguration.java create mode 100644 services/encryption-service/src/test/java/org/egov/enc/web/controllers/CryptoApiControllerTest.java diff --git a/docker-compose.yml b/docker-compose.yml index 531719bb8..1a1f0a687 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -319,3 +319,13 @@ services: - "9002:9000" healthcheck: test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 + encryption-service: + image: ghcr.io/sunbird-rc/encryption-service:v0.0.14 + ports: + - '8013:8013' + environment: + spring.datasource.url: jdbc:postgresql://db:5432/registry + spring.flyway.url: jdbc:postgresql://db:5432/registry + depends_on: + db: + condition: service_healthy \ No newline at end of file diff --git a/services/encryption-service/.gitignore b/services/encryption-service/.gitignore new file mode 100644 index 000000000..c8b241f22 --- /dev/null +++ b/services/encryption-service/.gitignore @@ -0,0 +1 @@ +target/* \ No newline at end of file diff --git a/services/encryption-service/CHANGELOG.md b/services/encryption-service/CHANGELOG.md new file mode 100644 index 000000000..99c93f4a6 --- /dev/null +++ b/services/encryption-service/CHANGELOG.md @@ -0,0 +1,32 @@ + + +# Changelog +All notable changes to this module will be documented in this file. + +## 1.1.4 - 2023-02-06 +- Transition from 1.1.4-beta version to 1.1.4 version + +## 1.1.4-beta - 2022-09-20 +- Upgraded spring-boot-starter-parent to 2.2.13 and spring beans to 5.2.20.RELEASE + +## 1.1.3 - 2022007016 +- Fixed: In a multi pod cluster, the service now checks if another deployment of the service has added a new key to the database, before throwing the key not found error. + + +## 1.1.2 - 2022-01-13 +- Updated to log4j2 version 2.17.1 + +## 1.1.1 - 2021-02-26 + +- Updated domain name in application.properties + +## 1.1.0 - 2020-06-18 + +- Added typescript definition generation plugin +- Upgraded to `tracer:2.0.0-SNAPSHOT` +- Upgraded to spring boot `2.2.6-RELEASE` +- Upgraded flyway version to `6.4.3` + +## 1.0.0 + +- Base version diff --git a/services/encryption-service/Dockerfile b/services/encryption-service/Dockerfile new file mode 100644 index 000000000..aff4b2b7f --- /dev/null +++ b/services/encryption-service/Dockerfile @@ -0,0 +1,6 @@ +FROM frolvlad/alpine-java:jdk8-slim +COPY ./BOOT-INF/lib /home/sunbirdrc/BOOT-INF/lib +COPY ./META-INF /home/sunbirdrc/META-INF +COPY ./org /home/sunbirdrc/org +COPY ./BOOT-INF/classes /home/sunbirdrc/BOOT-INF/classes +CMD ["java", "-Xms1024m", "-Xmx2048m", "-XX:+UseG1GC", "-Dserver.port=8013", "-cp", "/home/sunbirdrc/config:/home/sunbirdrc", "org.springframework.boot.loader.JarLauncher"] diff --git a/services/encryption-service/LOCALSETUP.md b/services/encryption-service/LOCALSETUP.md new file mode 100644 index 000000000..8a7a18999 --- /dev/null +++ b/services/encryption-service/LOCALSETUP.md @@ -0,0 +1,21 @@ +# Local Setup + +To setup the encryption service in your local system, clone the [Core Service repository](https://github.com/egovernments/core-services). + +## Dependencies + +### Infra Dependency + +- [X] Postgres DB +- [ ] Redis +- [ ] Elasticsearch +- [ ] Kafka + - [ ] Consumer + - [ ] Producer + +## Running Locally + +```bash +- Just run the service, no change is required + +``` \ No newline at end of file diff --git a/services/encryption-service/Makefile b/services/encryption-service/Makefile new file mode 100644 index 000000000..06ccce5d9 --- /dev/null +++ b/services/encryption-service/Makefile @@ -0,0 +1,15 @@ +RELEASE_VERSION=v0.0.14 +IMAGE_NAME=ghcr.io/sunbird-rc/encryption-service + +build: + rm -rf ./target + mvn clean install -DSkipTests + +build-image: build + cd target && jar xvf egov-enc-service-1.1.4.jar && cp ../Dockerfile . && docker build -t $(IMAGE_NAME) . + +release: build-image + docker tag $(IMAGE_NAME):latest $(IMAGE_NAME):$(RELEASE_VERSION) +# @docker push $(IMAGE_NAME):latest +# @docker push $(IMAGE_NAME):$(RELEASE_VERSION) + diff --git a/services/encryption-service/README.md b/services/encryption-service/README.md new file mode 100644 index 000000000..3eb4ccfa3 --- /dev/null +++ b/services/encryption-service/README.md @@ -0,0 +1,63 @@ +# eGov Encryption Service + +Encryption Service is used to secure the data. It provides functionality to encrypt and decrypt data + +### DB UML Diagram + +- To Do + + +### Swagger API Contract + +http://editor.swagger.io/?url=https://raw.githubusercontent.com/egovernments/core-services/gopesh67-patch-8/docs/enc-service-contract.yml#!/ + +## Service Details + +Encryption Service offers following features : + +- Encrypt - The service will encrypt the data based on given input parameters and data to be encrypted. The encrypted data will be mandatorily of type string. +- Decrypt - The decryption will happen solely based on the input data (any extra parameters are not required). The encrypted data will have identity of the key used at the time of encryption, the same key will be used for decryption. +- Sign - Encryption Service can hash and sign the data which can be used as unique identifier of the data. This can also be used for searching gicen value from a datastore. +- Verify - Based on the input sign and the claim, it can verify if the the given sign is correct for the provided claim. +- Rotate Key - Encryption Service supports changing the key used for encryption. The old key will still remain with the service which will be used to decrypt old data. All the new data will be encrypted by the new key. + +#### Configurations + +Following are the properties in application.properties file in egov-enc-service which are configurable. + +| Property | Default Value | Remarks | +| -----------------------------| ------------------| -----------------------------------------------------------------------------------------------------------------------------| +| `master-password` | asd@#$@$!132123 | Master password for encryption/ decryption. | +| `master.salt` | qweasdzx | A salt is random data that is used as an additional input to a one-way function that hashes data, a password or passphrase. | +| `master.initialvector` | qweasdzxqwea | An initialization vector is a fixed-size input to a cryptographic primitive. | +| `size.key.symmetric` | 256 | Default size of Symmetric key. | +| `size.key.asymmetric` | 1024 | Default size of Asymmetric key. | +| `size.initialvector` | 12 | Default size of Initial vector. | + +### API Details + +a) `POST /crypto/v1/_encrypt` + +Encrypts the given input value/s OR values of the object. + +b) `POST /crypto/v1/_decrypt` + +Decrypts the given input value/s OR values of the object. + +c) `/crypto/v1/_sign` + +Provide signature for a given value. + +d) `POST /crypto/v1/_verify` + +Check if the signature is correct for the provided value. + +e) `POST /crypto/v1/_rotatekey` + +Deactivate the keys for the given tenant and generate new keys. It will deactivate both symmetric and asymmetric keys for the provided tenant. + +### Kafka Consumers +NA + +### Kafka Producers +NA \ No newline at end of file diff --git a/services/encryption-service/egov-enc-service.postman_collection.json b/services/encryption-service/egov-enc-service.postman_collection.json new file mode 100644 index 000000000..7321f5562 --- /dev/null +++ b/services/encryption-service/egov-enc-service.postman_collection.json @@ -0,0 +1,203 @@ +{ + "info": { + "_postman_id": "4578ff36-17a2-4fc0-8614-ed3094f71a68", + "name": "egov-enc-service", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Encrypt", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"encryptionRequests\": [\n {\n \"tenantId\": \"pb\",\n \"type\": \"Imp\",\n \"value\": \"My email\"\n },\n {\n \"tenantId\": \"pb.jalandhar\",\n \"type\": \"Normal\",\n \"value\": [\n \"Personal\",\n \"Private\"\n ]\n },\n {\n \"tenantId\": \"pb.mohali\",\n \"type\": \"Normal\",\n \"value\": {\n \"userObject1\": {\n \"name\": \"John Doe\",\n \"mobileNumber\": \"98989121234\"\n }\n }\n }\n ]\n}" + }, + "url": { + "raw": "localhost:1234//egov-enc-service/crypto/v1/_encrypt", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "", + "egov-enc-service", + "crypto", + "v1", + "_encrypt" + ] + } + }, + "response": [] + }, + { + "name": "Decrypt", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "[\n \"437506|A5ag4DfbhHAHiqXRKFcAedFKtNOelHX+8+jB0ckNG/tihwimx7xu6akEoa+kaQPcIhSnYeveloIhdPBCOgrXWvkWGZfShx1i2bE2vAcWB+r0YIDdwZLKJbQGBHDqcEOn8mfO+LnmpJ5P4zPETtE+2EHhta+vKcE5OQj8ZQawHS4=\",\n [\n \"896077|I/8Xwqr5MwB6UucEP8/Q5wiCHpbaNqGE\",\n \"896077|I+gMx6TjN0BcLxudEiYQKIDKtSlmpJY=\"\n ],\n {\n \"userObject1\": {\n \"mobileNumber\": \"395551|eSfiPrQ1UK07d0SupYQYqbr2QFNOWSuYJYcU\",\n \"name\": \"395551|CnCzaK1ADfnx+4FINXIQ9zjnUs1ieAtz\"\n }\n }\n]" + }, + "url": { + "raw": "localhost:1234//egov-enc-service/crypto/v1/_decrypt", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "", + "egov-enc-service", + "crypto", + "v1", + "_decrypt" + ] + } + }, + "response": [] + }, + { + "name": "Sign", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"tenantId\": \"pb.jalandhar\",\n\t\"value\": \"value\"\n}" + }, + "url": { + "raw": "localhost:1234//egov-enc-service/crypto/v1/_sign", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "", + "egov-enc-service", + "crypto", + "v1", + "_sign" + ] + } + }, + "response": [] + }, + { + "name": "Verify", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"value\": \"value\",\n \"signature\": \"553916|dyQwMYZw7an2FhXg14yKki5L/jd2WrUMAfp1uzMPkNlwXILNeW0VMxa7USFI71QvCQCLPnQGCXcPXSo6jXiIJrZhSn8B4etGWrXXEjxSoSF8l/t+XiTJZoeBxnefb112iMxJCW2ZIDRdp83wItPJwxVfWHxnWOTHemQHUNNwzk4=\"\n}" + }, + "url": { + "raw": "localhost:1234//egov-enc-service/crypto/v1/_verify", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "", + "egov-enc-service", + "crypto", + "v1", + "_verify" + ] + } + }, + "response": [] + }, + { + "name": "Rotate Key", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"tenantId\": \"pb.phagwara\"\n}" + }, + "url": { + "raw": "localhost:1234/egov-enc-service/crypto/v1/_rotatekey", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "egov-enc-service", + "crypto", + "v1", + "_rotatekey" + ] + } + }, + "response": [] + }, + { + "name": "Fetch Tenants", + "request": { + "method": "POST", + "header": [ + { + "key": "Cache-Control", + "value": "no-cache" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Postman-Token", + "value": "5f9b2036-0eab-475d-9314-eb8ba585471f" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"RequestInfo\": {\n \"apiId\": \"asset-services\",\n \"ver\": null,\n \"ts\": null,\n \"action\": null,\n \"did\": null,\n \"key\": null,\n \"msgId\": \"search with from and to values\",\n \"authToken\": \"59854f79-7031-4157-9cb5-21c51cb61981\"\n },\n \"MdmsCriteria\": {\n \"tenantId\": \"pb\",\n \"moduleDetails\": [\n {\n \"moduleName\": \"tenant\",\n \"masterDetails\": [\n {\n \"name\": \"tenants\",\n \"filter\":\"$.*.code\"\n }\n ]\n }\n ]\n }\n}" + }, + "url": { + "raw": "https://egov-micro-dev.egovernments.org/egov-mdms-service/v1/_search", + "protocol": "https", + "host": [ + "egov-micro-dev", + "egovernments", + "org" + ], + "path": [ + "egov-mdms-service", + "v1", + "_search" + ] + }, + "description": "Generated from a curl request: \ncurl -X POST \\\n https://egov-micro-dev.egovernments.org/egov-mdms-service/v1/_search \\\n -H 'Cache-Control: no-cache' \\\n -H 'Content-Type: application/json' \\\n -H 'Postman-Token: 5f9b2036-0eab-475d-9314-eb8ba585471f' \\\n -d '{\n \\\"RequestInfo\\\": {\n \\\"apiId\\\": \\\"asset-services\\\",\n \\\"ver\\\": null,\n \\\"ts\\\": null,\n \\\"action\\\": null,\n \\\"did\\\": null,\n \\\"key\\\": null,\n \\\"msgId\\\": \\\"search with from and to values\\\",\n \\\"authToken\\\": \\\"59854f79-7031-4157-9cb5-21c51cb61981\\\"\n },\n \\\"MdmsCriteria\\\": {\n \\\"tenantId\\\": \\\"pb\\\",\n \\\"moduleDetails\\\": [\n {\n \\\"moduleName\\\": \\\"tenant\\\",\n \\\"masterDetails\\\": [\n {\n \\\"name\\\": \\\"tenants\\\",\n \\\"filter\\\":\\\"$.*.code\\\"\n }\n ]\n }\n ]\n }\n}'" + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/services/encryption-service/enc-service.yaml b/services/encryption-service/enc-service.yaml new file mode 100644 index 000000000..d0f748f8e --- /dev/null +++ b/services/encryption-service/enc-service.yaml @@ -0,0 +1,181 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: eGov Encryption Service + description: API for encryption / decryption + contact: + name: Egovernments Foundation + email: contact@egovernments.org + +paths: + + /crypto/v1/_encrypt: + post: + summary: Encrypts the given input value/s OR values of the object. + requestBody: + description: The request body can contain an array of Encryption Requests to support bulk encryption. The key for encryption will be decided based on the tenantId. Each tenant will have its own seperate key. The value to be encrypted can be simple string OR an array of string OR can be a JSON Object. In case the value is a JSON Object, all the values will get encrypted and keys will be left untouched. + content: + application/json: + schema: + $ref: '#/components/schemas/EncryptionRequest' + example: + { + "encryptionRequests": [ + { + "tenantId": "pb", + "type": "Important", + "value": "My email" + }, + { + "tenantId": "pb.jalandhar", + "type": "Normal", + "value": [ + "Personal", + "Private" + ] + }, + { + "tenantId": "pb.mohali", + "type": "Normal", + "value": { + "userObject1": { + "name": "John Doe", + "mobileNumber": "98989121234" + } + } + } + ] + } + + responses: + '200': + description: The returned encrypted value will have the same structure as the input value. + content: + application/json: + schema: + type: array + items: + type: object + example: + [ + "437506|A5ag4DfbhHAHiqXRKFcAedFKtNOelHX+8+jB0ckNG/tihwimx7xu6akEoa+kaQPcIhSnYeveloIhdPBCOgrXWvkWGZfShx1i2bE2vAcWB+r0YIDdwZLKJbQGBHDqcEOn8mfO+LnmpJ5P4zPETtE+2EHhta+vKcE5OQj8ZQawHS4=", + [ + "896077|I/8Xwqr5MwB6UucEP8/Q5wiCHpbaNqGE", + "896077|I+gMx6TjN0BcLxudEiYQKIDKtSlmpJY=" + ], + { + "userObject1": { + "mobileNumber": "395551|eSfiPrQ1UK07d0SupYQYqbr2QFNOWSuYJYcU", + "name": "395551|CnCzaK1ADfnx+4FINXIQ9zjnUs1ieAtz" + } + } + ] + + + tags: + - Crypto + + /crypto/v1/_decrypt: + post: + summary: Decrypts the given input value/s OR values of the object. + requestBody: + description: Input to a decryption request may be an simple string OR an array OR a JSON Object. Every Object/Array will be navigated through to find simple strings, and those strings will be decrypted. + content: + application/json: + schema: + type: array + items: + type: object + example: + [ + [ + "896077|I/8Xwqr5MwB6UucEP8/Q5wiCHpbaNqGE", + "896077|I+gMx6TjN0BcLxudEiYQKIDKtSlmpJY=" + ], + { + "userObject1": { + "mobileNumber": "395551|eSfiPrQ1UK07d0SupYQYqbr2QFNOWSuYJYcU", + "name": "395551|CnCzaK1ADfnx+4FINXIQ9zjnUs1ieAtz" + } + } + ] + + responses: + '200': + description: The response to a decryption request will have the same structure as the input. + content: + application/json: + schema: + type: array + items: + type: object + example: + [ + [ + "Personal", + "Private" + ], + { + "userObject1": { + "mobileNumber": "98989121234", + "name": "John Doe" + } + } + ] + + + tags: + - Crypto + + +components: + schemas: + + EncReqObject: + type: object + description: EncrReqObject contains data to be encrypted and meta-data required to perform the encryption. + properties: + tenantId: + type: string + description: Encryption Key will be decided based on tenant id. + type: + type: string + description: Method to be used for encryption ( AES / RSA ) + enum: + - Important + - Normal + value: + type: array + description: Value/s to be encrypted. Can be a string or object or array + items: {} + example: ["Personal", "Private"] + + example: + tenantId: "pb.jalandhar" + type: "Important" + value: { + "key": "secret" + } + + + + EncryptionRequest: + type: object + description: An encryption request can contain multiple EncReqObject. This will help to encrypt bulk requests which may have different tenant-id and/or method ( AES / RSA ). + properties: + encryptionRequests: + type: array + items: + $ref: '#/components/schemas/EncReqObject' + example: + { + "encryptionRequests": [ + { + "tenantId": "pb.amritsar", + "type": "Important", + "value": { + "key": "secret" + } + } + ] + } diff --git a/services/encryption-service/pom.xml b/services/encryption-service/pom.xml new file mode 100644 index 000000000..42a7cfcd4 --- /dev/null +++ b/services/encryption-service/pom.xml @@ -0,0 +1,162 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.13.RELEASE + + org.egov + egov-enc-service + 1.1.4 + egov-enc-service + + 2.17.1 + 1.8 + ${java.version} + ${java.version} + 1.18.8 + + + + org.springframework + spring-beans + 5.2.20.RELEASE + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-test + test + + + io.swagger + swagger-core + 1.5.18 + + + org.egov.services + tracer + 2.0.0-SNAPSHOT + + + org.projectlombok + lombok + true + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.json + json + 20180813 + + + javax.validation + validation-api + + + org.postgresql + postgresql + + + org.flywaydb + flyway-core + 6.4.3 + + + org.bouncycastle + bcprov-jdk15on + 1.60 + + + + com.amazonaws + aws-java-sdk-kms + 1.11.762 + + + + + repo.egovernments.org + eGov ERP Releases Repository + https://nexus-repo.egovernments.org/nexus/content/repositories/releases/ + + + repo.egovernments.org.snapshots + eGov ERP Releases Repository + https://nexus-repo.egovernments.org/nexus/content/repositories/snapshots/ + + + repo.egovernments.org.public + eGov Public Repository Group + https://nexus-repo.egovernments.org/nexus/content/groups/public/ + + + + src/main/java + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-devtools + + + + + + cz.habarta.typescript-generator + typescript-generator-maven-plugin + 2.22.595 + + + generate + + generate + + process-classes + + + + jackson2 + + org.egov.enc.web.models.EncryptionRequest + org.egov.enc.web.models.SignRequest + org.egov.enc.web.models.VerifyRequest + org.egov.enc.web.models.RotateKeyRequest + org.egov.enc.web.models.RotateKeyResponse + org.egov.enc.web.models.VerifyResponse + org.egov.enc.web.models.SignResponse + + Digit + module + + + + + diff --git a/services/encryption-service/postman/egov-enc-service.postman_collection.json b/services/encryption-service/postman/egov-enc-service.postman_collection.json new file mode 100644 index 000000000..7321f5562 --- /dev/null +++ b/services/encryption-service/postman/egov-enc-service.postman_collection.json @@ -0,0 +1,203 @@ +{ + "info": { + "_postman_id": "4578ff36-17a2-4fc0-8614-ed3094f71a68", + "name": "egov-enc-service", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Encrypt", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"encryptionRequests\": [\n {\n \"tenantId\": \"pb\",\n \"type\": \"Imp\",\n \"value\": \"My email\"\n },\n {\n \"tenantId\": \"pb.jalandhar\",\n \"type\": \"Normal\",\n \"value\": [\n \"Personal\",\n \"Private\"\n ]\n },\n {\n \"tenantId\": \"pb.mohali\",\n \"type\": \"Normal\",\n \"value\": {\n \"userObject1\": {\n \"name\": \"John Doe\",\n \"mobileNumber\": \"98989121234\"\n }\n }\n }\n ]\n}" + }, + "url": { + "raw": "localhost:1234//egov-enc-service/crypto/v1/_encrypt", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "", + "egov-enc-service", + "crypto", + "v1", + "_encrypt" + ] + } + }, + "response": [] + }, + { + "name": "Decrypt", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "[\n \"437506|A5ag4DfbhHAHiqXRKFcAedFKtNOelHX+8+jB0ckNG/tihwimx7xu6akEoa+kaQPcIhSnYeveloIhdPBCOgrXWvkWGZfShx1i2bE2vAcWB+r0YIDdwZLKJbQGBHDqcEOn8mfO+LnmpJ5P4zPETtE+2EHhta+vKcE5OQj8ZQawHS4=\",\n [\n \"896077|I/8Xwqr5MwB6UucEP8/Q5wiCHpbaNqGE\",\n \"896077|I+gMx6TjN0BcLxudEiYQKIDKtSlmpJY=\"\n ],\n {\n \"userObject1\": {\n \"mobileNumber\": \"395551|eSfiPrQ1UK07d0SupYQYqbr2QFNOWSuYJYcU\",\n \"name\": \"395551|CnCzaK1ADfnx+4FINXIQ9zjnUs1ieAtz\"\n }\n }\n]" + }, + "url": { + "raw": "localhost:1234//egov-enc-service/crypto/v1/_decrypt", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "", + "egov-enc-service", + "crypto", + "v1", + "_decrypt" + ] + } + }, + "response": [] + }, + { + "name": "Sign", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"tenantId\": \"pb.jalandhar\",\n\t\"value\": \"value\"\n}" + }, + "url": { + "raw": "localhost:1234//egov-enc-service/crypto/v1/_sign", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "", + "egov-enc-service", + "crypto", + "v1", + "_sign" + ] + } + }, + "response": [] + }, + { + "name": "Verify", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"value\": \"value\",\n \"signature\": \"553916|dyQwMYZw7an2FhXg14yKki5L/jd2WrUMAfp1uzMPkNlwXILNeW0VMxa7USFI71QvCQCLPnQGCXcPXSo6jXiIJrZhSn8B4etGWrXXEjxSoSF8l/t+XiTJZoeBxnefb112iMxJCW2ZIDRdp83wItPJwxVfWHxnWOTHemQHUNNwzk4=\"\n}" + }, + "url": { + "raw": "localhost:1234//egov-enc-service/crypto/v1/_verify", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "", + "egov-enc-service", + "crypto", + "v1", + "_verify" + ] + } + }, + "response": [] + }, + { + "name": "Rotate Key", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"tenantId\": \"pb.phagwara\"\n}" + }, + "url": { + "raw": "localhost:1234/egov-enc-service/crypto/v1/_rotatekey", + "host": [ + "localhost" + ], + "port": "1234", + "path": [ + "egov-enc-service", + "crypto", + "v1", + "_rotatekey" + ] + } + }, + "response": [] + }, + { + "name": "Fetch Tenants", + "request": { + "method": "POST", + "header": [ + { + "key": "Cache-Control", + "value": "no-cache" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Postman-Token", + "value": "5f9b2036-0eab-475d-9314-eb8ba585471f" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"RequestInfo\": {\n \"apiId\": \"asset-services\",\n \"ver\": null,\n \"ts\": null,\n \"action\": null,\n \"did\": null,\n \"key\": null,\n \"msgId\": \"search with from and to values\",\n \"authToken\": \"59854f79-7031-4157-9cb5-21c51cb61981\"\n },\n \"MdmsCriteria\": {\n \"tenantId\": \"pb\",\n \"moduleDetails\": [\n {\n \"moduleName\": \"tenant\",\n \"masterDetails\": [\n {\n \"name\": \"tenants\",\n \"filter\":\"$.*.code\"\n }\n ]\n }\n ]\n }\n}" + }, + "url": { + "raw": "https://egov-micro-dev.egovernments.org/egov-mdms-service/v1/_search", + "protocol": "https", + "host": [ + "egov-micro-dev", + "egovernments", + "org" + ], + "path": [ + "egov-mdms-service", + "v1", + "_search" + ] + }, + "description": "Generated from a curl request: \ncurl -X POST \\\n https://egov-micro-dev.egovernments.org/egov-mdms-service/v1/_search \\\n -H 'Cache-Control: no-cache' \\\n -H 'Content-Type: application/json' \\\n -H 'Postman-Token: 5f9b2036-0eab-475d-9314-eb8ba585471f' \\\n -d '{\n \\\"RequestInfo\\\": {\n \\\"apiId\\\": \\\"asset-services\\\",\n \\\"ver\\\": null,\n \\\"ts\\\": null,\n \\\"action\\\": null,\n \\\"did\\\": null,\n \\\"key\\\": null,\n \\\"msgId\\\": \\\"search with from and to values\\\",\n \\\"authToken\\\": \\\"59854f79-7031-4157-9cb5-21c51cb61981\\\"\n },\n \\\"MdmsCriteria\\\": {\n \\\"tenantId\\\": \\\"pb\\\",\n \\\"moduleDetails\\\": [\n {\n \\\"moduleName\\\": \\\"tenant\\\",\n \\\"masterDetails\\\": [\n {\n \\\"name\\\": \\\"tenants\\\",\n \\\"filter\\\":\\\"$.*.code\\\"\n }\n ]\n }\n ]\n }\n}'" + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/services/encryption-service/src/main/java/org/egov/enc/Main.java b/services/encryption-service/src/main/java/org/egov/enc/Main.java new file mode 100644 index 000000000..0a8e27cdc --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/Main.java @@ -0,0 +1,15 @@ +package org.egov.enc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = { "org.egov.enc", "org.egov.enc.web.controllers" , "org.egov.enc.config"}) +public class Main { + + public static void main(String[] args) throws Exception { + SpringApplication.run(Main.class, args); + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/config/AppProperties.java b/services/encryption-service/src/main/java/org/egov/enc/config/AppProperties.java new file mode 100644 index 000000000..95975c216 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/config/AppProperties.java @@ -0,0 +1,46 @@ +package org.egov.enc.config; + + +import lombok.Getter; +import lombok.ToString; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.util.HashMap; + +@Getter +@ToString +@Configuration +@Component +@PropertySource("classpath:application.properties") +public class AppProperties { + + @Value("${size.key.symmetric}") + private int symmetricKeySize; + + @Value("${size.initialvector}") + private int initialVectorSize; + + @Value("${size.key.asymmetric}") + private int asymmetricKeySize; + + @Value("${length.keyid}") + private Integer keyIdLength; + + @Value("${method.symmetric}") + private String symmetricMethod; + + @Value("${method.asymmetric}") + private String asymmetricMethod; + + @Value("${method.signature}") + private String signatureMathod; + + @Value("#{${type.to.method.map}}") + private HashMap typeToMethodMap; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/config/MainConfiguration.java b/services/encryption-service/src/main/java/org/egov/enc/config/MainConfiguration.java new file mode 100644 index 000000000..3c66d6f9a --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/config/MainConfiguration.java @@ -0,0 +1,40 @@ +package org.egov.enc.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.egov.tracer.config.TracerConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + +import javax.annotation.PostConstruct; +import java.util.TimeZone; + + +@Import({TracerConfiguration.class}) +public class MainConfiguration { + + @Value("${app.timezone}") + private String timeZone; + + @PostConstruct + public void initialize() { + TimeZone.setDefault(TimeZone.getTimeZone(timeZone)); + } + + @Bean + public ObjectMapper objectMapper(){ + return new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).setTimeZone(TimeZone.getTimeZone(timeZone)); + } + + @Bean + @Autowired + public MappingJackson2HttpMessageConverter jacksonConverter(ObjectMapper objectMapper) { + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(objectMapper); + return converter; + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyGenerator.java b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyGenerator.java new file mode 100644 index 000000000..eacba6688 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyGenerator.java @@ -0,0 +1,101 @@ +package org.egov.enc.keymanagement; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.egov.enc.config.AppProperties; +import org.egov.enc.keymanagement.masterkey.MasterKeyProvider; +import org.egov.enc.models.AsymmetricKey; +import org.egov.enc.models.SymmetricKey; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.crypto.*; +import javax.crypto.spec.SecretKeySpec; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; +import java.util.Base64; + +/* + KeyGenerator is used to generate random keys. + Keys will be encrypted with a master password. +*/ + +@Order(2) +@Component +public class KeyGenerator { + + private SecureRandom secureRandom; + + private AppProperties appProperties; + + @Autowired + private KeyIdGenerator keyIdGenerator; + @Autowired + private MasterKeyProvider masterKeyProvider; + + @Autowired + public KeyGenerator(AppProperties appProperties) throws NoSuchAlgorithmException, InvalidKeySpecException { + this.appProperties = appProperties; + + Security.addProvider(new BouncyCastleProvider()); + secureRandom = new SecureRandom(); + } + + //Generate random bytes with use of SecureRandom + //Being used to generate Initial Vector and Symmetric Key + private byte[] getRandomBytes(int size) { + byte[] randomBytes = new byte[size]; + secureRandom.nextBytes(randomBytes); + return randomBytes; + } + + //Returns a list of Symmetric Keys corresponding to the list of input tenants + //The returned keys will be encrypted with the master password + public ArrayList generateSymmetricKeys(ArrayList tenantIds) throws Exception { + int numberOfKeys = tenantIds.size(); + SecretKey[] keys = new SecretKey[numberOfKeys]; + byte[][] initialVectors = new byte[numberOfKeys][appProperties.getInitialVectorSize()]; + for(int i = 0; i < numberOfKeys; i++) { + keys[i] = new SecretKeySpec(getRandomBytes(appProperties.getSymmetricKeySize()/8), "AES"); + initialVectors[i] = getRandomBytes(appProperties.getInitialVectorSize()); + } + + ArrayList symmetricKeyArrayList = new ArrayList<>(); + + for(int i = 0; i < keys.length; i++) { + String keyAsString = + masterKeyProvider.encryptWithMasterPassword(Base64.getEncoder().encodeToString(keys[i].getEncoded())); + String initialVectorAsString = + masterKeyProvider.encryptWithMasterPassword(Base64.getEncoder().encodeToString(initialVectors[i])); + symmetricKeyArrayList.add(new SymmetricKey(i, keyIdGenerator.generateKeyId(), keyAsString, + initialVectorAsString, true, tenantIds.get(i))); + } + return symmetricKeyArrayList; + } + + //Returns a list of Asymmetric Keys corresponding to the list of input tenants + //The returned keys will be encrypted with the master password + public ArrayList generateAsymmetricKeys(ArrayList tenantIds) throws Exception { + int numberOfKeys = tenantIds.size(); + KeyPair[] keys = new KeyPair[numberOfKeys]; + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(appProperties.getAsymmetricKeySize()); + for(int i = 0; i < numberOfKeys; i++) { + keys[i] = keyPairGenerator.generateKeyPair(); + } + + ArrayList asymmetricKeyArrayList = new ArrayList<>(); + + for(int i = 0; i < keys.length; i++) { + String publicKey = + masterKeyProvider.encryptWithMasterPassword(Base64.getEncoder().encodeToString(keys[i].getPublic().getEncoded())); + String privateKey = + masterKeyProvider.encryptWithMasterPassword(Base64.getEncoder().encodeToString(keys[i].getPrivate().getEncoded())); + asymmetricKeyArrayList.add(new AsymmetricKey(i, keyIdGenerator.generateKeyId(), publicKey, privateKey, true, tenantIds.get(i))); + } + + return asymmetricKeyArrayList; + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyIdGenerator.java b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyIdGenerator.java new file mode 100644 index 000000000..248b2eac3 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyIdGenerator.java @@ -0,0 +1,59 @@ +package org.egov.enc.keymanagement; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.egov.enc.config.AppProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.security.SecureRandom; +import java.security.Security; +import java.util.ArrayList; + +@Component +@Order(3) +public class KeyIdGenerator implements ApplicationRunner { + + private SecureRandom secureRandom; + + private static ArrayList presentKeyIds; + + @Autowired + private KeyStore keyStore; + + @Autowired + private AppProperties appProperties; + + @Autowired + public KeyIdGenerator() { + Security.addProvider(new BouncyCastleProvider()); + secureRandom = new SecureRandom(); + } + + public void refreshKeyIds() { + presentKeyIds = keyStore.getKeyIds(); + } + + public Integer generateKeyId() { + Integer keyId = getRandomNumber(appProperties.getKeyIdLength()); + while(presentKeyIds.contains(keyId)) { + keyId = getRandomNumber(appProperties.getKeyIdLength()); + } + presentKeyIds.add(keyId); + return keyId; + } + + private Integer getRandomNumber(int digCount) { + StringBuilder sb = new StringBuilder(digCount); + for(int i = 0; i < digCount; i++) + sb.append((char)('0' + secureRandom.nextInt(10))); + return Integer.parseInt(sb.toString()); + } + + @Override + public void run(ApplicationArguments args) throws Exception { + refreshKeyIds(); + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyStore.java b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyStore.java new file mode 100644 index 000000000..1adbe2975 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/KeyStore.java @@ -0,0 +1,208 @@ +package org.egov.enc.keymanagement; + +import lombok.Getter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.egov.enc.config.AppProperties; +import org.egov.enc.keymanagement.masterkey.MasterKeyProvider; +import org.egov.enc.models.AsymmetricKey; +import org.egov.enc.models.MethodEnum; +import org.egov.enc.models.SymmetricKey; +import org.egov.enc.repository.KeyRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.crypto.*; +import javax.crypto.spec.SecretKeySpec; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; + + + +/* + KeyStore fetches keys from database. + All Keys will be stored inside the HashMaps. + Keys can be extracted from these maps based on Key_ID. + Active Key for a given Tenant can be got by providing Tenant_ID. +*/ + + +@Component +@Order(2) +public class KeyStore implements ApplicationRunner { + + @Autowired + private AppProperties appProperties; + @Autowired + private KeyRepository keyRepository; + @Getter + private ArrayList tenantIds; + + @Autowired + private MasterKeyProvider masterKeyProvider; + + private static ArrayList symmetricKeys; + private static ArrayList asymmetricKeys; + + private static HashMap symmetricKeyHashMap; + private static HashMap asymmetricKeyHashMap; + + private static HashMap activeSymmetricKeys; + private static HashMap activeAsymmetricKeys; + + @Autowired + public KeyStore() { + Security.addProvider(new BouncyCastleProvider()); + } + + //Reset and Initialize all the keys and HashMaps from the database + public void refreshKeys() throws Exception { + tenantIds = (ArrayList) keyRepository.fetchDistinctTenantIds(); + + symmetricKeys = (ArrayList) this.keyRepository.fetchSymmetricKeys(); + asymmetricKeys = (ArrayList) this.keyRepository.fetchAsymmtericKeys(); + + decryptAllKeys(); + + symmetricKeyHashMap = new HashMap<>(); + asymmetricKeyHashMap = new HashMap<>(); + + initializeKeys(); + + activeSymmetricKeys = new HashMap<>(); + activeAsymmetricKeys = new HashMap<>(); + + initializeActiveKeys(); + } + + + //Create HashMap to store keys indexed with keyId + private void initializeKeys() { + for(SymmetricKey symmetricKey : symmetricKeys) { + symmetricKeyHashMap.put(symmetricKey.getKeyId(), symmetricKey); + } + for(AsymmetricKey asymmetricKey : asymmetricKeys) { + asymmetricKeyHashMap.put(asymmetricKey.getKeyId(), asymmetricKey); + } + } + + //Create HashMap to store active keys indexed with tenantId + private void initializeActiveKeys() { + + for(String tenant : tenantIds) { + for(SymmetricKey symmetricKey : symmetricKeys) { + if(symmetricKey.getTenantId().equalsIgnoreCase(tenant) && symmetricKey.isActive()) { + activeSymmetricKeys.put(tenant, symmetricKey.getKeyId()); + break; + } + } + + for(AsymmetricKey asymmetricKey : asymmetricKeys) { + if(asymmetricKey.getTenantId().equalsIgnoreCase(tenant) && asymmetricKey.isActive()) { + activeAsymmetricKeys.put(tenant, asymmetricKey.getKeyId()); + break; + } + } + } + } + + //Get currently active symmetric key for given tenanId + public SymmetricKey getSymmetricKey(String tenantId) { + return getSymmetricKey(activeSymmetricKeys.get(tenantId)); + } + + //Get currently active asymmetric key for given tenanId + public AsymmetricKey getAsymmetricKey(String tenantId) { + return getAsymmetricKey(activeAsymmetricKeys.get(tenantId)); + } + + //Get symmetric key based on given keyId + public SymmetricKey getSymmetricKey(int keyId) { + return symmetricKeyHashMap.get(keyId); + } + + //Get asymmetric key based on given keyId + public AsymmetricKey getAsymmetricKey(int keyId) { + return asymmetricKeyHashMap.get(keyId); + } + + + //Return type of encryption method based on key id + public MethodEnum getTypeOfKey(Integer keyId) { + if(symmetricKeyHashMap.containsKey(keyId)) { + return MethodEnum.SYM; + } else { + return MethodEnum.ASY; + } + } + + public boolean checkIfKeyExists(int keyId) { + return symmetricKeyHashMap.containsKey(keyId) || asymmetricKeyHashMap.containsKey(keyId); + } + + //Generate Secret Key to be used by AES from custom object SymmetricKey + public SecretKey getSecretKey(SymmetricKey symmetricKey) { + String encodedKey = symmetricKey.getSecretKey(); + byte[] decodedKey = Base64.getDecoder().decode(encodedKey); + return new SecretKeySpec(decodedKey, "AES"); + } + + //Generate PublicKey to be used by RSA from custom object AsymmetricKey + public PublicKey getPublicKey(AsymmetricKey asymmetricKey) throws NoSuchAlgorithmException, InvalidKeySpecException { + String encodedPublicKey = asymmetricKey.getPublicKey(); + byte[] decodedPublicKey = Base64.getDecoder().decode(encodedPublicKey); + + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedPublicKey); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(keySpec); + } + + //Generate PrivateKey to be used by RSA from custom object AsymmetricKey + public PrivateKey getPrivateKey(AsymmetricKey asymmetricKey) throws NoSuchAlgorithmException, InvalidKeySpecException { + String encodedPrivateKey = asymmetricKey.getPrivateKey(); + byte[] decodedPrivateKey = Base64.getDecoder().decode(encodedPrivateKey); + + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedPrivateKey); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(keySpec); + } + + //Generate Initial Vecctor to be used by AES from custom object SymmetricKey + public byte[] getInitialVector(SymmetricKey symmetricKey) { + return Base64.getDecoder().decode(symmetricKey.getInitialVector()); + } + + //Decrypt all keys + private void decryptAllKeys() throws Exception { + for (SymmetricKey symmetricKey : symmetricKeys) { + symmetricKey.setSecretKey(masterKeyProvider.decryptWithMasterPassword(symmetricKey.getSecretKey())); + symmetricKey.setInitialVector(masterKeyProvider.decryptWithMasterPassword(symmetricKey.getInitialVector())); + } + for (AsymmetricKey asymmetricKey : asymmetricKeys) { + asymmetricKey.setPublicKey(masterKeyProvider.decryptWithMasterPassword(asymmetricKey.getPublicKey())); + asymmetricKey.setPrivateKey(masterKeyProvider.decryptWithMasterPassword(asymmetricKey.getPrivateKey())); + } + } + + + //Initialize keys after application has finished loading + @Override + public void run(ApplicationArguments applicationArguments) throws Exception { + refreshKeys(); + } + + public ArrayList getKeyIds() { + ArrayList keyIds = new ArrayList<>(); + keyIds.addAll(symmetricKeyHashMap.keySet()); + keyIds.addAll(asymmetricKeyHashMap.keySet()); + return keyIds; + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/MasterKeyProvider.java b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/MasterKeyProvider.java new file mode 100644 index 000000000..278f9769b --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/MasterKeyProvider.java @@ -0,0 +1,9 @@ +package org.egov.enc.keymanagement.masterkey; + +public interface MasterKeyProvider { + + public String encryptWithMasterPassword(String key) throws Exception; + + public String decryptWithMasterPassword(String encryptedKey) throws Exception; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/providers/AwsKmsMasterKey.java b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/providers/AwsKmsMasterKey.java new file mode 100644 index 000000000..dacdf47f3 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/providers/AwsKmsMasterKey.java @@ -0,0 +1,65 @@ +package org.egov.enc.keymanagement.masterkey.providers; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.services.kms.AWSKMSClientBuilder; +import com.amazonaws.services.kms.model.DecryptRequest; +import com.amazonaws.services.kms.model.DecryptResult; +import com.amazonaws.services.kms.model.EncryptRequest; +import com.amazonaws.services.kms.model.EncryptResult; +import org.egov.enc.keymanagement.masterkey.MasterKeyProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +@Component +@Order(1) +@ConditionalOnProperty( value = "master.password.provider", havingValue = "awskms") +public class AwsKmsMasterKey implements MasterKeyProvider { + + + @Value("${aws.kms.access.key:}") + private String awsAccessKey; + @Value("${aws.kms.secret.key:}") + private String awsSecretKey; + @Value("${aws.kms.region:}") + private String awsRegion; + + @Value("${aws.kms.master.password.key.id:}") + private String masterPasswordKeyId; + + private AWSKMS awskms; + + @PostConstruct + public void initializeConnection() { + AWSStaticCredentialsProvider awsCredentials = new AWSStaticCredentialsProvider(new BasicAWSCredentials(awsAccessKey, awsSecretKey)); + this.awskms = AWSKMSClientBuilder.standard().withCredentials(awsCredentials).withRegion(awsRegion).build(); + } + + @Override + public String encryptWithMasterPassword(String plainKey) throws Exception { + EncryptRequest encryptRequest = new EncryptRequest(); + encryptRequest.setKeyId(masterPasswordKeyId); + encryptRequest.setPlaintext(ByteBuffer.wrap(plainKey.getBytes(StandardCharsets.UTF_8))); + + EncryptResult encryptResult = awskms.encrypt(encryptRequest); + return Base64.getEncoder().encodeToString(encryptResult.getCiphertextBlob().array()); + } + + @Override + public String decryptWithMasterPassword(String encryptedKey) throws Exception { + DecryptRequest decryptRequest = new DecryptRequest(); + decryptRequest.setKeyId(masterPasswordKeyId); + decryptRequest.setCiphertextBlob(ByteBuffer.wrap(Base64.getDecoder().decode(encryptedKey))); + + DecryptResult decryptResult = awskms.decrypt(decryptRequest); + return new String(decryptResult.getPlaintext().array(), StandardCharsets.UTF_8); + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/providers/SoftwareBasedMasterKey.java b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/providers/SoftwareBasedMasterKey.java new file mode 100644 index 000000000..e303e2bea --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/keymanagement/masterkey/providers/SoftwareBasedMasterKey.java @@ -0,0 +1,81 @@ +package org.egov.enc.keymanagement.masterkey.providers; + + +import org.egov.enc.config.AppProperties; +import org.egov.enc.keymanagement.masterkey.MasterKeyProvider; +import org.egov.enc.utils.SymmetricEncryptionUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Base64; + +@Component +@Order(1) +@ConditionalOnProperty( value = "master.password.provider", havingValue = "software") +public class SoftwareBasedMasterKey implements MasterKeyProvider { + + @Value("${master.password:}") + private String masterPassword; + + @Value("${master.salt:}") + private String masterSalt; + + @Value("${master.initialvector:}") + private String masterInitialVectorString; + + @Autowired + private AppProperties appProperties; + + private SecretKey masterKey; + private byte[] masterInitialVector; + + //Master Key will be used to decrypt the keys read from the database + @PostConstruct + private void initializeMasterKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + String masterPassword = this.masterPassword; + + char[] masterSalt = this.masterSalt.toCharArray(); + byte[] salt = new byte[8]; + for(int i = 0; i < salt.length; i++) { + salt[i] = (byte) masterSalt[i]; + } + + char[] masterIV = this.masterInitialVectorString.toCharArray(); + masterInitialVector = new byte[appProperties.getInitialVectorSize()]; + for(int i = 0; i < masterInitialVector.length; i++) { + masterInitialVector[i] = (byte) masterIV[i]; + } + + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(masterPassword.toCharArray(), salt, 65536, 256); + SecretKey tmp = factory.generateSecret(spec); + masterKey = new SecretKeySpec(tmp.getEncoded(), "AES"); + } + + + @Override + public String encryptWithMasterPassword(String key) throws Exception { + byte[] encryptedKey = SymmetricEncryptionUtil.encrypt(key.getBytes(StandardCharsets.UTF_8), masterKey, masterInitialVector); + return Base64.getEncoder().encodeToString(encryptedKey); + } + + @Override + public String decryptWithMasterPassword(String encryptedKey) throws Exception { + byte[] decryptedKey = SymmetricEncryptionUtil.decrypt(Base64.getDecoder().decode(encryptedKey), masterKey, masterInitialVector); + return new String(decryptedKey, StandardCharsets.UTF_8); + } + + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/AsymmetricKey.java b/services/encryption-service/src/main/java/org/egov/enc/models/AsymmetricKey.java new file mode 100644 index 000000000..9356cb874 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/models/AsymmetricKey.java @@ -0,0 +1,27 @@ +package org.egov.enc.models; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class AsymmetricKey { + + private int id; + + private Integer keyId; + + private String publicKey; + + private String privateKey; + + private boolean active; + + private String tenantId; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/Ciphertext.java b/services/encryption-service/src/main/java/org/egov/enc/models/Ciphertext.java new file mode 100644 index 000000000..67c28eaf1 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/models/Ciphertext.java @@ -0,0 +1,30 @@ +package org.egov.enc.models; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.egov.tracer.model.CustomException; + +@AllArgsConstructor +@Getter +public class Ciphertext { + + private int keyId; + + private String ciphertext; + + public Ciphertext(String ciphertext) { + try{ + String[] cipherArray = ciphertext.split("\\|"); + this.keyId = Integer.parseInt(cipherArray[0]); + this.ciphertext = cipherArray[1]; + } catch (Exception e) { + throw new CustomException(ciphertext + ": Invalid Ciphertext", ciphertext + ": Invalid Ciphertext"); + } + } + + @Override + public String toString() { + return String.valueOf(keyId) + "|" + ciphertext; + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/MethodEnum.java b/services/encryption-service/src/main/java/org/egov/enc/models/MethodEnum.java new file mode 100644 index 000000000..f5c4a4eaf --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/models/MethodEnum.java @@ -0,0 +1,33 @@ +package org.egov.enc.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum MethodEnum { + SYM("SYM"), + + ASY("ASY"); + + private String value; + + MethodEnum(String value) { + this.value = value; + } + + @Override + @JsonValue + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static MethodEnum fromValue(String text) { + for (MethodEnum b : MethodEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/ModeEnum.java b/services/encryption-service/src/main/java/org/egov/enc/models/ModeEnum.java new file mode 100644 index 000000000..4052fcea1 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/models/ModeEnum.java @@ -0,0 +1,34 @@ +package org.egov.enc.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum ModeEnum { + + ENCRYPT("ENCRYPT"), + + DECRYPT("DECRYPT"); + + private String value; + + ModeEnum(String value) { + this.value = value; + } + + @Override + @JsonValue + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static ModeEnum fromValue(String text) { + for (ModeEnum b : ModeEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/Plaintext.java b/services/encryption-service/src/main/java/org/egov/enc/models/Plaintext.java new file mode 100644 index 000000000..e168b2509 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/models/Plaintext.java @@ -0,0 +1,22 @@ +package org.egov.enc.models; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class Plaintext { + + private String tenantId; + + private String plaintext; + + public Plaintext(String plaintext) { + this.plaintext = plaintext; + } + + @Override + public String toString() { + return plaintext; + } +} \ No newline at end of file diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/Signature.java b/services/encryption-service/src/main/java/org/egov/enc/models/Signature.java new file mode 100644 index 000000000..b52b3f0c4 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/models/Signature.java @@ -0,0 +1,31 @@ +package org.egov.enc.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.egov.tracer.model.CustomException; + +@Getter +@AllArgsConstructor +public class Signature { + + private Integer keyId; + + private String signatureValue; + + public Signature(String signatureValue) { + try{ + String[] cipherArray = signatureValue.split("\\|"); + this.keyId = Integer.parseInt(cipherArray[0]); + this.signatureValue = cipherArray[1]; + } catch (Exception e) { + throw new CustomException(signatureValue + ": Invalid Signature", signatureValue + ": Invalid Signature"); + } + } + + @Override + public String toString() { + return String.valueOf(keyId) + "|" + signatureValue; + } +} + diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/SymmetricKey.java b/services/encryption-service/src/main/java/org/egov/enc/models/SymmetricKey.java new file mode 100644 index 000000000..8ee0de197 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/models/SymmetricKey.java @@ -0,0 +1,27 @@ +package org.egov.enc.models; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class SymmetricKey { + + private int id; + + private Integer keyId; + + private String secretKey; + + private String initialVector; + + private boolean active; + + private String tenantId; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/repository/KeyRepository.java b/services/encryption-service/src/main/java/org/egov/enc/repository/KeyRepository.java new file mode 100644 index 000000000..5e29c460b --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/repository/KeyRepository.java @@ -0,0 +1,89 @@ +package org.egov.enc.repository; + +import org.egov.enc.models.AsymmetricKey; +import org.egov.enc.models.SymmetricKey; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class KeyRepository { + + private JdbcTemplate jdbcTemplate; + + private static final String selectSymmetricKeyQuery = "SELECT * FROM eg_enc_symmetric_keys"; + private static final String selectAsymmetricKeyQuery = "SELECT * FROM eg_enc_asymmetric_keys"; + + private static final String insertSymmetricKeyQuery = "INSERT INTO eg_enc_symmetric_keys (key_id, secret_key, " + + "initial_vector, active, tenant_id) VALUES (? ,?, ?, ?, ?)"; + private static final String insertAsymmetricKeyQuery = "INSERT INTO eg_enc_asymmetric_keys (key_id, public_key, " + + "private_key, active, tenant_id) VALUES (? ,?, ?, ?, ?)"; + + private static final String deactivateSymmetricKeyQuery = "UPDATE eg_enc_symmetric_keys SET active='false'"; + private static final String deactivateAsymmetricKeyQuery = "UPDATE eg_enc_asymmetric_keys SET active='false'"; + + private static final String deactivateSymmetricKeyForGivenTenantQuery = "UPDATE eg_enc_symmetric_keys SET " + + "active='false' WHERE active='true' AND tenant_id=?"; + private static final String deactivateAsymmetricKeyForGivenTenantQuery = "UPDATE eg_enc_asymmetric_keys SET " + + "active='false' WHERE active='true' AND tenant_id=?"; + + private static final String distinctTenantIdsQuery = "SELECT DISTINCT tenant_id FROM eg_enc_symmetric_keys WHERE active='true'"; + + + @Autowired + public KeyRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public int insertSymmetricKey(SymmetricKey symmetricKey) { + return jdbcTemplate.update(insertSymmetricKeyQuery, + symmetricKey.getKeyId(), + symmetricKey.getSecretKey(), + symmetricKey.getInitialVector(), + symmetricKey.isActive(), + symmetricKey.getTenantId() + ); + } + + public int insertAsymmetricKey(AsymmetricKey asymmetricKey) { + return jdbcTemplate.update(insertAsymmetricKeyQuery, + asymmetricKey.getKeyId(), + asymmetricKey.getPublicKey(), + asymmetricKey.getPrivateKey(), + asymmetricKey.isActive(), + asymmetricKey.getTenantId() + ); + } + + public int deactivateSymmetricKeyForGivenTenant(String tenantId) { + return jdbcTemplate.update(deactivateSymmetricKeyForGivenTenantQuery, tenantId); + } + + public int deactivateAsymmetricKeyForGivenTenant(String tenantId) { + return jdbcTemplate.update(deactivateAsymmetricKeyForGivenTenantQuery, tenantId); + } + + public int deactivateSymmetricKeys() { + return jdbcTemplate.update(deactivateSymmetricKeyQuery); + } + + public int deactivateAsymmetricKeys() { + return jdbcTemplate.update(deactivateAsymmetricKeyQuery); + } + + public List fetchSymmetricKeys() { + return jdbcTemplate.query(selectSymmetricKeyQuery, new BeanPropertyRowMapper<>(SymmetricKey.class)); + } + + public List fetchAsymmtericKeys() { + return jdbcTemplate.query(selectAsymmetricKeyQuery, new BeanPropertyRowMapper<>(AsymmetricKey.class)); + } + + public List fetchDistinctTenantIds() { + return jdbcTemplate.queryForList(distinctTenantIdsQuery, String.class); + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/AsymmetricEncryptionService.java b/services/encryption-service/src/main/java/org/egov/enc/services/AsymmetricEncryptionService.java new file mode 100644 index 000000000..c68523746 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/AsymmetricEncryptionService.java @@ -0,0 +1,50 @@ +package org.egov.enc.services; + +import org.egov.enc.keymanagement.KeyStore; +import org.egov.enc.models.AsymmetricKey; +import org.egov.enc.models.Ciphertext; +import org.egov.enc.models.Plaintext; +import org.egov.enc.utils.AsymmetricEncryptionUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.util.Base64; + +@Service +public class AsymmetricEncryptionService implements EncryptionServiceInterface { + + @Autowired + private KeyStore keyStore; + + public Ciphertext encrypt(Plaintext plaintext) throws InvalidKeySpecException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, InvalidAlgorithmParameterException { + AsymmetricKey asymmetricKey = keyStore.getAsymmetricKey(plaintext.getTenantId()); + PublicKey publicKey = keyStore.getPublicKey(asymmetricKey); + + byte[] cipherBytes = AsymmetricEncryptionUtil.encrypt(plaintext.getPlaintext().getBytes(StandardCharsets.UTF_8), publicKey); + + Ciphertext ciphertext = new Ciphertext(asymmetricKey.getKeyId(), Base64.getEncoder().encodeToString + (cipherBytes)); + + return ciphertext; + } + + + public Plaintext decrypt(Ciphertext ciphertext) throws InvalidKeySpecException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, InvalidAlgorithmParameterException { + AsymmetricKey asymmetricKey = keyStore.getAsymmetricKey(ciphertext.getKeyId()); + PrivateKey privateKey = keyStore.getPrivateKey(asymmetricKey); + + byte[] plainBytes = AsymmetricEncryptionUtil.decrypt(Base64.getDecoder().decode(ciphertext.getCiphertext()), privateKey); + String plain = new String(plainBytes, StandardCharsets.UTF_8); + + Plaintext plaintext = new Plaintext(plain); + + return plaintext; + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/EncryptionService.java b/services/encryption-service/src/main/java/org/egov/enc/services/EncryptionService.java new file mode 100644 index 000000000..b6ce1777e --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/EncryptionService.java @@ -0,0 +1,49 @@ +package org.egov.enc.services; + +import lombok.extern.slf4j.Slf4j; +import org.egov.enc.config.AppProperties; +import org.egov.enc.models.MethodEnum; +import org.egov.enc.models.ModeEnum; +import org.egov.enc.utils.Constants; +import org.egov.enc.utils.ProcessJSONUtil; +import org.egov.enc.web.models.EncReqObject; +import org.egov.enc.web.models.EncryptionRequest; +import org.egov.tracer.model.CustomException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.LinkedList; + + +@Slf4j +@Service +public class EncryptionService { + + @Autowired + private AppProperties appProperties; + @Autowired + private ProcessJSONUtil processJSONUtil; + @Autowired + private KeyManagementService keyManagementService; + + public Object encrypt(EncryptionRequest encryptionRequest) throws Exception { + LinkedList outputList = new LinkedList<>(); + for(EncReqObject encReqObject : encryptionRequest.getEncryptionRequests()) { + if(!keyManagementService.checkIfTenantExists(encReqObject.getTenantId())) { + throw new CustomException(encReqObject.getTenantId() + Constants.TENANT_NOT_FOUND, + encReqObject.getTenantId() + Constants.TENANT_NOT_FOUND ); + } + MethodEnum encryptionMethod = MethodEnum.fromValue(appProperties.getTypeToMethodMap().get(encReqObject.getType())); + if(encryptionMethod == null) { + throw new CustomException(encReqObject.getType() + Constants.INVALD_DATA_TYPE, + encReqObject.getType() + Constants.INVALD_DATA_TYPE); + } + outputList.add(processJSONUtil.processJSON(encReqObject.getValue(), ModeEnum.ENCRYPT, encryptionMethod, encReqObject.getTenantId())); + } + return outputList; + } + + public Object decrypt(Object decryptionRequest) throws Exception { + return processJSONUtil.processJSON(decryptionRequest, ModeEnum.DECRYPT, null, null); + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/EncryptionServiceInterface.java b/services/encryption-service/src/main/java/org/egov/enc/services/EncryptionServiceInterface.java new file mode 100644 index 000000000..fd7136f9d --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/EncryptionServiceInterface.java @@ -0,0 +1,20 @@ +package org.egov.enc.services; + +import org.egov.enc.models.Ciphertext; +import org.egov.enc.models.Plaintext; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +public interface EncryptionServiceInterface { + + public Ciphertext encrypt(Plaintext plaintext) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException; + + public Plaintext decrypt(Ciphertext ciphertext) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java b/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java new file mode 100644 index 000000000..0bd28b6a4 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java @@ -0,0 +1,143 @@ +package org.egov.enc.services; + +import lombok.extern.slf4j.Slf4j; +import org.egov.enc.keymanagement.KeyGenerator; +import org.egov.enc.keymanagement.KeyIdGenerator; +import org.egov.enc.keymanagement.KeyStore; +import org.egov.enc.models.AsymmetricKey; +import org.egov.enc.models.SymmetricKey; +import org.egov.enc.repository.KeyRepository; +import org.egov.enc.web.models.RotateKeyRequest; +import org.egov.enc.web.models.RotateKeyResponse; +import org.egov.tracer.model.CustomException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.List; + +@Slf4j +@Service +public class KeyManagementService implements ApplicationRunner { + + @Autowired + private KeyRepository keyRepository; + @Autowired + private KeyGenerator keyGenerator; + @Autowired + private KeyStore keyStore; + @Autowired + private KeyIdGenerator keyIdGenerator; + @Value("${tenant.ids}") + private List tenantIds; + + + //Initialize active tenant id list and Check for any new tenants + private void init() throws Exception { + generateKeyForNewTenants(); + } + + //Check if a given tenantId exists + public boolean checkIfTenantExists(String tenant) throws Exception { + if(keyStore.getTenantIds().contains(tenant)) { + return true; + } + generateKeyForNewTenants(); + return keyStore.getTenantIds().contains(tenant); + } + + //Generate Symmetric and Asymmetric Keys for each of the TenantId in the given input list + private void generateKeys(ArrayList tenantIds) throws Exception { + + int status; + ArrayList symmetricKeys = keyGenerator.generateSymmetricKeys(tenantIds); + for(SymmetricKey symmetricKey : symmetricKeys) { + status = keyRepository.insertSymmetricKey(symmetricKey); + if(status != 1) { + throw new CustomException("DB Insert Exception", "DB Insert Exception"); + } + } + + ArrayList asymmetricKeys = keyGenerator.generateAsymmetricKeys(tenantIds); + for(AsymmetricKey asymmetricKey : asymmetricKeys) { + status = keyRepository.insertAsymmetricKey(asymmetricKey); + if(status != 1) { + throw new CustomException("DB Insert Exception", "DB Insert Exception"); + } + } + } + + //Generate keys if there are any new tenants + //Returns the number of tenants for which the keys have been generated + private int generateKeyForNewTenants() throws Exception { + keyStore.refreshKeys(); + keyIdGenerator.refreshKeyIds(); + + Collection tenantIdsFromMdms = makeComprehensiveListOfTenantIds(); + tenantIdsFromMdms.removeAll(keyStore.getTenantIds()); + + if(tenantIdsFromMdms.size() != 0) { + ArrayList tenantIdList = new ArrayList<>(tenantIdsFromMdms); + generateKeys(tenantIdList); + + keyStore.refreshKeys(); + keyIdGenerator.refreshKeyIds(); + } + return tenantIdsFromMdms.size(); + } + + private Set makeComprehensiveListOfTenantIds() { + Set comprehensiveTenantIdsSet = new HashSet<>(tenantIds); + + for (String tenantId: tenantIds) { + int index = tenantId.indexOf("."); + while(index > 0) { + comprehensiveTenantIdsSet.add(tenantId.substring(0, index)); + index = tenantId.indexOf(".", index + 1); + } + } + + return comprehensiveTenantIdsSet; + } + + //Used to deactivate old keys at the time of key rotation + private void deactivateOldKeys() { + keyRepository.deactivateSymmetricKeys(); + keyRepository.deactivateAsymmetricKeys(); + } + + //Deactivate old keys and generate new keys for every tenantId + public RotateKeyResponse rotateAllKeys() throws Exception { + deactivateOldKeys(); + generateKeyForNewTenants(); + return new RotateKeyResponse(true); + } + + public RotateKeyResponse rotateKey(RotateKeyRequest rotateKeyRequest) throws Exception { + int status; + status = keyRepository.deactivateSymmetricKeyForGivenTenant(rotateKeyRequest.getTenantId()); + log.info("Key Rotate SYM Return Status: " + status); + if(status != 1) { + throw new CustomException("DB Exception", "DB Exception"); + } + status = keyRepository.deactivateAsymmetricKeyForGivenTenant(rotateKeyRequest.getTenantId()); + log.info("Key Rotate ASY Return Status: " + status); + if(status != 1) { + throw new CustomException("DB Exception", "DB Exception"); + } + + generateKeyForNewTenants(); + + return new RotateKeyResponse(true); + } + + @Override + public void run(ApplicationArguments applicationArguments) throws Exception { + init(); + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/SignatureService.java b/services/encryption-service/src/main/java/org/egov/enc/services/SignatureService.java new file mode 100644 index 000000000..40f890f95 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/SignatureService.java @@ -0,0 +1,46 @@ +package org.egov.enc.services; + +import org.egov.enc.keymanagement.KeyStore; +import org.egov.enc.models.AsymmetricKey; +import org.egov.enc.utils.SignatureUtil; +import org.egov.enc.web.models.*; +import org.egov.enc.models.Signature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.util.Base64; + +@Service +public class SignatureService { + + @Autowired + private KeyStore keyStore; + + public SignResponse hashAndSign(SignRequest signRequest) throws InvalidKeySpecException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException { + AsymmetricKey asymmetricKey = keyStore.getAsymmetricKey(signRequest.getTenantId()); + PrivateKey privateKey = keyStore.getPrivateKey(asymmetricKey); + + byte[] signBytes = SignatureUtil.hashAndSign(signRequest.getValue().getBytes(StandardCharsets.UTF_8), privateKey); + String sign = Base64.getEncoder().encodeToString(signBytes); + + Signature signature = new Signature(asymmetricKey.getKeyId(), sign); + + return new SignResponse(signRequest.getValue(), signature.toString()); + } + + public VerifyResponse hashAndVerify(VerifyRequest verifyRequest) throws InvalidKeySpecException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException { + AsymmetricKey asymmetricKey = keyStore.getAsymmetricKey(verifyRequest.getSignature().getKeyId()); + PublicKey publicKey = keyStore.getPublicKey(asymmetricKey); + + boolean verified = SignatureUtil.hashAndVerify(verifyRequest.getValue().getBytes(StandardCharsets.UTF_8), Base64.getDecoder() + .decode(verifyRequest.getSignature().getSignatureValue()), publicKey); + + return new VerifyResponse(verified); + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/SymmetricEncryptionService.java b/services/encryption-service/src/main/java/org/egov/enc/services/SymmetricEncryptionService.java new file mode 100644 index 000000000..cd3b0d8cf --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/SymmetricEncryptionService.java @@ -0,0 +1,56 @@ +package org.egov.enc.services; + +import org.egov.enc.keymanagement.KeyStore; +import org.egov.enc.models.Ciphertext; +import org.egov.enc.models.Plaintext; +import org.egov.enc.models.SymmetricKey; +import org.egov.enc.utils.SymmetricEncryptionUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Base64; + +@Service +public class SymmetricEncryptionService implements EncryptionServiceInterface { + + @Autowired + private KeyStore keyStore; + + public Ciphertext encrypt(Plaintext plaintext) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException { + SymmetricKey symmetricKey = keyStore.getSymmetricKey(plaintext.getTenantId()); + SecretKey secretKey = keyStore.getSecretKey(symmetricKey); + + byte[] initialVectorsBytes = keyStore.getInitialVector(symmetricKey); + + byte[] cipherBytes = SymmetricEncryptionUtil.encrypt(plaintext.getPlaintext().getBytes(StandardCharsets.UTF_8), secretKey, initialVectorsBytes); + + Ciphertext ciphertext = new Ciphertext(symmetricKey.getKeyId(), Base64.getEncoder().encodeToString + (cipherBytes)); + + return ciphertext; + } + + public Plaintext decrypt(Ciphertext ciphertext) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException { + SymmetricKey symmetricKey = keyStore.getSymmetricKey(ciphertext.getKeyId()); + SecretKey secretKey = keyStore.getSecretKey(symmetricKey); + + byte[] initialVectorsBytes = keyStore.getInitialVector(symmetricKey); + + byte[] plainBytes = SymmetricEncryptionUtil.decrypt(Base64.getDecoder().decode(ciphertext.getCiphertext()), secretKey, initialVectorsBytes); + String plain = new String(plainBytes, StandardCharsets.UTF_8); + + Plaintext plaintext = new Plaintext(plain); + + return plaintext; + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/utils/AsymmetricEncryptionUtil.java b/services/encryption-service/src/main/java/org/egov/enc/utils/AsymmetricEncryptionUtil.java new file mode 100644 index 000000000..a611b03d1 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/utils/AsymmetricEncryptionUtil.java @@ -0,0 +1,47 @@ +package org.egov.enc.utils; + +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.egov.enc.config.AppProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.*; + +@Slf4j +@Component +public class AsymmetricEncryptionUtil { + + private static String asymmetricEncryptionMethod; + + @Autowired + public void setAsymmetricEncryptionMethod(@Value("${method.asymmetric}") String method) { + asymmetricEncryptionMethod = method; + } + + @Autowired + public AsymmetricEncryptionUtil() { init(); } + + //Initialize Security Provider to BouncyCastleProvider + public static void init() { + Security.addProvider(new BouncyCastleProvider()); + } + + public static byte[] encrypt(byte[] plaintext, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + Cipher cipher = Cipher.getInstance(asymmetricEncryptionMethod); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + return cipher.doFinal(plaintext); + } + + public static byte[] decrypt(byte[] ciphertext, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + Cipher cipher = Cipher.getInstance(asymmetricEncryptionMethod); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + return cipher.doFinal(ciphertext); + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/utils/Constants.java b/services/encryption-service/src/main/java/org/egov/enc/utils/Constants.java new file mode 100644 index 000000000..5fe5056c4 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/utils/Constants.java @@ -0,0 +1,9 @@ +package org.egov.enc.utils; + +public class Constants { + + public static final String TENANT_NOT_FOUND = " : Tenant Id not found"; + + public static final String INVALD_DATA_TYPE = " is an Invalid Data Type"; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/utils/ProcessJSONUtil.java b/services/encryption-service/src/main/java/org/egov/enc/utils/ProcessJSONUtil.java new file mode 100644 index 000000000..7ec080f0f --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/utils/ProcessJSONUtil.java @@ -0,0 +1,120 @@ +package org.egov.enc.utils; + +import lombok.extern.slf4j.Slf4j; +import org.egov.enc.keymanagement.KeyStore; +import org.egov.enc.models.Ciphertext; +import org.egov.enc.models.MethodEnum; +import org.egov.enc.models.ModeEnum; +import org.egov.enc.models.Plaintext; +import org.egov.enc.services.SymmetricEncryptionService; +import org.egov.enc.services.AsymmetricEncryptionService; +import org.egov.tracer.model.CustomException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.*; + +/* + ProcessJSONUtil is used to navigate through a JSON Object. + All the values will be encrypted, keys will remain as it is. +*/ + +@Slf4j +@Component +public class ProcessJSONUtil { + + @Autowired + private SymmetricEncryptionService symmetricEncryptionService; + @Autowired + private AsymmetricEncryptionService asymmetricEncryptionService; + @Autowired + private KeyStore keyStore; + + //The input object may be JSON Object or a JSON Array + public Object processJSON(Object inputObject, ModeEnum mode, MethodEnum method, String tenantId) throws Exception { + Object outputObject; + + if(inputObject instanceof Map) { + outputObject = processJSONMap((Map) inputObject, mode, method, tenantId); + } else if(inputObject instanceof List) { + outputObject = processJSONList((List) inputObject, mode, method, tenantId); + } else { + outputObject = processValue(inputObject, mode, method, tenantId); + } + return outputObject; + } + + //Navigate through JSON Object + private Map processJSONMap(Map jsonMap, ModeEnum mode, MethodEnum method, String tenantId) throws Exception { + HashMap outputJSONMap = new HashMap<>(); + Set keySet = jsonMap.keySet(); + Iterator keyNames = keySet.iterator(); + while(keyNames.hasNext()) { + String key = keyNames.next(); + if(jsonMap.get(key) instanceof List) { + outputJSONMap.put(key, processJSONList((List) jsonMap.get(key), mode, method, tenantId)); + } else if(jsonMap.get(key) instanceof Map) { + outputJSONMap.put(key, processJSONMap((Map) jsonMap.get(key), mode, method, tenantId)); + } else { + outputJSONMap.put(key, processValue(jsonMap.get(key), mode, method, tenantId)); + } + } + return outputJSONMap; + } + + //Navigate through JSON Array + private List processJSONList(List jsonList, ModeEnum mode, MethodEnum method, String tenantId) throws Exception { + ArrayList outputJSONList = new ArrayList<>(); + for(int i = 0; i < jsonList.size(); i++) { + if(jsonList.get(i) instanceof List) { + outputJSONList.add(i, processJSONList((List) jsonList.get(i), mode, method, tenantId)); + } else if(jsonList.get(i) instanceof Map) { + outputJSONList.add(i, processJSONMap((Map) jsonList.get(i), mode, method, tenantId)); + } else { + outputJSONList.add(i, processValue(jsonList.get(i), mode, method, tenantId)); + } + } + return outputJSONList; + } + + //Each value in the object will be encrypted + private String processValue(Object value, ModeEnum mode, MethodEnum method, String tenantId) throws Exception { + if(value == null) { + return null; + } + if(mode.equals(ModeEnum.ENCRYPT)) { + Ciphertext ciphertext; + Plaintext plaintext = new Plaintext(tenantId, value.toString()); + if(method.equals(MethodEnum.SYM)) { + ciphertext = symmetricEncryptionService.encrypt(plaintext); + } else { + ciphertext = asymmetricEncryptionService.encrypt(plaintext); + } + return ciphertext.toString(); + } + else { + Plaintext plaintext; + Ciphertext ciphertext = new Ciphertext(value.toString()); + if(!keyStore.checkIfKeyExists(ciphertext.getKeyId())) { + keyStore.refreshKeys(); + if(!keyStore.checkIfKeyExists(ciphertext.getKeyId())) + throw new CustomException("KEY_NOT_FOUND", "Key not found in the database"); + } + method = keyStore.getTypeOfKey(ciphertext.getKeyId()); + if(method.equals(MethodEnum.SYM)) { + plaintext = symmetricEncryptionService.decrypt(ciphertext); + } else { + plaintext = asymmetricEncryptionService.decrypt(ciphertext); + } + return plaintext.toString(); + } + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/utils/SignatureUtil.java b/services/encryption-service/src/main/java/org/egov/enc/utils/SignatureUtil.java new file mode 100644 index 000000000..19510b047 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/utils/SignatureUtil.java @@ -0,0 +1,43 @@ +package org.egov.enc.utils; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.*; + +@Component +public class SignatureUtil { + + public static String signatureMethod; + + @Autowired + public void setSymmetricEncryptionMethod(@Value("${method.signature}") String method) { + signatureMethod = method; + } + + @Autowired + public SignatureUtil() { init(); } + + //Initialize Security Provider to BouncyCastleProvider + public static void init() { + Security.addProvider(new BouncyCastleProvider()); + } + + public static byte[] hashAndSign(byte[] data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature signature = Signature.getInstance(signatureMethod); + signature.initSign(privateKey); + signature.update(data); + return signature.sign(); + } + + + public static boolean hashAndVerify(byte[] data, byte[] sign, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature signature = Signature.getInstance(signatureMethod); + signature.initVerify(publicKey); + signature.update(data); + return signature.verify(sign); + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/utils/SymmetricEncryptionUtil.java b/services/encryption-service/src/main/java/org/egov/enc/utils/SymmetricEncryptionUtil.java new file mode 100644 index 000000000..642476bca --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/utils/SymmetricEncryptionUtil.java @@ -0,0 +1,46 @@ +package org.egov.enc.utils; + +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.egov.enc.config.AppProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.*; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.Security; + +@Slf4j +@Component +public class SymmetricEncryptionUtil { + + private static String symmetricEncryptionMethod; + + @Autowired + public void setSymmetricEncryptionMethod(@Value("${method.symmetric}") String method) { + symmetricEncryptionMethod = method; + } + + public SymmetricEncryptionUtil() { init(); } + + //Initialize Security Provider to BouncyCastleProvider + public static void init() { Security.addProvider(new BouncyCastleProvider()); } + + public static byte[] encrypt(byte[] plaintext, SecretKey secretKey, byte[] initialVector) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + Cipher cipher = Cipher.getInstance(symmetricEncryptionMethod); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, initialVector)); + return cipher.doFinal(plaintext); + } + + public static byte[] decrypt(byte[] ciphertext, SecretKey secretKey, byte[] initialVector) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + Cipher cipher = Cipher.getInstance(symmetricEncryptionMethod); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, initialVector)); + return cipher.doFinal(ciphertext); + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java b/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java new file mode 100644 index 000000000..23d85091a --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java @@ -0,0 +1,73 @@ +package org.egov.enc.web.controllers; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.egov.enc.services.KeyManagementService; +import org.egov.enc.services.EncryptionService; +import org.egov.enc.services.SignatureService; +import org.egov.enc.web.models.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; + +@Slf4j +@Controller +public class CryptoApiController{ + + private final ObjectMapper objectMapper; + private final HttpServletRequest request; + + @Autowired + private EncryptionService encryptionService; + @Autowired + private SignatureService signatureService; + @Autowired + private KeyManagementService keyManagementService; + + @Autowired + public CryptoApiController(ObjectMapper objectMapper, HttpServletRequest request) { + this.objectMapper = objectMapper; + this.request = request; + } + + @RequestMapping(value="/crypto/v1/_encrypt", method = RequestMethod.POST) + public ResponseEntity cryptoEncryptPost(@Valid @RequestBody EncryptionRequest encryptionRequest) throws Exception { + return new ResponseEntity<>(encryptionService.encrypt(encryptionRequest), HttpStatus.OK ); + } + + @RequestMapping(value="/crypto/v1/_decrypt", method = RequestMethod.POST) + public ResponseEntity cryptoDecryptPost(@Valid @RequestBody Object decryptionRequest) throws Exception { + return new ResponseEntity<>(encryptionService.decrypt(decryptionRequest), HttpStatus.OK ); + } + + @RequestMapping(value="/crypto/v1/_sign", method = RequestMethod.POST) + public ResponseEntity cryptoSignPost(@Valid @RequestBody SignRequest signRequest) throws Exception { + return new ResponseEntity<>(signatureService.hashAndSign(signRequest), HttpStatus.OK); + } + + @RequestMapping(value = "/crypto/v1/_verify", method = RequestMethod.POST) + public ResponseEntity cryptoVerifyPost(@Valid @RequestBody VerifyRequest verifyRequest) throws Exception { + return new ResponseEntity<>(signatureService.hashAndVerify(verifyRequest), HttpStatus.OK); + } + + @RequestMapping(value = "/crypto/v1/_rotateallkeys", method=RequestMethod.POST) + public ResponseEntity cryptoRotateAllKeys(@Valid @RequestBody RotateKeyRequest rotateKeyRequest) + throws Exception { + return new ResponseEntity(keyManagementService.rotateAllKeys(), HttpStatus.OK); + } + + @RequestMapping(value = "/crypto/v1/_rotatekey", method=RequestMethod.POST) + public ResponseEntity cryptoRotateKeys(@Valid @RequestBody RotateKeyRequest rotateKeyRequest) throws + Exception { + return new ResponseEntity(keyManagementService.rotateKey(rotateKeyRequest), HttpStatus.OK); + } + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/models/EncReqObject.java b/services/encryption-service/src/main/java/org/egov/enc/web/models/EncReqObject.java new file mode 100644 index 000000000..42c1a4bc1 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/models/EncReqObject.java @@ -0,0 +1,39 @@ +package org.egov.enc.web.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import lombok.*; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +/** + * Encryption / Decryption Request Meta-data and Values + */ +@ApiModel(description = "Encryption / Decryption Request Meta-data and Values") +@Validated +@javax.annotation.Generated(value = "org.egov.codegen.SpringBootCodegen", date = "2018-10-11T17:31:52.360+05:30") + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EncReqObject { + + @NotNull + @JsonProperty("tenantId") + private String tenantId = null; + + @NotNull + @JsonProperty("type") + private String type = null; + + @NotNull + @JsonProperty("value") + @Valid + private Object value = null; + +} + diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/models/EncryptionRequest.java b/services/encryption-service/src/main/java/org/egov/enc/web/models/EncryptionRequest.java new file mode 100644 index 000000000..76b1e1705 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/models/EncryptionRequest.java @@ -0,0 +1,20 @@ +package org.egov.enc.web.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import javax.validation.constraints.NotNull; +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EncryptionRequest { + + @NotNull + @JsonProperty("encryptionRequests") + private List encryptionRequests; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/models/RotateKeyRequest.java b/services/encryption-service/src/main/java/org/egov/enc/web/models/RotateKeyRequest.java new file mode 100644 index 000000000..8a4f35d37 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/models/RotateKeyRequest.java @@ -0,0 +1,19 @@ +package org.egov.enc.web.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import javax.validation.constraints.NotNull; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RotateKeyRequest { + + @NotNull + @JsonProperty("tenantId") + private String tenantId; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/models/RotateKeyResponse.java b/services/encryption-service/src/main/java/org/egov/enc/web/models/RotateKeyResponse.java new file mode 100644 index 000000000..47939c233 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/models/RotateKeyResponse.java @@ -0,0 +1,16 @@ +package org.egov.enc.web.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RotateKeyResponse { + + @JsonProperty("acknowledged") + private boolean acknowledged; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/models/SignRequest.java b/services/encryption-service/src/main/java/org/egov/enc/web/models/SignRequest.java new file mode 100644 index 000000000..84de5d19d --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/models/SignRequest.java @@ -0,0 +1,67 @@ +package org.egov.enc.web.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import lombok.*; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotNull; + +/** + * Object with the value to be signed + */ +@ApiModel(description = "Object with the value to be signed") +@Validated +@javax.annotation.Generated(value = "org.egov.codegen.SpringBootCodegen", date = "2018-10-11T17:31:52.360+05:30") + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SignRequest { + + /** + * Type of the value to be encrypted value / object. Sign object as a whole or sign values seperately inside that object. + */ +// public enum TypeOfValueEnum { +// VALUE("VALUE"), +// +// OBJECT("OBJECT"); +// +// private String value; +// +// TypeOfValueEnum(String value) { +// this.value = value; +// } +// +// @Override +// @JsonValue +// public String toString() { +// return String.valueOf(value); +// } +// +// @JsonCreator +// public static TypeOfValueEnum fromValue(String text) { +// for (TypeOfValueEnum b : TypeOfValueEnum.values()) { +// if (String.valueOf(b.value).equals(text)) { +// return b; +// } +// } +// return null; +// } +// } +// +// @JsonProperty("typeOfValue") +// private TypeOfValueEnum typeOfValue = null; + + @NotNull + @JsonProperty("tenantId") + private String tenantId = null; + + @NotNull + @JsonProperty("value") + private String value = null; + +} + diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/models/SignResponse.java b/services/encryption-service/src/main/java/org/egov/enc/web/models/SignResponse.java new file mode 100644 index 000000000..3f5bb0c23 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/models/SignResponse.java @@ -0,0 +1,19 @@ +package org.egov.enc.web.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SignResponse { + + @JsonProperty("value") + private String value; + + @JsonProperty("signature") + private String signature; + +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/models/VerifyRequest.java b/services/encryption-service/src/main/java/org/egov/enc/web/models/VerifyRequest.java new file mode 100644 index 000000000..6999faaa3 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/models/VerifyRequest.java @@ -0,0 +1,68 @@ +package org.egov.enc.web.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import lombok.*; +import org.egov.enc.models.Signature; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotNull; + +/** + * Object with the value and signature to be verified + */ +@ApiModel(description = "Object with the value and signature to be verified") +@Validated +@javax.annotation.Generated(value = "org.egov.codegen.SpringBootCodegen", date = "2018-10-11T17:31:52.360+05:30") + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class VerifyRequest { + + @NotNull + @JsonProperty("value") + private String value = null; + + @NotNull + @JsonProperty("signature") + private Signature signature = null; + + /** + * Type of the value to be encrypted value / object. Sign object as a whole or sign values seperately inside that object. + */ +// public enum TypeOfValueEnum { +// VALUE("VALUE"), +// +// OBJECT("OBJECT"); +// +// private String value; +// +// TypeOfValueEnum(String value) { +// this.value = value; +// } +// +// @Override +// @JsonValue +// public String toString() { +// return String.valueOf(value); +// } +// +// @JsonCreator +// public static TypeOfValueEnum fromValue(String text) { +// for (TypeOfValueEnum b : TypeOfValueEnum.values()) { +// if (String.valueOf(b.value).equals(text)) { +// return b; +// } +// } +// return null; +// } +// } +// +// @JsonProperty("typeOfValue") +// private TypeOfValueEnum typeOfValue = null; + +} + diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/models/VerifyResponse.java b/services/encryption-service/src/main/java/org/egov/enc/web/models/VerifyResponse.java new file mode 100644 index 000000000..8bf9ca740 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/web/models/VerifyResponse.java @@ -0,0 +1,17 @@ +package org.egov.enc.web.models; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class VerifyResponse { + + @JsonProperty("verified") + private boolean verified; + +} diff --git a/services/encryption-service/src/main/resources/application.properties b/services/encryption-service/src/main/resources/application.properties new file mode 100644 index 000000000..c25f18732 --- /dev/null +++ b/services/encryption-service/src/main/resources/application.properties @@ -0,0 +1,51 @@ +server.port=8013 +app.timezone=UTC + +##----------------------------- SPRING DS CONFIGURATIONS ------------------------------# +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://localhost:5432/enc_service +spring.datasource.username=postgres +spring.datasource.password=postgres +##----------------------------- FLYWAY CONFIGURATIONS ------------------------------# +spring.flyway.url=jdbc:postgresql://localhost:5432/enc_service +spring.flyway.user=postgres +spring.flyway.password=postgres +spring.flyway.table=flyway +spring.flyway.baseline-on-migrate=true +spring.flyway.outOfOrder=true +spring.flyway.locations=classpath:/db/migration/main +spring.flyway.enabled=true + + + +#-------------Encryption Methods (BouncyCastle Compliant Methods only)----------# +#---------Refer: https://www.bouncycastle.org/specifications.html --------------# +method.symmetric=AES/GCM/NoPadding +method.asymmetric=RSA/NONE/OAEPWithSHA3-256AndMGF1Padding +method.signature=SHA256withRSA + +#-------------Key Sizes---------# +#-------Symmetric Key Size (AES - 128/192/256)--------# +size.key.symmetric = 256 +#-------Initialization Vector (12B for AES-GCM)-------# +size.initialvector = 12 +#-------Asymmetric Key Size (RSA - 1024)---------------# +size.key.asymmetric = 1024 + +#-------Length of Id of the keys----------# +length.keyid = 6 + +#------------------Master Password------------------------# +master.password=asd@#$@$!132123 +#---------Salt for Master Password: LENGTH = 8 (128-bit AES)---------------# +master.salt=qweasdzx +#-------Initial Vector for Password: LENGTH = 12 (128-bit AES-GCM)---------# +master.initialvector=qweasdzxqwea + +type.to.method.map = {"Normal":"SYM","Important":"ASY"} + +#----------Tenant Ids-----------# +tenant.ids=sunbird + +#---------Master Password provider ; Currently supported - software, awskms--------# +master.password.provider=software diff --git a/services/encryption-service/src/main/resources/db/Dockerfile b/services/encryption-service/src/main/resources/db/Dockerfile new file mode 100644 index 000000000..a5699ff7d --- /dev/null +++ b/services/encryption-service/src/main/resources/db/Dockerfile @@ -0,0 +1,9 @@ +FROM egovio/flyway:4.1.2 + +COPY ./migration/main /flyway/sql + +COPY migrate.sh /usr/bin/migrate.sh + +RUN chmod +x /usr/bin/migrate.sh + +CMD ["/usr/bin/migrate.sh"] diff --git a/services/encryption-service/src/main/resources/db/migrate.sh b/services/encryption-service/src/main/resources/db/migrate.sh new file mode 100644 index 000000000..5593a173e --- /dev/null +++ b/services/encryption-service/src/main/resources/db/migrate.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +flyway -url=$DB_URL -table=$SCHEMA_TABLE -user=$FLYWAY_USER -password=$FLYWAY_PASSWORD -locations=$FLYWAY_LOCATIONS -baselineOnMigrate=true -outOfOrder=true migrate \ No newline at end of file diff --git a/services/encryption-service/src/main/resources/db/migration/main/V20180607185601__eg_enc.sql b/services/encryption-service/src/main/resources/db/migration/main/V20180607185601__eg_enc.sql new file mode 100644 index 000000000..600daf48b --- /dev/null +++ b/services/encryption-service/src/main/resources/db/migration/main/V20180607185601__eg_enc.sql @@ -0,0 +1,31 @@ +DROP TABLE IF EXISTS eg_enc_symmetric_keys; +DROP TABLE IF EXISTS eg_enc_asymmetric_keys; + + +CREATE TABLE public."eg_enc_symmetric_keys" +( + id SERIAL, + key_id integer NOT NULL, + secret_key text NOT NULL, + initial_vector text NOT NULL, + active boolean NOT NULL DEFAULT true, + tenant_id text NOT NULL, + PRIMARY KEY (id) +); + +CREATE UNIQUE INDEX eg_symmetric_key_id ON eg_enc_symmetric_keys (key_id); +CREATE UNIQUE INDEX active_tenant_symmetric_keys ON eg_enc_symmetric_keys (tenant_id) WHERE (active is true); + +CREATE TABLE public."eg_enc_asymmetric_keys" +( + id SERIAL, + key_id integer NOT NULL, + public_key text NOT NULL, + private_key text NOT NULL, + active boolean NOT NULL DEFAULT true, + tenant_id text NOT NULL, + PRIMARY KEY (id) +); + +CREATE UNIQUE INDEX eg_asymmetric_key_id ON eg_enc_asymmetric_keys (key_id); +CREATE UNIQUE INDEX active_tenant_asymmetric_keys ON eg_enc_asymmetric_keys (tenant_id) WHERE (active is true); diff --git a/services/encryption-service/src/test/java/org/egov/enc/TestConfiguration.java b/services/encryption-service/src/test/java/org/egov/enc/TestConfiguration.java new file mode 100644 index 000000000..9e93f73d0 --- /dev/null +++ b/services/encryption-service/src/test/java/org/egov/enc/TestConfiguration.java @@ -0,0 +1,16 @@ +package org.egov.enc; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.KafkaTemplate; + +import static org.mockito.Mockito.mock; + +@Configuration +public class TestConfiguration { + @Bean + @SuppressWarnings("unchecked") + public KafkaTemplate kafkaTemplate() { + return mock(KafkaTemplate.class); + } +} \ No newline at end of file diff --git a/services/encryption-service/src/test/java/org/egov/enc/web/controllers/CryptoApiControllerTest.java b/services/encryption-service/src/test/java/org/egov/enc/web/controllers/CryptoApiControllerTest.java new file mode 100644 index 000000000..163f02d4d --- /dev/null +++ b/services/encryption-service/src/test/java/org/egov/enc/web/controllers/CryptoApiControllerTest.java @@ -0,0 +1,86 @@ +package org.egov.enc.web.controllers; + +import org.junit.Test; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.egov.enc.TestConfiguration; + +import static org.mockito.Matchers.any; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** +* API tests for CryptoApiController +*/ +@Ignore +@RunWith(SpringRunner.class) +@WebMvcTest(CryptoApiController.class) +@Import(TestConfiguration.class) +public class CryptoApiControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void cryptoV1DecryptPostSuccess() throws Exception { + mockMvc.perform(post("/egov-enc-service/crypto/v1/_decrypt").contentType(MediaType + .APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + } + + @Test + public void cryptoV1DecryptPostFailure() throws Exception { + mockMvc.perform(post("/egov-enc-service/crypto/v1/_decrypt").contentType(MediaType + .APPLICATION_JSON_UTF8)) + .andExpect(status().isBadRequest()); + } + + @Test + public void cryptoV1EncryptPostSuccess() throws Exception { + mockMvc.perform(post("/egov-enc-service/crypto/v1/_encrypt").contentType(MediaType + .APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + } + + @Test + public void cryptoV1EncryptPostFailure() throws Exception { + mockMvc.perform(post("/egov-enc-service/crypto/v1/_encrypt").contentType(MediaType + .APPLICATION_JSON_UTF8)) + .andExpect(status().isBadRequest()); + } + + @Test + public void cryptoV1SignPostSuccess() throws Exception { + mockMvc.perform(post("/egov-enc-service/crypto/v1/_sign").contentType(MediaType + .APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + } + + @Test + public void cryptoV1SignPostFailure() throws Exception { + mockMvc.perform(post("/egov-enc-service/crypto/v1/_sign").contentType(MediaType + .APPLICATION_JSON_UTF8)) + .andExpect(status().isBadRequest()); + } + + @Test + public void cryptoV1VerifyPostSuccess() throws Exception { + mockMvc.perform(post("/egov-enc-service/crypto/v1/_verify").contentType(MediaType + .APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + } + + @Test + public void cryptoV1VerifyPostFailure() throws Exception { + mockMvc.perform(post("/egov-enc-service/crypto/v1/_verify").contentType(MediaType + .APPLICATION_JSON_UTF8)) + .andExpect(status().isBadRequest()); + } + +} From d208844d49087aa2e5f40598216febcfa94b1367 Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Fri, 22 Sep 2023 17:54:28 +0530 Subject: [PATCH 04/10] enabled build and test --- .github/workflows/maven.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 92d5bdd3e..b911613d8 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -28,8 +28,8 @@ jobs: # with: # ## limits ssh access and adds the ssh public key for the user which triggered the workflow ie Sreejith-K # limit-access-to-actor: true - # - name: Build and test - # run: make test + - name: Build and test + run: make test # test: # runs-on: ubuntu-latest # steps: From 25ef135a95e139c28b1f611ef1ab07888aa6cd28 Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Mon, 25 Sep 2023 12:28:40 +0530 Subject: [PATCH 05/10] fixed encryption tests --- .../impl/EncryptionServiceImplTest.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/EncryptionServiceImplTest.java b/java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/EncryptionServiceImplTest.java index 43862abef..1e666d790 100644 --- a/java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/EncryptionServiceImplTest.java +++ b/java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/EncryptionServiceImplTest.java @@ -24,6 +24,8 @@ import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; +import java.util.List; +import java.util.Collections; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsNull.notNullValue; @@ -45,8 +47,6 @@ public class EncryptionServiceImplTest{ @Mock private RetryRestTemplate retryRestTemplate; @Mock - private Gson gson; - @Mock public SunbirdRCInstrumentation watch; @InjectMocks private EncryptionServiceImpl encryptionServiceImpl; @@ -55,6 +55,7 @@ public class EncryptionServiceImplTest{ public void setUp(){ MockitoAnnotations.initMocks(this); ReflectionTestUtils.setField(encryptionServiceImpl, "encryptionEnabled", true); + ReflectionTestUtils.setField(encryptionServiceImpl, "gson", new Gson()); } @Test @@ -62,7 +63,7 @@ public void test_encrypt_api_with_object_as_input() throws Exception { when(retryRestTemplate.postForEntity(nullable(String.class), any(Object.class))).thenAnswer(new Answer>(){ @Override public ResponseEntity answer(InvocationOnMock invocation) throws Throwable { - String response = "success"; + String response = Collections.singletonList("success").toString(); return ResponseEntity.accepted().body(response); } }); @@ -77,13 +78,10 @@ public ResponseEntity answer(InvocationOnMock invocation) throws Throwa Map responseMap = new HashMap(); responseMap.put("A","1"); responseMap.put("B","2"); - return ResponseEntity.accepted().body(responseMap.toString()); + List list = Collections.singletonList(responseMap); + return ResponseEntity.accepted().body(list.toString()); } }); - - when(gson.toJson(any(Object.class))).thenReturn(new String()); - when(gson.fromJson(any(String.class),any(Type.class))).thenReturn(new HashMap()); - doNothing().when(watch).start(isA(String.class)); Map propertyMap = new HashMap(); propertyMap.put("school", "BVM"); propertyMap.put("name", "john"); @@ -131,10 +129,6 @@ public ResponseEntity answer(InvocationOnMock invocation) throws Throwa return ResponseEntity.accepted().body(responseMap.toString()); } }); - - when(gson.toJson(any(Object.class))).thenReturn(new String()); - when(gson.fromJson(any(String.class),any(Type.class))).thenReturn(new HashMap()); - doNothing().when(watch).start(isA(String.class)); Map propertyMap = new HashMap(); propertyMap.put("school", "BVM"); propertyMap.put("name", "john"); From 4b0402b86573ef99033fc58616cfaa8a364ae22a Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Mon, 25 Sep 2023 15:16:05 +0530 Subject: [PATCH 06/10] fixed docker image for encryption and added in make --- Makefile | 1 + services/encryption-service/Dockerfile | 9 ++++----- services/encryption-service/Makefile | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index ff544ce15..3e9b5dd8e 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ build: java/registry/target/registry.jar make -C services/metrics docker make -C services/digilocker-certificate-api docker make -C services/bulk_issuance docker + make -C services/encryption-service docker docker build -t ghcr.io/sunbird-rc/sunbird-rc-nginx . java/registry/target/registry.jar: $(SOURCES) diff --git a/services/encryption-service/Dockerfile b/services/encryption-service/Dockerfile index aff4b2b7f..418817e2b 100644 --- a/services/encryption-service/Dockerfile +++ b/services/encryption-service/Dockerfile @@ -1,6 +1,5 @@ FROM frolvlad/alpine-java:jdk8-slim -COPY ./BOOT-INF/lib /home/sunbirdrc/BOOT-INF/lib -COPY ./META-INF /home/sunbirdrc/META-INF -COPY ./org /home/sunbirdrc/org -COPY ./BOOT-INF/classes /home/sunbirdrc/BOOT-INF/classes -CMD ["java", "-Xms1024m", "-Xmx2048m", "-XX:+UseG1GC", "-Dserver.port=8013", "-cp", "/home/sunbirdrc/config:/home/sunbirdrc", "org.springframework.boot.loader.JarLauncher"] +WORKDIR /app +ADD ./target/egov-enc-service-1.1.4.jar enc-service.jar +EXPOSE 8013 +ENTRYPOINT ["java", "-jar", "enc-service.jar"] diff --git a/services/encryption-service/Makefile b/services/encryption-service/Makefile index 06ccce5d9..954aff7ca 100644 --- a/services/encryption-service/Makefile +++ b/services/encryption-service/Makefile @@ -3,13 +3,13 @@ IMAGE_NAME=ghcr.io/sunbird-rc/encryption-service build: rm -rf ./target - mvn clean install -DSkipTests + mvn clean install -build-image: build - cd target && jar xvf egov-enc-service-1.1.4.jar && cp ../Dockerfile . && docker build -t $(IMAGE_NAME) . +docker: build + docker build -t $(IMAGE_NAME) . -release: build-image +release: docker docker tag $(IMAGE_NAME):latest $(IMAGE_NAME):$(RELEASE_VERSION) -# @docker push $(IMAGE_NAME):latest -# @docker push $(IMAGE_NAME):$(RELEASE_VERSION) + @docker push $(IMAGE_NAME):latest + @docker push $(IMAGE_NAME):$(RELEASE_VERSION) From 270d8c2ed03eaee0a65fc0e5e07fdd3686c9bcc1 Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Fri, 29 Sep 2023 11:10:00 +0530 Subject: [PATCH 07/10] allowing encryption service to use db or mdms for tenants --- docker-compose.yml | 5 +- services/encryption-service/Dockerfile | 1 - services/encryption-service/Makefile | 2 +- services/encryption-service/README.md | 5 ++ .../main/java/org/egov/enc/models/Tenant.java | 19 +++++++ .../enc/repository/DBTenantRepository.java | 28 ++++++++++ .../egov/enc/services/DBTenantService.java | 27 ++++++++++ .../enc/services/KeyManagementService.java | 6 +-- .../egov/enc/services/MDMSTenantService.java | 54 +++++++++++++++++++ .../org/egov/enc/services/TenantService.java | 7 +++ .../src/main/resources/application.properties | 16 ++++-- .../main/V20230928185601__eg_tenants.sql | 11 ++++ 12 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 services/encryption-service/src/main/java/org/egov/enc/models/Tenant.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/repository/DBTenantRepository.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/DBTenantService.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/MDMSTenantService.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/TenantService.java create mode 100644 services/encryption-service/src/main/resources/db/migration/main/V20230928185601__eg_tenants.sql diff --git a/docker-compose.yml b/docker-compose.yml index c9d776a8c..232d4af92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -353,12 +353,15 @@ services: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 encryption-service: - image: ghcr.io/sunbird-rc/encryption-service:v0.0.14 + image: ghcr.io/sunbird-rc/encryption-service:main ports: - '8013:8013' environment: + server.port: 8013 + server.servlet.context-path: / spring.datasource.url: jdbc:postgresql://db:5432/registry spring.flyway.url: jdbc:postgresql://db:5432/registry + tenant.service: DBTenantService depends_on: db: condition: service_healthy diff --git a/services/encryption-service/Dockerfile b/services/encryption-service/Dockerfile index 418817e2b..d79782e01 100644 --- a/services/encryption-service/Dockerfile +++ b/services/encryption-service/Dockerfile @@ -1,5 +1,4 @@ FROM frolvlad/alpine-java:jdk8-slim WORKDIR /app ADD ./target/egov-enc-service-1.1.4.jar enc-service.jar -EXPOSE 8013 ENTRYPOINT ["java", "-jar", "enc-service.jar"] diff --git a/services/encryption-service/Makefile b/services/encryption-service/Makefile index 954aff7ca..98a74c0af 100644 --- a/services/encryption-service/Makefile +++ b/services/encryption-service/Makefile @@ -1,4 +1,4 @@ -RELEASE_VERSION=v0.0.14 +RELEASE_VERSION=main IMAGE_NAME=ghcr.io/sunbird-rc/encryption-service build: diff --git a/services/encryption-service/README.md b/services/encryption-service/README.md index 3eb4ccfa3..9124aaf33 100644 --- a/services/encryption-service/README.md +++ b/services/encryption-service/README.md @@ -6,6 +6,11 @@ Encryption Service is used to secure the data. It provides functionality to encr - To Do +### Service Dependencies + +If using TenantService as MDMSTenantService (default) +- egov-mdms-service + ### Swagger API Contract diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/Tenant.java b/services/encryption-service/src/main/java/org/egov/enc/models/Tenant.java new file mode 100644 index 000000000..90b4ef1ed --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/models/Tenant.java @@ -0,0 +1,19 @@ +package org.egov.enc.models; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class Tenant { + + private int id; + + private String tenantId; + +} + diff --git a/services/encryption-service/src/main/java/org/egov/enc/repository/DBTenantRepository.java b/services/encryption-service/src/main/java/org/egov/enc/repository/DBTenantRepository.java new file mode 100644 index 000000000..5c6a339fd --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/repository/DBTenantRepository.java @@ -0,0 +1,28 @@ +package org.egov.enc.repository; + +import lombok.extern.slf4j.Slf4j; +import org.egov.enc.models.Tenant; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@ConditionalOnProperty(name = "tenant.service", havingValue = "DBTenantService") +@Repository +public class DBTenantRepository { + private final JdbcTemplate jdbcTemplate; + + private static final String selectTenantsQuery = "SELECT * FROM eg_tenants"; + @Autowired + public DBTenantRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public List fetchTenants() { + return jdbcTemplate.query(selectTenantsQuery, new BeanPropertyRowMapper<>(Tenant.class)); + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/DBTenantService.java b/services/encryption-service/src/main/java/org/egov/enc/services/DBTenantService.java new file mode 100644 index 000000000..af089a5ca --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/DBTenantService.java @@ -0,0 +1,27 @@ +package org.egov.enc.services; + +import org.egov.enc.models.Tenant; +import org.egov.enc.repository.DBTenantRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@ConditionalOnProperty(name = "tenant.service", havingValue = "DBTenantService") +@Service +public class DBTenantService implements TenantService { + private final DBTenantRepository dbTenantRepository; + + public DBTenantService(DBTenantRepository dbTenantRepository) { + this.dbTenantRepository = dbTenantRepository; + + } + + @Override + public List getTenantIds() { + return dbTenantRepository.fetchTenants() + .stream().map(Tenant::getTenantId) + .collect(Collectors.toList()); + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java b/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java index 0bd28b6a4..22e117f76 100644 --- a/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java +++ b/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java @@ -11,7 +11,6 @@ import org.egov.enc.web.models.RotateKeyResponse; import org.egov.tracer.model.CustomException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Service; @@ -33,8 +32,8 @@ public class KeyManagementService implements ApplicationRunner { private KeyStore keyStore; @Autowired private KeyIdGenerator keyIdGenerator; - @Value("${tenant.ids}") - private List tenantIds; + @Autowired + private TenantService tenantService; //Initialize active tenant id list and Check for any new tenants @@ -92,6 +91,7 @@ private int generateKeyForNewTenants() throws Exception { } private Set makeComprehensiveListOfTenantIds() { + List tenantIds = tenantService.getTenantIds(); Set comprehensiveTenantIdsSet = new HashSet<>(tenantIds); for (String tenantId: tenantIds) { diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/MDMSTenantService.java b/services/encryption-service/src/main/java/org/egov/enc/services/MDMSTenantService.java new file mode 100644 index 000000000..bd204b3f8 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/MDMSTenantService.java @@ -0,0 +1,54 @@ +package org.egov.enc.services; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.List; +import org.json.JSONArray; +import org.json.JSONObject; + +@ConditionalOnProperty(name = "tenant.service", havingValue = "MDMSTenantService", matchIfMissing = true) +@Service +public class MDMSTenantService implements TenantService { + @Value("${egov.mdms.host}") + private String mdmsHost; + + @Value("${egov.mdms.search.endpoint}") + private String mdmsEndpoint; + + @Value(("${egov.state.level.tenant.id}")) + private String stateLevelTenantId; + @Override + public List getTenantIds() { + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + String requestJson = "{\"RequestInfo\":{},\"MdmsCriteria\":{\"tenantId\":\"" + stateLevelTenantId + "\"," + + "\"moduleDetails\":[{\"moduleName\":\"tenant\",\"masterDetails\":[{\"name\":\"tenants\"," + + "\"filter\":\"$.*.code\"}]}]}}"; + + String url = mdmsHost + mdmsEndpoint; + + HttpEntity entity = new HttpEntity<>(requestJson, headers); + ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); + + JSONObject jsonObject = new JSONObject(response.getBody()); + JSONArray jsonArray = jsonObject.getJSONObject("MdmsRes").getJSONObject("tenant").getJSONArray("tenants"); + + List tenantIds = new ArrayList<>(); + for(int i = 0; i < jsonArray.length(); i++) { + tenantIds.add(jsonArray.getString(i)); + } + + return tenantIds; + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/TenantService.java b/services/encryption-service/src/main/java/org/egov/enc/services/TenantService.java new file mode 100644 index 000000000..e55efb311 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/services/TenantService.java @@ -0,0 +1,7 @@ +package org.egov.enc.services; + +import java.util.List; + +public interface TenantService { + List getTenantIds(); +} diff --git a/services/encryption-service/src/main/resources/application.properties b/services/encryption-service/src/main/resources/application.properties index c25f18732..887b26258 100644 --- a/services/encryption-service/src/main/resources/application.properties +++ b/services/encryption-service/src/main/resources/application.properties @@ -1,4 +1,6 @@ -server.port=8013 +server.context-path=/egov-enc-service +server.servlet.context-path=/egov-enc-service +server.port=1234 app.timezone=UTC ##----------------------------- SPRING DS CONFIGURATIONS ------------------------------# @@ -42,10 +44,16 @@ master.salt=qweasdzx #-------Initial Vector for Password: LENGTH = 12 (128-bit AES-GCM)---------# master.initialvector=qweasdzxqwea -type.to.method.map = {"Normal":"SYM","Important":"ASY"} +type.to.method.map = {"Normal":"SYM","Imp":"ASY"} -#----------Tenant Ids-----------# -tenant.ids=sunbird +#----------------eGov MDMS----------------------# +egov.mdms.host=https://dev.digit.org +egov.mdms.search.endpoint=/egov-mdms-service/v1/_search +#----------State Level Tenant Id (for MDMS request)-----------# +egov.state.level.tenant.id=pb #---------Master Password provider ; Currently supported - software, awskms--------# master.password.provider=software + +#----------Tenant Service: MDMSTenantService or DBTenantService------------# +tenant.service=MDMSTenantService diff --git a/services/encryption-service/src/main/resources/db/migration/main/V20230928185601__eg_tenants.sql b/services/encryption-service/src/main/resources/db/migration/main/V20230928185601__eg_tenants.sql new file mode 100644 index 000000000..57d28ba51 --- /dev/null +++ b/services/encryption-service/src/main/resources/db/migration/main/V20230928185601__eg_tenants.sql @@ -0,0 +1,11 @@ +DROP TABLE IF EXISTS eg_tenants; + +CREATE TABLE public."eg_tenants" +( + id SERIAL, + tenant_id text NOT NULL, + PRIMARY KEY (id) +); + +CREATE UNIQUE INDEX eg_tenant_id ON eg_tenants (tenant_id); +INSERT INTO eg_tenants (tenant_id) VALUES ('default'); From 2db0fe79c25ef06712d769e6f4b02d2564fa175f Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Fri, 29 Sep 2023 11:27:36 +0530 Subject: [PATCH 08/10] added healthcheck to encryption service --- java/registry/src/main/resources/application.yml | 4 ++-- .../org/egov/enc/web/controllers/CryptoApiController.java | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/java/registry/src/main/resources/application.yml b/java/registry/src/main/resources/application.yml index ef3803471..6c6268aec 100644 --- a/java/registry/src/main/resources/application.yml +++ b/java/registry/src/main/resources/application.yml @@ -189,13 +189,13 @@ frame: encryption: enabled: ${encryption_enabled:false} - healthCheckURL: ${encryption_health_check_url:http://localhost:8013} + healthCheckURL: ${encryption_health_check_url:http://localhost:8013/health} uri: ${encryption_uri:http://localhost:8013/crypto/v1/_encrypt} batch: uri: ${encryption_batch_uri:http://localhost:8013/crypto/v1/_encrypt} method: ${encryption_method:Normal} tenant: - id: ${encryption_tenant_id:sunbird} + id: ${encryption_tenant_id:default} decryption: uri: ${decryption_uri:http://localhost:8013/crypto/v1/_decrypt} diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java b/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java index 23d85091a..3466003f4 100644 --- a/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java +++ b/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java @@ -70,4 +70,9 @@ public ResponseEntity cryptoRotateKeys(@Valid @RequestBody Ro return new ResponseEntity(keyManagementService.rotateKey(rotateKeyRequest), HttpStatus.OK); } + @RequestMapping(value = "/health", method=RequestMethod.GET) + public ResponseEntity healthCheck() { + return ResponseEntity.ok("UP"); + } + } From 815ed1d5dc0b1d691c1d30fb43c67cf9f6a7f5f6 Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Fri, 29 Sep 2023 11:30:54 +0530 Subject: [PATCH 09/10] encryption release version changes --- docker-compose.yml | 2 +- services/encryption-service/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 232d4af92..16afb88d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -353,7 +353,7 @@ services: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 encryption-service: - image: ghcr.io/sunbird-rc/encryption-service:main + image: ghcr.io/sunbird-rc/encryption-service:${RELEASE_VERSION} ports: - '8013:8013' environment: diff --git a/services/encryption-service/Makefile b/services/encryption-service/Makefile index 98a74c0af..954aff7ca 100644 --- a/services/encryption-service/Makefile +++ b/services/encryption-service/Makefile @@ -1,4 +1,4 @@ -RELEASE_VERSION=main +RELEASE_VERSION=v0.0.14 IMAGE_NAME=ghcr.io/sunbird-rc/encryption-service build: From 875c34fd7b3b29fe1b5be46cfb5911ae25a6f2de Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Wed, 18 Oct 2023 14:19:55 +0530 Subject: [PATCH 10/10] fixed encryption service and changed registry accordingly --- .gitignore | 3 +- Makefile | 3 +- docker-compose.yml | 4 +- .../registry/middleware/util/JSONUtil.java | 10 ++- .../registry/helper/EntityStateHelper.java | 8 +-- services/encryption-service/CHANGELOG.md | 12 ++-- services/encryption-service/Makefile | 3 - services/encryption-service/README.md | 3 +- services/encryption-service/pom.xml | 10 +-- .../src/main/java/org/egov/enc/Main.java | 32 +++++++++ .../org/egov/enc/config/AppProperties.java | 5 +- .../enc/masterdata/MasterDataProvider.java | 9 +++ .../provider/DBMasterDataProvider.java | 45 ++++++++++++ .../WebServiceMasterDataProvider.java | 72 +++++++++++++++++++ .../models/{Tenant.java => MDMSConfig.java} | 6 +- .../enc/repository/DBTenantRepository.java | 28 -------- .../enc/repository/MDMSConfigRepository.java | 26 +++++++ .../egov/enc/services/DBTenantService.java | 27 ------- .../enc/services/KeyManagementService.java | 22 +++++- .../egov/enc/services/MDMSTenantService.java | 54 -------------- .../org/egov/enc/services/TenantService.java | 7 -- .../java/org/egov/enc/utils/Constants.java | 5 ++ .../web/controllers/CryptoApiController.java | 5 -- .../src/main/resources/application.properties | 6 +- .../main/V20230928185601__eg_tenants.sql | 11 --- .../main/V20230930120401__eg_enc.sql | 10 +++ 26 files changed, 256 insertions(+), 170 deletions(-) create mode 100644 services/encryption-service/src/main/java/org/egov/enc/masterdata/MasterDataProvider.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/masterdata/provider/DBMasterDataProvider.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/masterdata/provider/WebServiceMasterDataProvider.java rename services/encryption-service/src/main/java/org/egov/enc/models/{Tenant.java => MDMSConfig.java} (90%) delete mode 100644 services/encryption-service/src/main/java/org/egov/enc/repository/DBTenantRepository.java create mode 100644 services/encryption-service/src/main/java/org/egov/enc/repository/MDMSConfigRepository.java delete mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/DBTenantService.java delete mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/MDMSTenantService.java delete mode 100644 services/encryption-service/src/main/java/org/egov/enc/services/TenantService.java delete mode 100644 services/encryption-service/src/main/resources/db/migration/main/V20230928185601__eg_tenants.sql create mode 100644 services/encryption-service/src/main/resources/db/migration/main/V20230930120401__eg_enc.sql diff --git a/.gitignore b/.gitignore index e8d1d0496..cb833ca7d 100644 --- a/.gitignore +++ b/.gitignore @@ -87,5 +87,6 @@ coverage out .ipynb_checkpoints -db-data +db-data* +es-data* keycloak-mobile*.jar \ No newline at end of file diff --git a/Makefile b/Makefile index 3e9b5dd8e..35e1e76ff 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ RELEASE_VERSION = v0.0.14 IMAGES := ghcr.io/sunbird-rc/sunbird-rc-core ghcr.io/sunbird-rc/sunbird-rc-nginx ghcr.io/sunbird-rc/sunbird-rc-context-proxy-service \ ghcr.io/sunbird-rc/sunbird-rc-public-key-service ghcr.io/sunbird-rc/sunbird-rc-keycloak ghcr.io/sunbird-rc/sunbird-rc-certificate-api \ ghcr.io/sunbird-rc/sunbird-rc-certificate-signer ghcr.io/sunbird-rc/sunbird-rc-notification-service ghcr.io/sunbird-rc/sunbird-rc-claim-ms \ - ghcr.io/sunbird-rc/sunbird-rc-digilocker-certificate-api ghcr.io/sunbird-rc/sunbird-rc-bulk-issuance ghcr.io/sunbird-rc/sunbird-rc-metrics + ghcr.io/sunbird-rc/sunbird-rc-digilocker-certificate-api ghcr.io/sunbird-rc/sunbird-rc-bulk-issuance ghcr.io/sunbird-rc/sunbird-rc-metrics \ + ghcr.io/sunbird-rc/encryption-service build: java/registry/target/registry.jar echo ${SOURCES} rm -rf java/claim/target/*.jar diff --git a/docker-compose.yml b/docker-compose.yml index 16afb88d7..5bdc981bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -353,7 +353,7 @@ services: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 encryption-service: - image: ghcr.io/sunbird-rc/encryption-service:${RELEASE_VERSION} + image: ghcr.io/sunbird-rc/encryption-service ports: - '8013:8013' environment: @@ -361,7 +361,7 @@ services: server.servlet.context-path: / spring.datasource.url: jdbc:postgresql://db:5432/registry spring.flyway.url: jdbc:postgresql://db:5432/registry - tenant.service: DBTenantService + egov.mdms.provider: org.egov.enc.masterdata.provider.DBMasterDataProvider depends_on: db: condition: service_healthy diff --git a/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/JSONUtil.java b/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/JSONUtil.java index 00769e6b9..be32ed459 100644 --- a/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/JSONUtil.java +++ b/java/middleware-commons/src/main/java/dev/sunbirdrc/registry/middleware/util/JSONUtil.java @@ -458,11 +458,19 @@ public static void removeNodes(JsonNode node, List backList) { } public static void replaceFieldByPointerPath(JsonNode node, String jsonPointer, JsonNode value) { - if (value != null) { + if (value != null && jsonPointer != null) { + jsonPointer = convertToJsonPointerPath(jsonPointer); ((ObjectNode) node.at(jsonPointer.substring(0, jsonPointer.lastIndexOf("/")))).put(jsonPointer.substring(jsonPointer.lastIndexOf("/") + 1), value); } } + public static String convertToJsonPointerPath(String docPath) { + if(docPath != null && docPath.startsWith("$.")) { + return docPath.replace("$", "").replaceAll("\\.", "/"); + } + return docPath; + } + public static String readValFromJsonTree(String path, JsonNode input) { Configuration alwaysReturnListConfig = Configuration.builder().options(Option.ALWAYS_RETURN_LIST).build(); List typeList = JsonPath.using(alwaysReturnListConfig).parse(input.toString()).read(path); diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/EntityStateHelper.java b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/EntityStateHelper.java index c89871739..9dc7daee7 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/EntityStateHelper.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/EntityStateHelper.java @@ -124,10 +124,10 @@ private void addOwnershipStateTransitions(JsonNode existing, String entityName, } private ObjectNode createOwnershipNode(JsonNode entityNode, String entityName, OwnershipsAttributes ownershipAttribute) { - String mobilePath = ownershipAttribute.getMobile(); - String emailPath = ownershipAttribute.getEmail(); - String userIdPath = ownershipAttribute.getUserId(); - String passwordPath = ownershipAttribute.getPassword(); + String mobilePath = JSONUtil.convertToJsonPointerPath(ownershipAttribute.getMobile()); + String emailPath = JSONUtil.convertToJsonPointerPath(ownershipAttribute.getEmail()); + String userIdPath = JSONUtil.convertToJsonPointerPath(ownershipAttribute.getUserId()); + String passwordPath = JSONUtil.convertToJsonPointerPath(ownershipAttribute.getPassword()); ObjectNode objectNode = new ObjectMapper().createObjectNode(); objectNode.put(MOBILE, entityNode.at(String.format("/%s%s", entityName, mobilePath)).asText("")); objectNode.put(EMAIL, entityNode.at(String.format("/%s%s", entityName, emailPath)).asText("")); diff --git a/services/encryption-service/CHANGELOG.md b/services/encryption-service/CHANGELOG.md index 99c93f4a6..6676c58e5 100644 --- a/services/encryption-service/CHANGELOG.md +++ b/services/encryption-service/CHANGELOG.md @@ -3,15 +3,13 @@ # Changelog All notable changes to this module will be documented in this file. -## 1.1.4 - 2023-02-06 -- Transition from 1.1.4-beta version to 1.1.4 version - -## 1.1.4-beta - 2022-09-20 -- Upgraded spring-boot-starter-parent to 2.2.13 and spring beans to 5.2.20.RELEASE +## 1.1.4 - 2023-08-11 +- Central Instance Library Integration ## 1.1.3 - 2022007016 -- Fixed: In a multi pod cluster, the service now checks if another deployment of the service has added a new key to the database, before throwing the key not found error. - +- Fixed: In a multi pod cluster, the service now checks if another deployment of the service has added a new key to the + database, + before throwing the key not found error. ## 1.1.2 - 2022-01-13 - Updated to log4j2 version 2.17.1 diff --git a/services/encryption-service/Makefile b/services/encryption-service/Makefile index 954aff7ca..dfeefadd9 100644 --- a/services/encryption-service/Makefile +++ b/services/encryption-service/Makefile @@ -1,4 +1,3 @@ -RELEASE_VERSION=v0.0.14 IMAGE_NAME=ghcr.io/sunbird-rc/encryption-service build: @@ -9,7 +8,5 @@ docker: build docker build -t $(IMAGE_NAME) . release: docker - docker tag $(IMAGE_NAME):latest $(IMAGE_NAME):$(RELEASE_VERSION) @docker push $(IMAGE_NAME):latest - @docker push $(IMAGE_NAME):$(RELEASE_VERSION) diff --git a/services/encryption-service/README.md b/services/encryption-service/README.md index 9124aaf33..cd0f523b7 100644 --- a/services/encryption-service/README.md +++ b/services/encryption-service/README.md @@ -8,7 +8,6 @@ Encryption Service is used to secure the data. It provides functionality to encr ### Service Dependencies -If using TenantService as MDMSTenantService (default) - egov-mdms-service @@ -25,6 +24,7 @@ Encryption Service offers following features : - Sign - Encryption Service can hash and sign the data which can be used as unique identifier of the data. This can also be used for searching gicen value from a datastore. - Verify - Based on the input sign and the claim, it can verify if the the given sign is correct for the provided claim. - Rotate Key - Encryption Service supports changing the key used for encryption. The old key will still remain with the service which will be used to decrypt old data. All the new data will be encrypted by the new key. +- Master Data Provider - This service supports two providers to get Master data (MDMS) from a WebService or from a Postgres Database #### Configurations @@ -38,6 +38,7 @@ Following are the properties in application.properties file in egov-enc-service | `size.key.symmetric` | 256 | Default size of Symmetric key. | | `size.key.asymmetric` | 1024 | Default size of Asymmetric key. | | `size.initialvector` | 12 | Default size of Initial vector. | +| `egov.mdms.provider` | ```org.egov.enc.masterdata.provider.WebServiceMasterDataProvider``` | Default Value of the MDMS provider | ### API Details diff --git a/services/encryption-service/pom.xml b/services/encryption-service/pom.xml index 42a7cfcd4..aaf636741 100644 --- a/services/encryption-service/pom.xml +++ b/services/encryption-service/pom.xml @@ -45,7 +45,7 @@ org.egov.services tracer - 2.0.0-SNAPSHOT + 2.1.0-SNAPSHOT org.projectlombok @@ -88,10 +88,10 @@ - repo.egovernments.org - eGov ERP Releases Repository - https://nexus-repo.egovernments.org/nexus/content/repositories/releases/ - + repo.egovernments.org + eGov DIGIT Releases Repository + https://nexus-repo.digit.org/nexus/content/repositories/snapshots/ + repo.egovernments.org.snapshots eGov ERP Releases Repository diff --git a/services/encryption-service/src/main/java/org/egov/enc/Main.java b/services/encryption-service/src/main/java/org/egov/enc/Main.java index 0a8e27cdc..81ebff4ad 100644 --- a/services/encryption-service/src/main/java/org/egov/enc/Main.java +++ b/services/encryption-service/src/main/java/org/egov/enc/Main.java @@ -1,15 +1,47 @@ package org.egov.enc; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.egov.enc.masterdata.MasterDataProvider; +import org.egov.enc.utils.Constants; +import org.egov.tracer.model.CustomException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.util.ObjectUtils; @SpringBootApplication @ComponentScan(basePackages = { "org.egov.enc", "org.egov.enc.web.controllers" , "org.egov.enc.config"}) public class Main { + @Value("${egov.mdms.provider}") + private String masterDataProviderClassName; + + public static final Logger LOGGER = LoggerFactory.getLogger(Main.class); + public static void main(String[] args) throws Exception { SpringApplication.run(Main.class, args); } + @Bean + public MasterDataProvider masterDataProvider() { + MasterDataProvider masterDataProvider = null; + try{ + if(ObjectUtils.isEmpty(masterDataProviderClassName)){ + masterDataProviderClassName = Constants.DEFAULT_MASTER_DATA_PROVIDER; + } + Class masterDataProviderClass = Class.forName(masterDataProviderClassName); + + masterDataProvider = (MasterDataProvider) masterDataProviderClass.newInstance(); + LOGGER.info("Invoked MasterDataProvider with Classname: {}", masterDataProviderClassName); + }catch(ClassNotFoundException | InstantiationException | IllegalAccessException e){ + LOGGER.error("MDMS provider class {} cannot be instantiate with exception: {}", masterDataProviderClassName, ExceptionUtils.getStackTrace(e)); + throw new CustomException("Unable to load MDMS provider class", "MDMS Provider Init Exception"); + } + return masterDataProvider; + } + } diff --git a/services/encryption-service/src/main/java/org/egov/enc/config/AppProperties.java b/services/encryption-service/src/main/java/org/egov/enc/config/AppProperties.java index 95975c216..ee52891ff 100644 --- a/services/encryption-service/src/main/java/org/egov/enc/config/AppProperties.java +++ b/services/encryption-service/src/main/java/org/egov/enc/config/AppProperties.java @@ -3,11 +3,9 @@ import lombok.Getter; import lombok.ToString; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; -import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import java.util.HashMap; @@ -43,4 +41,7 @@ public class AppProperties { @Value("#{${type.to.method.map}}") private HashMap typeToMethodMap; + @Value(("${egov.state.level.tenant.id:default}")) + private String stateLevelTenantId; + } diff --git a/services/encryption-service/src/main/java/org/egov/enc/masterdata/MasterDataProvider.java b/services/encryption-service/src/main/java/org/egov/enc/masterdata/MasterDataProvider.java new file mode 100644 index 000000000..698e11395 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/masterdata/MasterDataProvider.java @@ -0,0 +1,9 @@ +package org.egov.enc.masterdata; + +import org.egov.tracer.model.CustomException; + +import java.util.ArrayList; + +public interface MasterDataProvider { + ArrayList getTenantIds() throws CustomException; +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/masterdata/provider/DBMasterDataProvider.java b/services/encryption-service/src/main/java/org/egov/enc/masterdata/provider/DBMasterDataProvider.java new file mode 100644 index 000000000..e2946a454 --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/masterdata/provider/DBMasterDataProvider.java @@ -0,0 +1,45 @@ +package org.egov.enc.masterdata.provider; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.egov.enc.masterdata.MasterDataProvider; +import org.egov.enc.models.MDMSConfig; +import org.egov.enc.repository.MDMSConfigRepository; +import org.egov.enc.utils.Constants; +import org.egov.tracer.model.CustomException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class DBMasterDataProvider implements MasterDataProvider { + + @Autowired + private MDMSConfigRepository mdmsConfigRepository; + + public static final Logger LOGGER = LoggerFactory.getLogger(DBMasterDataProvider.class); + + @Override + public ArrayList getTenantIds() throws CustomException { + LOGGER.info("Inside DBMasterDataProvider"); + try{ + List mdmsConfigList = mdmsConfigRepository.fetchMDMSConfig(); + ArrayList tenantIdList = null; + if(mdmsConfigList == null || mdmsConfigList.isEmpty()){ + tenantIdList = new ArrayList<>(Arrays.asList(Constants.DEFAULT_TENANT_ID)); + }else{ + tenantIdList = mdmsConfigList.stream().map(MDMSConfig::getTenantId) + .collect(Collectors.toCollection(ArrayList::new)); + } + return tenantIdList; + }catch(DataAccessException e){ + LOGGER.error("Unable to access MDMS data from Database", ExceptionUtils.getStackTrace(e)); + throw new CustomException("Unable to access MDMS data from Database", "DB Exception"); + } + + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/masterdata/provider/WebServiceMasterDataProvider.java b/services/encryption-service/src/main/java/org/egov/enc/masterdata/provider/WebServiceMasterDataProvider.java new file mode 100644 index 000000000..7e5a90d1a --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/masterdata/provider/WebServiceMasterDataProvider.java @@ -0,0 +1,72 @@ +package org.egov.enc.masterdata.provider; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.egov.enc.config.AppProperties; +import org.egov.enc.masterdata.MasterDataProvider; +import org.egov.tracer.model.CustomException; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; + +import static org.egov.enc.utils.Constants.TENANTID_MDC_STRING; + +public class WebServiceMasterDataProvider implements MasterDataProvider { + + @Value("${egov.mdms.host}") + private String mdmsHost; + + @Value("${egov.mdms.search.endpoint}") + private String mdmsEndpoint; + + @Autowired + private AppProperties appProperties; + + public static final Logger LOGGER = LoggerFactory.getLogger(WebServiceMasterDataProvider.class); + + @Override + public ArrayList getTenantIds() throws CustomException{ + LOGGER.info("Inside WebServiceMasterDataProvider"); + try{ + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set(TENANTID_MDC_STRING,appProperties.getStateLevelTenantId()); + + String requestJson = "{\"RequestInfo\":{},\"MdmsCriteria\":{\"tenantId\":\"" + appProperties.getStateLevelTenantId() + "\"," + + "\"moduleDetails\":[{\"moduleName\":\"tenant\",\"masterDetails\":[{\"name\":\"tenants\"," + + "\"filter\":\"$.*.code\"}]}]}}"; + + String url = mdmsHost + mdmsEndpoint; + + HttpEntity entity = new HttpEntity<>(requestJson, headers); + ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); + + JSONObject jsonObject = new JSONObject(response.getBody()); + JSONArray jsonArray = jsonObject.getJSONObject("MdmsRes").getJSONObject("tenant").getJSONArray("tenants"); + + ArrayList tenantIds = new ArrayList<>(); + for(int i = 0; i < jsonArray.length(); i++) { + tenantIds.add(jsonArray.getString(i)); + } + + return tenantIds; + }catch(JSONException | RestClientException e){ + LOGGER.error("Unable to get data from WebService", ExceptionUtils.getStackTrace(e)); + throw new CustomException("Unable to fetch data from MDMS WebService", "WebService Exception"); + } + + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/models/Tenant.java b/services/encryption-service/src/main/java/org/egov/enc/models/MDMSConfig.java similarity index 90% rename from services/encryption-service/src/main/java/org/egov/enc/models/Tenant.java rename to services/encryption-service/src/main/java/org/egov/enc/models/MDMSConfig.java index 90b4ef1ed..55ef2c5ec 100644 --- a/services/encryption-service/src/main/java/org/egov/enc/models/Tenant.java +++ b/services/encryption-service/src/main/java/org/egov/enc/models/MDMSConfig.java @@ -9,11 +9,7 @@ @NoArgsConstructor @Getter @Setter -public class Tenant { - +public class MDMSConfig { private int id; - private String tenantId; - } - diff --git a/services/encryption-service/src/main/java/org/egov/enc/repository/DBTenantRepository.java b/services/encryption-service/src/main/java/org/egov/enc/repository/DBTenantRepository.java deleted file mode 100644 index 5c6a339fd..000000000 --- a/services/encryption-service/src/main/java/org/egov/enc/repository/DBTenantRepository.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.egov.enc.repository; - -import lombok.extern.slf4j.Slf4j; -import org.egov.enc.models.Tenant; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.jdbc.core.BeanPropertyRowMapper; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Slf4j -@ConditionalOnProperty(name = "tenant.service", havingValue = "DBTenantService") -@Repository -public class DBTenantRepository { - private final JdbcTemplate jdbcTemplate; - - private static final String selectTenantsQuery = "SELECT * FROM eg_tenants"; - @Autowired - public DBTenantRepository(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List fetchTenants() { - return jdbcTemplate.query(selectTenantsQuery, new BeanPropertyRowMapper<>(Tenant.class)); - } -} diff --git a/services/encryption-service/src/main/java/org/egov/enc/repository/MDMSConfigRepository.java b/services/encryption-service/src/main/java/org/egov/enc/repository/MDMSConfigRepository.java new file mode 100644 index 000000000..88c27eecd --- /dev/null +++ b/services/encryption-service/src/main/java/org/egov/enc/repository/MDMSConfigRepository.java @@ -0,0 +1,26 @@ +package org.egov.enc.repository; + +import org.egov.enc.models.MDMSConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@ConditionalOnProperty( value = "egov.mdms.provider", havingValue = "org.egov.enc.masterdata.provider.DBMasterDataProvider") +public class MDMSConfigRepository { + + @Autowired + private JdbcTemplate jdbcTemplate; + + private static final String selectAllConfigurationsQuery = "select * from eg_enc_mdms_config"; + + + public List fetchMDMSConfig() throws DataAccessException { + return jdbcTemplate.query(selectAllConfigurationsQuery, new BeanPropertyRowMapper<>(MDMSConfig.class)); + } +} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/DBTenantService.java b/services/encryption-service/src/main/java/org/egov/enc/services/DBTenantService.java deleted file mode 100644 index af089a5ca..000000000 --- a/services/encryption-service/src/main/java/org/egov/enc/services/DBTenantService.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.egov.enc.services; - -import org.egov.enc.models.Tenant; -import org.egov.enc.repository.DBTenantRepository; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; - -@ConditionalOnProperty(name = "tenant.service", havingValue = "DBTenantService") -@Service -public class DBTenantService implements TenantService { - private final DBTenantRepository dbTenantRepository; - - public DBTenantService(DBTenantRepository dbTenantRepository) { - this.dbTenantRepository = dbTenantRepository; - - } - - @Override - public List getTenantIds() { - return dbTenantRepository.fetchTenants() - .stream().map(Tenant::getTenantId) - .collect(Collectors.toList()); - } -} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java b/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java index 22e117f76..131b12dd9 100644 --- a/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java +++ b/services/encryption-service/src/main/java/org/egov/enc/services/KeyManagementService.java @@ -1,24 +1,30 @@ package org.egov.enc.services; import lombok.extern.slf4j.Slf4j; +import org.egov.enc.config.AppProperties; import org.egov.enc.keymanagement.KeyGenerator; import org.egov.enc.keymanagement.KeyIdGenerator; import org.egov.enc.keymanagement.KeyStore; +import org.egov.enc.masterdata.MasterDataProvider; import org.egov.enc.models.AsymmetricKey; import org.egov.enc.models.SymmetricKey; import org.egov.enc.repository.KeyRepository; import org.egov.enc.web.models.RotateKeyRequest; import org.egov.enc.web.models.RotateKeyResponse; import org.egov.tracer.model.CustomException; +import org.json.JSONException; +import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Service; + import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; -import java.util.List; + +import static org.egov.enc.utils.Constants.TENANTID_MDC_STRING; @Slf4j @Service @@ -33,11 +39,15 @@ public class KeyManagementService implements ApplicationRunner { @Autowired private KeyIdGenerator keyIdGenerator; @Autowired - private TenantService tenantService; + private AppProperties appProperties; + @Autowired + private MasterDataProvider masterDataProvider; //Initialize active tenant id list and Check for any new tenants private void init() throws Exception { + // Adding in MDC so that tracer can add it in header + MDC.put(TENANTID_MDC_STRING, appProperties.getStateLevelTenantId()); generateKeyForNewTenants(); } @@ -91,7 +101,7 @@ private int generateKeyForNewTenants() throws Exception { } private Set makeComprehensiveListOfTenantIds() { - List tenantIds = tenantService.getTenantIds(); + ArrayList tenantIds = getTenantIds(); Set comprehensiveTenantIdsSet = new HashSet<>(tenantIds); for (String tenantId: tenantIds) { @@ -136,6 +146,12 @@ public RotateKeyResponse rotateKey(RotateKeyRequest rotateKeyRequest) throws Exc return new RotateKeyResponse(true); } + + + private ArrayList getTenantIds() throws JSONException { + return masterDataProvider.getTenantIds(); + } + @Override public void run(ApplicationArguments applicationArguments) throws Exception { init(); diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/MDMSTenantService.java b/services/encryption-service/src/main/java/org/egov/enc/services/MDMSTenantService.java deleted file mode 100644 index bd204b3f8..000000000 --- a/services/encryption-service/src/main/java/org/egov/enc/services/MDMSTenantService.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.egov.enc.services; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; - -import java.util.ArrayList; -import java.util.List; -import org.json.JSONArray; -import org.json.JSONObject; - -@ConditionalOnProperty(name = "tenant.service", havingValue = "MDMSTenantService", matchIfMissing = true) -@Service -public class MDMSTenantService implements TenantService { - @Value("${egov.mdms.host}") - private String mdmsHost; - - @Value("${egov.mdms.search.endpoint}") - private String mdmsEndpoint; - - @Value(("${egov.state.level.tenant.id}")) - private String stateLevelTenantId; - @Override - public List getTenantIds() { - RestTemplate restTemplate = new RestTemplate(); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - - String requestJson = "{\"RequestInfo\":{},\"MdmsCriteria\":{\"tenantId\":\"" + stateLevelTenantId + "\"," + - "\"moduleDetails\":[{\"moduleName\":\"tenant\",\"masterDetails\":[{\"name\":\"tenants\"," + - "\"filter\":\"$.*.code\"}]}]}}"; - - String url = mdmsHost + mdmsEndpoint; - - HttpEntity entity = new HttpEntity<>(requestJson, headers); - ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); - - JSONObject jsonObject = new JSONObject(response.getBody()); - JSONArray jsonArray = jsonObject.getJSONObject("MdmsRes").getJSONObject("tenant").getJSONArray("tenants"); - - List tenantIds = new ArrayList<>(); - for(int i = 0; i < jsonArray.length(); i++) { - tenantIds.add(jsonArray.getString(i)); - } - - return tenantIds; - } -} diff --git a/services/encryption-service/src/main/java/org/egov/enc/services/TenantService.java b/services/encryption-service/src/main/java/org/egov/enc/services/TenantService.java deleted file mode 100644 index e55efb311..000000000 --- a/services/encryption-service/src/main/java/org/egov/enc/services/TenantService.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.egov.enc.services; - -import java.util.List; - -public interface TenantService { - List getTenantIds(); -} diff --git a/services/encryption-service/src/main/java/org/egov/enc/utils/Constants.java b/services/encryption-service/src/main/java/org/egov/enc/utils/Constants.java index 5fe5056c4..37e23cea2 100644 --- a/services/encryption-service/src/main/java/org/egov/enc/utils/Constants.java +++ b/services/encryption-service/src/main/java/org/egov/enc/utils/Constants.java @@ -6,4 +6,9 @@ public class Constants { public static final String INVALD_DATA_TYPE = " is an Invalid Data Type"; + public static final String TENANTID_MDC_STRING = "TENANTID"; + + public static final String DEFAULT_MASTER_DATA_PROVIDER = "org.egov.enc.masterdata.provider.WebServiceMasterDataProvider"; + + public static final String DEFAULT_TENANT_ID = "default"; } diff --git a/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java b/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java index 3466003f4..23d85091a 100644 --- a/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java +++ b/services/encryption-service/src/main/java/org/egov/enc/web/controllers/CryptoApiController.java @@ -70,9 +70,4 @@ public ResponseEntity cryptoRotateKeys(@Valid @RequestBody Ro return new ResponseEntity(keyManagementService.rotateKey(rotateKeyRequest), HttpStatus.OK); } - @RequestMapping(value = "/health", method=RequestMethod.GET) - public ResponseEntity healthCheck() { - return ResponseEntity.ok("UP"); - } - } diff --git a/services/encryption-service/src/main/resources/application.properties b/services/encryption-service/src/main/resources/application.properties index 887b26258..a570df024 100644 --- a/services/encryption-service/src/main/resources/application.properties +++ b/services/encryption-service/src/main/resources/application.properties @@ -46,6 +46,9 @@ master.initialvector=qweasdzxqwea type.to.method.map = {"Normal":"SYM","Imp":"ASY"} +#----------------eGov Master Data Provider------# +egov.mdms.provider=org.egov.enc.masterdata.provider.DBMasterDataProvider + #----------------eGov MDMS----------------------# egov.mdms.host=https://dev.digit.org egov.mdms.search.endpoint=/egov-mdms-service/v1/_search @@ -54,6 +57,3 @@ egov.state.level.tenant.id=pb #---------Master Password provider ; Currently supported - software, awskms--------# master.password.provider=software - -#----------Tenant Service: MDMSTenantService or DBTenantService------------# -tenant.service=MDMSTenantService diff --git a/services/encryption-service/src/main/resources/db/migration/main/V20230928185601__eg_tenants.sql b/services/encryption-service/src/main/resources/db/migration/main/V20230928185601__eg_tenants.sql deleted file mode 100644 index 57d28ba51..000000000 --- a/services/encryption-service/src/main/resources/db/migration/main/V20230928185601__eg_tenants.sql +++ /dev/null @@ -1,11 +0,0 @@ -DROP TABLE IF EXISTS eg_tenants; - -CREATE TABLE public."eg_tenants" -( - id SERIAL, - tenant_id text NOT NULL, - PRIMARY KEY (id) -); - -CREATE UNIQUE INDEX eg_tenant_id ON eg_tenants (tenant_id); -INSERT INTO eg_tenants (tenant_id) VALUES ('default'); diff --git a/services/encryption-service/src/main/resources/db/migration/main/V20230930120401__eg_enc.sql b/services/encryption-service/src/main/resources/db/migration/main/V20230930120401__eg_enc.sql new file mode 100644 index 000000000..0f02c65a5 --- /dev/null +++ b/services/encryption-service/src/main/resources/db/migration/main/V20230930120401__eg_enc.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS eg_enc_mdms_config; + +CREATE TABLE public."eg_enc_mdms_config" +( + id SERIAL, + tenant_id text NOT NULL, + PRIMARY KEY (id) +); + +CREATE UNIQUE INDEX eg_enc_mdms_config_tenant_id ON eg_enc_mdms_config (tenant_id);