Smartcard terminal removal : SCARD_E_NO_SERVICE CardException
Asked Answered
S

3

11

I am working on an Java application which uses smartcardio to work with smartcard. It must be possible to have one removing its USB card reader and then inserting it again without starting again the applet.

I am using the terminals() and waitForChange() methods to detect terminal changes and it is working fine on Linux, MacOS and Win7.

But on Windows 8 (and Windows 8 only), after the removal of the last terminal, these methods throw a SCARD_E_NO_SERVICE CardException, and don't detect any more changes.

I'm not sure what "Service" is it talking about. But I think this is launched in my thread when I call TerminalFactory.getDefault() to have a TerminalFactory singleton. And I think this singleton may have a way to manage the underlayed service and this is what is broken.

Has anyone any lead on how to manage terminal disconnection with smartcardio on Windows 8 ?

Swirly answered 4/6, 2013 at 15:31 Comment(0)
C
18

This post is quite old, but it has been useful for me to fix the problem described on Windows 8.

The solution from JR Utily did not work fully: in case of a reader unplugged then plugged again, there were errors on the CardTerminal instance.

So I added some code to clear the terminals list, as you can see in the code below.

        Class pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals");
        Field contextId = pcscterminal.getDeclaredField("contextId");
        contextId.setAccessible(true);

        if(contextId.getLong(pcscterminal) != 0L)
        {
            // First get a new context value
            Class pcsc = Class.forName("sun.security.smartcardio.PCSC");
            Method SCardEstablishContext = pcsc.getDeclaredMethod(
                                               "SCardEstablishContext",
                                               new Class[] {Integer.TYPE }
                                           );
            SCardEstablishContext.setAccessible(true);

            Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER");
            SCARD_SCOPE_USER.setAccessible(true);

            long newId = ((Long)SCardEstablishContext.invoke(pcsc, 
                    new Object[] { SCARD_SCOPE_USER.getInt(pcsc) }
            ));
            contextId.setLong(pcscterminal, newId);


            // Then clear the terminals in cache
            TerminalFactory factory = TerminalFactory.getDefault();
            CardTerminals terminals = factory.terminals();
            Field fieldTerminals = pcscterminal.getDeclaredField("terminals");
            fieldTerminals.setAccessible(true);
            Class classMap = Class.forName("java.util.Map");
            Method clearMap = classMap.getDeclaredMethod("clear");

            clearMap.invoke(fieldTerminals.get(terminals));
        }
Conversion answered 20/10, 2014 at 16:9 Comment(3)
Thanks!. I have to point out that PCSCTerminal Map field "stateMap" also has to be cleared.Korwun
I do no longer work on the project that needed this, but I will tell them about this better solution :)Swirly
I tryed the solution on Windows 10 but there's still something wrong to me. After applying your example the terminal "is back". Looks like being available, reads card metadata and so on... ...but when I try to login I see what follows. When terminal is disconnected SunPKCS11.uninitToken() is called. After the terminal is back, SunPKCS11.initToken() is called but slotInfo.flags doesn't contain CKF_TOKEN_PRESENT and init creates a poller to wait for it. The result is... that login fails. I'm sure I don't see something, but I don't know what...Azaria
S
4

I have found a way, but it's using reflective code. I'd rather found a cleaner method, but it seem there is no official API to manage Smart Card Context. All classes are private.

The initContext() method of sun.security.smartcardio.PCSCTerminals (http://www.docjar.com/html/api/sun/security/smartcardio/PCSCTerminals.java.html) prevents new threads from getting a new context after the first one was initialized : the method is called, but the context is seen as a singleton and isn't re-initialzed.

Passing through the private in everything around this with java.lang.reflect, it is possible to force the creation of a new context and store its new id as the "official" contextId. This should be done before instanciating the new TerminalFactory.

    // ...
    Class pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals");
    Field contextId = pcscterminal.getDeclaredField("contextId");
    contextId.setAccessible(true);

    if(contextId.getLong(pcscterminal) != 0L)
    {
        Class pcsc = Class.forName("sun.security.smartcardio.PCSC");
        Method SCardEstablishContext = pcsc.getDeclaredMethod(
                                           "SCardEstablishContext",
                                           new Class[] {Integer.TYPE }
                                       );
        SCardEstablishContext.setAccessible(true);

        Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER");
        SCARD_SCOPE_USER.setAccessible(true);

        long newId = ((Long)SCardEstablishContext.invoke(pcsc, 
              new Object[] { Integer.valueOf(SCARD_SCOPE_USER.getInt(pcsc)) }
              )).longValue();
        contextId.setLong(pcscterminal, newId);
    }
    // ...
Swirly answered 7/6, 2013 at 15:35 Comment(0)
T
3

(This is just a comment but I don't have enough rep to post comments.)

The service it refers to is the Windows Smart Card service, also known as the smart card resource manager. If you open the Services MMC console you'll see it there with the startup type set to Manual (Trigger Start). In Windows 8 this service was changed to run only while a smart card reader is attached to the system (to save resources) and the service is automatically stopped when the last reader is removed. Stopping the service invalidates any outstanding handles.

The native Windows solution is to call SCardAccessStartedEvent and use the handle it returns to wait for the service to start before using SCardEstablishContext to connect to the resource manager again.

Tenstrike answered 20/6, 2013 at 8:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.