How to get KeyStore from usb token in Java

2019-03-16 17:44发布

I have a SafeNet 5100 eToken already with a valid certificate in it that I use to access a web application from my company that requires it (multi-factor authentication).

I'm creating a desktop application to access this website. I am already able to add the website's certificate to the TrustStore and get my certificate into a KeyStore.

What I've got so far is:

System.setProperty("javax.net.ssl.trustStore", "U:\\Certificados\\efau.truestore");
System.setProperty("javax.net.ssl.trustStoreType", "jks");
System.setProperty("javax.net.ssl.trustStorePassword", "oiadad");

KeyManagerFactory kFac;
SSLContext sslContext;
SSLSocketFactory sockFactory = null;
KeyStore ks;

try {
    // load keystore present in windows and print aliases found (only one, so nextElement always prints same information (name of certificate inside usb token I want to open))
    ks = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
    ks.load(null, null);
    System.out.println(ks.aliases().nextElement());
    System.out.println(ks.aliases().nextElement());

    // try to load my certificate specifically from all certificates and passes necessary token password to it
    InputStream in = IOUtils.toInputStream(ks.aliases().nextElement(), "UTF-8");
    System.out.println(in);
    ks.load(in, password);

    // print certificate to check if I have it
    System.out.println(ks.getCertificate(ks.aliases().nextElement()));

    // get ssl context and key manager factory
    sslContext = SSLContext.getInstance("SSL", "SunJSSE");
    kFac = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kFac.init(ks,null);

    sslContext.init(kFac.getKeyManagers(), null, null);
    sockFactory = sslContext.getSocketFactory();

    // start connection with website
    HttpsURLConnection conn = (HttpsURLConnection)new URL(<my-https-url>).openConnection();
    conn.setRequestMethod("GET");
    conn.setDoInput(true);
    conn.setSSLSocketFactory(sockFactory);

    int responseCode = conn.getResponseCode();
    System.out.println("RESPONSE: " + responseCode);

} catch (KeyStoreException e) {
    e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
} catch (CertificateException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} catch (NoSuchProviderException e) {
    e.printStackTrace();
} catch (UnrecoverableKeyException e1) {
    e1.printStackTrace();
} catch (KeyManagementException e1) {
    e1.printStackTrace();
}

When I run this code I get:

javax.net.ssl.SSLHandshakeException: Received fatal alert: decrypt_error
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
    at java.net.HttpURLConnection.getResponseCode(Unknown Source)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(Unknown Source)
    at receita.system.monitoring.Ping.main(Ping.java:313)

And I get this error when I type the correct password for the token and when I type a wrong one, so I think I'm never passing it the password in the correct way.

Why am I receiving the exception?

--------- Updated ---------

I created a config file with the following information pointing to my PKCS11.dll library:

name = Aladdin
library = C:/WINDOWS/system32/eTPKCS11.dll

And in the main function I add:

SunPKCS11 newProvider = new SunPKCS11("u:/Certificados/etpkcs11.cfg");
Provider a = newProvider;
Security.addProvider(a);

KeyStore ks;
try {
    ks = KeyStore.getInstance("PKCS11");
    ...
}

And now I'm getting this as an error:

java.security.KeyStoreException: PKCS11 not found
    at java.security.KeyStore.getInstance(Unknown Source)
    at receita.system.monitoring.Ping.main(Ping.java:292)
Caused by: java.security.NoSuchAlgorithmException: PKCS11 KeyStore not available
    at sun.security.jca.GetInstance.getInstance(Unknown Source)
    at java.security.Security.getImpl(Unknown Source)
    ... 2 more

I also tried to modify Keystore.getInstance to:

ks = KeyStore.getInstance("PKCS11", a);

and then I get this different error:

java.security.KeyStoreException: PKCS11 not found
    at java.security.KeyStore.getInstance(Unknown Source)
    at receita.system.monitoring.Ping.main(Ping.java:292)
Caused by: java.security.NoSuchAlgorithmException: no such algorithm: PKCS11 for provider SunPKCS11-Aladdin
    at sun.security.jca.GetInstance.getService(Unknown Source)
    at sun.security.jca.GetInstance.getInstance(Unknown Source)
    at java.security.Security.getImpl(Unknown Source)
    ... 2 more

--------- Updated 2 (Working Code) ---------

My final working code is:

System.setProperty("javax.net.ssl.trustStore", "U:\\Certificados\\efau.truestore");
System.setProperty("javax.net.ssl.trustStoreType", "jks");
System.setProperty("javax.net.ssl.trustStorePassword", "oiadad");

KeyManagerFactory kFac;
SSLContext sslContext;
SSLSocketFactory sockFactory = null;

SunPKCS11 providerMSCAPI = new SunPKCS11("u:/Certificados/etpkcs11.cfg");
Provider a = providerMSCAPI;
Security.addProvider(a);

