如何从Java中的USB令牌获取KeyStore
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23665092/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to get KeyStore from usb token in Java
提问by t.pimentel
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).
我有一个 SafeNet 5100 eToken,里面已经有一个有效的证书,我用它来访问我公司的需要它的 Web 应用程序(多因素身份验证)。
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
.
我正在创建一个桌面应用程序来访问这个网站。我已经能够将网站的证书添加TrustStore
到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:
我创建了一个配置文件,其中包含指向我的 PKCS11.dll 库的以下信息:
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:
我还尝试将 Keystore.getInstance 修改为:
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) ---------
--------- 更新 2(工作代码)---------
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:
或者在 .cfg 文件中设置插槽:
name=SafeNet
library=C:\Windows\System32\eTPKCS11.dll
slot=4
采纳答案by Bruno
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.
SunMSCAPI 实现并不完美(例如,如果您拥有具有相同“友好名称”的证书,则某些证书将无法访问,因为它也是用于密钥库别名的唯一密钥)。我不确定它与硬件令牌的配合情况。
Since your token seems to support PKCS#11, you might as well make use of the Oracle JRE's direct support for PKCS11
keystores.
由于您的令牌似乎支持 PKCS#11,您不妨利用 Oracle JRE 对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, [...]".
本质上,您的令牌驱动程序应该带有一个实现 PKCS#11 接口的 DLL,并且您需要将 Java 指向它(如 PKCS#11 指南中所述)。为了获得更大的灵活性,动态安装提供程序可能更方便(请参阅以“动态安装提供程序,[...]”开头的段落。
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.)
如果您添加"slot=...\n"
到配置字符串并使用循环来尝试各种值,直到它停止抛出异常,它可能会起作用。您可能需要删除失败的安全提供程序,或者也更改名称。(我并不是说这是一种干净的方法。)
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.
您的回调处理程序可能是“ new com.sun.security.auth.callback.DialogCallbackHandler()
”。我通常不建议使用com.sun.*
或sun.*
包中的任何一个,因为它们不是公共 Java API 的一部分,但是您在sun.security.pkcs11.SunPKCS11
这里使用,因此您的代码无论如何都将绑定到这个 JRE 家族。
回答by ARAVIND
Try the below code to get the keystore from usb token using java
尝试使用以下代码使用 java 从 USB 令牌获取密钥库
class Test { public static void main(String args[]) throws IOException, GeneralSecurityException, DocumentException, CertificateVerificationException{ // Create instance of SunPKCS11 provider
class Test { public static void main(String args[]) throws IOException, GeneralSecurityException, DocumentException, CertificateVerificationException{ // 创建 SunPKCS11 提供程序的实例
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);
}
}
}