2013-06-04 35 views
6

Ich arbeite an einer Java-Anwendung, die smartcardio verwendet, um mit Smartcard zu arbeiten. Es muss möglich sein, dass man seinen USB-Kartenleser entfernt und dann wieder einführt, ohne das Applet erneut zu starten.Entfernung der Smartcard-Terminals: SCARD_E_NO_SERVICE CardException

Ich verwende die terminals() und waitForChange() Methoden, um Terminaländerungen zu erkennen, und es funktioniert gut unter Linux, MacOS und Win7.

Aber auf Windows 8 (und Windows 8 nur) nach dem Entfernen des letzten Terminal, werfen diese Methoden ein SCARD_E_NO_SERVICECardException, und keine weiteren Änderungen erkennen.

Ich bin nicht sicher, worüber "Service" es spricht. Aber ich denke, dass dies in meinem Thread gestartet wird, wenn ich TerminalFactory.getDefault() anrufen, um einen TerminalFactory Singleton zu haben. Und ich denke, dass dieser Singleton eine Möglichkeit haben kann, den darunterliegenden Dienst zu verwalten, und das ist, was gebrochen ist.

Hat irgendjemand irgendwelche Hinweise zur Verwaltung der Terminaltrennung mit smartcardio unter Windows 8?

Antwort

9

Dieser Beitrag ist ziemlich alt, aber es war nützlich für mich, das Problem unter Windows beschrieben zu beheben 8.

Die Lösung von JR Utily nicht vollständig funktionierte: bei einem Leser unplugged dann wieder eingesteckt, dort waren Fehler in der CardTerminal-Instanz.

Also habe ich einen Code hinzugefügt, um die Terminalliste zu löschen, wie Sie in dem Code unten sehen können.

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

Danke !. Ich muss darauf hinweisen, dass das PCSCTerminal Map Feld "stateMap" ebenfalls gelöscht werden muss. – vellotis

+0

Ich arbeite nicht mehr an dem Projekt, das dies benötigt, aber ich werde ihnen von dieser besseren Lösung erzählen :) –

+0

Danke @vellotis, so funktioniert für mich. –

4

Ich habe einen Weg gefunden, aber es verwendet reflektierenden Code. Ich hätte lieber eine sauberere Methode gefunden, aber es scheint, dass es keine offizielle API zur Verwaltung von Smart Card Context gibt. Alle Klassen sind privat.

initContext() Die Methode des sun.security.smartcardio.PCSCTerminals (http://www.docjar.com/html/api/sun/security/smartcardio/PCSCTerminals.java.html) verhindert, dass neue Themen aus einem neuen Kontext immer nach dem ersten initialisiert wurde: die Methode aufgerufen wird, aber der Rahmen ist als ein Singleton gesehen und wird nicht erneut initialzed.

Durch die private in alles um diese mit java.lang.reflect passieren, ist es möglich, die Erstellung eines neuen Kontexts zu erzwingen und speichern Sie seine neue ID als "offizielle" contextId. Dies sollte vor der Instanziierung der neuen TerminalFactory erfolgen.

// ... 
    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); 
    } 
    // ... 
2

(Dies ist nur ein Kommentar, aber ich habe nicht genug rep um Kommentare zu schreiben.)

Der Service es die Windows-Smart-Card-Service ist verweist, die auch als Smart-Card-Ressource-Manager bekannt . Wenn Sie die MMC-Konsole öffnen, sehen Sie sie dort mit dem Starttyp, der auf Manual gesetzt ist (Trigger Start). In Windows 8 wurde dieser Dienst so geändert, dass er nur ausgeführt wird, während ein Smartcard-Leser an das System angeschlossen ist (um Ressourcen zu sparen) und der Dienst automatisch gestoppt wird, wenn der letzte Reader entfernt wird. Durch das Stoppen des Diensts werden alle ausstehenden Handles ungültig.

Die native Windows-Lösung ruft SCardAccessStartedEvent auf und verwendet das zurückgegebene Handle, um auf den Start des Dienstes zu warten, bevor SCardEstablishContext für die erneute Verbindung mit dem Ressourcenmanager verwendet wird.