如何从Java中的usb令牌获取KeyStore [英] How to get KeyStore from usb token in Java

查看:300
本文介绍了如何从Java中的usb令牌获取KeyStore的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个SafeNet 5100 eToken已经有一个有效的证书,我用它来访问我公司需要它的Web应用程序(多因素身份验证)。



<我正在创建一个桌面应用程序来访问这个网站。我已经能够将网站的证书添加到 TrustStore ,并将我的证书转换为 KeyStore



到目前为止我得到的是:

  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 {
//加载存在于windows中的密钥库并找到打印别名(只有一个,所以nextElement总是打印相同的信息(我要打开的usb令牌中的证书名称))
ks = KeyStore.getInstance(Windows-MY,SunMSCAPI);
ks.load(null,null);
System.out.println(ks.aliases()。nextElement());
System.out.println(ks.aliases()。nextElement());

//尝试从所有证书中专门加载我的证书并将必要的令牌密码传递给它
InputStream in = IOUtils.toInputStream(ks.aliases()。nextElement(),UTF- 8\" );
System.out.println(in);
ks.load(in,password);

//打印证书以检查我是否有
System.out.println(ks.getCertificate(ks.aliases()。nextElement()));

//获取ssl上下文和密钥管理器工厂
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)新网址(< 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();
}

当我运行此代码时,我得到:

  javax.net.ssl.SSLHandshakeException:在com.sun.net.ssl.internal.ssl.Alerts.getSSLException收到致命警报:decrypt_error 
(未知来源)
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(未知来源)
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.getInpu tStream(未知来源)
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)

当我得到这个错误输入令牌的正确密码,当我输入错误的密码时,我想我从来没有以正确的方式传递密码。



为什么我收到例外?



---------更新了---------



<我创建了一个配置文件,其中包含指向我的PKCS11.dll库的以下信息:

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

在主函数中我添加:

  SunPKCS11 newProvider = new SunPKCS11(u:/Certificados/etpkcs11.cfg); 
提供者a = newProvider;
Security.addProvider(a);

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

现在我把它作为一个错误:

  java.security.KeyStoreException:在java.security.KeyStore.getInstance(未知来源)找不到PKCS11 

at receita.system.monitoring.Ping.main(Ping.java:292)
引起:java.security.NoSuchAlgorithmException:PKCS11 KeyStore在sun.security.jca上不可用
。 GetInstance.getInstance(未知来源)
at java.security.Security.getImpl(Unknown Source)
... 2 more

我还尝试将Keystore.getInstance修改为:

  ks = KeyStore.getInstance (PKCS11,a); 

然后我得到这个不同的错误:

  java.security.KeyStoreException:在java.security.KeyStore.getInstance(未知来源)找不到PKCS11 
来自receita.system.monitoring.Ping.main的
(Ping.java:292)
引起:java.security.NoSuchAlgorithmException:没有这样的算法:提供者的PKCS11 SunPKCS11-Aladdin
at sun.security.jca.GetInstance.getService(Unknown Source)
at sun.security.jca.GetInstance.getInstance(未知来源)
at java.security.Security.getImpl(未知来源)
... 2更多

---------更新2(工作代码)---------



我的最终工作代码是:

  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);
提供者a =提供者MSCAPI;
Security.addProvider(a);

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

ks.load(null,密码);

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)新网址(/ *< 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();
}

我必须在运行配置中设置调试参数:

  -Djava.security.debug = sunpkcs11 

或者设置.cfg文件中的插槽:

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


解决方案

SunMSCAPI实现并不完美(例如,如果您的证书具有相同的友好名称,则某些证书将无法访问,因为它也是用于密钥库别名的唯一密钥) 。我不确定硬件令牌的效果如何。



您的令牌似乎支持PKCS#11 ,您也可以利用Oracle JRE对 PKCS11 keystores



基本上,你的令牌驱动程序应该带有一个实现PKCS#11接口的DLL,你需要将Java指向它(如PKCS#11指南中所述)。为了获得更大的灵活性,动态安装提供程序可能更方便(请参阅以动态安装提供程序,[...] 开头的段落。






根据您的意见,也许您可​​以使用反复试验(通过捕获这些例外)来找到合适的插槽。您可以使用配置文件,而不是使用配置文件从字符串加载配置。

  String password =xxxxxxxxx; 
String storeType =PKCS11;

String configuration =name = OpenSC\\\

+library = /usr/lib/opensc-pkcs11.so\\\
;
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());

如果添加slot = ... \ n到配置字符串并使用循环来尝试各种值,直到它停止抛出异常,它可能会工作。您可能需要删除失败的安全提供程序,或者也更改名称。 (我并不是说这是一个干净的方法。)



顺便说一句,如果你不想硬编码你的密码(当然!)或从某个配置文件加载它,你可以使用这样的回调处理程序:

  KeyStore keyStore = KeyStore.getInstance( storeType,provider); 
LoadStoreParameter param = new LoadStoreParameter(){
@Override
public ProtectionParameter getProtectionParameter(){
return new KeyStore.CallbackHandlerProtection(...把你的回调处理程序放在这里......) ;
}
};
keyStore.load(param);

您的回调处理程序可以是 new com.sun.security.auth。 callback.DialogCallbackHandler()。我通常不建议使用任何 com.sun。* sun。* 包,因为它们是不是公共Java API的一部分,但是你在这里使用 sun.security.pkcs11.SunPKCS11 ,所以无论如何你的代码都将绑定到这个JRE系列。


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.

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.

这篇关于如何从Java中的usb令牌获取KeyStore的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