DEB packaging of PZ Signer from ePUAP (including Java 9+ fixes)
Jacek Kowalski
2019-09-25 207ea2ca32736f2d08d48410bc9ec6246c8c1c13
commit | author | age
207ea2 1 package com.pentacomp.signer;
JK 2
3 import java.io.ByteArrayInputStream;
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.lang.reflect.Constructor;
8 import java.lang.reflect.Field;
9 import java.lang.reflect.Method;
10 import java.nio.file.Files;
11 import java.nio.file.StandardCopyOption;
12 import java.security.GeneralSecurityException;
13 import java.security.KeyStore;
14 import java.security.KeyStoreException;
15 import java.security.KeyStoreSpi;
16 import java.security.NoSuchAlgorithmException;
17 import java.security.PrivateKey;
18 import java.security.Provider;
19 import java.security.Security;
20 import java.security.UnrecoverableKeyException;
21 import java.security.cert.X509Certificate;
22 import java.util.Collection;
23 import java.util.Enumeration;
24 import java.util.Map;
25 import java.util.TreeMap;
26 import javax.security.auth.callback.Callback;
27 import javax.security.auth.callback.CallbackHandler;
28 import javax.security.auth.callback.PasswordCallback;
29 import javax.security.auth.callback.UnsupportedCallbackException;
30 import javax.security.auth.x500.X500Principal;
31
32 import org.eclipse.swt.widgets.Shell;
33
34 public class KeyStoreManager {
35     private KeyStore keyStore;
36     private Map<String, String> aliasMap;
37     private static Boolean java9SunPKCS11;
38
39     public static KeyStoreManager initialize(Shell shell) throws GeneralSecurityException {
40         KeyStoreManager ksm = new KeyStoreManager();
41         ksm.init(shell);
42         return ksm;
43     }
44
45     private void init(Shell shell) throws GeneralSecurityException {
46         try {
47             if (isWindows()) {
48                 this.keyStore = KeyStore.getInstance("Windows-MY");
49                 this.keyStore.load(null, null);
50                 fixAliases();
51             } else {
52                 this.keyStore = process(shell);
53             }
54
55             this.aliasMap = new TreeMap();
56             Enumeration<String> aliases = this.keyStore.aliases();
57             while (aliases.hasMoreElements()) {
58                 String alias = (String) aliases.nextElement();
59                 System.out.println("! " + alias + ": " + this.keyStore.isKeyEntry(alias) + " / " + this.keyStore.isCertificateEntry(alias));
60                 X509Certificate cert = (X509Certificate) this.keyStore.getCertificate(alias);
61                 if (cert != null) {
62                     this.aliasMap.put(extractName(cert.getSubjectX500Principal()) + " (" + extractName(cert.getIssuerX500Principal()) + " / " + cert.getSerialNumber().toString(16) + ")", alias);
63                 }
64             }
65         } catch (Exception e) {
66             throw new GeneralSecurityException("Błąd podczas ładowania keystore'a", e);
67         }
68     }
69
70     private KeyStore process(Shell shell) throws Exception {
71         String libpath = System.getProperty("pz.signer.pkcs11.libpath");
72         long[] slots = listSlots(libpath);
73         for (int i = slots.length - 1; i >= 0; i--) {
74             System.out.println("!!! slot " + slots[i]);
75             try {
76                 Provider provider;
77                 do {
78                     String cfg = "library=" + libpath + "\nname=XaDESAppletCrypto\nslot=" + slots[i] + "\n";
79                     provider = createPkcs11Provider(new ByteArrayInputStream(cfg.getBytes()));
80                 } while (provider.getService("KeyStore", "PKCS11") == null);
81                 if (Security.getProvider(provider.getName()) != null) {
82                     Security.removeProvider(provider.getName());
83                 }
84                 Security.addProvider(provider);
85                 KeyStore.Builder keystoreBuilder = KeyStore.Builder.newInstance("PKCS11", provider, new KeyStore.CallbackHandlerProtection(new Kallback(shell)));
86                 return keystoreBuilder.getKeyStore();
87             } catch (Exception e) {
88                 e.printStackTrace();
89
90                 if (!e.getMessage().contains("CKR_TOKEN_NOT_RECOGNIZED")) {
91                     throw e;
92                 }
93             }
94         }
95
96         return null;
97     }
98
99     private class Kallback implements CallbackHandler {
100         private char[] pin;
101         private Shell shell;
102
103         public Kallback(Shell shell) {
104             this.shell = shell;
105         }
106
107         public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
108             for (Callback callback : callbacks) {
109                 if (callback instanceof PasswordCallback) {
110                     System.out.println("!!! callback... pin.length='" + ((this.pin != null) ? Integer.valueOf(this.pin.length) : null) + "'");
111                     PasswordCallback pc = (PasswordCallback) callback;
112                     if (this.pin == null || this.pin.length == 0) {
113                         PinDialog dlg = new PinDialog(this.shell);
114                         dlg.open();
115                         if (dlg.getPin() != null) {
116                             this.pin = dlg.getPin();
117                             pc.setPassword(this.pin);
118                         }
119                     }
120                 } else {
121                     throw new UnsupportedCallbackException(callback, "Unrecognized Callback");
122                 }
123             }
124         }
125     }
126
127     private void fixAliases() {
128         try {
129             Field field = this.keyStore.getClass().getDeclaredField("keyStoreSpi");
130             field.setAccessible(true);
131             KeyStoreSpi keyStoreVeritable = (KeyStoreSpi) field.get(this.keyStore);
132             if ("sun.security.mscapi.KeyStore$MY".equals(keyStoreVeritable.getClass().getName())) {
133                 field = keyStoreVeritable.getClass().getEnclosingClass().getDeclaredField("entries");
134                 field.setAccessible(true);
135                 Collection entries = (Collection) field.get(keyStoreVeritable);
136                 for (Object entry : entries) {
137                     field = entry.getClass().getDeclaredField("certChain");
138                     field.setAccessible(true);
139                     X509Certificate[] certificates = (X509Certificate[]) field.get(entry);
140                     String hashCode = Integer.toString(certificates[0].hashCode(), 16);
141                     field = entry.getClass().getDeclaredField("alias");
142                     field.setAccessible(true);
143                     String alias = (String) field.get(entry);
144                     if (!alias.equals(hashCode)) {
145                         field.set(entry, alias.concat("-").concat(hashCode));
146                     }
147                 }
148             }
149         } catch (Exception e) {
150             e.printStackTrace();
151         }
152     }
153
154     private static boolean isWindows() {
155         return System.getProperty("os.name").toUpperCase().contains("WINDOWS");
156     }
157
158     private static long[] listSlots(String path) throws Exception {
159         Method[] methods = Class.forName("sun.security.pkcs11.wrapper.PKCS11").getMethods();
160         for (Method method : methods) {
161             if (method.getName().equals("getInstance")) {
162                 Object o = method.invoke(null, new Object[]{path, "C_GetFunctionList", null, Boolean.valueOf(false)});
163                 Method m = o.getClass().getMethod("C_GetSlotList", new Class[]{boolean.class});
164                 if (!m.isAccessible()) {
165                     m.setAccessible(true);
166                 }
167                 return (long[]) m.invoke(o, new Object[]{Boolean.valueOf(true)});
168             }
169         }
170         return new long[0];
171     }
172
173     private static boolean isJava9SunPKCS11() {
174         if (java9SunPKCS11 != null) {
175             return java9SunPKCS11;
176         }
177         java9SunPKCS11 = Boolean.FALSE;
178         try {
179             Provider provider = Security.getProvider("SunPKCS11");
180             if (provider != null) {
181                 provider.getClass().getMethod("configure", String.class);
182                 java9SunPKCS11 = Boolean.TRUE;
183             }
184         } catch (NoSuchMethodException ignore) {
185         }
186         return java9SunPKCS11;
187     }
188
189     private static Provider createPkcs11ProviderJava9(InputStream configStream) throws Exception {
190         Provider provider = Security.getProvider("SunPKCS11");
191         Method configure = provider.getClass().getMethod("configure", String.class);
192         File configFile = File.createTempFile("pkcs11", ".cfg");
193         configFile.deleteOnExit();
194         Files.copy(configStream, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
195         return (Provider) configure.invoke(provider, configFile.getAbsolutePath());
196     }
197
198     private static Provider createPkcs11ProviderJava8(InputStream configStream) throws Exception {
199         Class providerClass = Class.forName("sun.security.pkcs11.SunPKCS11");
200         Constructor<Provider> ctor = providerClass.getConstructor(new Class[]{InputStream.class});
201         return (Provider) ctor.newInstance(new Object[]{configStream});
202     }
203
204     private static Provider createPkcs11Provider(InputStream configStream) throws Exception {
205         if (isJava9SunPKCS11()) {
206             return createPkcs11ProviderJava9(configStream);
207         } else {
208             return createPkcs11ProviderJava8(configStream);
209         }
210     }
211
212     private static String extractName(X500Principal p) {
213         String[] parts = p.getName().split(",");
214         for (String part : parts) {
215             String[] el = part.split("=");
216             if ("cn".equals(el[0].trim().toLowerCase())) {
217                 return el[1].trim().replaceAll("\\\\00", "");
218             }
219         }
220         return p.getName().replaceAll("\\\\00", "");
221     }
222
223     public Collection<String> getAliasLabels() {
224         return this.aliasMap.keySet();
225     }
226
227     public String getAlias(String text) {
228         return (String) this.aliasMap.get(text);
229     }
230
231     public PrivateKey getPrivateKey(String alias) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
232         return (PrivateKey) this.keyStore.getKey(alias, null);
233     }
234
235     public X509Certificate getCertificate(String alias) throws KeyStoreException {
236         return (X509Certificate) this.keyStore.getCertificate(alias);
237     }
238 }