mirror of
https://github.com/moparisthebest/davmail
synced 2025-01-07 03:38:05 -05:00
Fix 3315942, merge patch provided by Jeremiah Albrant: Ask user to select client certificate
git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1715 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
parent
c85225384b
commit
7fef14109f
9
pom.xml
9
pom.xml
@ -86,6 +86,13 @@
|
|||||||
<role>Java Contributor</role>
|
<role>Java Contributor</role>
|
||||||
</roles>
|
</roles>
|
||||||
</contributor>
|
</contributor>
|
||||||
|
<contributor>
|
||||||
|
<name>Jeremiah Albrant</name>
|
||||||
|
<url>http://sourceforge.net/users/?user_id=2903536</url>
|
||||||
|
<roles>
|
||||||
|
<role>Java Contributor</role>
|
||||||
|
</roles>
|
||||||
|
</contributor>
|
||||||
<contributor>
|
<contributor>
|
||||||
<name>Henning Holtschneider</name>
|
<name>Henning Holtschneider</name>
|
||||||
<email>hehol@users.sourceforge.net</email>
|
<email>hehol@users.sourceforge.net</email>
|
||||||
@ -225,7 +232,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse</groupId>
|
<groupId>org.eclipse</groupId>
|
||||||
<artifactId>swt</artifactId>
|
<artifactId>swt</artifactId>
|
||||||
<version>3.6.0</version>
|
<version>3.7.0</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -1,216 +1,226 @@
|
|||||||
/*
|
/*
|
||||||
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
||||||
* Copyright (C) 2009 Mickael Guessant
|
* Copyright (C) 2009 Mickael Guessant
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
* as published by the Free Software Foundation; either version 2
|
* as published by the Free Software Foundation; either version 2
|
||||||
* of the License, or (at your option) any later version.
|
* of the License, or (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program; if not, write to the Free Software
|
* along with this program; if not, write to the Free Software
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
package davmail.http;
|
package davmail.http;
|
||||||
|
|
||||||
import davmail.BundleMessage;
|
import davmail.BundleMessage;
|
||||||
import davmail.Settings;
|
import davmail.Settings;
|
||||||
import davmail.ui.PasswordPromptDialog;
|
import davmail.ui.PasswordPromptDialog;
|
||||||
import davmail.ui.tray.DavGatewayTray;
|
import davmail.ui.tray.DavGatewayTray;
|
||||||
import org.apache.commons.httpclient.HttpsURL;
|
import org.apache.commons.httpclient.HttpsURL;
|
||||||
import org.apache.commons.httpclient.params.HttpConnectionParams;
|
import org.apache.commons.httpclient.params.HttpConnectionParams;
|
||||||
import org.apache.commons.httpclient.protocol.Protocol;
|
import org.apache.commons.httpclient.protocol.Protocol;
|
||||||
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
|
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
|
||||||
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
|
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
import javax.net.ssl.*;
|
||||||
import javax.security.auth.callback.Callback;
|
import javax.security.auth.callback.Callback;
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
import javax.security.auth.callback.PasswordCallback;
|
import javax.security.auth.callback.PasswordCallback;
|
||||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manual Socket Factory.
|
* Manual Socket Factory.
|
||||||
* Let user choose to accept or reject certificate
|
* Let user choose to accept or reject certificate
|
||||||
*/
|
*/
|
||||||
public class DavGatewaySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
|
public class DavGatewaySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
|
||||||
/**
|
/**
|
||||||
* Register custom Socket Factory to let user accept or reject certificate
|
* Register custom Socket Factory to let user accept or reject certificate
|
||||||
*/
|
*/
|
||||||
public static void register() {
|
public static void register() {
|
||||||
String urlString = Settings.getProperty("davmail.url");
|
String urlString = Settings.getProperty("davmail.url");
|
||||||
try {
|
try {
|
||||||
URL url = new URL(urlString);
|
URL url = new URL(urlString);
|
||||||
String protocol = url.getProtocol();
|
String protocol = url.getProtocol();
|
||||||
if ("https".equals(protocol)) {
|
if ("https".equals(protocol)) {
|
||||||
int port = url.getPort();
|
int port = url.getPort();
|
||||||
if (port < 0) {
|
if (port < 0) {
|
||||||
port = HttpsURL.DEFAULT_PORT;
|
port = HttpsURL.DEFAULT_PORT;
|
||||||
}
|
}
|
||||||
Protocol.registerProtocol(url.getProtocol(),
|
Protocol.registerProtocol(url.getProtocol(),
|
||||||
new Protocol(protocol, (ProtocolSocketFactory) new DavGatewaySSLProtocolSocketFactory(), port));
|
new Protocol(protocol, (ProtocolSocketFactory) new DavGatewaySSLProtocolSocketFactory(), port));
|
||||||
}
|
}
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
DavGatewayTray.error(new BundleMessage("LOG_INVALID_URL", urlString));
|
DavGatewayTray.error(new BundleMessage("LOG_INVALID_URL", urlString));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStore.ProtectionParameter getProtectionParameter(String password) {
|
private KeyStore.ProtectionParameter getProtectionParameter(String password) {
|
||||||
if (password != null && password.length() > 0) {
|
if (password != null && password.length() > 0) {
|
||||||
// password provided: create a PasswordProtection
|
// password provided: create a PasswordProtection
|
||||||
return new KeyStore.PasswordProtection(password.toCharArray());
|
return new KeyStore.PasswordProtection(password.toCharArray());
|
||||||
} else {
|
} else {
|
||||||
// request password at runtime through a callback
|
// request password at runtime through a callback
|
||||||
return new KeyStore.CallbackHandlerProtection(new CallbackHandler() {
|
return new KeyStore.CallbackHandlerProtection(new CallbackHandler() {
|
||||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
if (callbacks.length > 0 && callbacks[0] instanceof PasswordCallback) {
|
if (callbacks.length > 0 && callbacks[0] instanceof PasswordCallback) {
|
||||||
PasswordPromptDialog passwordPromptDialog = new PasswordPromptDialog(((PasswordCallback) callbacks[0]).getPrompt());
|
PasswordPromptDialog passwordPromptDialog = new PasswordPromptDialog(((PasswordCallback) callbacks[0]).getPrompt());
|
||||||
((PasswordCallback) callbacks[0]).setPassword(passwordPromptDialog.getPassword());
|
((PasswordCallback) callbacks[0]).setPassword(passwordPromptDialog.getPassword());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SSLContext sslcontext;
|
private SSLContext sslcontext;
|
||||||
|
|
||||||
private SSLContext createSSLContext() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyManagementException, KeyStoreException {
|
private SSLContext createSSLContext() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyManagementException, KeyStoreException {
|
||||||
// PKCS11 client certificate settings
|
// PKCS11 client certificate settings
|
||||||
String pkcs11Library = Settings.getProperty("davmail.ssl.pkcs11Library");
|
String pkcs11Library = Settings.getProperty("davmail.ssl.pkcs11Library");
|
||||||
|
|
||||||
String clientKeystoreType = Settings.getProperty("davmail.ssl.clientKeystoreType");
|
String clientKeystoreType = Settings.getProperty("davmail.ssl.clientKeystoreType");
|
||||||
// set default keystore type
|
// set default keystore type
|
||||||
if (clientKeystoreType == null || clientKeystoreType.length() == 0) {
|
if (clientKeystoreType == null || clientKeystoreType.length() == 0) {
|
||||||
clientKeystoreType = "PKCS11";
|
clientKeystoreType = "PKCS11";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pkcs11Library != null && pkcs11Library.length() > 0 && "PKCS11".equals(clientKeystoreType)) {
|
if (pkcs11Library != null && pkcs11Library.length() > 0 && "PKCS11".equals(clientKeystoreType)) {
|
||||||
StringBuilder pkcs11Buffer = new StringBuilder();
|
StringBuilder pkcs11Buffer = new StringBuilder();
|
||||||
pkcs11Buffer.append("name=DavMail\n");
|
pkcs11Buffer.append("name=DavMail\n");
|
||||||
pkcs11Buffer.append("library=").append(pkcs11Library).append('\n');
|
pkcs11Buffer.append("library=").append(pkcs11Library).append('\n');
|
||||||
String pkcs11Config = Settings.getProperty("davmail.ssl.pkcs11Config");
|
String pkcs11Config = Settings.getProperty("davmail.ssl.pkcs11Config");
|
||||||
if (pkcs11Config != null && pkcs11Config.length() > 0) {
|
if (pkcs11Config != null && pkcs11Config.length() > 0) {
|
||||||
pkcs11Buffer.append(pkcs11Config).append('\n');
|
pkcs11Buffer.append(pkcs11Config).append('\n');
|
||||||
}
|
}
|
||||||
SunPKCS11ProviderHandler.registerProvider(pkcs11Buffer.toString());
|
SunPKCS11ProviderHandler.registerProvider(pkcs11Buffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(/*KeyManagerFactory.getDefaultAlgorithm()*/"NewSunX509");
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(/*KeyManagerFactory.getDefaultAlgorithm()*/"NewSunX509");
|
||||||
|
|
||||||
ArrayList<KeyStore.Builder> keyStoreBuilders = new ArrayList<KeyStore.Builder>();
|
ArrayList<KeyStore.Builder> keyStoreBuilders = new ArrayList<KeyStore.Builder>();
|
||||||
// PKCS11 (smartcard) keystore with password callback
|
// PKCS11 (smartcard) keystore with password callback
|
||||||
KeyStore.Builder scBuilder = KeyStore.Builder.newInstance("PKCS11", null, getProtectionParameter(null));
|
KeyStore.Builder scBuilder = KeyStore.Builder.newInstance("PKCS11", null, getProtectionParameter(null));
|
||||||
keyStoreBuilders.add(scBuilder);
|
keyStoreBuilders.add(scBuilder);
|
||||||
|
|
||||||
String clientKeystoreFile = Settings.getProperty("davmail.ssl.clientKeystoreFile");
|
String clientKeystoreFile = Settings.getProperty("davmail.ssl.clientKeystoreFile");
|
||||||
String clientKeystorePass = Settings.getProperty("davmail.ssl.clientKeystorePass");
|
String clientKeystorePass = Settings.getProperty("davmail.ssl.clientKeystorePass");
|
||||||
if (clientKeystoreFile != null && clientKeystoreFile.length() > 0
|
if (clientKeystoreFile != null && clientKeystoreFile.length() > 0
|
||||||
&& ("PKCS12".equals(clientKeystoreType) || "JKS".equals(clientKeystoreType))) {
|
&& ("PKCS12".equals(clientKeystoreType) || "JKS".equals(clientKeystoreType))) {
|
||||||
// PKCS12 file based keystore
|
// PKCS12 file based keystore
|
||||||
KeyStore.Builder fsBuilder = KeyStore.Builder.newInstance(clientKeystoreType, null,
|
KeyStore.Builder fsBuilder = KeyStore.Builder.newInstance(clientKeystoreType, null,
|
||||||
new File(clientKeystoreFile), getProtectionParameter(clientKeystorePass));
|
new File(clientKeystoreFile), getProtectionParameter(clientKeystorePass));
|
||||||
keyStoreBuilders.add(fsBuilder);
|
keyStoreBuilders.add(fsBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
ManagerFactoryParameters keyStoreBuilderParameters = new KeyStoreBuilderParameters(keyStoreBuilders);
|
ManagerFactoryParameters keyStoreBuilderParameters = new KeyStoreBuilderParameters(keyStoreBuilders);
|
||||||
keyManagerFactory.init(keyStoreBuilderParameters);
|
keyManagerFactory.init(keyStoreBuilderParameters);
|
||||||
|
|
||||||
SSLContext context = SSLContext.getInstance("SSL");
|
// Get a list of key managers
|
||||||
context.init(keyManagerFactory.getKeyManagers(), new TrustManager[]{new DavGatewayX509TrustManager()}, null);
|
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
|
||||||
return context;
|
|
||||||
}
|
// Walk through the key managers and replace all X509 Key Managers with
|
||||||
|
// a specialized wrapped DavMail X509 Key Manager
|
||||||
private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, InvalidAlgorithmParameterException {
|
for (int i = 0; i < keyManagers.length; i++) {
|
||||||
if (this.sslcontext == null) {
|
KeyManager keyManager = keyManagers[i];
|
||||||
this.sslcontext = createSSLContext();
|
if (keyManager instanceof X509KeyManager) {
|
||||||
}
|
keyManagers[i] = new DavMailX509KeyManager((X509KeyManager) keyManager);
|
||||||
return this.sslcontext;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SSLContext context = SSLContext.getInstance("SSL");
|
||||||
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
|
context.init(keyManagers, new TrustManager[]{new DavGatewayX509TrustManager()}, null);
|
||||||
try {
|
return context;
|
||||||
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
|
}
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IOException(e + " " + e.getMessage());
|
private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, InvalidAlgorithmParameterException {
|
||||||
} catch (KeyManagementException e) {
|
if (this.sslcontext == null) {
|
||||||
throw new IOException(e + " " + e.getMessage());
|
this.sslcontext = createSSLContext();
|
||||||
} catch (KeyStoreException e) {
|
}
|
||||||
throw new IOException(e + " " + e.getMessage());
|
return this.sslcontext;
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
}
|
||||||
throw new IOException(e + " " + e.getMessage());
|
|
||||||
}
|
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
|
||||||
}
|
try {
|
||||||
|
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
|
||||||
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort, HttpConnectionParams params) throws IOException {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
try {
|
throw new IOException(e + " " + e.getMessage());
|
||||||
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
|
} catch (KeyManagementException e) {
|
||||||
} catch (NoSuchAlgorithmException e) {
|
throw new IOException(e + " " + e.getMessage());
|
||||||
throw new IOException(e + " " + e.getMessage());
|
} catch (KeyStoreException e) {
|
||||||
} catch (KeyManagementException e) {
|
throw new IOException(e + " " + e.getMessage());
|
||||||
throw new IOException(e + " " + e.getMessage());
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
} catch (KeyStoreException e) {
|
throw new IOException(e + " " + e.getMessage());
|
||||||
throw new IOException(e + " " + e.getMessage());
|
}
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
}
|
||||||
throw new IOException(e + " " + e.getMessage());
|
|
||||||
}
|
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort, HttpConnectionParams params) throws IOException {
|
||||||
}
|
try {
|
||||||
|
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
public Socket createSocket(String host, int port) throws IOException {
|
throw new IOException(e + " " + e.getMessage());
|
||||||
try {
|
} catch (KeyManagementException e) {
|
||||||
return getSSLContext().getSocketFactory().createSocket(host, port);
|
throw new IOException(e + " " + e.getMessage());
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (KeyStoreException e) {
|
||||||
throw new IOException(e + " " + e.getMessage());
|
throw new IOException(e + " " + e.getMessage());
|
||||||
} catch (KeyManagementException e) {
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
throw new IOException(e + " " + e.getMessage());
|
throw new IOException(e + " " + e.getMessage());
|
||||||
} catch (KeyStoreException e) {
|
}
|
||||||
throw new IOException(e + " " + e.getMessage());
|
}
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
throw new IOException(e + " " + e.getMessage());
|
public Socket createSocket(String host, int port) throws IOException {
|
||||||
}
|
try {
|
||||||
}
|
return getSSLContext().getSocketFactory().createSocket(host, port);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
|
throw new IOException(e + " " + e.getMessage());
|
||||||
try {
|
} catch (KeyManagementException e) {
|
||||||
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
|
throw new IOException(e + " " + e.getMessage());
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (KeyStoreException e) {
|
||||||
throw new IOException(e + " " + e.getMessage());
|
throw new IOException(e + " " + e.getMessage());
|
||||||
} catch (KeyManagementException e) {
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
throw new IOException(e + " " + e.getMessage());
|
throw new IOException(e + " " + e.getMessage());
|
||||||
} catch (KeyStoreException e) {
|
}
|
||||||
throw new IOException(e + " " + e.getMessage());
|
}
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
throw new IOException(e + " " + e.getMessage());
|
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
|
||||||
}
|
try {
|
||||||
}
|
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
/**
|
throw new IOException(e + " " + e.getMessage());
|
||||||
* All instances of SSLProtocolSocketFactory are the same.
|
} catch (KeyManagementException e) {
|
||||||
*/
|
throw new IOException(e + " " + e.getMessage());
|
||||||
@Override
|
} catch (KeyStoreException e) {
|
||||||
public boolean equals(Object obj) {
|
throw new IOException(e + " " + e.getMessage());
|
||||||
return ((obj != null) && obj.getClass().equals(this.getClass()));
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
}
|
throw new IOException(e + " " + e.getMessage());
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* All instances of SSLProtocolSocketFactory have the same hash code.
|
|
||||||
*/
|
/**
|
||||||
@Override
|
* All instances of SSLProtocolSocketFactory are the same.
|
||||||
public int hashCode() {
|
*/
|
||||||
return this.getClass().hashCode();
|
@Override
|
||||||
}
|
public boolean equals(Object obj) {
|
||||||
}
|
return ((obj != null) && obj.getClass().equals(this.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All instances of SSLProtocolSocketFactory have the same hash code.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.getClass().hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
165
src/java/davmail/http/DavMailX509KeyManager.java
Normal file
165
src/java/davmail/http/DavMailX509KeyManager.java
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
||||||
|
* Copyright (C) 2011 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.
|
||||||
|
*/
|
||||||
|
package davmail.http;
|
||||||
|
|
||||||
|
import davmail.ui.SelectCertificateDialog;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special X509 Key Manager that handles cases where more than one private key
|
||||||
|
* is sufficient to establish the HTTPs connection by asking the user to
|
||||||
|
* select one.
|
||||||
|
*/
|
||||||
|
public class DavMailX509KeyManager implements X509KeyManager {
|
||||||
|
|
||||||
|
protected static final Logger LOGGER = Logger.getLogger(DavMailX509KeyManager.class);
|
||||||
|
|
||||||
|
// Wrap an existing key manager to handle most of the interface as a pass through
|
||||||
|
private final X509KeyManager keyManager;
|
||||||
|
|
||||||
|
// Remember selected alias so we don't continually bug the user
|
||||||
|
private String cachedAlias;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the specialized key manager wrapping the default one
|
||||||
|
*
|
||||||
|
* @param keyManager original key manager
|
||||||
|
*/
|
||||||
|
public DavMailX509KeyManager(X509KeyManager keyManager) {
|
||||||
|
this.keyManager = keyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the client aliases, simply pass this through to wrapped key manager
|
||||||
|
*/
|
||||||
|
public String[] getClientAliases(String string, Principal[] principals) {
|
||||||
|
return keyManager.getClientAliases(string, principals);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a client alias. Some servers are misconfigured and claim to accept
|
||||||
|
* any client certificate during the SSL handshake, however OWA only authenticates
|
||||||
|
* using a single certificate.
|
||||||
|
* <p/>
|
||||||
|
* This method allows the user to select the right client certificate
|
||||||
|
*/
|
||||||
|
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
|
||||||
|
// Build a list of all aliases
|
||||||
|
ArrayList<String> aliases = new ArrayList<String>();
|
||||||
|
for (String keyTypeValue : keyType) {
|
||||||
|
String[] keyAliases = keyManager.getClientAliases(keyTypeValue, issuers);
|
||||||
|
|
||||||
|
if (keyAliases != null) {
|
||||||
|
aliases.addAll(Arrays.asList(keyAliases));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are more than one show a dialog and return the selected alias
|
||||||
|
if (aliases.size() > 1) {
|
||||||
|
|
||||||
|
//If there's a saved pattern try to match it
|
||||||
|
if (cachedAlias != null) {
|
||||||
|
for (String alias : aliases) {
|
||||||
|
if (cachedAlias.equals(stripAlias(alias))) {
|
||||||
|
LOGGER.debug(alias + " matched cached alias: " + cachedAlias);
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pattern didn't match, clear the pattern and ask user to select an alias
|
||||||
|
cachedAlias = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] aliasesArray = aliases.toArray(new String[aliases.size()]);
|
||||||
|
SelectCertificateDialog selectCertificateDialog = new SelectCertificateDialog(aliasesArray);
|
||||||
|
|
||||||
|
LOGGER.debug("User selected Key Alias: " + selectCertificateDialog.getSelectedAlias());
|
||||||
|
|
||||||
|
cachedAlias = stripAlias(selectCertificateDialog.getSelectedAlias().substring(10));
|
||||||
|
LOGGER.debug("Stored Key Alias Pattern: " + cachedAlias);
|
||||||
|
|
||||||
|
return selectCertificateDialog.getSelectedAlias();
|
||||||
|
|
||||||
|
// exactly one, simply return that and don't bother the user
|
||||||
|
} else if (aliases.size() == 1) {
|
||||||
|
LOGGER.debug("One Private Key found, returning that");
|
||||||
|
return aliases.get(0);
|
||||||
|
|
||||||
|
// none, return null
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("No Private Keys found");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PKCS11 aliases are in the format: dd.0, dd is incremented
|
||||||
|
* every time the SSL connection is re-negotiated
|
||||||
|
*
|
||||||
|
* @param alias original alias
|
||||||
|
* @return alias without prefix
|
||||||
|
*/
|
||||||
|
protected String stripAlias(String alias) {
|
||||||
|
String value = alias;
|
||||||
|
if (value != null && value.length() > 1) {
|
||||||
|
char firstChar = value.charAt(0);
|
||||||
|
int dotIndex = value.indexOf('.');
|
||||||
|
if (firstChar >= '0' && firstChar <= '9' && dotIndex >= 0) {
|
||||||
|
value = value.substring(dotIndex+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passthrough to wrapped keymanager
|
||||||
|
*/
|
||||||
|
public String[] getServerAliases(String string, Principal[] prncpls) {
|
||||||
|
return keyManager.getServerAliases(string, prncpls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passthrough to wrapped keymanager
|
||||||
|
*/
|
||||||
|
public String chooseServerAlias(String string, Principal[] prncpls, Socket socket) {
|
||||||
|
return keyManager.chooseServerAlias(string, prncpls, socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passthrough to wrapped keymanager
|
||||||
|
*/
|
||||||
|
public X509Certificate[] getCertificateChain(String string) {
|
||||||
|
return keyManager.getCertificateChain(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passthrough to wrapped keymanager
|
||||||
|
*/
|
||||||
|
public PrivateKey getPrivateKey(String string) {
|
||||||
|
return keyManager.getPrivateKey(string);
|
||||||
|
}
|
||||||
|
}
|
111
src/java/davmail/ui/SelectCertificateDialog.java
Normal file
111
src/java/davmail/ui/SelectCertificateDialog.java
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
||||||
|
* Copyright (C) 2011 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.
|
||||||
|
*/
|
||||||
|
package davmail.ui;
|
||||||
|
|
||||||
|
import davmail.BundleMessage;
|
||||||
|
import davmail.ui.tray.DavGatewayTray;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let user select a client certificate
|
||||||
|
*/
|
||||||
|
public class SelectCertificateDialog extends JDialog {
|
||||||
|
protected JList aliasListBox;
|
||||||
|
protected String selectedAlias;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets user selected alias.
|
||||||
|
*
|
||||||
|
* @return user selected alias
|
||||||
|
*/
|
||||||
|
public String getSelectedAlias() {
|
||||||
|
return this.selectedAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a client certificate
|
||||||
|
*
|
||||||
|
* @param aliases An array of certificate aliases for the user to pick from
|
||||||
|
*/
|
||||||
|
public SelectCertificateDialog(String[] aliases) {
|
||||||
|
setAlwaysOnTop(true);
|
||||||
|
|
||||||
|
setTitle(BundleMessage.format("UI_CERTIFICATE_ALIAS_PROMPT"));
|
||||||
|
try {
|
||||||
|
setIconImage(DavGatewayTray.getFrameIcon());
|
||||||
|
} catch (NoSuchMethodError error) {
|
||||||
|
DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
JPanel questionPanel = new JPanel();
|
||||||
|
questionPanel.setLayout(new BoxLayout(questionPanel, BoxLayout.Y_AXIS));
|
||||||
|
JLabel imageLabel = new JLabel();
|
||||||
|
imageLabel.setIcon(UIManager.getIcon("OptionPane.questionIcon"));
|
||||||
|
imageLabel.setText(BundleMessage.format("UI_CERTIFICATE_ALIAS_PROMPT"));
|
||||||
|
questionPanel.add(imageLabel);
|
||||||
|
|
||||||
|
aliasListBox = new JList(aliases);
|
||||||
|
aliasListBox.setMaximumSize(aliasListBox.getPreferredSize());
|
||||||
|
|
||||||
|
JPanel aliasPanel = new JPanel();
|
||||||
|
aliasPanel.setLayout(new BoxLayout(aliasPanel, BoxLayout.Y_AXIS));
|
||||||
|
aliasPanel.add(aliasListBox);
|
||||||
|
|
||||||
|
add(questionPanel, BorderLayout.NORTH);
|
||||||
|
add(aliasPanel, BorderLayout.CENTER);
|
||||||
|
add(getButtonPanel(), BorderLayout.SOUTH);
|
||||||
|
setModal(true);
|
||||||
|
|
||||||
|
pack();
|
||||||
|
// center frame
|
||||||
|
setLocation(getToolkit().getScreenSize().width / 2 -
|
||||||
|
getSize().width / 2,
|
||||||
|
getToolkit().getScreenSize().height / 2 -
|
||||||
|
getSize().height / 2);
|
||||||
|
setVisible(true);
|
||||||
|
requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JPanel getButtonPanel() {
|
||||||
|
JPanel buttonPanel = new JPanel();
|
||||||
|
JButton okButton = new JButton(BundleMessage.format("UI_BUTTON_OK"));
|
||||||
|
JButton cancelButton = new JButton(BundleMessage.format("UI_BUTTON_CANCEL"));
|
||||||
|
okButton.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent evt) {
|
||||||
|
selectedAlias = aliasListBox.getSelectedValue().toString();
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cancelButton.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent evt) {
|
||||||
|
selectedAlias = null;
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonPanel.add(okButton);
|
||||||
|
buttonPanel.add(cancelButton);
|
||||||
|
return buttonPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user