1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package wjhk.jupload2.upload.helper;
27
28 import java.awt.BorderLayout;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.security.MessageDigest;
36 import java.security.NoSuchAlgorithmException;
37 import java.security.UnrecoverableKeyException;
38 import java.security.cert.CertificateException;
39 import java.security.cert.CertificateExpiredException;
40 import java.security.cert.CertificateNotYetValidException;
41 import java.security.cert.X509Certificate;
42 import java.util.Iterator;
43 import java.util.StringTokenizer;
44 import java.util.Vector;
45
46 import javax.crypto.BadPaddingException;
47 import javax.net.ssl.KeyManager;
48 import javax.net.ssl.KeyManagerFactory;
49 import javax.net.ssl.TrustManager;
50 import javax.net.ssl.TrustManagerFactory;
51 import javax.net.ssl.X509TrustManager;
52 import javax.security.auth.callback.Callback;
53 import javax.security.auth.callback.CallbackHandler;
54 import javax.security.auth.callback.PasswordCallback;
55 import javax.security.auth.callback.UnsupportedCallbackException;
56 import javax.swing.BorderFactory;
57 import javax.swing.JButton;
58 import javax.swing.JEditorPane;
59 import javax.swing.JLabel;
60 import javax.swing.JOptionPane;
61 import javax.swing.JPanel;
62 import javax.swing.JPasswordField;
63
64 import wjhk.jupload2.policies.UploadPolicy;
65
66
67
68
69
70
71
72
73
74
75
76
77
78 public class InteractiveTrustManager implements X509TrustManager,
79 CallbackHandler {
80
81
82
83
84 public final static int NONE = 0;
85
86
87
88
89 public final static int SERVER = 1;
90
91
92
93
94 public final static int CLIENT = 2;
95
96
97
98
99
100 public final static int STRICT = SERVER + CLIENT;
101
102 private UploadPolicy uploadPolicy;
103
104 private int mode = STRICT;
105
106 private String hostname;
107
108 private final static String TS = ".truststore";
109
110 private final static String TSKEY = "javax.net.ssl.trustStore";
111
112 private final static String USERTS = System.getProperty("user.home")
113 + File.separator + TS;
114
115
116
117
118 private String tsname = null;
119
120 private String tspasswd = null;
121
122 private TrustManagerFactory tmf = null;
123
124 private KeyManagerFactory kmf = null;
125
126
127
128
129 private KeyStore ts = null;
130
131
132
133
134 private KeyStore ks = null;
135
136 private String getPassword(String storename) {
137 JPasswordField pwf = new JPasswordField(16);
138 JLabel l = new JLabel(this.uploadPolicy.getLocalizedString(
139 "itm_prompt_pass", storename));
140 l.setLabelFor(pwf);
141 JPanel p = new JPanel(new BorderLayout(10, 0));
142 p.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
143 p.add(l, BorderLayout.LINE_START);
144 p.add(pwf, BorderLayout.LINE_END);
145 int res = JOptionPane.showConfirmDialog(null, p, this.uploadPolicy
146 .getLocalizedString("itm_title_pass", storename),
147 JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
148 if (res == JOptionPane.OK_OPTION)
149 return new String(pwf.getPassword());
150 return null;
151 }
152
153
154
155
156 public void handle(Callback[] callbacks)
157 throws UnsupportedCallbackException {
158
159 for (int i = 0; i < callbacks.length; i++) {
160 if (callbacks[i] instanceof PasswordCallback) {
161
162 PasswordCallback pc = (PasswordCallback) callbacks[i];
163 String pw = getPassword(pc.getPrompt());
164 pc.setPassword((pw == null) ? null : pw.toCharArray());
165 pw = null;
166 } else {
167 throw new UnsupportedCallbackException(callbacks[i],
168 "Unrecognized Callback");
169 }
170 }
171 }
172
173
174
175
176
177
178
179
180
181
182
183
184
185 public InteractiveTrustManager(UploadPolicy p, String hostname,
186 String passwd) throws NoSuchAlgorithmException, KeyStoreException,
187 CertificateException, IllegalArgumentException,
188 UnrecoverableKeyException {
189 this.mode = p.getSslVerifyCert();
190 this.uploadPolicy = p;
191 if ((this.mode & SERVER) != 0) {
192 if (null == passwd)
193
194 passwd = "changeit";
195 this.tsname = System.getProperty(TSKEY);
196 if (null == this.tsname) {
197
198 this.tsname = System.getProperty("java.home") + File.separator
199 + "lib" + File.separator + "security" + File.separator
200 + "cacerts";
201
202 if (new File(USERTS).exists())
203 this.tsname = USERTS;
204 }
205 if (null == hostname || hostname.length() == 0)
206 throw new IllegalArgumentException(
207 "hostname may not be null or empty.");
208 this.hostname = hostname;
209
210
211 if (null == this.ts) {
212 this.ts = KeyStore.getInstance(KeyStore.getDefaultType());
213 while (true) {
214 try {
215 FileInputStream is = new FileInputStream(this.tsname);
216 this.ts.load(is, passwd.toCharArray());
217 is.close();
218
219 this.tspasswd = passwd;
220 break;
221 } catch (IOException e) {
222 if (e
223 .getMessage()
224 .equals(
225 "Keystore was tampered with, or password was incorrect")) {
226 passwd = getPassword(this.uploadPolicy
227 .getLocalizedString("itm_tstore"));
228 if (null != passwd)
229 continue;
230 }
231 throw new KeyStoreException("Could not load truststore");
232 }
233 }
234 }
235 this.tmf = TrustManagerFactory.getInstance(TrustManagerFactory
236 .getDefaultAlgorithm());
237 this.tmf.init(this.ts);
238 }
239 if ((this.mode & CLIENT) != 0) {
240 String ksname = System.getProperty("javax.net.ssl.keyStore");
241 if (null == ksname)
242 ksname = System.getProperty("user.home") + File.separator
243 + ".keystore";
244 String cpass = "changeit";
245 File f = new File(ksname);
246 if (!(f.exists() && f.isFile()))
247 throw new KeyStoreException("Keystore " + ksname
248 + " does not exist.");
249 if (null == this.kmf) {
250 String kstype = ksname.toLowerCase().endsWith(".p12") ? "PKCS12"
251 : KeyStore.getDefaultType();
252 this.ks = KeyStore.getInstance(kstype);
253 while (true) {
254 try {
255 FileInputStream is = new FileInputStream(ksname);
256 this.ks.load(is, cpass.toCharArray());
257 is.close();
258 break;
259 } catch (IOException e) {
260 if ((e.getCause() instanceof BadPaddingException)
261 || (e.getMessage()
262 .equals("Keystore was tampered with, or password was incorrect"))) {
263 cpass = getPassword("Keystore");
264 if (null != cpass)
265 continue;
266 }
267 throw new KeyStoreException("Could not load keystore: "
268 + e.getMessage());
269 }
270 }
271 this.kmf = KeyManagerFactory.getInstance(KeyManagerFactory
272 .getDefaultAlgorithm());
273 this.kmf.init(this.ks, cpass.toCharArray());
274 }
275 }
276
277 }
278
279
280
281
282
283
284 public KeyManager[] getKeyManagers() {
285 return ((this.mode & CLIENT) == 0) ? null : this.kmf.getKeyManagers();
286 }
287
288
289
290
291
292
293 public X509TrustManager[] getTrustManagers() {
294 return new X509TrustManager[] {
295 this
296 };
297 }
298
299
300
301
302
303
304
305
306 public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
307
308 }
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 private String formatDN(String dn, String cn, Vector<String> reason) {
326 StringBuffer ret = new StringBuffer();
327 StringTokenizer t = new StringTokenizer(dn, ",");
328 while (t.hasMoreTokens()) {
329 String tok = t.nextToken();
330 while (tok.endsWith("\\"))
331 tok += t.nextToken();
332 String kv[] = tok.split("=", 2);
333 if (kv.length == 2) {
334 if (kv[0].equals("C"))
335 ret.append("<tr><td>").append(
336 this.uploadPolicy.getLocalizedString("itm_cert_C"))
337 .append("</td><td>").append(kv[1]).append(
338 "</td></tr>\n");
339 if (kv[0].equals("CN")) {
340 boolean ok = true;
341 if (null != cn)
342 ok = cn.equals(kv[1]);
343 ret.append("<tr><td>")
344 .append(
345 this.uploadPolicy
346 .getLocalizedString("itm_cert_CN"))
347 .append("</td><td");
348 ret.append(ok ? ">" : " class=\"err\">").append(kv[1])
349 .append("</td></tr>\n");
350 if (!ok)
351 reason.add(this.uploadPolicy.getLocalizedString(
352 "itm_reason_cnmatch", cn));
353 }
354 if (kv[0].equals("L"))
355 ret.append("<tr><td>").append(
356 this.uploadPolicy.getLocalizedString("itm_cert_L"))
357 .append("</td><td>").append(kv[1]).append(
358 "</td></tr>\n");
359 if (kv[0].equals("ST"))
360 ret.append("<tr><td>")
361 .append(
362 this.uploadPolicy
363 .getLocalizedString("itm_cert_ST"))
364 .append("</td><td>").append(kv[1]).append(
365 "</td></tr>\n");
366 if (kv[0].equals("O"))
367 ret.append("<tr><td>").append(
368 this.uploadPolicy.getLocalizedString("itm_cert_O"))
369 .append("</td><td>").append(kv[1]).append(
370 "</td></tr>\n");
371 if (kv[0].equals("OU"))
372 ret.append("<tr><td>")
373 .append(
374 this.uploadPolicy
375 .getLocalizedString("itm_cert_OU"))
376 .append("</td><td>").append(kv[1]).append(
377 "</td></tr>\n");
378 }
379 }
380 return ret.toString();
381 }
382
383 private void CertDialog(X509Certificate c) throws CertificateException {
384 int i;
385 boolean expired = false;
386 boolean notyet = false;
387 Vector<String> reason = new Vector<String>();
388 reason.add(this.uploadPolicy.getLocalizedString("itm_reason_itrust"));
389 try {
390 c.checkValidity();
391 } catch (CertificateExpiredException e1) {
392 expired = true;
393 reason.add(this.uploadPolicy
394 .getLocalizedString("itm_reason_expired"));
395 } catch (CertificateNotYetValidException e2) {
396 notyet = true;
397 reason.add(this.uploadPolicy
398 .getLocalizedString("itm_reason_notyet"));
399 }
400
401 StringBuffer msg = new StringBuffer();
402 msg.append("<html><head>");
403 msg.append("<style type=\"text/css\">\n");
404 msg.append("td, th, p, body { ");
405 msg.append("font-family: Arial, Helvetica, sans-serif; ");
406 msg.append("font-size: 12pt; ");
407
408
409
410
411 Integer ii = Integer
412 .valueOf(new JButton(".").getForeground().getRGB() & 0x00ffffff);
413 msg.append("color: ").append(String.format("#%06x", ii)).append(" }\n");
414 msg.append("th { text-align: left; }\n");
415 msg.append("td { margin-left: 20; }\n");
416 msg.append(".err { color: red; }\n");
417 msg.append("</style>\n");
418 msg.append("</head><body>");
419 msg.append("<h3>").append(
420 this.uploadPolicy.getLocalizedString("itm_fail_verify"))
421 .append("</h3>");
422 msg.append("<h4>").append(
423 this.uploadPolicy.getLocalizedString("itm_cert_details"))
424 .append("</h4>");
425 msg.append("<table>");
426 msg.append("<tr><th colspan=2>").append(
427 this.uploadPolicy.getLocalizedString("itm_cert_subject"))
428 .append("</th></tr>");
429 msg.append(formatDN(c.getSubjectX500Principal().getName(),
430 this.hostname, reason));
431 msg.append("<tr><td>").append(
432 this.uploadPolicy.getLocalizedString("itm_cert_nbefore"))
433 .append("</td>");
434 msg.append(notyet ? "<td class=\"err\">" : "<td>").append(
435 c.getNotBefore()).append("</td></tr>\n");
436 msg.append("<tr><td>").append(
437 this.uploadPolicy.getLocalizedString("itm_cert_nafter"))
438 .append("</td>");
439 msg.append(expired ? "<td class=\"err\">" : "<td>").append(
440 c.getNotAfter()).append("</td></tr>\n");
441 msg.append("<tr><td>").append(
442 this.uploadPolicy.getLocalizedString("itm_cert_serial"))
443 .append("</td><td>");
444 msg.append(c.getSerialNumber());
445 msg.append("</td></tr>\n");
446 msg.append("<tr><td>")
447 .append(
448 this.uploadPolicy.getLocalizedString("itm_cert_fprint",
449 "SHA1")).append("</td><td>");
450 MessageDigest d;
451 StringBuffer fp = new StringBuffer();
452 try {
453 d = MessageDigest.getInstance("SHA1");
454 } catch (NoSuchAlgorithmException e) {
455 throw new CertificateException(
456 "Unable to calculate certificate SHA1 fingerprint: "
457 + e.getMessage());
458 }
459 byte[] sha1sum = d.digest(c.getEncoded());
460 for (i = 0; i < sha1sum.length; i++) {
461 if (i > 0)
462 fp.append(":");
463 fp.append(Integer.toHexString((sha1sum[i] >> 4) & 0x0f));
464 fp.append(Integer.toHexString(sha1sum[i] & 0x0f));
465 }
466 msg.append(fp).append("</td></tr>\n");
467 fp.setLength(0);
468 msg.append("<tr><td>").append(
469 this.uploadPolicy.getLocalizedString("itm_cert_fprint", "MD5"))
470 .append("</td><td>");
471 try {
472 d = MessageDigest.getInstance("MD5");
473 } catch (NoSuchAlgorithmException e) {
474 throw new CertificateException(
475 "Unable to calculate certificate MD5 fingerprint: "
476 + e.getMessage());
477 }
478 byte[] md5sum = d.digest(c.getEncoded());
479 for (i = 0; i < md5sum.length; i++) {
480 if (i > 0)
481 fp.append(":");
482 fp.append(Integer.toHexString((md5sum[i] >> 4) & 0x0f));
483 fp.append(Integer.toHexString(md5sum[i] & 0x0f));
484 }
485 msg.append(fp).append("</td></tr>\n");
486 msg.append("</table><table>");
487 msg.append("<tr><th colspan=2>").append(
488 this.uploadPolicy.getLocalizedString("itm_cert_issuer"))
489 .append("</th></tr>");
490 msg
491 .append(formatDN(c.getIssuerX500Principal().getName(), null,
492 reason));
493 msg.append("</table>");
494 msg.append("<p><b>").append(
495 this.uploadPolicy.getLocalizedString("itm_reasons")).append(
496 "</b><br><ul>");
497 Iterator<String> it = reason.iterator();
498 while (it.hasNext()) {
499 msg.append("<li>" + it.next() + "</li>\n");
500 }
501 msg.append("</ul></p>");
502 msg.append("<p><b>").append(
503 this.uploadPolicy.getLocalizedString("itm_accept_prompt"))
504 .append("</b></p>");
505 msg.append("</body></html>");
506
507 JPanel p = new JPanel();
508 p.setLayout(new BorderLayout());
509 JEditorPane ep = new JEditorPane("text/html", msg.toString());
510 ep.setEditable(false);
511 ep.setBackground(p.getBackground());
512 p.add(ep, BorderLayout.CENTER);
513
514 String no = this.uploadPolicy.getLocalizedString("itm_accept_no");
515 int ans = JOptionPane.showOptionDialog(null, p,
516 "SSL Certificate Alert", JOptionPane.YES_NO_CANCEL_OPTION,
517 JOptionPane.WARNING_MESSAGE, null, new String[] {
518 this.uploadPolicy
519 .getLocalizedString("itm_accept_always"),
520 this.uploadPolicy.getLocalizedString("itm_accept_now"),
521 no
522 }, no);
523 switch (ans) {
524 case JOptionPane.CANCEL_OPTION:
525 case JOptionPane.CLOSED_OPTION:
526 throw new CertificateException("Server certificate rejected.");
527 case JOptionPane.NO_OPTION:
528 case JOptionPane.YES_OPTION:
529
530 try {
531 this.ts.setCertificateEntry(fp.toString(), c);
532 } catch (KeyStoreException e) {
533 throw new CertificateException(
534 "Unable to add certificate: " + e.getMessage());
535 }
536 if (ans == JOptionPane.YES_OPTION) {
537
538
539
540 if (null == System.getProperty(TSKEY))
541 this.tsname = USERTS;
542 while (true) {
543 try {
544 File f = new File(this.tsname);
545 boolean old = false;
546 if (f.exists()) {
547 if (!f.renameTo(new File(this.tsname + ".old")))
548 throw new IOException(
549 "Could not rename truststore");
550 old = true;
551 } else {
552
553 this.tspasswd = this
554 .getPassword(this.uploadPolicy
555 .getLocalizedString("itm_new_tstore"));
556 if (null == this.tspasswd)
557 this.tspasswd = "changeit";
558 }
559 FileOutputStream os = new FileOutputStream(
560 this.tsname);
561 this.ts.store(os, this.tspasswd.toCharArray());
562 os.close();
563 if (old && (!f.delete()))
564 throw new IOException(
565 "Could not delete old truststore");
566
567 this.tmf.init(this.ts);
568 System.out.println("Saved cert to " + this.tsname);
569 break;
570 } catch (Exception e) {
571 if (this.tsname.equals(USERTS))
572 throw new CertificateException(e);
573 this.tsname = USERTS;
574 }
575 }
576 }
577 }
578 }
579
580
581
582
583
584 public void checkServerTrusted(X509Certificate[] chain, String authType)
585 throws CertificateException {
586 if ((this.mode & SERVER) != 0) {
587 if (null == chain || chain.length == 0)
588 throw new IllegalArgumentException(
589 "Certificate chain is null or empty");
590
591 int i;
592 TrustManager[] mgrs = this.tmf.getTrustManagers();
593 for (i = 0; i < mgrs.length; i++) {
594 if (mgrs[i] instanceof X509TrustManager) {
595 X509TrustManager m = (X509TrustManager) (mgrs[i]);
596 try {
597 m.checkServerTrusted(chain, authType);
598 return;
599 } catch (Exception e) {
600
601 }
602 }
603 }
604
605
606
607 CertDialog(chain[0]);
608 }
609
610 }
611
612
613
614
615 public X509Certificate[] getAcceptedIssuers() {
616 System.out.println("getAcceptedIssuers");
617 return new X509Certificate[0];
618 }
619 }