From 790351c8db790ae274d5d51b09cce2d9ebacf81a Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Tue, 11 Jun 2024 12:49:46 +0530 Subject: [PATCH] fixed verify api in registry and added verify any credential api in credentials --- docker-compose.yml | 1 + .../service/impl/SignatureV2ServiceImpl.java | 71 +++++++++++++++---- .../src/main/resources/application.yml | 1 + .../impl/SignatureV2ServiceImplTest.java | 34 ++++++--- .../src/credentials/credentials.controller.ts | 10 ++- .../src/credentials/credentials.service.ts | 51 ++++++------- .../src/credentials/schema/VC.schema.ts | 4 +- 7 files changed, 118 insertions(+), 54 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2a270d479..90da2ec54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,6 +95,7 @@ services: - signature_v2_get_url=http://credential:3000/credentials/{id} - signature_v2_delete_url=http://credential:3000/credentials/{id} - signature_v2_verify_url=http://credential:3000/credentials/{id}/verify + - signature_v2_verify_any_url=http://credential:3000/credentials/verify - signature_v2_revocation_list_url=http://credential:3000/credentials/revocation-list?issuerId={issuerDid}&page={page}&limit={limit} - signature_v2_schema_health_check_url=http://credential-schema:3333/health - signature_v2_schema_create_url=http://credential-schema:3333/credential-schema diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImpl.java b/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImpl.java index 8daeb36e7..397e74af8 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImpl.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImpl.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.Template; +import com.google.gson.Gson; import dev.sunbirdrc.pojos.ComponentHealthInfo; import dev.sunbirdrc.registry.dao.NotFoundException; import dev.sunbirdrc.registry.exception.SignatureException; @@ -27,13 +28,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; import java.io.IOException; import java.net.URLDecoder; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; @@ -53,6 +52,8 @@ public class SignatureV2ServiceImpl implements SignatureService, ICertificateSer @Value("${signature.v2.deleteCredentialByIdURL}") private String deleteCredentialByIdURL; @Value("${signature.v2.verifyCredentialURL}") + private String verifyCredentialByIdURL; + @Value("${signature.v2.verifyAnyCredentialURL}") private String verifyCredentialURL; @Value("${signature.v2.getRevocationListURL}") private String getRevocationListURL; @@ -73,6 +74,8 @@ public class SignatureV2ServiceImpl implements SignatureService, ICertificateSer private CredentialSchemaService credentialSchemaService; @Autowired private DIDService didService; + @Autowired + private Gson gson; @Override public Object sign(Map propertyValue) throws SignatureException.UnreachableException, SignatureException.CreationException { @@ -89,18 +92,39 @@ public Object sign(Map propertyValue) throws SignatureException. @Override public boolean verify(Object propertyValue) throws SignatureException.UnreachableException, SignatureException.VerificationException { - String credentialId = (String) ((Map) propertyValue).get("credentialId"); - ObjectNode credential = (ObjectNode) ((Map) propertyValue).get("signedCredentials"); - if(credentialId == null || credentialId.isEmpty()) { - credentialId = credential.get("credentialId").asText(); - } - JsonNode resultNode = null; + ObjectNode properties = objectMapper.convertValue(propertyValue, ObjectNode.class); + JsonNode signedCredential = properties.get("signedCredentials"); try { - resultNode = this.verifyCredential(credentialId); + JsonNode resultNode = null; + if(signedCredential.isTextual()) { + resultNode = this.verifyCredentialById(signedCredential.asText()); + } else if(signedCredential.isObject()) { + resultNode = verifyCredential(signedCredential, null); + } + if(resultNode == null) { + throw new RuntimeException("Invalid result while verifying"); + } + AtomicReference verified = new AtomicReference<>(true); + if(resultNode.has("status")) { + verified.set(resultNode.get("status").asText().equals("ISSUED")); + } + String expectedValue = "OK"; + if(resultNode.has("errors")) { + throw new SignatureException.VerificationException(resultNode.asText()); + } + if(resultNode.has("checks")) { + for (JsonNode check : resultNode.get("checks")) { + check.fields().forEachRemaining(field -> { + if(!field.getValue().asText().equalsIgnoreCase(expectedValue)) { + verified.set(false); + } + }); + } + } + return verified.get(); } catch (IOException e) { throw new SignatureException.VerificationException(e.getMessage()); } - return resultNode.get("verified").asBoolean(); } @Override @@ -216,8 +240,27 @@ public ArrayNode revocationList(String issuerDid, Integer page, Integer limit) t return JsonNodeFactory.instance.arrayNode(); } - public JsonNode verifyCredential(String credentialId) throws IOException { - ResponseEntity response = retryRestTemplate.getForEntity(verifyCredentialURL, credentialId); + public JsonNode verifyCredential(Object vc, Object options) { + Map vcMap = objectMapper.convertValue(vc, Map.class); + Map requestMap = new HashMap<>(); + requestMap.put("verifiableCredential", vcMap); + requestMap.put("options", options); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity request = new HttpEntity<>(gson.toJson(requestMap), headers); + try { + ResponseEntity response = retryRestTemplate.postForEntity(verifyCredentialURL, request); + if (response.getStatusCode().is2xxSuccessful()) { + return JSONUtil.convertStringJsonNode(response.getBody()); + } + } catch (RestClientException | IOException e) { + logger.error("Exception while verifying a VC: {}, {}", vc, ExceptionUtils.getStackTrace(e)); + } + return null; + } + + public JsonNode verifyCredentialById(String credentialId) throws IOException { + ResponseEntity response = retryRestTemplate.getForEntity(verifyCredentialByIdURL, credentialId); if (response.getStatusCode().is2xxSuccessful()) { return JSONUtil.convertStringJsonNode(response.getBody()); } diff --git a/java/registry/src/main/resources/application.yml b/java/registry/src/main/resources/application.yml index f919b8887..cff05e9c3 100644 --- a/java/registry/src/main/resources/application.yml +++ b/java/registry/src/main/resources/application.yml @@ -377,6 +377,7 @@ signature: getCredentialByIdURL: ${signature_v2_get_url:http://localhost:3000/credentials/{id}} deleteCredentialByIdURL: ${signature_v2_delete_url:http://localhost:3000/credentials/{id}} verifyCredentialURL: ${signature_v2_verify_url:http://localhost:3000/credentials/{id}/verify} + verifyAnyCredentialURL: ${signature_v2_verify_any_url:http://localhost:3000/credentials/verify} getRevocationListURL: ${signature_v2_revocation_list_url:http://localhost:3000/credentials/revocation-list?issuerId={issuerDid}&page={page}&limit={limit}} schema: author: ${signature_v2_schema_author:Registry} diff --git a/java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImplTest.java b/java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImplTest.java index 8273a6790..58bbbc17f 100644 --- a/java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImplTest.java +++ b/java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImplTest.java @@ -24,6 +24,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.util.ReflectionTestUtils; import java.io.IOException; import java.util.Collections; @@ -113,27 +114,38 @@ public void testSign_Exception() throws Exception { public void testVerify_Success() throws SignatureException.VerificationException, SignatureException.UnreachableException, IOException { // Prepare test data ObjectNode credential = JsonNodeFactory.instance.objectNode(); - credential.put("credentialId", "12345"); + credential.put("signedCredentials", "12345"); ObjectNode result = JsonNodeFactory.instance.objectNode(); - result.put("verified", "true"); - - doReturn(result).when(signatureServiceMock).verifyCredential(any()); - assertTrue(signatureServiceMock.verify(Collections.singletonMap("credentialId", "12345"))); - - result.put("verified", "false"); - assertFalse(signatureServiceMock.verify(Collections.singletonMap("credentialId", "12345"))); + result.put("status", "ISSUED"); + result.set("checks", JsonNodeFactory.instance.arrayNode() + .add(JsonNodeFactory.instance.objectNode() + .put("revoked", "ok") + .put("expired", "ok") + )); + ReflectionTestUtils.setField(signatureServiceMock, "objectMapper", new ObjectMapper()); + doReturn(result).when(signatureServiceMock).verifyCredentialById(any()); + assertTrue(signatureServiceMock.verify(Collections.singletonMap("signedCredentials", "12345"))); + + result.put("status", "REVOKED"); + assertFalse(signatureServiceMock.verify(Collections.singletonMap("signedCredentials", "12345"))); } @Test public void testVerify_Exception() throws Exception { // Prepare test data ObjectNode credential = JsonNodeFactory.instance.objectNode(); - credential.put("credentialId", "12345"); + credential.put("signedCredentials", "12345"); - doThrow(new IOException()).when(signatureServiceMock).verifyCredential(any()); + ObjectNode result = JsonNodeFactory.instance.objectNode(); + result.put("status", "ISSUED"); + result.set("errors", JsonNodeFactory.instance.arrayNode() + .add(JsonNodeFactory.instance.textNode("Exception while fetching the did") + )); + ReflectionTestUtils.setField(signatureServiceMock, "objectMapper", new ObjectMapper()); + doReturn(result).when(signatureServiceMock).verifyCredentialById(any()); try { - signatureServiceMock.verify(Collections.singletonMap("credentialId", "12345")); + signatureServiceMock.verify(Collections.singletonMap("signedCredentials", "12345")); fail("Exception should be thrown"); } catch (Exception e) { assertTrue(true); diff --git a/services/credentials-service/src/credentials/credentials.controller.ts b/services/credentials-service/src/credentials/credentials.controller.ts index c0661c977..879a6efce 100644 --- a/services/credentials-service/src/credentials/credentials.controller.ts +++ b/services/credentials-service/src/credentials/credentials.controller.ts @@ -22,6 +22,7 @@ import { Credential } from 'src/app.interface'; import { GetCredentialsByTagsResponseDTO } from './dto/getCredentialsByTags.dto'; import { GetCredentialByIdResponseDTO } from './dto/getCredentialById.dto'; import { RevocationListDTO } from './dto/revocaiton-list.dto'; +import { VerifyCredentialDTO } from './dto/verify-credential.dto'; @Controller('credentials') export class CredentialsController { @@ -115,8 +116,13 @@ export class CredentialsController { } @Get(':id/verify') - verifyCredential(@Param('id') credId: string) { - return this.credentialsService.verifyCredential(credId); + verifyCredentialById(@Param('id') credId: string) { + return this.credentialsService.verifyCredentialById(credId); + } + + @Post('/verify') + verifyCredential(@Body() verifyRequest: VerifyCredentialDTO) { + return this.credentialsService.verifyCredential(verifyRequest.verifiableCredential); } @Get('revocation-list') diff --git a/services/credentials-service/src/credentials/credentials.service.ts b/services/credentials-service/src/credentials/credentials.service.ts index 7b06d1fc2..92a388da6 100644 --- a/services/credentials-service/src/credentials/credentials.service.ts +++ b/services/credentials-service/src/credentials/credentials.service.ts @@ -146,27 +146,7 @@ export class CredentialsService { } } - async verifyCredential(credId: string) { - // getting the credential from the db - const stored = - (await this.prisma.verifiableCredentials.findUnique({ - where: { - id: credId, - }, - select: { - signed: true, - status: true, - }, - })); - const { signed: credToVerify, status } = (stored || {}) as { signed: Verifiable; status: VCStatus }; - - this.logger.debug('Fetched credntial from db to verify'); - - // invalid request in case credential is not found - if (!credToVerify) { - this.logger.error('Credential not found'); - throw new NotFoundException({ errors: ['Credential not found'] }); - } + async verifyCredential(credToVerify: Verifiable, status?: VCStatus) { try { // calling identity service to verify the issuer DID const issuerId = (credToVerify.issuer?.id || credToVerify.issuer) as string; @@ -174,7 +154,7 @@ export class CredentialsService { issuerId ); const credVerificationMethod = (credToVerify?.proof || {})[Object.keys(credToVerify?.proof || {}) - .find(d => d.indexOf("verificationMethod") > -1)] + .find(d => d.indexOf("verificationMethod") > -1)] // VERIFYING THE JWS const vm = did.verificationMethod?.find(d => (d.id === credVerificationMethod || d.id === credVerificationMethod?.id)); @@ -206,8 +186,7 @@ export class CredentialsService { status: status, checks: [ { - active: 'OK', - revoked: status === VCStatus.REVOKED ? 'NOK' : 'OK', // NOK represents revoked + ...(status && {revoked: status === VCStatus.REVOKED ? 'NOK' : 'OK'}), // NOK represents revoked expired: new Date(credToVerify.expirationDate).getTime() < Date.now() ? 'NOK' @@ -224,6 +203,30 @@ export class CredentialsService { } } + async verifyCredentialById(credId: string) { + // getting the credential from the db + const stored = + (await this.prisma.verifiableCredentials.findUnique({ + where: { + id: credId, + }, + select: { + signed: true, + status: true, + }, + })); + const { signed: credToVerify, status } = (stored || {}) as { signed: Verifiable; status: VCStatus }; + + this.logger.debug('Fetched credntial from db to verify'); + + // invalid request in case credential is not found + if (!credToVerify) { + this.logger.error('Credential not found'); + throw new NotFoundException({ errors: ['Credential not found'] }); + } + return this.verifyCredential(credToVerify, status); + } + async getSuite(verificationMethod: VerificationMethod, signatureType: string) { const supportedSignatures = { "Ed25519Signature2020": ["Ed25519VerificationKey2020", "JsonWebKey2020", "Ed25519VerificationKey2018"], diff --git a/services/credentials-service/src/credentials/schema/VC.schema.ts b/services/credentials-service/src/credentials/schema/VC.schema.ts index fd9122177..648dd6570 100644 --- a/services/credentials-service/src/credentials/schema/VC.schema.ts +++ b/services/credentials-service/src/credentials/schema/VC.schema.ts @@ -2,9 +2,7 @@ export class IssuedVerifiableCredential { '@context': ReadonlyArray; id: string; type: ReadonlyArray; - issuer: { - id: string; - }; + issuer: string | { id: string }; issuanceDate: string; expirationDate: string; credentialSubject: JSON;