Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,61 +7,21 @@
import io.mosip.certify.api.spi.VCIssuancePlugin;
import io.mosip.certify.api.util.ErrorConstants;
import io.mosip.certify.constants.VCFormats;
import io.mosip.certify.core.exception.CertifyException;
import io.mosip.certify.mock.integration.mocks.MdocGenerator;
import io.mosip.esignet.core.dto.OIDCTransaction;
import io.mosip.kernel.core.keymanager.spi.KeyStore;
import io.mosip.kernel.keymanagerservice.constant.KeymanagerConstant;
import io.mosip.kernel.keymanagerservice.entity.KeyAlias;
import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;

@ConditionalOnProperty(value = "mosip.certify.integration.vci-plugin", havingValue = "MDocMockVCIssuancePlugin")
@Component
@Slf4j
public class MDocMockVCIssuancePlugin implements VCIssuancePlugin {
private static final String AES_CIPHER_FAILED = "aes_cipher_failed";
private static final String NO_UNIQUE_ALIAS = "no_unique_alias";
private static final String USERINFO_CACHE = "userinfo";

@Autowired
private CacheManager cacheManager;

@Autowired
private KeyStore keyStore;

@Autowired
private KeymanagerDBHelper dbHelper;

@Value("${mosip.certify.cache.security.secretkey.reference-id}")
private String cacheSecretKeyRefId;

@Value("${mosip.certify.cache.security.algorithm-name}")
private String aesECBTransformation;

@Value("${mosip.certify.cache.secure.individual-id}")
private boolean secureIndividualId;

@Value("${mosip.certify.cache.store.individual-id}")
private boolean storeIndividualId;

@Value("${mosip.certify.mock.vciplugin.mdoc.issuer-key-cert:empty}")
private String issuerKeyAndCertificate = null;

private static final String ACCESS_TOKEN_HASH = "accessTokenHash";

public static final String CERTIFY_SERVICE_APP_ID = "CERTIFY_SERVICE";

@Autowired
private MdocGenerator mdocGenerator;
Expand All @@ -74,10 +34,13 @@ public VCResult<JsonLDObject> getVerifiableCredentialWithLinkedDataProof(VCReque

@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));
if(identityDetails.get("sub") == null){
log.error("sub field is missing in identityDetails claims. Check the access token or auth configurations.");
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED);
}
documentNumber = (String) identityDetails.get("sub");
} catch (Exception e) {
log.error("Error getting documentNumber", e);
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED);
Expand Down Expand Up @@ -113,47 +76,4 @@ private Map<String, Object> mockDataForMsoMdoc(String documentNumber) {
}});
return data;
}

/**
* TODO: This function getIndividualId is duplicated with Other VCIPlugin class and can be moved to commons
*/
protected String getIndividualId(OIDCTransaction transaction) {
if(!storeIndividualId)
return null;
return secureIndividualId ? decryptIndividualId(transaction.getIndividualId()) : transaction.getIndividualId();
}

private String decryptIndividualId(String encryptedIndividualId) {
try {
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));
} catch(Exception e) {
log.error("Error Cipher Operations of provided secret data.", e);
throw new CertifyException(AES_CIPHER_FAILED);
}
}

private OIDCTransaction getUserInfoTransaction(String accessTokenHash) {
return cacheManager.getCache(USERINFO_CACHE).get(accessTokenHash, OIDCTransaction.class);
}

private Key getSecretKeyFromHSM() {
String keyAlias = getKeyAlias(CERTIFY_SERVICE_APP_ID, cacheSecretKeyRefId);
if (Objects.nonNull(keyAlias)) {
return keyStore.getSymmetricKey(keyAlias);
}
throw new CertifyException(NO_UNIQUE_ALIAS);
}

