Skip to content

Commit c00b5df

Browse files
authored
Merge pull request #331 from holashchand/issue-1023
[BUG]: fixed verify api in registry and added verify any credential api
2 parents f180784 + 790351c commit c00b5df

File tree

7 files changed

+118
-54
lines changed

7 files changed

+118
-54
lines changed

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ services:
9595
- signature_v2_get_url=http://credential:3000/credentials/{id}
9696
- signature_v2_delete_url=http://credential:3000/credentials/{id}
9797
- signature_v2_verify_url=http://credential:3000/credentials/{id}/verify
98+
- signature_v2_verify_any_url=http://credential:3000/credentials/verify
9899
- signature_v2_revocation_list_url=http://credential:3000/credentials/revocation-list?issuerId={issuerDid}&page={page}&limit={limit}
99100
- signature_v2_schema_health_check_url=http://credential-schema:3333/health
100101
- signature_v2_schema_create_url=http://credential-schema:3333/credential-schema

java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImpl.java

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.fasterxml.jackson.databind.node.ObjectNode;
88
import com.github.jknack.handlebars.Handlebars;
99
import com.github.jknack.handlebars.Template;
10+
import com.google.gson.Gson;
1011
import dev.sunbirdrc.pojos.ComponentHealthInfo;
1112
import dev.sunbirdrc.registry.dao.NotFoundException;
1213
import dev.sunbirdrc.registry.exception.SignatureException;
@@ -27,13 +28,11 @@
2728
import org.springframework.http.ResponseEntity;
2829
import org.springframework.stereotype.Component;
2930
import org.springframework.util.StringUtils;
31+
import org.springframework.web.client.RestClientException;
3032

3133
import java.io.IOException;
3234
import java.net.URLDecoder;
33-
import java.util.Collections;
34-
import java.util.LinkedHashMap;
35-
import java.util.Map;
36-
import java.util.Objects;
35+
import java.util.*;
3736
import java.util.concurrent.atomic.AtomicReference;
3837
import java.util.stream.Stream;
3938

@@ -53,6 +52,8 @@ public class SignatureV2ServiceImpl implements SignatureService, ICertificateSer
5352
@Value("${signature.v2.deleteCredentialByIdURL}")
5453
private String deleteCredentialByIdURL;
5554
@Value("${signature.v2.verifyCredentialURL}")
55+
private String verifyCredentialByIdURL;
56+
@Value("${signature.v2.verifyAnyCredentialURL}")
5657
private String verifyCredentialURL;
5758
@Value("${signature.v2.getRevocationListURL}")
5859
private String getRevocationListURL;
@@ -73,6 +74,8 @@ public class SignatureV2ServiceImpl implements SignatureService, ICertificateSer
7374
private CredentialSchemaService credentialSchemaService;
7475
@Autowired
7576
private DIDService didService;
77+
@Autowired
78+
private Gson gson;
7679

