Удаление терминала смарт-карты: SCARD E нет службы CardException

Я работаю над Java-приложением, которое использует smartcardio для работы со смарт-картой. Должно быть возможно, чтобы один из них удалил считыватель USB-карт, а затем вставил его снова без повторного запуска апплета.

я использую terminals() и waitForChange() методы обнаружения изменений терминала, и он отлично работает на Linux, MacOS и Win7.

но на Windows 8 (и только Windows 8), после удаления последнего терминала, эти методы бросают SCARD_E_NO_SERVICE CardException, и не обнаружено каких-либо изменений.

Я не уверен, о каком "обслуживании" идет речь. Но я думаю, что это запускается в моем потоке, когда я вызываю TerminalFactory.getDefault() иметь TerminalFactory синглтон. И я думаю, что у этого синглтона может быть способ управлять подложной службой, и это то, что сломано.

кто-нибудь знает, как управлять отключением терминала с помощью smartcardio на Windows 8 ?

3 ответов


этот пост довольно старый, но мне было полезно исправить проблему, описанную в Windows 8.

решение от JR Utily не работал полностью: в случае отключения считывателя, а затем снова подключен, были ошибки на экземпляре CardTerminal.

поэтому я добавил код, чтобы очистить список терминалов, как вы можете видеть в коде ниже.

        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));
        }

Я нашел способ, но он использует светоотражающие код. Я бы предпочел найти более чистый метод, но, похоже, нет официального API для управления контекстом смарт-карты. Все занятия частные.

на initContext() метод sun.security.smartcardio.PCSCTerminals (http://www.docjar.com/html/api/sun/security/smartcardio/PCSCTerminals.java.html) предотвращает получение новых потоков нового контекста после инициализации первого: вызывается метод, но контекст рассматривается как одноэлементный и не повторно инициализирован.

, проходящей через private во всем вокруг этого с java.lang.reflect, можно принудительно создать новый контекст и сохранить его новый идентификатор как "официальный" contextId. Это должно быть сделано до создания экземпляра нового 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);
    }
    // ...

(Это просто комментарий, но у меня недостаточно репутации, чтобы публиковать комментарии.)

служба, на которую он ссылается, - это служба смарт-карт Windows, также известная как диспетчер ресурсов смарт-карт. Если вы откроете услуги консоль MMC вы увидите его там с типом запуска, установленным в Руководство (Запуск Триггера). В Windows 8 эта служба была изменена для запуска только в то время как считыватель смарт-карт подключен к системе (для экономии ресурсов) и служба автоматически остановлено при удалении последнего считывателя. Остановка службы делает недействительными все оставшиеся дескрипторы.

собственное решение Windows-вызвать SCardAccessStartedEvent и используйте дескриптор, который он возвращает, чтобы дождаться запуска службы перед использованием SCardEstablishContext для повторного подключения к менеджеру ресурсов.