diff --git a/.gitignore b/.gitignore
index f271844..9a6b505 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,4 @@ target/
.setting/
.mvn/
.project/
+*.DS_Store
diff --git a/mock-certify-plugin/pom.xml b/mock-certify-plugin/pom.xml
index 2cb66e4..8bcd249 100644
--- a/mock-certify-plugin/pom.xml
+++ b/mock-certify-plugin/pom.xml
@@ -53,23 +53,27 @@
0.8.11
3.6.3
1.3.0-beta.1
+ 2.0.0
-
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-cbor
+ 2.10.1
+
org.projectlombok
lombok
1.18.30
provided
-
io.mosip.certify
certify-core
0.10.0-SNAPSHOT
provided
-
io.mosip.esignet
esignet-core
@@ -131,6 +135,26 @@
slf4j-api
2.0.12
+
+ org.jetbrains.kotlinx
+ kotlinx-datetime-jvm
+ 0.6.0
+
+
+ com.android.identity
+ identity-credential
+ 20231002
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.10.1
+
+
+ co.nstant.in
+ cbor
+ 0.9
+
@@ -156,6 +180,10 @@
danubetech-maven-public
https://repo.danubetech.com/repository/maven-public/
+
+ google
+ https://maven.google.com/
+
@@ -356,6 +384,70 @@
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+
+
+
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+
+
+
+
+
+ ${maven.compiler.target}
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ default-compile
+ none
+
+
+ default-testCompile
+ none
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
\ No newline at end of file
diff --git a/mock-certify-plugin/src/main/java/io.mosip.certify.mock.integration/service/MockVCIssuancePlugin.java b/mock-certify-plugin/src/main/java/io.mosip.certify.mock.integration/service/MockVCIssuancePlugin.java
index 22b7aef..7163e79 100644
--- a/mock-certify-plugin/src/main/java/io.mosip.certify.mock.integration/service/MockVCIssuancePlugin.java
+++ b/mock-certify-plugin/src/main/java/io.mosip.certify.mock.integration/service/MockVCIssuancePlugin.java
@@ -24,7 +24,6 @@
import io.mosip.certify.api.exception.VCIExchangeException;
import io.mosip.certify.api.spi.VCIssuancePlugin;
import io.mosip.certify.api.util.ErrorConstants;
-import io.mosip.certify.core.dto.ParsedAccessToken;
import io.mosip.certify.core.exception.CertifyException;
import io.mosip.esignet.core.dto.OIDCTransaction;
import org.springframework.beans.factory.annotation.Autowired;
@@ -93,7 +92,13 @@ public class MockVCIssuancePlugin implements VCIssuancePlugin {
@Value("#{${mosip.certify.mock.vciplugin.vc-credential-contexts:{'https://www.w3.org/2018/credentials/v1','https://schema.org/'}}}")
private List vcCredentialContexts;
- private static final String ACCESS_TOKEN_HASH = "accessTokenHash";
+ @Value("${mosip.certify.mock.vciplugin.issuer.key-cert:empty}")
+ private String issuerKeyAndCertificate = null;
+
+ @Value("${mosip.certify.mock.vciplugin.ca.key-cert:empty}")
+ private String caKeyAndCertificate = null;
+
+ private static final String ACCESS_TOKEN_HASH = "accessTokenHash";
public static final String UTC_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
@@ -203,7 +208,7 @@ private String decryptIndividualId(String encryptedIndividualId) {
Cipher cipher = Cipher.getInstance(aesECBTransformation);
byte[] decodedBytes = Base64.getUrlDecoder().decode(encryptedIndividualId);
cipher.init(Cipher.DECRYPT_MODE, getSecretKeyFromHSM());
- return new String(cipher.doFinal(decodedBytes, 0, decodedBytes.length));
+ return new String(cipher.doFinal(decodedBytes, 0, decodedBytes.length));
} catch(Exception e) {
log.error("Error Cipher Operations of provided secret data.", e);
throw new CertifyException(AES_CIPHER_FAILED);
@@ -235,10 +240,52 @@ private static String getUTCDateTime() {
@Override
public VCResult getVerifiableCredential(VCRequestDto vcRequestDto, String holderId,
Map identityDetails) throws VCIExchangeException {
+ String accessTokenHash = identityDetails.get(ACCESS_TOKEN_HASH).toString();
+ String documentNumber;
+ try {
+ documentNumber = getIndividualId(getUserInfoTransaction(accessTokenHash));
+ } catch (Exception e) {
+ log.error("Error getting documentNumber", e);
+ throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED);
+ }
+
+ if(vcRequestDto.getFormat().equals("mso_mdoc")){
+ VCResult vcResult = new VCResult<>();
+ String mdocVc = null;
+ try {
+ mdocVc = new io.mosip.certify.mock.integration.mocks.MdocGenerator().generate(mockDataForMsoMdoc(documentNumber),holderId, caKeyAndCertificate,issuerKeyAndCertificate);
+ } catch (Exception e) {
+ log.error("Exception on mdoc creation", e);
+ throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED);
+ }
+ vcResult.setCredential(mdocVc);
+ vcResult.setFormat("mso_mdoc");
+ return vcResult;
+ }
+ log.error("not implemented the format {}", vcRequestDto);
throw new VCIExchangeException(ErrorConstants.NOT_IMPLEMENTED);
}
+ private Map mockDataForMsoMdoc(String documentNumber) {
+ Map data = new HashMap<>();
+ log.info("Setting up the data for mDoc");
+ //TODO: Populate datetime in real time
+ data.put("issue_date", "2024-01-12");
+ data.put("expiry_date", "2025-01-12");
+ data.put("family_name","Agatha");
+ data.put("given_name","Joseph");
+ data.put("birth_date", "1994-11-06");
+ data.put("issuing_country", "Island");
+ data.put("document_number", documentNumber);
+ data.put("driving_privileges",new HashMap<>(){{
+ put("vehicle_category_code","A");
+ put("issue_date","2023-01-01");
+ put("expiry_date","2043-01-01");
+ }});
+ return data;
+ }
+
public OIDCTransaction getUserInfoTransaction(String accessTokenHash) {
- return cacheManager.getCache(USERINFO_CACHE).get(accessTokenHash, OIDCTransaction.class);
+ return cacheManager.getCache(USERINFO_CACHE).get(accessTokenHash, OIDCTransaction.class);
}
-}
+}
\ No newline at end of file
diff --git a/mock-certify-plugin/src/main/java/io/mosip/certify/mock/integration/mocks/MdocGenerator.kt b/mock-certify-plugin/src/main/java/io/mosip/certify/mock/integration/mocks/MdocGenerator.kt
new file mode 100644
index 0000000..35a3c5f
--- /dev/null
+++ b/mock-certify-plugin/src/main/java/io/mosip/certify/mock/integration/mocks/MdocGenerator.kt
@@ -0,0 +1,119 @@
+package io.mosip.certify.mock.integration.mocks
+
+import co.nstant.`in`.cbor.CborBuilder
+import co.nstant.`in`.cbor.CborEncoder
+import co.nstant.`in`.cbor.model.DataItem
+import com.android.identity.credential.NameSpacedData
+import com.android.identity.internal.Util
+import com.android.identity.mdoc.mso.MobileSecurityObjectGenerator
+import com.android.identity.mdoc.util.MdocUtil
+import com.android.identity.util.Timestamp
+import io.mosip.certify.util.*
+import java.io.ByteArrayOutputStream
+import io.mosip.certify.util.IssuerKeyPairAndCertificate
+import java.util.*
+
+
+class MdocGenerator {
+ companion object {
+ const val NAMESPACE: String = "org.iso.18013.5.1"
+ const val DOCTYPE: String = "$NAMESPACE.mDL"
+ const val DIGEST_ALGORITHM = "SHA-256"
+ const val ECDSA_ALGORITHM = "SHA256withECDSA"
+ const val SEED = 42L
+ }
+
+ fun generate(
+ data: MutableMap,
+ holderId: String,
+ caKeyAndCertificate: String,
+ issuerKeyAndCertificate: String
+ ): String? {
+ val issuerKeyPairAndCertificate: IssuerKeyPairAndCertificate? = readKeypairAndCertificates(
+ caKeyAndCertificate,issuerKeyAndCertificate
+ )
+ if(issuerKeyPairAndCertificate == null) {
+ throw RuntimeException("Unable to load Crypto details")
+ }
+ val devicePublicKey = JwkToKeyConverter().convertToPublicKey(holderId.replace("did:jwk:", ""))
+ val issuerKeypair = issuerKeyPairAndCertificate.issuerKeypair()
+
+ val nameSpacedDataBuilder: NameSpacedData.Builder = NameSpacedData.Builder()
+ data.keys.forEach { key ->
+ nameSpacedDataBuilder.putEntryString(NAMESPACE, key, data[key].toString())
+ }
+ val nameSpacedData: NameSpacedData =
+ nameSpacedDataBuilder
+ .build()
+ val generatedIssuerNameSpaces: MutableMap> =
+ MdocUtil.generateIssuerNameSpaces(nameSpacedData, Random(SEED), 16)
+ val calculateDigestsForNameSpace =
+ MdocUtil.calculateDigestsForNameSpace(NAMESPACE, generatedIssuerNameSpaces, DIGEST_ALGORITHM)
+
+ val mobileSecurityObjectGenerator = MobileSecurityObjectGenerator(DIGEST_ALGORITHM, NAMESPACE, devicePublicKey)
+ mobileSecurityObjectGenerator.addDigestIdsForNamespace(NAMESPACE, calculateDigestsForNameSpace)
+ val expirationTime: Long = kotlinx.datetime.Instant.Companion.DISTANT_FUTURE.toEpochMilliseconds()
+ mobileSecurityObjectGenerator.setValidityInfo(
+ Timestamp.now(),
+ Timestamp.now(),
+ Timestamp.ofEpochMilli(expirationTime),
+ null
+ )
+ val mso: ByteArray = mobileSecurityObjectGenerator.generate()
+
+ val coseSign1Sign: DataItem = Util.coseSign1Sign(
+ issuerKeypair.private,
+ ECDSA_ALGORITHM,
+ mso.copyOf(),
+ null,
+ listOf(issuerKeyPairAndCertificate.caCertificate(), issuerKeyPairAndCertificate.issuerCertificate())
+ )
+
+ return construct(generatedIssuerNameSpaces, coseSign1Sign)
+ }
+
+ @Throws(Exception::class)
+ private fun readKeypairAndCertificates(caKeyAndCertificate: String,issuerKeyAndCertificate: String): IssuerKeyPairAndCertificate? {
+ val pkcS12Reader = PKCS12Reader()
+ val caDetails: KeyPairAndCertificate = pkcS12Reader.extract(caKeyAndCertificate)
+ val issuerDetails: KeyPairAndCertificate = pkcS12Reader.extract(issuerKeyAndCertificate)
+ if (issuerDetails != null && caDetails != null) {
+ return IssuerKeyPairAndCertificate(
+ issuerDetails.keyPair,
+ issuerDetails.certificate,
+ caDetails.certificate
+ )
+ }
+ return null
+ }
+
+ private fun construct(nameSpaces: MutableMap>, issuerAuth: DataItem): String? {
+ val mDoc = MDoc(DOCTYPE, IssuerSigned(nameSpaces, issuerAuth))
+ val cbor = mDoc.toCBOR()
+ return Base64.getUrlEncoder().encodeToString(cbor)
+ }
+}
+
+data class MDoc(val docType: String, val issuerSigned: IssuerSigned) {
+ fun toCBOR(): ByteArray {
+ val byteArrayOutputStream = ByteArrayOutputStream()
+ CborEncoder(byteArrayOutputStream).encode(
+ CborBuilder().addMap()
+ .put("docType", docType)
+ .put(CBORConverter.toDataItem("issuerSigned"), CBORConverter.toDataItem(issuerSigned.toMap()))
+ .end()
+ .build()
+ )
+ return byteArrayOutputStream.toByteArray()
+
+ }
+}
+
+data class IssuerSigned(val nameSpaces: MutableMap>, val issuerAuth: DataItem) {
+ fun toMap(): Map {
+ return buildMap {
+ put("nameSpaces", CBORConverter.toDataItem(nameSpaces))
+ put("issuerAuth", issuerAuth)
+ }
+ }
+}
diff --git a/mock-certify-plugin/src/main/java/io/mosip/certify/util/CBORConverter.kt b/mock-certify-plugin/src/main/java/io/mosip/certify/util/CBORConverter.kt
new file mode 100644
index 0000000..6971aa7
--- /dev/null
+++ b/mock-certify-plugin/src/main/java/io/mosip/certify/util/CBORConverter.kt
@@ -0,0 +1,66 @@
+package io.mosip.certify.util
+
+import co.nstant.`in`.cbor.CborDecoder
+import co.nstant.`in`.cbor.model.*
+import co.nstant.`in`.cbor.model.Map
+import java.io.ByteArrayInputStream
+import kotlin.Any
+import kotlin.Array
+import kotlin.Boolean
+import kotlin.ByteArray
+import kotlin.IllegalArgumentException
+import kotlin.Int
+import kotlin.Long
+import kotlin.String
+
+
+class CBORConverter() {
+
+ companion object {
+ fun toDataItem(value: Any): DataItem {
+ return when (value) {
+ is DataItem -> value
+ is String -> UnicodeString(value)
+ is Int -> UnsignedInteger(value.toLong())
+ is Long -> UnsignedInteger(value)
+ is Boolean -> {
+ if (value) SimpleValue.TRUE else SimpleValue.FALSE
+ }
+
+ is kotlin.collections.Map<*, *> -> {
+ val cborMap = Map()
+ value.forEach { (key, value) ->
+ cborMap.put(UnicodeString(key as String), toDataItem(value!!))
+ }
+ cborMap
+ }
+
+ is List<*> -> {
+ val cborArray = Array()
+ value.forEach { item ->
+ cborArray.add(toDataItem(item!!))
+ }
+ cborArray
+ }
+
+ is Array<*> -> {
+ val cborArray = Array()
+ value.forEach { item ->
+ cborArray.add(toDataItem(item!!))
+ }
+ cborArray
+ }
+
+ is ByteArray -> {
+ val byteArrayInputStream = ByteArrayInputStream(value)
+ val dataItems = CborDecoder(byteArrayInputStream).decode()
+ return dataItems.firstOrNull()!!
+ }
+
+ else -> throw IllegalArgumentException("Unsupported value: $value ${value.javaClass.simpleName}")
+ }
+ }
+ }
+}
+
+
diff --git a/mock-certify-plugin/src/main/java/io/mosip/certify/util/IssuerKeyPairAndCertificate.java b/mock-certify-plugin/src/main/java/io/mosip/certify/util/IssuerKeyPairAndCertificate.java
new file mode 100644
index 0000000..41ddbd1
--- /dev/null
+++ b/mock-certify-plugin/src/main/java/io/mosip/certify/util/IssuerKeyPairAndCertificate.java
@@ -0,0 +1,8 @@
+package io.mosip.certify.util;
+
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+public record IssuerKeyPairAndCertificate(KeyPair issuerKeypair, X509Certificate issuerCertificate,
+ X509Certificate caCertificate) {
+}
diff --git a/mock-certify-plugin/src/main/java/io/mosip/certify/util/JwkToKeyConverter.java b/mock-certify-plugin/src/main/java/io/mosip/certify/util/JwkToKeyConverter.java
new file mode 100644
index 0000000..9af04a6
--- /dev/null
+++ b/mock-certify-plugin/src/main/java/io/mosip/certify/util/JwkToKeyConverter.java
@@ -0,0 +1,27 @@
+package io.mosip.certify.util;
+
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.RSAKey;
+
+import java.nio.charset.StandardCharsets;
+import java.security.PublicKey;
+import java.util.Base64;
+
+
+public class JwkToKeyConverter {
+
+ public PublicKey convertToPublicKey(String encodedData) throws Exception {
+ String jwkJsonString = new String(Base64.getUrlDecoder().decode(encodedData), StandardCharsets.UTF_8);
+ JWK jwk = JWK.parse(jwkJsonString);
+
+ if (jwk instanceof RSAKey) {
+ return ((RSAKey) jwk).toPublicKey();
+ } else if (jwk instanceof ECKey) {
+ return ((ECKey) jwk).toPublicKey();
+ }
+
+ throw new IllegalArgumentException("Unsupported key type");
+ }
+
+}
diff --git a/mock-certify-plugin/src/main/java/io/mosip/certify/util/KeyPairAndCertificate.java b/mock-certify-plugin/src/main/java/io/mosip/certify/util/KeyPairAndCertificate.java
new file mode 100644
index 0000000..c95e10f
--- /dev/null
+++ b/mock-certify-plugin/src/main/java/io/mosip/certify/util/KeyPairAndCertificate.java
@@ -0,0 +1,7 @@
+package io.mosip.certify.util;
+
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+public record KeyPairAndCertificate(KeyPair keyPair, X509Certificate certificate) {
+}
diff --git a/mock-certify-plugin/src/main/java/io/mosip/certify/util/PKCS12Reader.java b/mock-certify-plugin/src/main/java/io/mosip/certify/util/PKCS12Reader.java
new file mode 100644
index 0000000..34f04a3
--- /dev/null
+++ b/mock-certify-plugin/src/main/java/io/mosip/certify/util/PKCS12Reader.java
@@ -0,0 +1,49 @@
+package io.mosip.certify.util;
+
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.ByteArrayInputStream;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+
+@Slf4j
+public class PKCS12Reader {
+ public KeyPairAndCertificate extract(String keyCert) {
+ String[] splitKeyCert = keyCert.split("\\|\\|");
+ try {
+ X509Certificate certificate = convertStringToX509Certificate((splitKeyCert[1]));
+ return (new KeyPairAndCertificate(getKeyPair(splitKeyCert[0], certificate), certificate));
+ } catch (Exception e) {
+ log.error("Failed to extract key certificate", e);
+ }
+
+ return null;
+ }
+
+ private X509Certificate convertStringToX509Certificate(String certString) throws CertificateException {
+ byte[] certBytes = Base64.getDecoder().decode(certString);
+
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certBytes));
+ }
+
+ private KeyPair getKeyPair(String base64PrivateKey, X509Certificate certificate) throws NoSuchAlgorithmException, InvalidKeySpecException {
+ byte[] privateKeyBytes = Base64.getDecoder().decode(base64PrivateKey);
+
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
+
+ PublicKey publicKey = certificate.getPublicKey();
+
+ return new KeyPair(publicKey, privateKey);
+ }
+}
+
+
diff --git a/mock-certify-plugin/src/test/java/io/mosip/certify/mock/integration/service/MockVCIssuancePluginTest.java b/mock-certify-plugin/src/test/java/io/mosip/certify/mock/integration/service/MockVCIssuancePluginTest.java
index b4c91fe..0614e33 100644
--- a/mock-certify-plugin/src/test/java/io/mosip/certify/mock/integration/service/MockVCIssuancePluginTest.java
+++ b/mock-certify-plugin/src/test/java/io/mosip/certify/mock/integration/service/MockVCIssuancePluginTest.java
@@ -4,6 +4,7 @@
import io.mosip.certify.api.dto.VCRequestDto;
import io.mosip.certify.api.dto.VCResult;
import io.mosip.certify.api.exception.VCIExchangeException;
+import io.mosip.certify.core.dto.ParsedAccessToken;
import io.mosip.esignet.core.dto.OIDCTransaction;
import io.mosip.kernel.signature.dto.JWTSignatureResponseDto;
import io.mosip.kernel.signature.service.SignatureService;