7780
@Override
7881
public Object sign(Map<String, Object> propertyValue) throws SignatureException.UnreachableException, SignatureException.CreationException {
@@ -89,18 +92,39 @@ public Object sign(Map<String, Object> propertyValue) throws SignatureException.
8992

9093
@Override
9194
public boolean verify(Object propertyValue) throws SignatureException.UnreachableException, SignatureException.VerificationException {
92-
String credentialId = (String) ((Map<String, Object>) propertyValue).get("credentialId");
93-
ObjectNode credential = (ObjectNode) ((Map<String, Object>) propertyValue).get("signedCredentials");
94-
if(credentialId == null || credentialId.isEmpty()) {
95-
credentialId = credential.get("credentialId").asText();
96-
}
97-
JsonNode resultNode = null;
95+
ObjectNode properties = objectMapper.convertValue(propertyValue, ObjectNode.class);
96+
JsonNode signedCredential = properties.get("signedCredentials");
9897
try {
99-
resultNode = this.verifyCredential(credentialId);
98+
JsonNode resultNode = null;
99+
if(signedCredential.isTextual()) {
100+
resultNode = this.verifyCredentialById(signedCredential.asText());
101+
} else if(signedCredential.isObject()) {
102+
resultNode = verifyCredential(signedCredential, null);
103+
}
104+
if(resultNode == null) {
105+
throw new RuntimeException("Invalid result while verifying");
106+
}
107+
AtomicReference<Boolean> verified = new AtomicReference<>(true);
108+
if(resultNode.has("status")) {
109+
verified.set(resultNode.get("status").asText().equals("ISSUED"));
110+
}
111+
String expectedValue = "OK";
112+
if(resultNode.has("errors")) {
113+
throw new SignatureException.VerificationException(resultNode.asText());
114+
}
115+
if(resultNode.has("checks")) {
116+
for (JsonNode check : resultNode.get("checks")) {
117+
check.fields().forEachRemaining(field -> {
118+
if(!field.getValue().asText().equalsIgnoreCase(expectedValue)) {
119+
verified.set(false);
120+
}
121+
});
122+
}
123+
}
124+
return verified.get();
100125
} catch (IOException e) {
101126
throw new SignatureException.VerificationException(e.getMessage());
102127
}
103-
return resultNode.get("verified").asBoolean();
104128
}
105129

106130
@Override
@@ -219,8 +243,27 @@ public ArrayNode revocationList(String issuerDid, Integer page, Integer limit) t
219243
return JsonNodeFactory.instance.arrayNode();
220244
}
221245