private String getKeyAlias(String keyAppId, String keyRefId) {
Map<String, List<KeyAlias>> keyAliasMap = dbHelper.getKeyAliases(keyAppId, keyRefId, LocalDateTime.now(ZoneOffset.UTC));
List<KeyAlias> currentKeyAliases = keyAliasMap.get(KeymanagerConstant.CURRENTKEYALIAS);
if (!currentKeyAliases.isEmpty() && currentKeyAliases.size() == 1) {
return currentKeyAliases.getFirst().getAlias();
}
log.error("CurrentKeyAlias is not unique. KeyAlias count: {}", currentKeyAliases.size());
throw new CertifyException(NO_UNIQUE_ALIAS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@
import io.mosip.certify.api.dto.VCResult;
import io.mosip.certify.api.exception.VCIExchangeException;
import io.mosip.certify.constants.VCFormats;
import io.mosip.certify.core.exception.CertifyException;
import io.mosip.certify.mock.integration.mocks.MdocGenerator;
import io.mosip.esignet.core.dto.OIDCTransaction;
import io.mosip.kernel.core.keymanager.spi.KeyStore;
import io.mosip.kernel.keymanagerservice.entity.KeyAlias;
import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.test.util.ReflectionTestUtils;

import java.security.Key;
import java.time.LocalDateTime;
import java.util.*;

import static org.junit.Assert.*;
Expand All @@ -31,119 +22,57 @@ public class MDocMockVCIssuancePluginTest {
@InjectMocks
private MDocMockVCIssuancePlugin plugin;

@Mock
private CacheManager cacheManager;
@Mock
private KeyStore keyStore;
@Mock
private KeymanagerDBHelper dbHelper;
@Mock
private Cache cache;
@Mock
private Key key;
@Mock
private MdocGenerator mdocGenerator;

@Before
public void setUp() {
ReflectionTestUtils.setField(plugin, "issuerKeyAndCertificate", "empty");
ReflectionTestUtils.setField(plugin, "cacheSecretKeyRefId", "refId");
ReflectionTestUtils.setField(plugin, "aesECBTransformation", "AES/ECB/PKCS5Padding");
ReflectionTestUtils.setField(plugin, "storeIndividualId", true);
ReflectionTestUtils.setField(plugin, "secureIndividualId", false);
}

@Test
public void testGetVerifiableCredential_Success() throws Exception {
VCRequestDto dto = mock(VCRequestDto.class);
when(dto.getFormat()).thenReturn(VCFormats.MSO_MDOC);

// identityDetails must contain "sub" because implementation casts it to String
Map<String, Object> identityDetails = new HashMap<>();
identityDetails.put("sub", "12345");
identityDetails.put("accessTokenHash", "tokenHash");

OIDCTransaction transaction = mock(OIDCTransaction.class);
when(transaction.getIndividualId()).thenReturn("docNum");

when(cacheManager.getCache(anyString())).thenReturn(cache);
when(cache.get(anyString(), eq(OIDCTransaction.class))).thenReturn(transaction);

when(mdocGenerator.generate(anyMap(), anyString(), anyString())).thenReturn("mockedMdoc");
when(mdocGenerator.generate(anyMap(), anyString(), anyString()))
.thenReturn("mockedMdoc");

VCResult<String> result = plugin.getVerifiableCredential(dto, "holderId", identityDetails);

assertNotNull(result);
// Ensure we compare like types; adjust if VCFormats.MSO_MDOC is not a String
assertEquals(VCFormats.MSO_MDOC, result.getFormat());
assertEquals("mockedMdoc", result.getCredential());
}


@Test(expected = VCIExchangeException.class)
public void testGetVerifiableCredential_NotImplemented() throws Exception {
VCRequestDto dto = mock(VCRequestDto.class);

// Use a format that MDocMockVCIssuancePlugin does not implement
when(dto.getFormat()).thenReturn(VCFormats.LDP_VC); // or any other unsupported format

Map<String, Object> identityDetails = new HashMap<>();
identityDetails.put("accessTokenHash", "tokenHash");
identityDetails.put("sub", "12345");

plugin.getVerifiableCredential(dto, "holderId", identityDetails);
}


@Test(expected = VCIExchangeException.class)
public void testGetVerifiableCredentialWithLinkedDataProof_NotImplemented() throws Exception {
VCRequestDto dto = mock(VCRequestDto.class);
plugin.getVerifiableCredentialWithLinkedDataProof(dto, "holderId", new HashMap<>());
}

@Test
public void testGetIndividualId_SecureFalse() {
OIDCTransaction transaction = mock(OIDCTransaction.class);
when(transaction.getIndividualId()).thenReturn("docNum");
ReflectionTestUtils.setField(plugin, "secureIndividualId", false);
ReflectionTestUtils.setField(plugin, "storeIndividualId", true);
String result = (String) ReflectionTestUtils.invokeMethod(plugin, "getIndividualId", transaction);
assertEquals("docNum", result);
}

@Test
public void testGetIndividualId_StoreFalse() {
OIDCTransaction transaction = mock(OIDCTransaction.class);
ReflectionTestUtils.setField(plugin, "storeIndividualId", false);
String result = (String) ReflectionTestUtils.invokeMethod(plugin, "getIndividualId", transaction);
assertNull(result);
}

@Test(expected = CertifyException.class)
public void testDecryptIndividualId_Exception() {
ReflectionTestUtils.setField(plugin, "aesECBTransformation", "invalid");
ReflectionTestUtils.invokeMethod(plugin, "decryptIndividualId", "invalid");
}

@Test(expected = CertifyException.class)
public void testGetSecretKeyFromHSM_NoAlias() {
when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class)))
.thenReturn(Collections.singletonMap("currentKeyAlias", new ArrayList<>()));
ReflectionTestUtils.invokeMethod(plugin, "getSecretKeyFromHSM");
}

@Test
public void testGetKeyAlias_Success() {
KeyAlias alias = mock(KeyAlias.class);
when(alias.getAlias()).thenReturn("alias");
List<KeyAlias> aliases = Collections.singletonList(alias);
Map<String, List<KeyAlias>> map = new HashMap<>();
map.put("currentKeyAlias", aliases);
when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))).thenReturn(map);
String result = (String) ReflectionTestUtils.invokeMethod(plugin, "getKeyAlias", "appId", "refId");
assertEquals("alias", result);
}

@Test(expected = CertifyException.class)
public void testGetKeyAlias_NotUnique() {
List<KeyAlias> aliases = Arrays.asList(mock(KeyAlias.class), mock(KeyAlias.class));
Map<String, List<KeyAlias>> map = new HashMap<>();
map.put("currentKeyAlias", aliases);
when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))).thenReturn(map);
ReflectionTestUtils.invokeMethod(plugin, "getKeyAlias", "appId", "refId");
}

@Test
public void testMockDataForMsoMdoc() {
String docNum = "12345";
Expand All @@ -158,16 +87,4 @@ public void testMockDataForMsoMdoc() {
assertTrue(data.get("driving_privileges") instanceof Map);
assertEquals("A", ((Map<?, ?>) data.get("driving_privileges")).get("vehicle_category_code"));
}

@Test
public void testGetUserInfoTransaction() {
String accessTokenHash = "tokenHash";
OIDCTransaction transaction = mock(OIDCTransaction.class);
when(cacheManager.getCache(anyString())).thenReturn(cache);
when(cache.get(eq(accessTokenHash), eq(OIDCTransaction.class))).thenReturn(transaction);

OIDCTransaction result = (OIDCTransaction) ReflectionTestUtils.invokeMethod(plugin, "getUserInfoTransaction", accessTokenHash);
assertNotNull(result);
assertEquals(transaction, result);
}
}