Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat]: encryption/decryption for private fields #255

Merged
merged 11 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,6 @@ coverage
out

.ipynb_checkpoints
db-data
db-data*
es-data*
keycloak-mobile*.jar
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +21,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)
Expand Down
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,16 @@ services:
test:
wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit
1
encryption-service:
image: ghcr.io/sunbird-rc/encryption-service
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
egov.mdms.provider: org.egov.enc.masterdata.provider.DBMasterDataProvider
depends_on:
db:
condition: service_healthy
Original file line number Diff line number Diff line change
Expand Up @@ -458,11 +458,19 @@ public static void removeNodes(JsonNode node, List<String> 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<String> typeList = JsonPath.using(alwaysReturnListConfig).parse(input.toString()).read(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(""));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
resultNode = decryptionHelper.getDecryptedJson(resultNode);
}
logger.debug("readEntity ends");
if(isEventsEnabled) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String, Object> performOperation(Map<String, Object> plainMap) throws EncryptionException {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,8 +19,9 @@ protected Map<String, Object> performOperation(Map<String, Object> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}")
Expand All @@ -62,20 +65,7 @@ public class EncryptionServiceImpl implements EncryptionService {
*/
@Override
public String encrypt(Object propertyValue) throws EncryptionException {
logger.debug("encrypt starts with value");
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("value", propertyValue);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(map);
try {
ResponseEntity<String> 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
Expand All @@ -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<String, Object> map = new LinkedMultiValueMap<>();
map.add("value", propertyValue);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(map);
try {
ResponseEntity<String> 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
Expand All @@ -109,19 +85,39 @@ public String decrypt(Object propertyValue) throws EncryptionException {
*/
@Override
public Map<String, Object> encrypt(Map<String, Object> propertyValue) throws EncryptionException {
return this.doEncrypt(propertyValue, encryptionBatchUri);
}

/** decrypts the input which is in Map format
* @param propertyValue - input is in format Map<String, Object>
* @return Map<String, Object>
* @throws EncryptionException
*/
@Override
public Map<String, Object> decrypt(Map<String, Object> propertyValue) throws EncryptionException {
return this.doDecrypt(propertyValue, decryptionBatchUri);
}

private <T> T doEncrypt(T propertyValue, String uri) throws EncryptionException {
logger.debug("encrypt starts with value {}", propertyValue);
Map<String, Object> map = new HashMap<>();
map.put("value", propertyValue);
Map<String, Object> 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<String> entity = new HttpEntity<>(gson.toJson(map), headers);
try {
watch.start("EncryptionServiceImpl.encryptBatch");
ResponseEntity<String> response = retryRestTemplate.postForEntity(encryptionBatchUri,entity);
ResponseEntity<String> response = retryRestTemplate.postForEntity(uri, entity);
watch.stop("EncryptionServiceImpl.encryptBatch");
return gson.fromJson(response.getBody(), new TypeToken<HashMap<String, Object>>() {
List<T> results = gson.fromJson(response.getBody(), new TypeToken<List<T>>() {
}.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! ");
Expand All @@ -131,26 +127,18 @@ public Map<String, Object> encrypt(Map<String, Object> propertyValue) throws Enc
}
}

/** decrypts the input which is in Map format
* @param propertyValue - input is in format Map<String, Object>
* @return Map<String, Object>
* @throws EncryptionException
*/
@Override
public Map<String, Object> decrypt(Map<String, Object> propertyValue) throws EncryptionException {
private <T> T doDecrypt(T propertyValue, String uri) throws EncryptionException {
logger.debug("decrypt starts with value {}", propertyValue);
Map<String, Object> map = new HashMap<>();
map.put("value", propertyValue);

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(gson.toJson(map), headers);
HttpEntity<String> entity = new HttpEntity<>(gson.toJson(propertyValue), headers);

try {
watch.start("EncryptionServiceImpl.decryptBatch");
ResponseEntity<String> response = retryRestTemplate.postForEntity(decryptionBatchUri,entity);
ResponseEntity<String> response = retryRestTemplate.postForEntity(uri, entity);
watch.stop("EncryptionServiceImpl.decryptBatch");
return gson.fromJson(response.getBody(), new TypeToken<HashMap<String, Object>>() {
return gson.fromJson(response.getBody(), new TypeToken<T>() {
}.getType());
} catch (ResourceAccessException e) {
logger.error("Exception while connecting decryption service : {}", ExceptionUtils.getStackTrace(e));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
Loading
Loading