source: 3thparty/jupload/src/main/java/wjhk/jupload2/upload/helper/InteractiveTrustManager.java @ 3951

Revision 3951, 25.2 KB checked in by alexandrecorreia, 13 years ago (diff)

Ticket #1709 - Adicao de codigo fonte java do componente jupload

Line 
1//
2// $Id: InteractiveTrustManager.java 918 2010-01-08 22:21:44Z etienne_sf $
3//
4// jupload - A file upload applet.
5//
6// Copyright 2007 The JUpload Team
7//
8// Created: 30.05.2007
9// Creator: felfert
10// Last modified: $Date: 2010-01-08 20:21:44 -0200 (Sex, 08 Jan 2010) $
11//
12// This program is free software; you can redistribute it and/or modify
13// it under the terms of the GNU General Public License as published by
14// the Free Software Foundation; either version 2 of the License, or
15// (at your option) any later version.
16//
17// This program is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20// GNU General Public License for more details.
21//
22// You should have received a copy of the GNU General Public License
23// along with this program; if not, write to the Free Software
24// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25
26package wjhk.jupload2.upload.helper;
27
28import java.awt.BorderLayout;
29import java.io.File;
30import java.io.FileInputStream;
31import java.io.FileOutputStream;
32import java.io.IOException;
33import java.security.KeyStore;
34import java.security.KeyStoreException;
35import java.security.MessageDigest;
36import java.security.NoSuchAlgorithmException;
37import java.security.UnrecoverableKeyException;
38import java.security.cert.CertificateException;
39import java.security.cert.CertificateExpiredException;
40import java.security.cert.CertificateNotYetValidException;
41import java.security.cert.X509Certificate;
42import java.util.Iterator;
43import java.util.StringTokenizer;
44import java.util.Vector;
45
46import javax.crypto.BadPaddingException;
47import javax.net.ssl.KeyManager;
48import javax.net.ssl.KeyManagerFactory;
49import javax.net.ssl.TrustManager;
50import javax.net.ssl.TrustManagerFactory;
51import javax.net.ssl.X509TrustManager;
52import javax.security.auth.callback.Callback;
53import javax.security.auth.callback.CallbackHandler;
54import javax.security.auth.callback.PasswordCallback;
55import javax.security.auth.callback.UnsupportedCallbackException;
56import javax.swing.BorderFactory;
57import javax.swing.JButton;
58import javax.swing.JEditorPane;
59import javax.swing.JLabel;
60import javax.swing.JOptionPane;
61import javax.swing.JPanel;
62import javax.swing.JPasswordField;
63
64import wjhk.jupload2.policies.UploadPolicy;
65
66/**
67 * An implementation of {@link javax.net.ssl.X509TrustManager} which can operate
68 * in different modes. If mode is {@link #NONE}, then any server certificate is
69 * accepted and no certificate-based client authentication is performed. If mode
70 * is SERVER, then server certificates are verified and if verification is
71 * unsuccessful, a dialog is presented to the user, which allows accepting a
72 * certificate temporarily or permanently. If mode is CLIENT, then
73 * certificate-based client authentication is performed. Finally, there is a
74 * mode STRICT, which combines both SERVER and CLIENT modes.
75 *
76 * @author felfert
77 */
78public class InteractiveTrustManager implements X509TrustManager,
79        CallbackHandler {
80
81    /**
82     * Mode for accepting any certificate.
83     */
84    public final static int NONE = 0;
85
86    /**
87     * Mode for verifying server certificate chains.
88     */
89    public final static int SERVER = 1;
90
91    /**
92     * Mode for using client certificates.
93     */
94    public final static int CLIENT = 2;
95
96    /**
97     * Mode for performing both client authentication and server cert
98     * verification.
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     * Absolute path of the truststore to use.
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     * The truststore for validation of server certificates
128     */
129    private KeyStore ts = null;
130
131    /**
132     * The keystore for client certificates.
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     * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
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                // prompt the user for sensitive information
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     * Create a new instance.
175     *
176     * @param p The UploadPolicy to use for this instance.
177     * @param hostname
178     * @param passwd An optional password for the truststore.
179     * @throws NoSuchAlgorithmException
180     * @throws KeyStoreException
181     * @throws CertificateException
182     * @throws IllegalArgumentException
183     * @throws UnrecoverableKeyException
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                // The default password as distributed by Sun.
194                passwd = "changeit";
195            this.tsname = System.getProperty(TSKEY);
196            if (null == this.tsname) {
197                // The default system-wide truststore
198                this.tsname = System.getProperty("java.home") + File.separator
199                        + "lib" + File.separator + "security" + File.separator
200                        + "cacerts";
201                // If the a user-specific truststore exists, it has precedence.
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            // Initialize the keystore only once, so that we can
210            // reuse it during the session
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                        // need it later for eventual storing.
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     * Retrieve key managers.
281     *
282     * @return The current array of key managers.
283     */
284    public KeyManager[] getKeyManagers() {
285        return ((this.mode & CLIENT) == 0) ? null : this.kmf.getKeyManagers();
286    }
287
288    /**
289     * Retrieve trust managers.
290     *
291     * @return The current array of trust managers
292     */
293    public X509TrustManager[] getTrustManagers() {
294        return new X509TrustManager[] {
295            this
296        };
297    }
298
299    /**
300     * As this class is used on the client side only, The implementation of this
301     * method does nothing.
302     *
303     * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[],
304     *      java.lang.String)
305     */
306    public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
307        // Nothing to do.
308    }
309
310    /**
311     * Format a DN. This method formats a DN (Distinguished Name) string as
312     * returned from {@link javax.security.auth.x500.X500Principal#getName()} to
313     * HTML table columns.
314     *
315     * @param dn The DN to format.
316     * @param cn An optional CN (Common Name) to match against the CN in the DN.
317     *            If this parameter is non null and the CN, encoded in the DN
318     *            does not match the CN specified, it is considered an error and
319     *            the CN is printed accordingly (red).
320     * @param reason A vector of error-strings. If the CN-comparison fails, an
321     *            explanation is added to this vector.
322     * @return A string, containing the HTML code rendering the given DN in a
323     *         table.
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        // PLAF hassle. The PLAF renders controls with different text colors,
408        // but
409        // does not set SystemColor.controlText. So we create a dummy button and
410        // retrieve its text color.
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                // Add certificate to truststore
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                    // Save truststore for permanent acceptance.
538                    // If not explicitely specified, we save to a
539                    // user-truststore.
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                                // New truststore, get a new password.
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                            // Must re-initialize TrustManagerFactory
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     * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[],
582     *      java.lang.String)
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                        // try next
601                    }
602                }
603            }
604
605            // If we get here, the certificate could not be verified.
606            // Ask the user what to do.
607            CertDialog(chain[0]);
608        }
609        // In dummy mode: Nothing to do.
610    }
611
612    /**
613     * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
614     */
615    public X509Certificate[] getAcceptedIssuers() {
616        System.out.println("getAcceptedIssuers");
617        return new X509Certificate[0];
618    }
619}
Note: See TracBrowser for help on using the repository browser.