From 89de37166035aacaaa2b029bc000af7775a68396 Mon Sep 17 00:00:00 2001 From: Sahil-tarento <140611066+Sahil-tarento@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:28:07 +0530 Subject: [PATCH] HMAC implementation for digiLocker Impl (#505) --- .../common/util/CbExtServerProperties.java | 11 +++ .../org/sunbird/common/util/Constants.java | 1 + .../DigiLockerIntegrationController.java | 11 ++- .../service/DigiLockerIntegrationService.java | 4 +- .../DigiLockerIntegrationServiceImpl.java | 83 +++++++++++++++++-- src/main/resources/application.properties | 3 +- 6 files changed, 99 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/sunbird/common/util/CbExtServerProperties.java b/src/main/java/org/sunbird/common/util/CbExtServerProperties.java index fa5fcad90..24a5425b2 100644 --- a/src/main/java/org/sunbird/common/util/CbExtServerProperties.java +++ b/src/main/java/org/sunbird/common/util/CbExtServerProperties.java @@ -675,6 +675,9 @@ public void setRedisWheeboxKey(String redisWheeboxKey) { @Value("${es.default.result.limit}") private int esDefaultResultLimit; + @Value("${digilocker-api-key}") + private String digiLockerAPIKey; + public boolean qListFromCacheEnabled() { return qListFromCacheEnabled; } @@ -2380,4 +2383,12 @@ public String getSbRoleRead() { public void setSbRoleRead(String sbRoleRead) { this.sbRoleRead = sbRoleRead; } + + public String getDigiLockerAPIKey() { + return digiLockerAPIKey; + } + + public void setDigiLockerAPIKey(String digiLockerAPIKey) { + this.digiLockerAPIKey = digiLockerAPIKey; + } } \ No newline at end of file diff --git a/src/main/java/org/sunbird/common/util/Constants.java b/src/main/java/org/sunbird/common/util/Constants.java index f1b14a3b9..3d65f9870 100644 --- a/src/main/java/org/sunbird/common/util/Constants.java +++ b/src/main/java/org/sunbird/common/util/Constants.java @@ -998,6 +998,7 @@ public class Constants { public static final String MDO_ADMIN = "MDO_ADMIN"; public static final String REPORT_ACCESS_EXPIRY_TABLE = "report_access_expiry"; public static final String REPORT_EXPIRY_DATE = "reportExpiryDate"; + public static final String X_DIGILOCKER_HMAC = "x-digilocker-hmac"; private Constants() { diff --git a/src/main/java/org/sunbird/digilocker/controller/DigiLockerIntegrationController.java b/src/main/java/org/sunbird/digilocker/controller/DigiLockerIntegrationController.java index acf9d8d6a..8d53d55f2 100644 --- a/src/main/java/org/sunbird/digilocker/controller/DigiLockerIntegrationController.java +++ b/src/main/java/org/sunbird/digilocker/controller/DigiLockerIntegrationController.java @@ -4,9 +4,8 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.sunbird.digilocker.model.PullDocRequest; +import org.sunbird.common.util.Constants; import org.sunbird.digilocker.model.PullDocResponse; -import org.sunbird.digilocker.model.PullURIRequest; import org.sunbird.digilocker.model.PullURIResponse; import org.sunbird.digilocker.service.DigiLockerIntegrationService; @@ -17,15 +16,15 @@ public class DigiLockerIntegrationController { DigiLockerIntegrationService digiLockerIntegrationService; @PostMapping(value = "/v1/retrieveURI", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE) - public @ResponseBody ResponseEntity getURIRequestV1(@RequestBody PullURIRequest request) { + public @ResponseBody ResponseEntity getURIRequestV1(@RequestHeader(value= Constants.X_DIGILOCKER_HMAC) String digiLockerHmac, @RequestBody String requestBody) { - PullURIResponse response = digiLockerIntegrationService.generateURIResponse(request); + PullURIResponse response = digiLockerIntegrationService.generateURIResponse(digiLockerHmac, requestBody); return ResponseEntity.ok().body(response); } @PostMapping(value = "/v1/retrieveDoc", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE) - public @ResponseBody ResponseEntity getDocRequestV1(@RequestBody PullDocRequest request) { - PullDocResponse response = digiLockerIntegrationService.generateDocResponse(request); + public @ResponseBody ResponseEntity getDocRequestV1(@RequestHeader(value= Constants.X_DIGILOCKER_HMAC) String digiLockerHmac, @RequestBody String requestBody) { + PullDocResponse response = digiLockerIntegrationService.generateDocResponse(digiLockerHmac, requestBody); return ResponseEntity.ok().body(response); } } diff --git a/src/main/java/org/sunbird/digilocker/service/DigiLockerIntegrationService.java b/src/main/java/org/sunbird/digilocker/service/DigiLockerIntegrationService.java index 3f5cf739a..6d004b1e7 100644 --- a/src/main/java/org/sunbird/digilocker/service/DigiLockerIntegrationService.java +++ b/src/main/java/org/sunbird/digilocker/service/DigiLockerIntegrationService.java @@ -7,7 +7,7 @@ public interface DigiLockerIntegrationService { - PullURIResponse generateURIResponse(PullURIRequest request); + PullURIResponse generateURIResponse(String digiLockerHmac, String requestBody); - PullDocResponse generateDocResponse(PullDocRequest request); + PullDocResponse generateDocResponse(String digiLockerHmac, String requestBody); } diff --git a/src/main/java/org/sunbird/digilocker/service/DigiLockerIntegrationServiceImpl.java b/src/main/java/org/sunbird/digilocker/service/DigiLockerIntegrationServiceImpl.java index f1d92109d..c1d8554aa 100644 --- a/src/main/java/org/sunbird/digilocker/service/DigiLockerIntegrationServiceImpl.java +++ b/src/main/java/org/sunbird/digilocker/service/DigiLockerIntegrationServiceImpl.java @@ -1,6 +1,8 @@ package org.sunbird.digilocker.service; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; @@ -20,10 +22,15 @@ import org.sunbird.digilocker.model.*; import org.sunbird.user.service.UserUtilityService; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; @@ -44,16 +51,33 @@ public class DigiLockerIntegrationServiceImpl implements DigiLockerIntegrationSe @Autowired CbExtServerProperties serverProperties; + @Autowired + ObjectMapper objectMapper; private Logger logger = LoggerFactory.getLogger(getClass().getName()); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy"); @Override - public PullURIResponse generateURIResponse(PullURIRequest request) { + public PullURIResponse generateURIResponse(String digiLockerHmac, String requestBody) { PullURIResponse response = new PullURIResponse(); ResponseStatus responseStatus = response.getResponseStatus(); + responseStatus.setTs(dateFormat.format(new Date())); + if (!validateRequest(digiLockerHmac, requestBody)) { + responseStatus.setStatus("0"); + logger.error("Not able to validate the request for hmac: " + digiLockerHmac + " requestBody:" + requestBody); + return response; + } + PullURIRequest request = null; + try { + objectMapper = new XmlMapper(); + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + request = objectMapper.readValue(requestBody, PullURIRequest.class); + } catch (IOException e) { + logger.error("Not able to process the request requestBody: " + requestBody, e); + responseStatus.setStatus("0"); + return response; + } CertificateAddInfoDTO certificateAddInfoDTO = new CertificateAddInfoDTO(); try { - responseStatus.setTs(dateFormat.format(new Date())); responseStatus.setTxn(request.getTxn()); Map getUserInfo = userUtilityService.getUserDetails(Constants.PHONE, request.getDocDetails().getMobile()); String certificateAccessCode = request.getDocDetails().getCertificateAccessCode(); @@ -167,9 +191,24 @@ public PullURIResponse generateURIResponse(PullURIRequest request) { } @Override - public PullDocResponse generateDocResponse(PullDocRequest request) { + public PullDocResponse generateDocResponse(String digiLockerHmac, String requestBody) { PullDocResponse response = new PullDocResponse(); ResponseStatus responseStatus = response.getResponseStatus(); + if (!validateRequest(digiLockerHmac, requestBody)) { + responseStatus.setStatus("0"); + logger.error("Not able to validate the request for hmac: " + digiLockerHmac + " requestBody:" + requestBody); + return response; + } + PullDocRequest request = null; + try { + objectMapper = new XmlMapper(); + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + request = objectMapper.readValue(requestBody, PullDocRequest.class); + } catch (IOException e) { + logger.error("Not able to process the request requestBody: " + requestBody, e); + responseStatus.setStatus("0"); + return response; + } CertificateAddInfoDTO certificateAddInfoDTO = new CertificateAddInfoDTO(); try { DocResponseDetails docDetails = response.getDocDetails(); @@ -313,8 +352,42 @@ private CertificateInfo addCertificateInfo(CertificateAddInfoDTO certificateAddI return certificateInfo; } - public static byte[] convertObjectToJsonBytes(Object object) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); + public byte[] convertObjectToJsonBytes(Object object) throws Exception { return objectMapper.writeValueAsBytes(object); } + + private boolean validateRequest(String digiLockerHmac, String request) { + logger.debug("The digiLocker Hmac shared: " + digiLockerHmac); + String hmacRequestValue = calculateHMACSHA256(request, serverProperties.getDigiLockerAPIKey()); + logger.debug("The hmacRequestValue is: "+ hmacRequestValue); + if (hmacRequestValue.equals(digiLockerHmac)) { + return true; + } + return false; + } + + private String calculateHMACSHA256(String requestBody, String apiKey) { + try { + logger.debug("The API key is: " + apiKey); + Mac sha256Hmac = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKey = new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + sha256Hmac.init(secretKey); + byte[] hmacBytes = sha256Hmac.doFinal(requestBody.getBytes()); + + // Convert bytes to hexadecimal string + StringBuilder hexString = new StringBuilder(); + for (byte b : hmacBytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + byte[] base64EncodedHmacBytes = Base64.getEncoder().encode(hexString.toString().getBytes(StandardCharsets.UTF_8)); + return new String(base64EncodedHmacBytes); + } catch (NoSuchAlgorithmException ex) { + logger.error("Not able to convert SHA256: ", ex); + } catch (InvalidKeyException e) { + logger.error("Invalid Key for converting: ", e); + } + return ""; + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 94dc47baa..9266e89fe 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -384,7 +384,8 @@ redis.wheebox.key=wheebox_usersMapping cert-registry-service-host=http://cert-registry-service:9000 cert-registry-certificate-download-url=/certs/v2/registry/download/ -digilocker-issuer-id=in.gov.karamyogi +digilocker-issuer-id=karmayogibharat.gov.in +digilocker-api-key=api-key report.property.file.allMdo=ContentCompetencyMapping.csv operational.reports.passwordlength=15