I need to send a message to a smartcard. First of all, I sent it using gpshell in order to test the correctness and get an answer:
send_apdu_nostop -sc 0 -APDU 802A80B05F87410061DA7A1E2F02602A255063713FD657797063C6C7ACC12072F5340B1C0126A616BC66C65F49132EED10AE071DC661AA1333BEA92F67A5BEFFDFA7A0F31FC8B3D981105D1EF96B000FD90098C7FF031906A1018E0897C5DA580059AD2900 Command --> 802A80B05F87410061DA7A1E2F02602A255063713FD657797063C6C7ACC12072F5340B1C0126A616BC66C65F49132EED10AE071DC661AA1333BEA92F67A5BEFFDFA7A0F31FC8B3D981105D1EF96B000FD90098C7FF031906A1018E0897C5DA580059AD2900
Wrapped command --> 802A80B05F87410061DA7A1E2F02602A255063713FD657797063C6C7ACC12072F5340B1C0126A616BC66C65F49132EED10AE071DC661AA1333BEA92F67A5BEFFDFA7A0F31FC8B3D981105D1EF96B000FD90098C7FF031906A1018E0897C5DA580059AD2900
Response <-- 604001544F50434F4E31392020202020000000FF00FF0000FF00020000000000000000FF5D11DCAD000000005D11DCAD005D1EF96B9000
With this result I was confident to replicate the behaviour in java using smartcardio. The following is the code I wrote:
void testCard()
{
TerminalFactory factory = TerminalFactory.getDefault();
List<CardTerminal> terminals;
try {
terminals = factory.terminals().list();
} catch (CardException ex) {
return;
}
CardTerminal cardTerm = terminals.get(0);
Card card;
try {
card = cardTerm.connect("T=0");
} catch (CardException ex) {
return;
}
CardChannel cach = card.getBasicChannel();
ResponseAPDU r;
try {
CommandAPDU ca = new CommandAPDU(new byte[]{(byte)0x80,(byte)0x2A,(byte)0x80,(byte)0xB0,(byte)0x5F,(byte)0x87,(byte)0x41,(byte)0x00,(byte)0x61,(byte)0xDA,(byte)0x7A,(byte)0x1E,(byte)0x2F,(byte)0x02,(byte)0x60,(byte)0x2A,(byte)0x25,(byte)0x50,(byte)0x63,(byte)0x71,(byte)0x3F,(byte)0xD6,(byte)0x57,(byte)0x79,(byte)0x70,(byte)0x63,(byte)0xC6,(byte)0xC7,(byte)0xAC,(byte)0xC1,(byte)0x20,(byte)0x72,(byte)0xF5,(byte)0x34,(byte)0x0B,(byte)0x1C,(byte)0x01,(byte)0x26,(byte)0xA6,(byte)0x16,(byte)0xBC,(byte)0x66,(byte)0xC6,(byte)0x5F,(byte)0x49,(byte)0x13,(byte)0x2E,(byte)0xED,(byte)0x10,(byte)0xAE,(byte)0x07,(byte)0x1D,(byte)0xC6,(byte)0x61,(byte)0xAA,(byte)0x13,(byte)0x33,(byte)0xBE,(byte)0xA9,(byte)0x2F,(byte)0x67,(byte)0xA5,(byte)0xBE,(byte)0xFF,(byte)0xDF,(byte)0xA7,(byte)0xA0,(byte)0xF3,(byte)0x1F,(byte)0xC8,(byte)0xB3,(byte)0xD9,(byte)0x81,(byte)0x10,(byte)0x5D,(byte)0x1E,(byte)0xF9,(byte)0x6B,(byte)0x00,(byte)0x0F,(byte)0xD9,(byte)0x00,(byte)0x98,(byte)0xC7,(byte)0xFF,(byte)0x03,(byte)0x19,(byte)0x06,(byte)0xA1,(byte)0x01,(byte)0x8E,(byte)0x08,(byte)0x97,(byte)0xC5,(byte)0xDA,(byte)0x58,(byte)0x00,(byte)0x59,(byte)0xAD,(byte)0x29,(byte)0x00});
r = cach.transmit(ca);
} catch (CardException ex) {
return;
}
}
When the code runs I always get the error 0x6E00 which means: "Class not supported".
Reading the javadoc of transmit I understand that "The CLA byte of the command APDU is automatically adjusted to match the channel number of this CardChannel."
I suspect that for some reasons the class byte was changed somehow and for that reason the card answers with the error.
Is there any way to send directly the message to the card in java?
NOTE: I have not worked with Smart cards and don't have one to validate this answer. But it may point you in the right direction
If you look at the source code of gshell. For sending the APDU it reads it like below
else if (_tcscmp(token, _T("-APDU")) == 0)
{
token = strtokCheckComment(NULL);
if (token == NULL)
{
_tprintf(_T("Error: option -APDU not followed by data\n"));
rv = EXIT_FAILURE;
goto end;
}
else
{
pOptionStr->APDULen = ConvertStringToByteArray(token, APDULEN, pOptionStr->APDU);
}
}
https://github.com/sigma/globalplatform/blob/7f1c8669c5991a60b6fada9b1187d2ee6d040223/gpshell/src/gpshell.c#L1648
So what you passed is complete APDU command
if (platform_mode == PLATFORM_MODE_OP_201)
{
status = OP201_send_APDU(cardContext, cardInfo,
(optionStr.secureChannel == 0 ? NULL : &securityInfo201),
(PBYTE)(optionStr.APDU), optionStr.APDULen,
recvAPDU, &recvAPDULen);
}
Which in turn goes to
https://github.com/sigma/globalplatform/blob/7f1c8669c5991a60b6fada9b1187d2ee6d040223/globalplatform/src/connection.c#L202
OPGP_ERROR_STATUS OPGP_send_APDU(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE capdu, DWORD capduLength, PBYTE rapdu, PDWORD rapduLength) {
OPGP_ERROR_STATUS errorStatus;
OPGP_ERROR_STATUS securityStatus;
OPGP_ERROR_STATUS(*plugin_sendAPDUFunction) (OPGP_CARD_CONTEXT, OPGP_CARD_INFO, PBYTE, DWORD, PBYTE, PDWORD);
BYTE apduCommand[261];
DWORD apduCommandLength = 261;
int i=0;
OPGP_LOG_START(_T("OPGP_send_APDU"));
plugin_sendAPDUFunction = (OPGP_ERROR_STATUS(*)(OPGP_CARD_CONTEXT, OPGP_CARD_INFO, PBYTE, DWORD, PBYTE, PDWORD)) cardContext.connectionFunctions.sendAPDU;
So I think an extra cardContext is required to be appended in your command, which is currently not there
See few implementations also and see if you can make out the bytes
https://github.com/handywings/tutorial_hdw_mc/blob/a1dceae1b243b0c79dda5c73218e3a9ff84c259c/src/main/java/com/hdw/mccable/utils/ThaiLandIdCard.java
https://www.javaworld.com/article/2076450/how-to-write-a-java-card-applet--a-developer-s-guide.html
https://github.com/arg3s/ClassAttendanceMaster/blob/a368b07e9b94bd387f91d7d6f57f12e402d58b32/src/main/java/com/classattendancemaster/SmartCard/SmartcardConnector.java
I found the solution. It seems that using "T=0" is not a suitable way to send the second command. Using "T=1" or "*" solve the problem. The correction is the following:
card = cardTerm.connect("T=1");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With