KeyStore ks;
try {
    ks = KeyStore.getInstance("PKCS11");

    ks.load(null, password);

    InputStream in = IOUtils.toInputStream(ks.aliases().nextElement(), "UTF-8");
    ks.load(in, password);


    sslContext = SSLContext.getInstance("SSL", "SunJSSE");
    kFac = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kFac.init(ks,null);

    sslContext.init(kFac.getKeyManagers(), null, null);
    sockFactory = sslContext.getSocketFactory();

    HttpsURLConnection conn = (HttpsURLConnection)new URL(/*<my-url>*/).openConnection();
    conn.setRequestMethod("GET");
    conn.setDoInput(true);
    conn.setSSLSocketFactory(sockFactory);

    int responseCode = conn.getResponseCode();

    InputStream inputstream = conn.getInputStream();
    InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
    BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

    String line = null;
    String htmlResponse = "";

    while ((line = bufferedreader.readLine()) != null) {
        htmlResponse += line + "\n";
    }

} catch (KeyStoreException e) {
    e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
} catch (CertificateException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} catch (NoSuchProviderException e) {
    e.printStackTrace();
} catch (UnrecoverableKeyException e1) {
    e1.printStackTrace();
} catch (KeyManagementException e1) {
    e1.printStackTrace();
}

And I have to or set the debug argument in run configurations:

-Djava.security.debug=sunpkcs11

Or set the slot in the .cfg file:

name=SafeNet
library=C:\Windows\System32\eTPKCS11.dll
slot=4

2条回答
劫难
2楼-- · 2019-03-16 18:17

The SunMSCAPI implementation isn't perfect (for example, if you have certificates with the same "friendly name", some will be inaccessible, since it's also the unique key used for the keystore alias). I'm not sure how well it works with hardware tokens.

Since your token seems to support PKCS#11, you might as well make use of the Oracle JRE's direct support for PKCS11 keystores.

Essentially, your token driver should come with a DLL implementing the PKCS#11 interface, and you need to point Java to it (as described in the PKCS#11 guide). For more flexibility, it might be more convenient to install the provider dynamically (see the paragraph that starts with "To install the provider dynamically, [...]".


Following your comments, perhaps you could use trial and error (by catching these exceptions) to find the right slot. Instead of using a configuration file, you could load the configuration from a string.

String password = "xxxxxxxxx";
String storeType = "PKCS11";

String configuration = "name = OpenSC\n"
        + "library = /usr/lib/opensc-pkcs11.so\n";
Provider provider = new sun.security.pkcs11.SunPKCS11(
        new ByteArrayInputStream(configuration.getBytes("UTF-8")));

Security.addProvider(provider);

KeyStore keyStore = KeyStore.getInstance(storeType, provider);
keyStore.load(null, password.toCharArray());

If you add "slot=...\n" to the configuration string and use a loop to try various values until it stops throwing exceptions, it might work. You may need to remove the security providers where it failed, or change the name too. (I'm not suggesting this is a clean way to do it.)

By the way, if you don't want to hard-code your password (of course!) or load it from some configuration file, you can use a callback hander like this:

KeyStore keyStore = KeyStore.getInstance(storeType, provider);
LoadStoreParameter param = new LoadStoreParameter() {
    @Override
    public ProtectionParameter getProtectionParameter() {
        return new KeyStore.CallbackHandlerProtection(... put your callback handler here...);
    }
};
keyStore.load(param);

Your callback handler could be "new com.sun.security.auth.callback.DialogCallbackHandler()". I wouldn't generally advise using any of the com.sun.* or sun.* packages since they're not part of the public Java API, but you're using sun.security.pkcs11.SunPKCS11 here, so your code will be tied to this family of JREs anyway.

查看更多
趁早两清
3楼-- · 2019-03-16 18:24

Try the below code to get the keystore from usb token using java

class Test { public static void main(String args[]) throws IOException, GeneralSecurityException, DocumentException, CertificateVerificationException{ // Create instance of SunPKCS11 provider

            String pkcs11Config = "name=eToken\nlibrary=C:\\Windows\\System32\\eps2003csp11.dll";
            java.io.ByteArrayInputStream pkcs11ConfigStream = new java.io.ByteArrayInputStream(pkcs11Config.getBytes());
            sun.security.pkcs11.SunPKCS11 providerPKCS11 = new sun.security.pkcs11.SunPKCS11(pkcs11ConfigStream);
            java.security.Security.addProvider(providerPKCS11);

            // Get provider KeyStore and login with PIN
            String pin = "12345678";
            java.security.KeyStore keyStore = java.security.KeyStore.getInstance("PKCS11", providerPKCS11);
            keyStore.load(null, pin.toCharArray());

        // Enumerate items (certificates and private keys) in th KeyStore
            java.util.Enumeration<String> aliases = keyStore.aliases();
            String alias = null;
            while (aliases.hasMoreElements()) {
                alias = aliases.nextElement();
                System.out.println(alias);
            }
        }
}   
查看更多
登录 后发表回答