View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2009  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  package davmail.http;
20  
21  import davmail.BundleMessage;
22  import davmail.Settings;
23  import davmail.ui.AcceptCertificateDialog;
24  import davmail.ui.tray.DavGatewayTray;
25  
26  import javax.net.ssl.TrustManager;
27  import javax.net.ssl.TrustManagerFactory;
28  import javax.net.ssl.X509TrustManager;
29  import java.awt.*;
30  import java.io.BufferedReader;
31  import java.io.IOException;
32  import java.io.InputStreamReader;
33  import java.security.*;
34  import java.security.cert.CertificateEncodingException;
35  import java.security.cert.CertificateException;
36  import java.security.cert.X509Certificate;
37  import java.text.SimpleDateFormat;
38  
39  /**
40   * Custom Trust Manager, let user accept or deny.
41   */
42  public class DavGatewayX509TrustManager implements X509TrustManager {
43      private final X509TrustManager standardTrustManager;
44  
45      /**
46       * Create a new custom X509 trust manager.
47       *
48       * @throws NoSuchAlgorithmException on error
49       * @throws KeyStoreException        on error
50       */
51      public DavGatewayX509TrustManager() throws NoSuchAlgorithmException, KeyStoreException {
52          TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
53          factory.init((KeyStore) null);
54          TrustManager[] trustManagers = factory.getTrustManagers();
55          if (trustManagers.length == 0) {
56              throw new NoSuchAlgorithmException("No trust manager found");
57          }
58          this.standardTrustManager = (X509TrustManager) trustManagers[0];
59      }
60  
61      public void checkServerTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException {
62          try {
63              // first try standard Trust Manager
64              this.standardTrustManager.checkServerTrusted(x509Certificates, authType);
65          } catch (CertificateException e) {
66              if ((x509Certificates != null) && (x509Certificates.length > 0)) {
67                  userCheckServerTrusted(x509Certificates);
68              } else {
69                  throw e;
70              }
71  
72          }
73      }
74  
75      public void checkClientTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException {
76          this.standardTrustManager.checkClientTrusted(x509Certificates, authType);
77      }
78  
79      public X509Certificate[] getAcceptedIssuers() {
80          return this.standardTrustManager.getAcceptedIssuers();
81      }
82  
83      protected void userCheckServerTrusted(final X509Certificate[] x509Certificates) throws CertificateException {
84          String acceptedCertificateHash = Settings.getProperty("davmail.server.certificate.hash");
85          String certificateHash = getFormattedHash(x509Certificates[0]);
86          // if user already accepted a certificate,
87          if (acceptedCertificateHash != null && acceptedCertificateHash.length() > 0
88                  && acceptedCertificateHash.equals(certificateHash)) {
89              DavGatewayTray.debug(new BundleMessage("LOG_FOUND_ACCEPTED_CERTIFICATE", acceptedCertificateHash));
90          } else {
91              boolean isCertificateTrusted;
92              if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
93                  // headless or server mode
94                  isCertificateTrusted = isCertificateTrusted(x509Certificates[0]);
95              } else {
96                  isCertificateTrusted = AcceptCertificateDialog.isCertificateTrusted(x509Certificates[0]);
97              }
98              if (!isCertificateTrusted) {
99                  throw new CertificateException("User rejected certificate");
100             }
101             // certificate accepted, store in settings
102             Settings.saveProperty("davmail.server.certificate.hash", certificateHash);
103         }
104     }
105 
106     @SuppressWarnings({"UseOfSystemOutOrSystemErr"})
107     protected boolean isCertificateTrusted(X509Certificate certificate) {
108         BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
109         String answer = null;
110         String yes = BundleMessage.format("UI_ANSWER_YES");
111         String no = BundleMessage.format("UI_ANSWER_NO");
112         StringBuilder buffer = new StringBuilder();
113         buffer.append(BundleMessage.format("UI_SERVER_CERTIFICATE")).append(":\n");
114         buffer.append(BundleMessage.format("UI_ISSUED_TO")).append(": ")
115                 .append(DavGatewayX509TrustManager.getRDN(certificate.getSubjectDN())).append('\n');
116         buffer.append(BundleMessage.format("UI_ISSUED_BY")).append(": ")
117                 .append(getRDN(certificate.getIssuerDN())).append('\n');
118         SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
119         String notBefore = formatter.format(certificate.getNotBefore());
120         buffer.append(BundleMessage.format("UI_VALID_FROM")).append(": ").append(notBefore).append('\n');
121         String notAfter = formatter.format(certificate.getNotAfter());
122         buffer.append(BundleMessage.format("UI_VALID_UNTIL")).append(": ").append(notAfter).append('\n');
123         buffer.append(BundleMessage.format("UI_SERIAL")).append(": ").append(getFormattedSerial(certificate)).append('\n');
124         String sha1Hash = DavGatewayX509TrustManager.getFormattedHash(certificate);
125         buffer.append(BundleMessage.format("UI_FINGERPRINT")).append(": ").append(sha1Hash).append('\n');
126         buffer.append('\n');
127         buffer.append(BundleMessage.format("UI_UNTRUSTED_CERTIFICATE")).append('\n');
128         try {
129             while (!yes.equals(answer) && !no.equals(answer)) {
130                 System.out.println(buffer.toString());
131                 answer = inReader.readLine();
132                 if (answer == null) {
133                     answer = no;
134                 }
135                 answer = answer.toLowerCase();
136             }
137         } catch (IOException e) {
138             System.err.println(e);
139         }
140         return yes.equals(answer);
141     }
142 
143     /**
144      * Get rdn value from principal dn.
145      *
146      * @param principal security principal
147      * @return rdn
148      */
149     public static String getRDN(Principal principal) {
150         String dn = principal.getName();
151         int start = dn.indexOf('=');
152         int end = dn.indexOf(',');
153         if (start >= 0 && end >= 0) {
154             return dn.substring(start + 1, end);
155         } else {
156             return dn;
157         }
158     }
159 
160     /**
161      * Build a formatted certificate serial string.
162      *
163      * @param certificate X509 certificate
164      * @return formatted serial
165      */
166     public static String getFormattedSerial(X509Certificate certificate) {
167         StringBuilder builder = new StringBuilder();
168         String serial = certificate.getSerialNumber().toString(16);
169         for (int i = 0; i < serial.length(); i++) {
170             if (i > 0 && i % 2 == 0) {
171                 builder.append(' ');
172             }
173             builder.append(serial.charAt(i));
174         }
175         return builder.toString().toUpperCase();
176     }
177 
178     /**
179      * Build a formatted hash string.
180      *
181      * @param certificate X509 certificate
182      * @return formatted hash
183      */
184     public static String getFormattedHash(X509Certificate certificate) {
185         String sha1Hash;
186         try {
187             MessageDigest md = MessageDigest.getInstance("SHA1");
188             byte[] digest = md.digest(certificate.getEncoded());
189             sha1Hash = formatHash(digest);
190         } catch (NoSuchAlgorithmException nsa) {
191             sha1Hash = nsa.getMessage();
192         } catch (CertificateEncodingException cee) {
193             sha1Hash = cee.getMessage();
194         }
195         return sha1Hash;
196     }
197 
198     /**
199      * Format byte buffer to a hexadecimal hash string.
200      *
201      * @param buffer byte array
202      * @return hexadecimal hash string
203      */
204     protected static String formatHash(byte[] buffer) {
205         StringBuilder builder = new StringBuilder();
206         for (int i = 0; i < buffer.length; i++) {
207             if (i > 0) {
208                 builder.append(':');
209             }
210             builder.append(Integer.toHexString(buffer[i] & 0xFF));
211         }
212         return builder.toString().toUpperCase();
213     }
214 }