2009-07-21 04:39:18 -04:00
|
|
|
/*
|
|
|
|
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
|
|
|
* Copyright (C) 2009 Mickael Guessant
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
2008-11-04 08:57:22 -05:00
|
|
|
package davmail.http;
|
|
|
|
|
2009-04-23 10:54:06 -04:00
|
|
|
import davmail.BundleMessage;
|
2011-06-08 15:58:29 -04:00
|
|
|
import davmail.Settings;
|
2008-11-04 08:57:22 -05:00
|
|
|
import davmail.ui.AcceptCertificateDialog;
|
2011-06-08 15:58:29 -04:00
|
|
|
import davmail.ui.tray.DavGatewayTray;
|
2008-11-04 08:57:22 -05:00
|
|
|
|
|
|
|
import javax.net.ssl.TrustManager;
|
|
|
|
import javax.net.ssl.TrustManagerFactory;
|
|
|
|
import javax.net.ssl.X509TrustManager;
|
|
|
|
import java.awt.*;
|
2011-06-08 15:58:29 -04:00
|
|
|
import java.io.BufferedReader;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStreamReader;
|
2009-02-17 09:21:16 -05:00
|
|
|
import java.security.*;
|
2008-11-04 08:57:22 -05:00
|
|
|
import java.security.cert.CertificateEncodingException;
|
|
|
|
import java.security.cert.CertificateException;
|
|
|
|
import java.security.cert.X509Certificate;
|
2009-02-17 09:21:16 -05:00
|
|
|
import java.text.SimpleDateFormat;
|
2008-11-04 08:57:22 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Custom Trust Manager, let user accept or deny.
|
|
|
|
*/
|
|
|
|
public class DavGatewayX509TrustManager implements X509TrustManager {
|
2009-04-16 18:20:30 -04:00
|
|
|
private final X509TrustManager standardTrustManager;
|
2008-11-04 08:57:22 -05:00
|
|
|
|
2009-08-21 05:58:19 -04:00
|
|
|
/**
|
|
|
|
* Create a new custom X509 trust manager.
|
|
|
|
*
|
|
|
|
* @throws NoSuchAlgorithmException on error
|
|
|
|
* @throws KeyStoreException on error
|
|
|
|
*/
|
2008-11-04 08:57:22 -05:00
|
|
|
public DavGatewayX509TrustManager() throws NoSuchAlgorithmException, KeyStoreException {
|
|
|
|
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
|
|
factory.init((KeyStore) null);
|
2009-12-20 05:51:02 -05:00
|
|
|
TrustManager[] trustManagers = factory.getTrustManagers();
|
|
|
|
if (trustManagers.length == 0) {
|
2008-11-04 08:57:22 -05:00
|
|
|
throw new NoSuchAlgorithmException("No trust manager found");
|
|
|
|
}
|
2009-12-20 05:51:02 -05:00
|
|
|
this.standardTrustManager = (X509TrustManager) trustManagers[0];
|
2008-11-04 08:57:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void checkServerTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException {
|
|
|
|
try {
|
|
|
|
// first try standard Trust Manager
|
|
|
|
this.standardTrustManager.checkServerTrusted(x509Certificates, authType);
|
|
|
|
} catch (CertificateException e) {
|
2009-06-09 17:54:24 -04:00
|
|
|
if ((x509Certificates != null) && (x509Certificates.length > 0)) {
|
2008-11-04 08:57:22 -05:00
|
|
|
userCheckServerTrusted(x509Certificates);
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void checkClientTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException {
|
|
|
|
this.standardTrustManager.checkClientTrusted(x509Certificates, authType);
|
|
|
|
}
|
|
|
|
|
2009-04-16 18:20:30 -04:00
|
|
|
public X509Certificate[] getAcceptedIssuers() {
|
2008-11-04 08:57:22 -05:00
|
|
|
return this.standardTrustManager.getAcceptedIssuers();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void userCheckServerTrusted(final X509Certificate[] x509Certificates) throws CertificateException {
|
2009-02-17 09:21:16 -05:00
|
|
|
String acceptedCertificateHash = Settings.getProperty("davmail.server.certificate.hash");
|
|
|
|
String certificateHash = getFormattedHash(x509Certificates[0]);
|
|
|
|
// if user already accepted a certificate,
|
|
|
|
if (acceptedCertificateHash != null && acceptedCertificateHash.length() > 0
|
|
|
|
&& acceptedCertificateHash.equals(certificateHash)) {
|
2009-04-23 10:54:06 -04:00
|
|
|
DavGatewayTray.debug(new BundleMessage("LOG_FOUND_ACCEPTED_CERTIFICATE", acceptedCertificateHash));
|
2009-02-17 09:21:16 -05:00
|
|
|
} else {
|
|
|
|
boolean isCertificateTrusted;
|
2009-06-09 17:54:24 -04:00
|
|
|
if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
|
|
|
|
// headless or server mode
|
2009-02-17 09:21:16 -05:00
|
|
|
isCertificateTrusted = isCertificateTrusted(x509Certificates[0]);
|
2008-11-04 08:57:22 -05:00
|
|
|
} else {
|
2009-02-17 09:21:16 -05:00
|
|
|
isCertificateTrusted = AcceptCertificateDialog.isCertificateTrusted(x509Certificates[0]);
|
|
|
|
}
|
|
|
|
if (!isCertificateTrusted) {
|
|
|
|
throw new CertificateException("User rejected certificate");
|
|
|
|
}
|
|
|
|
// certificate accepted, store in settings
|
|
|
|
Settings.saveProperty("davmail.server.certificate.hash", certificateHash);
|
|
|
|
}
|
|
|
|
}
|
2008-11-04 08:57:22 -05:00
|
|
|
|
2009-04-16 17:52:17 -04:00
|
|
|
@SuppressWarnings({"UseOfSystemOutOrSystemErr"})
|
2009-02-17 09:21:16 -05:00
|
|
|
protected boolean isCertificateTrusted(X509Certificate certificate) {
|
|
|
|
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
|
|
|
|
String answer = null;
|
2009-04-23 10:54:06 -04:00
|
|
|
String yes = BundleMessage.format("UI_ANSWER_YES");
|
|
|
|
String no = BundleMessage.format("UI_ANSWER_NO");
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
|
|
|
buffer.append(BundleMessage.format("UI_SERVER_CERTIFICATE")).append(":\n");
|
|
|
|
buffer.append(BundleMessage.format("UI_ISSUED_TO")).append(": ")
|
|
|
|
.append(DavGatewayX509TrustManager.getRDN(certificate.getSubjectDN())).append('\n');
|
|
|
|
buffer.append(BundleMessage.format("UI_ISSUED_BY")).append(": ")
|
|
|
|
.append(getRDN(certificate.getIssuerDN())).append('\n');
|
|
|
|
SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
|
|
|
|
String notBefore = formatter.format(certificate.getNotBefore());
|
|
|
|
buffer.append(BundleMessage.format("UI_VALID_FROM")).append(": ").append(notBefore).append('\n');
|
|
|
|
String notAfter = formatter.format(certificate.getNotAfter());
|
|
|
|
buffer.append(BundleMessage.format("UI_VALID_UNTIL")).append(": ").append(notAfter).append('\n');
|
|
|
|
buffer.append(BundleMessage.format("UI_SERIAL")).append(": ").append(getFormattedSerial(certificate)).append('\n');
|
|
|
|
String sha1Hash = DavGatewayX509TrustManager.getFormattedHash(certificate);
|
|
|
|
buffer.append(BundleMessage.format("UI_FINGERPRINT")).append(": ").append(sha1Hash).append('\n');
|
|
|
|
buffer.append('\n');
|
|
|
|
buffer.append(BundleMessage.format("UI_UNTRUSTED_CERTIFICATE")).append('\n');
|
2010-09-14 17:13:31 -04:00
|
|
|
try {
|
|
|
|
while (!yes.equals(answer) && !no.equals(answer)) {
|
|
|
|
System.out.println(buffer.toString());
|
2009-09-18 08:17:57 -04:00
|
|
|
answer = inReader.readLine();
|
|
|
|
if (answer == null) {
|
|
|
|
answer = no;
|
|
|
|
}
|
|
|
|
answer = answer.toLowerCase();
|
2008-11-04 08:57:22 -05:00
|
|
|
}
|
2010-09-14 17:13:31 -04:00
|
|
|
} catch (IOException e) {
|
|
|
|
System.err.println(e);
|
2009-02-17 09:21:16 -05:00
|
|
|
}
|
2009-04-23 10:54:06 -04:00
|
|
|
return yes.equals(answer);
|
2009-02-17 09:21:16 -05:00
|
|
|
}
|
|
|
|
|
2009-08-21 05:58:19 -04:00
|
|
|
/**
|
|
|
|
* Get rdn value from principal dn.
|
|
|
|
*
|
|
|
|
* @param principal security principal
|
|
|
|
* @return rdn
|
|
|
|
*/
|
2009-02-17 09:21:16 -05:00
|
|
|
public static String getRDN(Principal principal) {
|
|
|
|
String dn = principal.getName();
|
|
|
|
int start = dn.indexOf('=');
|
|
|
|
int end = dn.indexOf(',');
|
|
|
|
if (start >= 0 && end >= 0) {
|
|
|
|
return dn.substring(start + 1, end);
|
|
|
|
} else {
|
|
|
|
return dn;
|
2008-11-04 08:57:22 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-21 05:58:19 -04:00
|
|
|
/**
|
|
|
|
* Build a formatted certificate serial string.
|
|
|
|
*
|
|
|
|
* @param certificate X509 certificate
|
|
|
|
* @return formatted serial
|
|
|
|
*/
|
2009-02-17 09:21:16 -05:00
|
|
|
public static String getFormattedSerial(X509Certificate certificate) {
|
|
|
|
StringBuilder builder = new StringBuilder();
|
|
|
|
String serial = certificate.getSerialNumber().toString(16);
|
|
|
|
for (int i = 0; i < serial.length(); i++) {
|
|
|
|
if (i > 0 && i % 2 == 0) {
|
|
|
|
builder.append(' ');
|
|
|
|
}
|
|
|
|
builder.append(serial.charAt(i));
|
|
|
|
}
|
|
|
|
return builder.toString().toUpperCase();
|
|
|
|
}
|
|
|
|
|
2009-08-21 05:58:19 -04:00
|
|
|
/**
|
|
|
|
* Build a formatted hash string.
|
|
|
|
*
|
|
|
|
* @param certificate X509 certificate
|
|
|
|
* @return formatted hash
|
|
|
|
*/
|
2009-02-17 09:21:16 -05:00
|
|
|
public static String getFormattedHash(X509Certificate certificate) {
|
2008-11-04 08:57:22 -05:00
|
|
|
String sha1Hash;
|
2009-02-17 09:21:16 -05:00
|
|
|
try {
|
|
|
|
MessageDigest md = MessageDigest.getInstance("SHA1");
|
|
|
|
byte[] digest = md.digest(certificate.getEncoded());
|
|
|
|
sha1Hash = formatHash(digest);
|
|
|
|
} catch (NoSuchAlgorithmException nsa) {
|
|
|
|
sha1Hash = nsa.getMessage();
|
|
|
|
} catch (CertificateEncodingException cee) {
|
|
|
|
sha1Hash = cee.getMessage();
|
|
|
|
}
|
2008-11-04 08:57:22 -05:00
|
|
|
return sha1Hash;
|
|
|
|
}
|
|
|
|
|
2009-08-21 05:58:19 -04:00
|
|
|
/**
|
|
|
|
* Format byte buffer to a hexadecimal hash string.
|
|
|
|
*
|
|
|
|
* @param buffer byte array
|
|
|
|
* @return hexadecimal hash string
|
|
|
|
*/
|
|
|
|
protected static String formatHash(byte[] buffer) {
|
2008-11-04 08:57:22 -05:00
|
|
|
StringBuilder builder = new StringBuilder();
|
|
|
|
for (int i = 0; i < buffer.length; i++) {
|
|
|
|
if (i > 0) {
|
|
|
|
builder.append(':');
|
|
|
|
}
|
|
|
|
builder.append(Integer.toHexString(buffer[i] & 0xFF));
|
|
|
|
}
|
|
|
|
return builder.toString().toUpperCase();
|
|
|
|
}
|
|
|
|
}
|