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
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.
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:
Your callback handler could be "
new com.sun.security.auth.callback.DialogCallbackHandler()
". I wouldn't generally advise using any of thecom.sun.*
orsun.*
packages since they're not part of the public Java API, but you're usingsun.security.pkcs11.SunPKCS11
here, so your code will be tied to this family of JREs anyway.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