package com.pentacomp.signer; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.KeyStoreSpi; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Enumeration; import java.util.Map; import java.util.TreeMap; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.x500.X500Principal; import org.eclipse.swt.widgets.Shell; public class KeyStoreManager { private KeyStore keyStore; private Map aliasMap; private static Boolean java9SunPKCS11; public static KeyStoreManager initialize(Shell shell) throws GeneralSecurityException { KeyStoreManager ksm = new KeyStoreManager(); ksm.init(shell); return ksm; } private void init(Shell shell) throws GeneralSecurityException { try { if (isWindows()) { this.keyStore = KeyStore.getInstance("Windows-MY"); this.keyStore.load(null, null); fixAliases(); } else { this.keyStore = process(shell); } this.aliasMap = new TreeMap(); Enumeration aliases = this.keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = (String) aliases.nextElement(); System.out.println("! " + alias + ": " + this.keyStore.isKeyEntry(alias) + " / " + this.keyStore.isCertificateEntry(alias)); X509Certificate cert = (X509Certificate) this.keyStore.getCertificate(alias); if (cert != null) { this.aliasMap.put(extractName(cert.getSubjectX500Principal()) + " (" + extractName(cert.getIssuerX500Principal()) + " / " + cert.getSerialNumber().toString(16) + ")", alias); } } } catch (Exception e) { throw new GeneralSecurityException("Błąd podczas ładowania keystore'a", e); } } private KeyStore process(Shell shell) throws Exception { String libpath = System.getProperty("pz.signer.pkcs11.libpath"); long[] slots = listSlots(libpath); for (int i = slots.length - 1; i >= 0; i--) { System.out.println("!!! slot " + slots[i]); try { Provider provider; do { String cfg = "library=" + libpath + "\nname=XaDESAppletCrypto\nslot=" + slots[i] + "\n"; provider = createPkcs11Provider(new ByteArrayInputStream(cfg.getBytes())); } while (provider.getService("KeyStore", "PKCS11") == null); if (Security.getProvider(provider.getName()) != null) { Security.removeProvider(provider.getName()); } Security.addProvider(provider); KeyStore.Builder keystoreBuilder = KeyStore.Builder.newInstance("PKCS11", provider, new KeyStore.CallbackHandlerProtection(new Kallback(shell))); return keystoreBuilder.getKeyStore(); } catch (Exception e) { e.printStackTrace(); if (!e.getMessage().contains("CKR_TOKEN_NOT_RECOGNIZED")) { throw e; } } } return null; } private class Kallback implements CallbackHandler { private char[] pin; private Shell shell; public Kallback(Shell shell) { this.shell = shell; } public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof PasswordCallback) { System.out.println("!!! callback... pin.length='" + ((this.pin != null) ? Integer.valueOf(this.pin.length) : null) + "'"); PasswordCallback pc = (PasswordCallback) callback; if (this.pin == null || this.pin.length == 0) { PinDialog dlg = new PinDialog(this.shell); dlg.open(); if (dlg.getPin() != null) { this.pin = dlg.getPin(); pc.setPassword(this.pin); } } } else { throw new UnsupportedCallbackException(callback, "Unrecognized Callback"); } } } } private void fixAliases() { try { Field field = this.keyStore.getClass().getDeclaredField("keyStoreSpi"); field.setAccessible(true); KeyStoreSpi keyStoreVeritable = (KeyStoreSpi) field.get(this.keyStore); if ("sun.security.mscapi.KeyStore$MY".equals(keyStoreVeritable.getClass().getName())) { field = keyStoreVeritable.getClass().getEnclosingClass().getDeclaredField("entries"); field.setAccessible(true); Collection entries = (Collection) field.get(keyStoreVeritable); for (Object entry : entries) { field = entry.getClass().getDeclaredField("certChain"); field.setAccessible(true); X509Certificate[] certificates = (X509Certificate[]) field.get(entry); String hashCode = Integer.toString(certificates[0].hashCode(), 16); field = entry.getClass().getDeclaredField("alias"); field.setAccessible(true); String alias = (String) field.get(entry); if (!alias.equals(hashCode)) { field.set(entry, alias.concat("-").concat(hashCode)); } } } } catch (Exception e) { e.printStackTrace(); } } private static boolean isWindows() { return System.getProperty("os.name").toUpperCase().contains("WINDOWS"); } private static long[] listSlots(String path) throws Exception { Method[] methods = Class.forName("sun.security.pkcs11.wrapper.PKCS11").getMethods(); for (Method method : methods) { if (method.getName().equals("getInstance")) { Object o = method.invoke(null, new Object[]{path, "C_GetFunctionList", null, Boolean.valueOf(false)}); Method m = o.getClass().getMethod("C_GetSlotList", new Class[]{boolean.class}); if (!m.isAccessible()) { m.setAccessible(true); } return (long[]) m.invoke(o, new Object[]{Boolean.valueOf(true)}); } } return new long[0]; } private static boolean isJava9SunPKCS11() { if (java9SunPKCS11 != null) { return java9SunPKCS11; } java9SunPKCS11 = Boolean.FALSE; try { Provider provider = Security.getProvider("SunPKCS11"); if (provider != null) { provider.getClass().getMethod("configure", String.class); java9SunPKCS11 = Boolean.TRUE; } } catch (NoSuchMethodException ignore) { } return java9SunPKCS11; } private static Provider createPkcs11ProviderJava9(InputStream configStream) throws Exception { Provider provider = Security.getProvider("SunPKCS11"); Method configure = provider.getClass().getMethod("configure", String.class); File configFile = File.createTempFile("pkcs11", ".cfg"); configFile.deleteOnExit(); Files.copy(configStream, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING); return (Provider) configure.invoke(provider, configFile.getAbsolutePath()); } private static Provider createPkcs11ProviderJava8(InputStream configStream) throws Exception { Class providerClass = Class.forName("sun.security.pkcs11.SunPKCS11"); Constructor ctor = providerClass.getConstructor(new Class[]{InputStream.class}); return (Provider) ctor.newInstance(new Object[]{configStream}); } private static Provider createPkcs11Provider(InputStream configStream) throws Exception { if (isJava9SunPKCS11()) { return createPkcs11ProviderJava9(configStream); } else { return createPkcs11ProviderJava8(configStream); } } private static String extractName(X500Principal p) { String[] parts = p.getName().split(","); for (String part : parts) { String[] el = part.split("="); if ("cn".equals(el[0].trim().toLowerCase())) { return el[1].trim().replaceAll("\\\\00", ""); } } return p.getName().replaceAll("\\\\00", ""); } public Collection getAliasLabels() { return this.aliasMap.keySet(); } public String getAlias(String text) { return (String) this.aliasMap.get(text); } public PrivateKey getPrivateKey(String alias) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { return (PrivateKey) this.keyStore.getKey(alias, null); } public X509Certificate getCertificate(String alias) throws KeyStoreException { return (X509Certificate) this.keyStore.getCertificate(alias); } }