Skip to content

Commit

Permalink
[INJIMOB-1588] add mso_mdoc vc format support for mock plugin (mosip#60)
Browse files Browse the repository at this point in the history
* [INJIMOB-1588] add mock mdoc

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] Get issuer certificate + keypair from local p12 file

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] Populate mock mDoc from DB

Build mdoc from mock DB using the cached transaction and in case of any issue give hardcoded set of data

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1788] generate mdoc

Library used - open-wallet-foundation-labs/identity-credetial

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] produce mso_mdoc VC from individualId provided

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] read crypto details for signing mso_mdoc vc

Other changes: removing unused classes/ debug logs

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] remove debug logs , optimize imports

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] remove unused declared dependencies

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] add respository for identity-credential dependency

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] remove debug logs

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] refactor unused fields, renaming

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] git ignore .DS_Store

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] perform base64 url safe encoding

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] refactor variable name

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] Get issuer and ca cryto details from config property

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] revert IDE format changes

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] fix merge conflict issue

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] remove unused repo declaration

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] revert remove unused plugin

Signed-off-by: KiruthikaJeyashankar <[email protected]>

* [INJIMOB-1588] extract data generation for mdoc to a separate function

Signed-off-by: KiruthikaJeyashankar <[email protected]>

---------

Signed-off-by: KiruthikaJeyashankar <[email protected]>
Signed-off-by: KiruthikaJeyashankar <[email protected]>
  • Loading branch information
KiruthikaJeyashankar authored Oct 9, 2024
1 parent 563a383 commit 2680e45
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ target/
.setting/
.mvn/
.project/
*.DS_Store
98 changes: 95 additions & 3 deletions mock-certify-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,27 @@
<maven.jacoco.version>0.8.11</maven.jacoco.version>
<maven-javadoc-plugin.version>3.6.3</maven-javadoc-plugin.version>
<kernel-keymanager-service.version>1.3.0-beta.1</kernel-keymanager-service.version>
<kotlin.version>2.0.0</kotlin.version>
</properties>
<dependencies>

<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.mosip.certify</groupId>
<artifactId>certify-core</artifactId>
<version>0.10.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.mosip.esignet</groupId>
<artifactId>esignet-core</artifactId>
Expand Down Expand Up @@ -131,6 +135,26 @@
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-datetime-jvm</artifactId>
<version>0.6.0</version>
</dependency>
<dependency>
<groupId>com.android.identity</groupId>
<artifactId>identity-credential</artifactId>
<version>20231002</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>co.nstant.in</groupId>
<artifactId>cbor</artifactId>
<version>0.9</version>
</dependency>
</dependencies>

<repositories>
Expand All @@ -156,6 +180,10 @@
<id>danubetech-maven-public</id>
<url>https://repo.danubetech.com/repository/maven-public/</url>
</repository>
<repository>
<id>google</id>
<url>https://maven.google.com/</url>
</repository>
</repositories>

<distributionManagement>
Expand Down Expand Up @@ -356,6 +384,70 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<source>src/main/java</source>
<source>target/generated-sources/annotations</source>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<source>src/test/java</source>
<source>target/generated-test-sources/test-annotations</source>
</sourceDirs>
</configuration>
</execution>
</executions>
<configuration>
<jvmTarget>${maven.compiler.target}</jvmTarget>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> 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'";

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -235,10 +240,52 @@ private static String getUTCDateTime() {
@Override
public VCResult<String> getVerifiableCredential(VCRequestDto vcRequestDto, String holderId,
Map<String, Object> 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<String> 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<String, Object> mockDataForMsoMdoc(String documentNumber) {
Map<String, Object> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String, out Any>,
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<String, MutableList<ByteArray>> =
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<String, MutableList<ByteArray>>, 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<String, MutableList<ByteArray>>, val issuerAuth: DataItem) {
fun toMap(): Map<String, Any> {
return buildMap {
put("nameSpaces", CBORConverter.toDataItem(nameSpaces))
put("issuerAuth", issuerAuth)
}
}
}
Loading

0 comments on commit 2680e45

Please sign in to comment.