From 5e2ba7e374660c696d12e4c5151da0c7666bb598 Mon Sep 17 00:00:00 2001 From: Markus Sabadello Date: Sun, 18 May 2025 23:16:21 +0200 Subject: [PATCH] feat: Add verifier functionality --- pom.xml | 27 ++ .../verifier/VerifyingException.java | 33 +++ .../verifier/checker/AbstractChecker.java | 77 +++++ .../verifier/checker/Checker.java | 9 + .../checker/ExpirationDateChecker.java | 33 +++ .../verifier/checker/IssuanceDateChecker.java | 33 +++ .../verifier/checker/JsonLDChecker.java | 24 ++ .../verifier/checker/NotarizationChecker.java | 103 +++++++ .../verifier/checker/ProofChecker.java | 198 +++++++++++++ .../verifier/checker/RevocationChecker.java | 107 +++++++ .../VerifiableCredentialProofChecker.java | 11 + ...VerifiableCredentialValidationChecker.java | 35 +++ .../VerifiablePresentationProofChecker.java | 11 + ...rifiablePresentationValidationChecker.java | 35 +++ .../verifier/request/VerifyRequest.java | 116 ++++++++ .../verifier/result/VerifyResult.java | 274 ++++++++++++++++++ 16 files changed, 1126 insertions(+) create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/VerifyingException.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/AbstractChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/Checker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/ExpirationDateChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/IssuanceDateChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/JsonLDChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/NotarizationChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/ProofChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/RevocationChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiableCredentialProofChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiableCredentialValidationChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiablePresentationProofChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiablePresentationValidationChecker.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/request/VerifyRequest.java create mode 100644 src/main/java/com/danubetech/verifiablecredentials/verifier/result/VerifyResult.java diff --git a/pom.xml b/pom.xml index ef68e43..8a33ecd 100644 --- a/pom.xml +++ b/pom.xml @@ -208,11 +208,38 @@ jsonld-common-java 1.14.0 + + decentralized-identity + uni-resolver-local + 0.30.0 + true + + + commons-logging + commons-logging + + + com.danubetech data-integrity-java 1.17.0 + + com.danubetech + danubetech-common-java + 1.29.0 + + + com.danubetech + notarization-service-client + 0.1.0 + + + com.danubetech + revocation-service-client + 0.2.0 + com.nimbusds nimbus-jose-jwt diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/VerifyingException.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/VerifyingException.java new file mode 100644 index 0000000..8f98af7 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/VerifyingException.java @@ -0,0 +1,33 @@ +package com.danubetech.verifiablecredentials.verifier; + +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; + +public class VerifyingException extends Exception { + + private VerifyResult verifyResult; + + public VerifyingException(String message) { + super(message); + } + + public VerifyingException(String message, Throwable ex) { + super(message, ex); + } + + public VerifyingException(VerifyResult verifyResult) { + this(verifyResult.getErrorMessage() == null ? "(unknown error)" : verifyResult.getErrorMessage()); + this.verifyResult = verifyResult; + } + + /* + * Getters and setters + */ + + public VerifyResult getVerifyResult() { + return verifyResult; + } + + public void setVerifyResult(VerifyResult verifyResult) { + this.verifyResult = verifyResult; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/AbstractChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/AbstractChecker.java new file mode 100644 index 0000000..ebdc430 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/AbstractChecker.java @@ -0,0 +1,77 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedHashMap; +import java.util.Map; + +public abstract class AbstractChecker implements Checker { + + private static Logger log = LoggerFactory.getLogger(AbstractChecker.class); + + private final String checkName; + + protected AbstractChecker(String checkName) { + this.checkName = checkName; + } + + protected abstract R checkInternal(I i, VerifyResult verifyResult) throws VerifyingException; + + @Override + public final R check(I i, VerifyResult verifyResult) { + + try { + + R r = this.checkInternal(i, verifyResult); + if (log.isDebugEnabled()) log.debug("Result for " + this.getClass().getSimpleName() + ": " + (r == null ? null : r.getClass().getSimpleName())); + if (r != null) this.getCheckMetadata(verifyResult); + return r; + } catch (VerifyingException ex) { + + String message = ex.getMessage() != null ? ex.getMessage() : ex.getClass().getSimpleName(); + if (log.isWarnEnabled()) log.warn("Verifying exception for " + this.getClass().getSimpleName() + ": " + message, ex); + this.getCheckMetadata(verifyResult); + this.addProblem(verifyResult, message); + return null; + } catch (Exception ex) { + + String message = ex.getMessage() != null ? ex.getMessage() : ex.getClass().getSimpleName(); + if (log.isWarnEnabled()) log.warn("Exception for " + this.getClass().getSimpleName() + ": " + message, ex); + this.getCheckMetadata(verifyResult); + this.addProblem(verifyResult, message); + return null; + } + } + + protected final Map getCheckMetadata(VerifyResult verifyResult) { + return verifyResult.getCheckMetadata(this.getCheckName()); + } + + protected final void addProblem(VerifyResult verifyResult, String message, Map problemMetadata) { + if (problemMetadata == null) problemMetadata = new LinkedHashMap<>(); + problemMetadata.put("check", this.getCheckName()); + verifyResult.addProblem(message, problemMetadata); + } + + protected final void addProblem(VerifyResult verifyResult, String message) { + this.addProblem(verifyResult, message, null); + } + + protected final void addWarning(VerifyResult verifyResult, String message, Map warningMetadata) { + if (warningMetadata == null) warningMetadata = new LinkedHashMap<>(); + warningMetadata.put("check", this.getCheckName()); + verifyResult.addWarning(message, warningMetadata); + } + + protected final void addWarning(VerifyResult verifyResult, String message) { + this.addWarning(verifyResult, message, null); + } + + @Override + public String getCheckName() { + return this.checkName; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/Checker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/Checker.java new file mode 100644 index 0000000..58804e8 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/Checker.java @@ -0,0 +1,9 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; + +public interface Checker { + + public R check(I i, VerifyResult verifyResult); + public String getCheckName(); +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/ExpirationDateChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/ExpirationDateChecker.java new file mode 100644 index 0000000..0ced526 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/ExpirationDateChecker.java @@ -0,0 +1,33 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.verifiablecredentials.VerifiableCredential; +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +public class ExpirationDateChecker extends AbstractChecker implements Checker { + + private static Logger log = LoggerFactory.getLogger(ExpirationDateChecker.class); + + public ExpirationDateChecker() { + super("expiration-date"); + } + + @Override + public Void checkInternal(VerifiableCredential verifiableCredential, VerifyResult verifyResult) throws VerifyingException { + + if (verifiableCredential.getExpirationDate() == null) return null; + + // check issuance date + + if (verifiableCredential.getExpirationDate().before(new Date())) throw new VerifyingException("Expiration date is in the past."); + this.getCheckMetadata(verifyResult).put("verified", Boolean.TRUE); + + // done + + return null; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/IssuanceDateChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/IssuanceDateChecker.java new file mode 100644 index 0000000..aeafc1e --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/IssuanceDateChecker.java @@ -0,0 +1,33 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.verifiablecredentials.VerifiableCredential; +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +public class IssuanceDateChecker extends AbstractChecker implements Checker { + + private static Logger log = LoggerFactory.getLogger(IssuanceDateChecker.class); + + public IssuanceDateChecker() { + super("issuance-date"); + } + + @Override + public Void checkInternal(VerifiableCredential verifiableCredential, VerifyResult verifyResult) throws VerifyingException { + + if (verifiableCredential.getIssuanceDate() == null) return null; + + // check issuance date + + if (verifiableCredential.getIssuanceDate().after(new Date())) throw new VerifyingException("Issuance date is in the future."); + this.getCheckMetadata(verifyResult).put("verified", Boolean.TRUE); + + // done + + return null; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/JsonLDChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/JsonLDChecker.java new file mode 100644 index 0000000..2c2b378 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/JsonLDChecker.java @@ -0,0 +1,24 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; +import foundation.identity.jsonld.JsonLDObject; +import foundation.identity.jsonld.validation.Validation; + +public class JsonLDChecker extends AbstractChecker implements Checker { + + public JsonLDChecker() { + + super("jsonld"); + } + + @Override + public Void checkInternal(JsonLDObject jsonLdObject, VerifyResult verifyResult) throws VerifyingException { + + if (jsonLdObject == null) return null; + + Validation.validate(jsonLdObject); + + return null; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/NotarizationChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/NotarizationChecker.java new file mode 100644 index 0000000..7aa959e --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/NotarizationChecker.java @@ -0,0 +1,103 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.dataintegrity.DataIntegrityProof; +import com.danubetech.dataintegrity.canonicalizer.RDFC10SHA256Canonicalizer; +import com.danubetech.notarizationservice.client.ApiException; +import com.danubetech.notarizationservice.client.NotarizationServiceClient; +import com.danubetech.notarizationservice.client.swagger.model.BitcoinVerifyResponse; +import com.danubetech.verifiablecredentials.VerifiableCredential; +import com.danubetech.verifiablecredentials.proof.BlockchainHashProof2020; +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; +import foundation.identity.jsonld.JsonLDException; +import foundation.identity.jsonld.JsonLDObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Map; + +public class NotarizationChecker extends AbstractChecker implements Checker { + + private static Logger log = LoggerFactory.getLogger(NotarizationChecker.class); + + private NotarizationServiceClient notarizationServiceClient; + + public NotarizationChecker(NotarizationServiceClient notarizationServiceClient) { + + super("notarization"); + + this.notarizationServiceClient = notarizationServiceClient; + } + + @Override + public Void checkInternal(VerifiableCredential verifiableCredential, VerifyResult verifyResult) throws VerifyingException { + + Map jsonObject = verifiableCredential.getJsonObject(); + + // obtain the normalizedDocument document + + String normalizedDocument; + + try { + + JsonLDObject jsonLdDocumentWithoutProof = JsonLDObject.builder().base(verifiableCredential).build(); + jsonLdDocumentWithoutProof.setDocumentLoader(verifiableCredential.getDocumentLoader()); + DataIntegrityProof.removeFromJsonLdObject(jsonLdDocumentWithoutProof); + normalizedDocument = RDFC10SHA256Canonicalizer.getInstance().canonicalize(jsonLdDocumentWithoutProof); + } catch (JsonLDException | IOException ex) { + + throw new VerifyingException("Normalization problem: " + ex.getMessage(), ex); + } + + // check the notarization + + BlockchainHashProof2020 ldProof = BlockchainHashProof2020.getFromJsonLDObject(verifiableCredential); + if (log.isDebugEnabled()) log.debug("Found proof: " + ldProof); + if (ldProof == null) return null; + + String proofValue = ldProof.getProofValue(); + String[] proofValueParts = proofValue.split(" "); + String txId = proofValueParts[0]; + String signature = proofValueParts[1]; + + BitcoinVerifyResponse bitcoinVerifyResponse; + + try { + + bitcoinVerifyResponse = this.getNotarizationServiceClient().verifyUsingPOST("SHA-256", null, null, Boolean.FALSE, signature, txId, normalizedDocument); + if (log.isDebugEnabled()) log.debug("Notarization service response: " + bitcoinVerifyResponse); + } catch (ApiException ex) { + + throw new VerifyingException("Cannot check notarization: " + ex.getCode() + " - " + ex.getResponseBody(), ex); + } + + if (Boolean.TRUE != bitcoinVerifyResponse.getVerified()) { + + throw new VerifyingException("Credential notarization cannot be verified. txId: " + bitcoinVerifyResponse.getTxId() + ", notarizationBlockHeight: " + bitcoinVerifyResponse.getNotarizationBlockHeight() + ", currentBlockHeight: " + bitcoinVerifyResponse.getCurrentBlockHeight()); + } + + // CHECK METADATA + + this.getCheckMetadata(verifyResult).put("txId", bitcoinVerifyResponse.getTxId()); + this.getCheckMetadata(verifyResult).put("notarizationBlockHeight", bitcoinVerifyResponse.getNotarizationBlockHeight()); + this.getCheckMetadata(verifyResult).put("currentBlockHeight", bitcoinVerifyResponse.getCurrentBlockHeight()); + this.getCheckMetadata(verifyResult).put("verified", bitcoinVerifyResponse.getVerified()); + + // done + + return null; + } + + /* + * Getters and setters + */ + + public NotarizationServiceClient getNotarizationServiceClient() { + return this.notarizationServiceClient; + } + + public void setNotarizationServiceClient(NotarizationServiceClient notarizationServiceClient) { + this.notarizationServiceClient = notarizationServiceClient; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/ProofChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/ProofChecker.java new file mode 100644 index 0000000..73908ab --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/ProofChecker.java @@ -0,0 +1,198 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.dataintegrity.DataIntegrityProof; +import com.danubetech.dataintegrity.suites.DataIntegritySuite; +import com.danubetech.dataintegrity.suites.DataIntegritySuites; +import com.danubetech.dataintegrity.verifier.LdVerifier; +import com.danubetech.dataintegrity.verifier.LdVerifierRegistry; +import com.danubetech.did.util.VerificationMethodUtil; +import com.danubetech.keyformats.JWK_to_PublicKey; +import com.danubetech.keyformats.crypto.PublicKeyVerifier; +import com.danubetech.keyformats.crypto.PublicKeyVerifierFactory; +import com.danubetech.keyformats.jose.JWK; +import com.danubetech.keyformats.jose.KeyTypeName; +import com.danubetech.uniresolver.local.extensions.impl.FragmentDIDExtension; +import com.danubetech.uniresolver.local.extensions.impl.FragmentJSONExtension; +import com.danubetech.uniresolver.local.extensions.impl.FragmentJSONLDExtension; +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; +import foundation.identity.did.VerificationMethod; +import foundation.identity.did.representations.Representations; +import foundation.identity.jsonld.JsonLDException; +import foundation.identity.jsonld.JsonLDObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uniresolver.DereferencingException; +import uniresolver.ResolutionException; +import uniresolver.UniResolver; +import uniresolver.local.LocalUniDereferencer; +import uniresolver.result.DereferenceResult; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.Map; + +public abstract class ProofChecker extends AbstractChecker implements Checker { + + private static Logger log = LoggerFactory.getLogger(ProofChecker.class); + + private UniResolver uniResolver; + + public ProofChecker(String checkName, UniResolver uniResolver) { + super(checkName); + this.uniResolver = uniResolver; + } + + @Override + public Void checkInternal(JsonLDObject jsonLDObject, VerifyResult verifyResult) throws VerifyingException { + + // look up proof from Verifiable Credential + + DataIntegrityProof dataIntegrityProof = DataIntegrityProof.getFromJsonLDObject(jsonLDObject); + if (dataIntegrityProof == null) throw new VerifyingException("No proof."); + this.getCheckMetadata(verifyResult).put("proofType", dataIntegrityProof.getType()); + + // look up cryptosuite + + String cryptosuite = dataIntegrityProof.getCryptosuite(); + this.getCheckMetadata(verifyResult).put("cryptosuite", cryptosuite); + + // look up verification method URI + + URI verificationMethodUri = dataIntegrityProof.getVerificationMethod(); + if (verificationMethodUri == null) throw new VerifyingException("No verification method URI for proof " + dataIntegrityProof); + this.getCheckMetadata(verifyResult).put("verificationMethod", verificationMethodUri); + + // dereference verification method + + LocalUniDereferencer localUniDereferencer = new LocalUniDereferencer(); + localUniDereferencer.setUniResolver(this.getUniResolver()); + localUniDereferencer.getExtensions().add(new FragmentDIDExtension()); + localUniDereferencer.getExtensions().add(new FragmentJSONLDExtension()); + localUniDereferencer.getExtensions().add(new FragmentJSONExtension()); + + VerificationMethod verificationMethod = null; + + try { + DereferenceResult dereferenceResult = localUniDereferencer.dereference(verificationMethodUri.toString(), Map.of("accept", Representations.DEFAULT_MEDIA_TYPE)); + if (dereferenceResult.getContent() == null || dereferenceResult.getContent().length < 1) { + throw new VerifyingException("No dereferencing result for " + verificationMethodUri); + } + + verificationMethod = VerificationMethod.fromJson(new InputStreamReader(new ByteArrayInputStream(dereferenceResult.getContent()), StandardCharsets.UTF_8)); + if (log.isDebugEnabled()) log.debug("Found verification method for " + verificationMethodUri + ": " + verificationMethod.toJson()); + this.getCheckMetadata(verifyResult).put("verificationMethodType", verificationMethod.getType()); + } catch (ResolutionException | DereferencingException ex) { + throw new VerifyingException("Cannot dereference URI " + verificationMethodUri + ": " + ex.getMessage(), ex); + } + + // prepare LD verifier + + LdVerifier ldVerifier; + + KeyTypeName verificationMethodKeyTypeName = findKeyTypeNameForVerificationMethod(verificationMethod); + this.getCheckMetadata(verifyResult).put("verificationMethodKeyType", verificationMethodKeyTypeName); + + DataIntegritySuite dataIntegritySuite = findDataIntegritySuiteByLdProof(dataIntegrityProof); + this.getCheckMetadata(verifyResult).put("dataIntegritySuite", dataIntegritySuite.getTerm()); + + String jwsAlgorithm = findJwsAlgorithmByDataIntegritySuiteAndVerificationMethodKeyTypeName(dataIntegritySuite, verificationMethodKeyTypeName, cryptosuite); + this.getCheckMetadata(verifyResult).put("jwsAlgorithm", jwsAlgorithm); + + JWK verificationMethodJwk = findVerificationMethodJwkByVerificationMethod(verificationMethod); + this.getCheckMetadata(verifyResult).put("verificationMethodJwk", verificationMethodJwk.toMap()); + + Object publicKey = findPublicKeyByVerificationMethodJwk(verificationMethodJwk); + + PublicKeyVerifier publicKeyVerifier = findPublicKeyVerifierForVerificationMethodKeyTypeNameAndJwsAlgorithmAndPublicKey(verificationMethodKeyTypeName, jwsAlgorithm, publicKey); + + ldVerifier = findLdVerifierForDataIntegritySuite(dataIntegritySuite); + ldVerifier.setVerifier(publicKeyVerifier); + + // verify + + try { + + if (! ldVerifier.verify(jsonLDObject)) throw new VerifyingException("Cannot verify signature."); + if (log.isInfoEnabled()) log.info("Verified signature: " + dataIntegrityProof); + } catch (JsonLDException | IOException ex) { + + throw new VerifyingException("Cannot verify signature (JSON-LD problem): " + ex.getMessage(), ex); + } catch (GeneralSecurityException ex) { + + throw new VerifyingException("Cannot verify signature (security problem): " + ex.getMessage(), ex); + } + + // done + + return null; + } + + /* + * Helper methods + */ + + public static KeyTypeName findKeyTypeNameForVerificationMethod(VerificationMethod verificationMethod) throws VerifyingException { + KeyTypeName verificationMethodKeyTypeName = verificationMethod == null ? null : VerificationMethodUtil.getKeyTypeName(verificationMethod); + if (verificationMethodKeyTypeName == null) throw new VerifyingException("No key type name for verification method " + (verificationMethod == null ? null : verificationMethod.getId())); + if (log.isDebugEnabled()) log.debug("For verification method " + verificationMethod.getId() + " found key type name: " + verificationMethodKeyTypeName); + return verificationMethodKeyTypeName; + } + + public static DataIntegritySuite findDataIntegritySuiteByLdProof(DataIntegrityProof dataIntegrityProof) throws VerifyingException { + DataIntegritySuite dataIntegritySuite = dataIntegrityProof == null ? null : DataIntegritySuites.findDataIntegritySuiteByTerm(dataIntegrityProof.getType()); + if (dataIntegritySuite == null) throw new VerifyingException("No data integrity suite for proof type " + (dataIntegrityProof == null ? null : dataIntegrityProof.getType())); + if (log.isDebugEnabled()) log.debug("For proof type " + dataIntegrityProof.getType() + " found data integrity suite " + dataIntegritySuite.getId()); + return dataIntegritySuite; + } + + public static String findJwsAlgorithmByDataIntegritySuiteAndVerificationMethodKeyTypeName(DataIntegritySuite dataIntegritySuite, KeyTypeName verificationMethodKeyTypeName, String cryptosuite) throws VerifyingException { + String jwsAlgorithm = dataIntegritySuite == null ? null : dataIntegritySuite.findDefaultJwsAlgorithmForKeyTypeName(verificationMethodKeyTypeName, cryptosuite); + if (jwsAlgorithm == null) throw new VerifyingException("No JWS algorithm for data integrity suite " + (dataIntegritySuite == null ? null : dataIntegritySuite.getId()) + " and key type name " + verificationMethodKeyTypeName); + if (log.isDebugEnabled()) log.debug("For data integrity suite " + (dataIntegritySuite == null ? null : dataIntegritySuite.getId()) + " and key type name " + verificationMethodKeyTypeName + " found JWS algorithm: " + jwsAlgorithm); + return jwsAlgorithm; + } + + public static JWK findVerificationMethodJwkByVerificationMethod(VerificationMethod verificationMethod) throws VerifyingException { + JWK verificationMethodJwk = verificationMethod == null ? null : VerificationMethodUtil.getVerificationMethodJwk(verificationMethod); + if (verificationMethodJwk == null) throw new VerifyingException("No verification method JWK for verification method " + (verificationMethod == null ? null : verificationMethod.getId())); + if (log.isDebugEnabled()) log.debug("For verification method " + verificationMethod.getId() + " found verification method JWK: " + verificationMethodJwk); + return verificationMethodJwk; + } + + public static Object findPublicKeyByVerificationMethodJwk(JWK verificationMethodJwk) throws VerifyingException { + Object publicKey = verificationMethodJwk == null ? null : JWK_to_PublicKey.JWK_to_anyPublicKey(verificationMethodJwk); + if (publicKey == null) throw new VerifyingException("No public key for verification method JWK " + verificationMethodJwk); + if (log.isDebugEnabled()) log.debug("For verification method JWK " + verificationMethodJwk + " found public key " + publicKey.getClass()); + return publicKey; + } + + public static PublicKeyVerifier findPublicKeyVerifierForVerificationMethodKeyTypeNameAndJwsAlgorithmAndPublicKey(KeyTypeName verificationMethodKeyTypeName, String jwsAlgorithm, Object publicKey) { + PublicKeyVerifier publicKeyVerifier = verificationMethodKeyTypeName == null ? null : PublicKeyVerifierFactory.publicKeyVerifierForKey(verificationMethodKeyTypeName, jwsAlgorithm, publicKey); + if (log.isDebugEnabled()) log.debug("For key type name " + verificationMethodKeyTypeName + " and JWS algorithm " + jwsAlgorithm + " and public key " + (publicKey == null ? null : publicKey.getClass()) + " found public key verifier " + (publicKeyVerifier == null ? null : publicKeyVerifier.getClass())); + return publicKeyVerifier; + } + + public static LdVerifier findLdVerifierForDataIntegritySuite(DataIntegritySuite dataIntegritySuite) throws VerifyingException { + LdVerifier ldVerifier = dataIntegritySuite == null ? null : LdVerifierRegistry.getLdVerifierByDataIntegritySuite(dataIntegritySuite); + if (ldVerifier == null) throw new VerifyingException("No LD verifier for data integrity suite " + (dataIntegritySuite == null ? null : dataIntegritySuite.getId())); + if (log.isDebugEnabled()) log.debug("For data integrity suite " + dataIntegritySuite.getId() + " found LD verifier " + ldVerifier.getClass()); + return ldVerifier; + } + + /* + * Getters and setters + */ + + public UniResolver getUniResolver() { + return uniResolver; + } + + public void setUniResolver(UniResolver uniResolver) { + this.uniResolver = uniResolver; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/RevocationChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/RevocationChecker.java new file mode 100644 index 0000000..6c62551 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/RevocationChecker.java @@ -0,0 +1,107 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.revocationservice.client.ApiException; +import com.danubetech.revocationservice.client.RevocationServiceClient; +import com.danubetech.revocationservice.client.openapi.model.CheckRequest; +import com.danubetech.revocationservice.client.openapi.model.CheckResponse; +import com.danubetech.verifiablecredentials.VerifiableCredential; +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RevocationChecker extends AbstractChecker implements Checker { + + private static Logger log = LoggerFactory.getLogger(RevocationChecker.class); + + private RevocationServiceClient revocationServiceClient; + + public RevocationChecker(RevocationServiceClient revocationServiceClient) { + super("credential-status"); + this.revocationServiceClient = revocationServiceClient; + } + + @Override + public Void checkInternal(VerifiableCredential verifiableCredential, VerifyResult verifyResult) throws VerifyingException { + + // check revocation + + CheckRequest checkRequest = new CheckRequest(); + checkRequest.setCredential(verifiableCredential); + + CheckResponse checkResponse; + + try { + + checkResponse = this.getRevocationServiceClient().check("jsonld", checkRequest); + } catch (ApiException ex) { + + throw new VerifyingException("Cannot check revocation: " + ex.getCode() + " - " + ex.getResponseBody(), ex); + } + if (checkResponse == null || checkResponse.getRevoked() == null) throw new VerifyingException("Revocation Check Failed: " + checkResponse ); + if (! Boolean.FALSE.equals(checkResponse.getRevoked())) throw new VerifyingException("Credential has been revoked"); + + this.getCheckMetadata(verifyResult).put("revoked", checkResponse.getRevoked()); + this.getCheckMetadata(verifyResult).put("metadata", checkResponse.getMetadata()); + + return null; + +/* // check the revocation + + RevocationQuery2020Status ldCredentialStatus = RevocationQuery2020Status.getFromJsonLDObject(verifiableCredential); + if (log.isDebugEnabled()) log.debug("Found credential status: " + ldCredentialStatus); + if (ldCredentialStatus == null) return null; + + String credentialReference = ldCredentialStatus.getCredentialReference(); + String revocationService = ldCredentialStatus.getRevocationService(); + + Revocation revocation; + + try { + + String revocationServiceBasePath = revocationService.substring(0, revocationService.lastIndexOf("/")); + if (log.isDebugEnabled()) log.debug("Revocation service base path: " + revocationServiceBasePath); + RevocationServiceClient revocationServiceClient = RevocationServiceClient.create(revocationServiceBasePath); + + revocation = revocationServiceClient.getRevocation(UUID.fromString(credentialReference)); + if (log.isDebugEnabled()) log.debug("Revocation service response: " + revocation); + } catch (ApiException ex) { + + if (ex.getCode() == 404) { + + revocation = null; + } else { + + String exMessage = ex.getCode() + ": " + ex.getMessage(); + throw new VerifyingException("Revocation problem: " + exMessage, ex); + } + } + + if (revocation != null) { + + throw new VerifyingException("Credential has been revoked. timestamp: " + revocation.getTimestamp() + ", reason: " + revocation.getReason()); + } + + // CHECK METADATA + + this.getCheckMetadata().put("credentialReference", credentialReference); + + // done + + return null;*/ + } + + /* + * Getters and setters + */ + + public RevocationServiceClient getRevocationServiceClient() { + + return this.revocationServiceClient; + } + + public void setRevocationServiceClient(RevocationServiceClient revocationServiceClient) { + + this.revocationServiceClient = revocationServiceClient; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiableCredentialProofChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiableCredentialProofChecker.java new file mode 100644 index 0000000..f35ea95 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiableCredentialProofChecker.java @@ -0,0 +1,11 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.verifiablecredentials.VerifiableCredential; +import uniresolver.UniResolver; + +public class VerifiableCredentialProofChecker extends ProofChecker { + + public VerifiableCredentialProofChecker(UniResolver uniResolver) { + super("credential-proof", uniResolver); + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiableCredentialValidationChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiableCredentialValidationChecker.java new file mode 100644 index 0000000..666e714 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiableCredentialValidationChecker.java @@ -0,0 +1,35 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.danubetech.verifiablecredentials.VerifiableCredential; +import com.danubetech.verifiablecredentials.validation.Validation; +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; + +import java.util.Map; + +public class VerifiableCredentialValidationChecker extends AbstractChecker, VerifiableCredential> implements Checker, VerifiableCredential> { + + private DocumentLoader documentLoader; + + public VerifiableCredentialValidationChecker(DocumentLoader documentLoader) { + + super("credential-parse"); + + this.documentLoader = documentLoader; + } + + @Override + public VerifiableCredential checkInternal(Map verifiableCredentialJson, VerifyResult verifyResult) throws VerifyingException { + + if (verifiableCredentialJson == null) throw new NullPointerException(); + + VerifiableCredential verifiableCredential = VerifiableCredential.fromJsonObject(verifiableCredentialJson); + verifiableCredential.setDocumentLoader(this.documentLoader); + + Validation.validate(verifiableCredential); + this.getCheckMetadata(verifyResult).put("parsed", Boolean.TRUE); + + return verifiableCredential; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiablePresentationProofChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiablePresentationProofChecker.java new file mode 100644 index 0000000..e01f7e6 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiablePresentationProofChecker.java @@ -0,0 +1,11 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.danubetech.verifiablecredentials.VerifiablePresentation; +import uniresolver.UniResolver; + +public class VerifiablePresentationProofChecker extends ProofChecker { + + public VerifiablePresentationProofChecker(UniResolver uniResolver) { + super("presentation-proof", uniResolver); + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiablePresentationValidationChecker.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiablePresentationValidationChecker.java new file mode 100644 index 0000000..70385c3 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/checker/VerifiablePresentationValidationChecker.java @@ -0,0 +1,35 @@ +package com.danubetech.verifiablecredentials.verifier.checker; + +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.danubetech.verifiablecredentials.VerifiablePresentation; +import com.danubetech.verifiablecredentials.validation.Validation; +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.danubetech.verifiablecredentials.verifier.result.VerifyResult; + +import java.util.Map; + +public class VerifiablePresentationValidationChecker extends AbstractChecker, VerifiablePresentation> implements Checker, VerifiablePresentation> { + + private DocumentLoader documentLoader; + + public VerifiablePresentationValidationChecker(DocumentLoader documentLoader) { + + super("presentation-parse"); + + this.documentLoader = documentLoader; + } + + @Override + public VerifiablePresentation checkInternal(Map verifiablePresentationJson, VerifyResult verifyResult) throws VerifyingException { + + if (verifiablePresentationJson == null) throw new NullPointerException(); + + VerifiablePresentation verifiablePresentation = VerifiablePresentation.fromJsonObject(verifiablePresentationJson); + verifiablePresentation.setDocumentLoader(this.documentLoader); + + Validation.validate(verifiablePresentation); + this.getCheckMetadata(verifyResult).put("parsed", Boolean.TRUE); + + return verifiablePresentation; + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/request/VerifyRequest.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/request/VerifyRequest.java new file mode 100644 index 0000000..dffa0f4 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/request/VerifyRequest.java @@ -0,0 +1,116 @@ +package com.danubetech.verifiablecredentials.verifier.request; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.Reader; +import java.util.LinkedHashMap; +import java.util.Map; + +@JsonPropertyOrder({ "verifiableCredential", "verifiablePresentation", "options" }) +public class VerifyRequest { + + public static final String MIME_TYPE = "application/json"; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @JsonProperty + private Map verifiableCredential; + + @JsonProperty + private Map verifiablePresentation; + + @JsonProperty + private Map options; + + public VerifyRequest() { + } + + public VerifyRequest(Map verifiableCredential, Map verifiablePresentation, Map options) { + this.verifiableCredential = verifiableCredential; + this.verifiablePresentation = verifiablePresentation; + this.options = options; + } + + /* + * Factory methods + */ + + @JsonCreator + public static VerifyRequest build(@JsonProperty(value="verifiableCredential", required=true) Map verifiableCredential, @JsonProperty(value="verifiablePresentation", required=true) Map verifiablePresentation, @JsonProperty(value="options", required=true) Map options) { + return new VerifyRequest(verifiableCredential, verifiablePresentation, options); + } + + @JsonIgnore + public static VerifyRequest build() { + return new VerifyRequest(null, null, new LinkedHashMap()); + } + + /* + * Serialization + */ + + public static VerifyRequest fromJson(String json) throws JsonParseException, JsonMappingException, IOException { + return objectMapper.readValue(json, VerifyRequest.class); + } + + public static VerifyRequest fromJson(Reader reader) throws JsonParseException, JsonMappingException, IOException { + return objectMapper.readValue(reader, VerifyRequest.class); + } + + public Map toMap() { + return objectMapper.convertValue(this, LinkedHashMap.class); + } + + public String toJson() throws JsonProcessingException { + return objectMapper.writeValueAsString(this); + } + + /* + * Getters and setters + */ + + public Map getVerifiableCredential() { + return verifiableCredential; + } + + public void setVerifiableCredential(Map verifiableCredential) { + this.verifiableCredential = verifiableCredential; + } + + public Map getVerifiablePresentation() { + return verifiablePresentation; + } + + public void setVerifiablePresentation(Map verifiablePresentation) { + this.verifiablePresentation = verifiablePresentation; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } + + /* + * Object methods + */ + + @Override + public String toString() { + try { + return this.toJson(); + } catch (JsonProcessingException ex) { + throw new RuntimeException(ex.getMessage(), ex); + } + } +} diff --git a/src/main/java/com/danubetech/verifiablecredentials/verifier/result/VerifyResult.java b/src/main/java/com/danubetech/verifiablecredentials/verifier/result/VerifyResult.java new file mode 100644 index 0000000..0879489 --- /dev/null +++ b/src/main/java/com/danubetech/verifiablecredentials/verifier/result/VerifyResult.java @@ -0,0 +1,274 @@ +package com.danubetech.verifiablecredentials.verifier.result; + +import com.danubetech.verifiablecredentials.verifier.VerifyingException; +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@JsonPropertyOrder({ "verified", "issuer", "subject", "claims", "verifierMetadata", "documentMetadata" }) +public class VerifyResult { + + private static final Logger log = LoggerFactory.getLogger(VerifyResult.class); + + public static final String MIME_TYPE = "application/json"; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @JsonProperty + private Boolean verified; + + @JsonProperty + private String issuer; + + @JsonProperty + private String subject; + + @JsonProperty + private Map claims; + + @JsonProperty + private Map verifierMetadata; + + @JsonProperty + private Map documentMetadata; + + private VerifyResult(Boolean verified, String issuer, String subject, Map claims, Map verifierMetadata, Map documentMetadata) { + this.verified = verified; + this.issuer = issuer; + this.subject = subject; + this.claims = claims; + this.verifierMetadata = verifierMetadata; + this.documentMetadata = documentMetadata; + } + + /* + * Factory methods + */ + + @JsonCreator + public static VerifyResult build(@JsonProperty(value="verified", required=true) Boolean verified, @JsonProperty(value="issuer", required=true) String issuer, @JsonProperty(value="subject", required=true) String subject, @JsonProperty(value="claims", required=true) Map claims, @JsonProperty(value="verifierMetadata", required=true) Map verifierMetadata, @JsonProperty(value="documentMetadata", required=true) Map documentMetadata) { + return new VerifyResult(verified, issuer, subject, claims, verifierMetadata, documentMetadata); + } + + @JsonIgnore + public static VerifyResult build() { + return new VerifyResult(null, null, null, null, new LinkedHashMap<>(), new LinkedHashMap<>()); + } + + public static VerifyResult toErrorResult(String errorMessage) { + VerifyResult verifyResult = VerifyResult.build(); + verifyResult.setVerified(Boolean.FALSE); + verifyResult.addProblem(errorMessage); + if (log.isDebugEnabled()) log.debug("Created error verify result: " + verifyResult); + return verifyResult; + } + + public static VerifyResult toErrorResult(VerifyingException ex) { + if (ex.getVerifyResult() != null) { + return ex.getVerifyResult(); + } + return toErrorResult(ex.getMessage()); + } + + /* + * Serialization + */ + + public static VerifyResult fromJson(String json) throws JsonParseException, JsonMappingException, IOException { + return objectMapper.readValue(json, VerifyResult.class); + } + + public static VerifyResult fromJson(Reader reader) throws JsonParseException, JsonMappingException, IOException { + return objectMapper.readValue(reader, VerifyResult.class); + } + + public Map toMap() { + return objectMapper.convertValue(this, LinkedHashMap.class); + } + + public String toJson() throws JsonProcessingException { + return objectMapper.writeValueAsString(this); + } + + /* + * Check methods + */ + + @JsonIgnore + public Map getChecks() { + return this.getDocumentMetadata() == null ? null : (Map) this.getDocumentMetadata().get("checks"); + } + + public Map getCheckMetadata(String checkName) { + if (this.getDocumentMetadata() == null) this.setDocumentMetadata(new LinkedHashMap<>()); + Map checks = (Map) this.getDocumentMetadata().get("checks"); + if (checks == null) { checks = new LinkedHashMap<>(); this.getDocumentMetadata().put("checks", checks); } + Map checkMetadata = (Map) checks.get(checkName); + if (checkMetadata == null) { checkMetadata = new LinkedHashMap<>(); checks.put(checkName, checkMetadata); } + return checkMetadata; + } + + /* + * Error methods + */ + + @JsonIgnore + public boolean isErrorResult() { + return this.getErrorMessage() != null; + } + + @JsonIgnore + public String getErrorMessage() { + List> problems = this.getProblems(); + if (problems == null) return null; + if (problems.size() == 1) return (String) problems.get(0).get("message"); + return "Multiple problems: " + problems.stream().map((x) -> (String) x.get("message")).collect(Collectors.joining(",")); + } + + /* + * Problem/warning methods + */ + + @JsonIgnore + public boolean hasProblems() { + return this.getProblems() != null && this.getProblems().size() > 0; + } + + @JsonIgnore + public List> getProblems() { + return this.getDocumentMetadata() == null ? null : (List>) this.getDocumentMetadata().get("problems"); + } + + @JsonIgnore + public void addProblem(String message, Map problemMetadata) { + if (message == null) throw new NullPointerException(); + if (this.getDocumentMetadata() == null) this.setDocumentMetadata(new LinkedHashMap<>()); + List> problems = (List>) this.getDocumentMetadata().get("problems"); + if (problems == null) { problems = new ArrayList<>(); this.getDocumentMetadata().put("problems", problems); } + Map problem = new LinkedHashMap<>(); + if (message != null) problem.put("message", message); + if (problemMetadata != null) problem.putAll(problemMetadata); + problems.add(problem); + } + + @JsonIgnore + public void addProblem(String message) { + this.addProblem(message, null); + } + + @JsonIgnore + public boolean hasWarnings() { + return this.getWarnings() != null && this.getWarnings().size() > 0; + } + + @JsonIgnore + public List> getWarnings() { + return this.getDocumentMetadata() == null ? null : (List>) this.getDocumentMetadata().get("warnings"); + } + + @JsonIgnore + public void addWarning(String message, Map warningMetadata) { + if (message == null) throw new NullPointerException(); + if (this.getDocumentMetadata() == null) this.setDocumentMetadata(new LinkedHashMap<>()); + List> warnings = (List>) this.getDocumentMetadata().get("warnings"); + if (warnings == null) { warnings = new ArrayList<>(); this.getDocumentMetadata().put("warnings", warnings); } + Map warning = new LinkedHashMap<>(); + if (message != null) warning.put("message", message); + if (warningMetadata != null) warning.putAll(warningMetadata); + warnings.add(warning); + } + + @JsonIgnore + public void addWarning(String message) { + this.addWarning(message, null); + } + + /* + * Getters and setters + */ + + @JsonGetter + public Boolean getVerified() { + return verified; + } + + @JsonSetter + public void setVerified(Boolean verified) { + this.verified = verified; + } + + @JsonGetter + public String getIssuer() { + return issuer; + } + + @JsonSetter + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + @JsonGetter + public String getSubject() { + return subject; + } + + @JsonSetter + public void setSubject(String subject) { + this.subject = subject; + } + + @JsonGetter + public Map getClaims() { + return claims; + } + + @JsonSetter + public void setClaims(Map claims) { + this.claims = claims; + } + + @JsonGetter + public Map getVerifierMetadata() { + return verifierMetadata; + } + + @JsonSetter + public void setVerifierMetadata(Map verifierMetadata) { + this.verifierMetadata = verifierMetadata; + } + + @JsonGetter + public Map getDocumentMetadata() { + return documentMetadata; + } + + @JsonSetter + public void setDocumentMetadata(Map documentMetadata) { + this.documentMetadata = documentMetadata; + } + + /* + * Object methods + */ + + @Override + public String toString() { + try { + return this.toJson(); + } catch (JsonProcessingException ex) { + return ex.getMessage(); + } + } +}