222-
public JsonNode verifyCredential(String credentialId) throws IOException {
223-
ResponseEntity<String> response = retryRestTemplate.getForEntity(verifyCredentialURL, credentialId);
246+
public JsonNode verifyCredential(Object vc, Object options) {
247+
Map vcMap = objectMapper.convertValue(vc, Map.class);
248+
Map<String, Object> requestMap = new HashMap<>();
249+
requestMap.put("verifiableCredential", vcMap);
250+
requestMap.put("options", options);
251+
HttpHeaders headers = new HttpHeaders();
252+
headers.setContentType(MediaType.APPLICATION_JSON);
253+
HttpEntity<String> request = new HttpEntity<>(gson.toJson(requestMap), headers);
254+
try {
255+
ResponseEntity<String> response = retryRestTemplate.postForEntity(verifyCredentialURL, request);
256+
if (response.getStatusCode().is2xxSuccessful()) {
257+
return JSONUtil.convertStringJsonNode(response.getBody());
258+
}
259+
} catch (RestClientException | IOException e) {
260+
logger.error("Exception while verifying a VC: {}, {}", vc, ExceptionUtils.getStackTrace(e));
261+
}
262+
return null;
263+
}
264+
265+
public JsonNode verifyCredentialById(String credentialId) throws IOException {
266+
ResponseEntity<String> response = retryRestTemplate.getForEntity(verifyCredentialByIdURL, credentialId);
224267
if (response.getStatusCode().is2xxSuccessful()) {
225268
return JSONUtil.convertStringJsonNode(response.getBody());
226269
}

java/registry/src/main/resources/application.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ signature:
377377
getCredentialByIdURL: ${signature_v2_get_url:http://localhost:3000/credentials/{id}}
378378
deleteCredentialByIdURL: ${signature_v2_delete_url:http://localhost:3000/credentials/{id}}
379379
verifyCredentialURL: ${signature_v2_verify_url:http://localhost:3000/credentials/{id}/verify}
380+
verifyAnyCredentialURL: ${signature_v2_verify_any_url:http://localhost:3000/credentials/verify}
380381
getRevocationListURL: ${signature_v2_revocation_list_url:http://localhost:3000/credentials/revocation-list?issuerId={issuerDid}&page={page}&limit={limit}}
381382
schema:
382383
author: ${signature_v2_schema_author:Registry}

java/registry/src/test/java/dev/sunbirdrc/registry/service/impl/SignatureV2ServiceImplTest.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.http.ResponseEntity;
2525
import org.springframework.test.context.ActiveProfiles;
2626
import org.springframework.test.context.junit4.SpringRunner;
27+
import org.springframework.test.util.ReflectionTestUtils;
2728

2829
import java.io.IOException;
2930
import java.util.Collections;
@@ -113,27 +114,38 @@ public void testSign_Exception() throws Exception {
113114
public void testVerify_Success() throws SignatureException.VerificationException, SignatureException.UnreachableException, IOException {
114115
// Prepare test data
115116
ObjectNode credential = JsonNodeFactory.instance.objectNode();
116-
credential.put("credentialId", "12345");
117+
credential.put("signedCredentials", "12345");
117118

118119
ObjectNode result = JsonNodeFactory.instance.objectNode();
119-
result.put("verified", "true");
120-
121-
doReturn(result).when(signatureServiceMock).verifyCredential(any());
122-
assertTrue(signatureServiceMock.verify(Collections.singletonMap("credentialId", "12345")));
123-
124-
result.put("verified", "false");
125-
assertFalse(signatureServiceMock.verify(Collections.singletonMap("credentialId", "12345")));
120+
result.put("status", "ISSUED");
121+
result.set("checks", JsonNodeFactory.instance.arrayNode()
122+
.add(JsonNodeFactory.instance.objectNode()
123+
.put("revoked", "ok")
124+
.put("expired", "ok")
125+
));
126+
ReflectionTestUtils.setField(signatureServiceMock, "objectMapper", new ObjectMapper());
127+
doReturn(result).when(signatureServiceMock).verifyCredentialById(any());
128+
assertTrue(signatureServiceMock.verify(Collections.singletonMap("signedCredentials", "12345")));
129+
130+
result.put("status", "REVOKED");
131+
assertFalse(signatureServiceMock.verify(Collections.singletonMap("signedCredentials", "12345")));
126132
}
127133

128134
@Test
129135
public void testVerify_Exception() throws Exception {
130136
// Prepare test data
131137
ObjectNode credential = JsonNodeFactory.instance.objectNode();
132-
credential.put("credentialId", "12345");
138+
credential.put("signedCredentials", "12345");
133139

134-
doThrow(new IOException()).when(signatureServiceMock).verifyCredential(any());
140+
ObjectNode result = JsonNodeFactory.instance.objectNode();
141+
result.put("status", "ISSUED");
142+
result.set("errors", JsonNodeFactory.instance.arrayNode()
143+
.add(JsonNodeFactory.instance.textNode("Exception while fetching the did")
144+
));
145+
ReflectionTestUtils.setField(signatureServiceMock, "objectMapper", new ObjectMapper());
146+
doReturn(result).when(signatureServiceMock).verifyCredentialById(any());
135147
try {
136-
signatureServiceMock.verify(Collections.singletonMap("credentialId", "12345"));
148+
signatureServiceMock.verify(Collections.singletonMap("signedCredentials", "12345"));
137149
fail("Exception should be thrown");
138150
} catch (Exception e) {
139151
assertTrue(true);

services/credentials-service/src/credentials/credentials.controller.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Credential } from 'src/app.interface';
2222
import { GetCredentialsByTagsResponseDTO } from './dto/getCredentialsByTags.dto';
2323
import { GetCredentialByIdResponseDTO } from './dto/getCredentialById.dto';
2424
import { RevocationListDTO } from './dto/revocaiton-list.dto';
25+
import { VerifyCredentialDTO } from './dto/verify-credential.dto';
2526

2627
@Controller('credentials')
2728
export class CredentialsController {
@@ -115,8 +116,13 @@ export class CredentialsController {
115116
}
116117

117118
@Get(':id/verify')
118-
verifyCredential(@Param('id') credId: string) {
119-
return this.credentialsService.verifyCredential(credId);
119+
verifyCredentialById(@Param('id') credId: string) {
120+
return this.credentialsService.verifyCredentialById(credId);
121+
}
122+
123+
@Post('/verify')
124+
verifyCredential(@Body() verifyRequest: VerifyCredentialDTO) {
125+
return this.credentialsService.verifyCredential(verifyRequest.verifiableCredential);
120126
}
121127

122128
@Get('revocation-list')

services/credentials-service/src/credentials/credentials.service.ts

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -146,35 +146,15 @@ export class CredentialsService {
146146
}
147147
}
148148

149-
async verifyCredential(credId: string) {
150-
// getting the credential from the db
151-
const stored =
152-
(await this.prisma.verifiableCredentials.findUnique({
153-
where: {
154-
id: credId,
155-
},
156-
select: {
157-
signed: true,
158-
status: true,
159-
},
160-
}));
161-
const { signed: credToVerify, status } = (stored || {}) as { signed: Verifiable<W3CCredential>; status: VCStatus };
162-
163-
this.logger.debug('Fetched credntial from db to verify');
164-
165-
// invalid request in case credential is not found
166-
if (!credToVerify) {
167-
this.logger.error('Credential not found');
168-
throw new NotFoundException({ errors: ['Credential not found'] });
169-
}
149+
async verifyCredential(credToVerify: Verifiable<W3CCredential>, status?: VCStatus) {
170150
try {
171151
// calling identity service to verify the issuer DID
172152
const issuerId = (credToVerify.issuer?.id || credToVerify.issuer) as string;
173153
const did: DIDDocument = await this.identityUtilsService.resolveDID(
174154
issuerId
175155
);
176156
const credVerificationMethod = (credToVerify?.proof || {})[Object.keys(credToVerify?.proof || {})
177-
.find(d => d.indexOf("verificationMethod") > -1)]
157+
.find(d => d.indexOf("verificationMethod") > -1)]
178158

179159
// VERIFYING THE JWS
180160
const vm = did.verificationMethod?.find(d => (d.id === credVerificationMethod || d.id === credVerificationMethod?.id));
@@ -206,8 +186,7 @@ export class CredentialsService {
206186
status: status,
207187
checks: [
208188
{
209-
active: 'OK',
210-
revoked: status === VCStatus.REVOKED ? 'NOK' : 'OK', // NOK represents revoked
189+
...(status && {revoked: status === VCStatus.REVOKED ? 'NOK' : 'OK'}), // NOK represents revoked
211190
expired:
212191
new Date(credToVerify.expirationDate).getTime() < Date.now()
213192
? 'NOK'
@@ -224,6 +203,30 @@ export class CredentialsService {
224203
}
225204
}
226205

206+
async verifyCredentialById(credId: string) {
207+
// getting the credential from the db
208+
const stored =
209+
(await this.prisma.verifiableCredentials.findUnique({
210+
where: {
211+
id: credId,
212+
},
213+
select: {
214+
signed: true,
215+
status: true,
216+
},
217+
}));
218+
const { signed: credToVerify, status } = (stored || {}) as { signed: Verifiable<W3CCredential>; status: VCStatus };
219+
220+
this.logger.debug('Fetched credntial from db to verify');
221+
222+
// invalid request in case credential is not found
223+
if (!credToVerify) {
224+
this.logger.error('Credential not found');
225+
throw new NotFoundException({ errors: ['Credential not found'] });
226+
}
227+
return this.verifyCredential(credToVerify, status);
228+
}
229+
227230
async getSuite(verificationMethod: VerificationMethod, signatureType: string) {
228231
const supportedSignatures = {
229232
"Ed25519Signature2020": ["Ed25519VerificationKey2020", "JsonWebKey2020", "Ed25519VerificationKey2018"],

services/credentials-service/src/credentials/schema/VC.schema.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ export class IssuedVerifiableCredential {
22
'@context': ReadonlyArray<string>;
33
id: string;
44
type: ReadonlyArray<string>;
5-
issuer: {
6-
id: string;
7-
};
5+
issuer: string | { id: string };
86
issuanceDate: string;
97
expirationDate: string;
108
credentialSubject: JSON;

0 commit comments

Comments
 (0)