5

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
t.pimentel
  • 1,465
  • 3
  • 17
  • 24
  • 1
    Does your token also come with a PKCS#11 library, instead of going through the Windows keystore? – Bruno May 15 '14 at 10:30
  • I don't know, how can I check that? In [SafeNet eToken 5100](http://www.globalforte.com/download00034678/SafeNet/Products/SafeNet_eToken_5100.pdf) I see: API & standards support - PKCS#11 V2.20, Microsoft CAPI, PC/SC, X.509 v3 certificate storage, SSL v3, IPsec/IKE CCID 1.0 – t.pimentel May 15 '14 at 17:55
  • well, there is PKCS#11 mentioned, so yes. Check for something like `cryptoki.dll` or similar. – Maarten Bodewes May 15 '14 at 18:31

2 Answers2

2

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.

Bruno
  • 119,590
  • 31
  • 270
  • 376
  • I found a PKCS library in my computer, eTPKCS11.dll. I tried to create a provider pointing to it and got some new errors. I added some information at the end of my question with the new modifications, thanks for helping @Bruno. – t.pimentel May 15 '14 at 19:12
  • It sounds like your DLL wasn't loaded properly. Perhaps you could try with another piece of software to check if it works ([Firefox and Thunberbird also support PKCS#11](https://www.secure-endpoints.com/kcacred/instkpkcs11.html)). It's also possible that your DLL is for 32 bits and you're using a 64-bit JVM (in this case, FF might work if it's 32 bits too). Using `-Djava.security.debug=sunpkcs11` might also give you more details. You may also need to specify a "slot" in your configuration. – Bruno May 15 '14 at 19:42
  • Thanks a lot @Bruno, i had tried pointing the cfg file to slot=0 to 3 and it hadn't worked, mine was in `slot=4`. When I used `-Djava.security.debug=sunpkcs11` it found in first try. Now I have to or keep the `-Djava.security.debug=sunpkcs11` or specify the `slot=4`. Do you know if there's anyway to automate the slot finding so I do not have to specify a slot and do not need to keep the debug flag? (I'll update my answer with the final working code and .cfg file) – t.pimentel May 15 '14 at 20:20
  • I've just notice [this answer to another question](http://stackoverflow.com/a/16776155/372643), which might be useful. – Bruno May 21 '14 at 17:21
0

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);
        }
    }
}
James
  • 2,346
  • 1
  • 16
  • 18
ARAVIND
  • 51
  • 1
  • 8