Как получить KeyStore с токена usb в Java
У меня уже есть SafeNet 5100 eToken с действующим сертификатом, который я использую для доступа к веб-приложению от моей компании, которому оно требуется (многофакторная аутентификация).
Я создаю настольное приложение для доступа к этому сайту. Я уже могу добавить сертификат сайта в 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 {
// 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();
}
Когда я запускаю этот код, я получаю:
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)
И я получаю эту ошибку, когда я набираю правильный пароль для токена и когда я набираю неправильный пароль, поэтому я думаю, что никогда не передаю ему пароль правильным способом.
Почему я получаю исключение?
--------- Обновлено ---------
Я создал файл конфигурации со следующей информацией, указывающей на мою библиотеку PKCS11.dll:
name = Aladdin
library = C:/WINDOWS/system32/eTPKCS11.dll
И в основной функции я добавляю:
SunPKCS11 newProvider = new SunPKCS11("u:/Certificados/etpkcs11.cfg");
Provider a = newProvider;
Security.addProvider(a);
KeyStore ks;
try {
ks = KeyStore.getInstance("PKCS11");
...
}
И теперь я получаю это как ошибку:
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
Я также попытался изменить Keystore.getInstance, чтобы:
ks = KeyStore.getInstance("PKCS11", a);
и тогда я получаю эту другую ошибку:
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
--------- Обновлено 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");
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();
}
И я должен или установить аргумент отладки в конфигурации запуска:
-Djava.security.debug=sunpkcs11
Или установите слот в файле.cfg:
name=SafeNet
library=C:\Windows\System32\eTPKCS11.dll
slot=4
2 ответа
Реализация SunMSCAPI не идеальна (например, если у вас есть сертификаты с тем же "дружественным именем", некоторые из них будут недоступны, так как это также уникальный ключ, используемый для псевдонима хранилища ключей). Я не уверен, насколько хорошо он работает с аппаратными токенами.
Поскольку ваш токен поддерживает PKCS#11, вы также можете использовать прямую поддержку Oracle JRE для PKCS11
хранилища ключей.
По сути, ваш токен-драйвер должен поставляться с DLL, реализующей интерфейс PKCS#11, и вам нужно указать на него Java (как описано в руководстве PKCS#11). Для большей гибкости может быть удобнее установить провайдера динамически (см. Параграф, начинающийся с "Чтобы установить провайдера динамически, [...]".
После ваших комментариев, возможно, вы могли бы использовать метод проб и ошибок (перехватывая эти исключения), чтобы найти правильный слот. Вместо использования файла конфигурации вы можете загрузить конфигурацию из строки.
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());
Если вы добавите "slot=...\n"
к строке конфигурации и используйте цикл для проверки различных значений, пока он не перестанет генерировать исключения, это может работать. Возможно, вам придется удалить провайдеров безопасности, если это не удалось, или изменить имя. (Я не предполагаю, что это чистый способ сделать это.)
Кстати, если вы не хотите жестко кодировать свой пароль (конечно!) Или загружать его из какого-либо файла конфигурации, вы можете использовать средство обратного вызова, например:
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);
Ваш обработчик обратного вызова может бытьnew com.sun.security.auth.callback.DialogCallbackHandler()
". Я бы вообще не советовал использовать com.sun.*
или же sun.*
пакеты, так как они не являются частью общедоступного API Java, но вы используете sun.security.pkcs11.SunPKCS11
здесь, так что ваш код в любом случае будет связан с этим семейством JRE.
Попробуйте приведенный ниже код, чтобы получить хранилище ключей из токена USB с использованием Java
Класс Test { public static void main(String args[]) выдает 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);
}
}
}