|
|
|
|
@ -6,15 +6,20 @@
|
|
|
|
|
*/
|
|
|
|
|
package net.java.sip.communicator.impl.protocol.jabber;
|
|
|
|
|
|
|
|
|
|
import java.io.*;
|
|
|
|
|
import java.net.*;
|
|
|
|
|
import java.security.*;
|
|
|
|
|
import java.security.cert.*;
|
|
|
|
|
import java.text.*;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import javax.net.ssl.*;
|
|
|
|
|
|
|
|
|
|
import net.java.sip.communicator.service.protocol.*;
|
|
|
|
|
import net.java.sip.communicator.service.protocol.event.*;
|
|
|
|
|
import net.java.sip.communicator.service.protocol.jabberconstants.*;
|
|
|
|
|
import net.java.sip.communicator.util.*;
|
|
|
|
|
import net.java.sip.communicator.impl.protocol.jabber.sasl.*;
|
|
|
|
|
import net.java.sip.communicator.service.gui.*;
|
|
|
|
|
|
|
|
|
|
import org.jivesoftware.smack.*;
|
|
|
|
|
import org.jivesoftware.smack.packet.*;
|
|
|
|
|
@ -22,6 +27,8 @@
|
|
|
|
|
import org.jivesoftware.smackx.*;
|
|
|
|
|
import org.jivesoftware.smackx.packet.*;
|
|
|
|
|
|
|
|
|
|
import org.osgi.framework.*;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An implementation of the protocol provider service over the Jabber protocol
|
|
|
|
|
*
|
|
|
|
|
@ -113,6 +120,54 @@ public class ProtocolProviderServiceJabberImpl
|
|
|
|
|
*/
|
|
|
|
|
private JabberStatusEnum jabberStatusEnum;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The service we use to interact with user.
|
|
|
|
|
*/
|
|
|
|
|
private CertificateVerificationService guiVerification;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
private boolean additionalCertificateChecks = true;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The key store holding stored certificate during previous sessions.
|
|
|
|
|
*/
|
|
|
|
|
private KeyStore keyStore;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The property for the configuration value to store the
|
|
|
|
|
* KeyStore file location.
|
|
|
|
|
*/
|
|
|
|
|
private static final String KEYSTORE_FILE_PROP =
|
|
|
|
|
"net.java.sip.communicator.impl.protocol.sip.net.KEYSTORE";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The default password used for the keystore.
|
|
|
|
|
*/
|
|
|
|
|
private char[] defaultPassword = new char[0];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Used with tls connecting when certificates are not trusted
|
|
|
|
|
* and we ask the user to confirm connection. When some timeout expires
|
|
|
|
|
* connect method returns, and we use abortConnecting to abort further
|
|
|
|
|
* execution cause after user chooses we make further processing from there.
|
|
|
|
|
*/
|
|
|
|
|
private boolean abortConnecting = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Used with tls. When certificate was confirmed we abort current
|
|
|
|
|
* connecting and try connecting again.
|
|
|
|
|
*/
|
|
|
|
|
private boolean abortConnectingAndReconnect = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This are the certificates which are temporally allowed
|
|
|
|
|
* only for this session.
|
|
|
|
|
*/
|
|
|
|
|
private ArrayList<X509Certificate> temporalyAllowed =
|
|
|
|
|
new ArrayList<X509Certificate>();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the state of the registration of this protocol provider
|
|
|
|
|
* @return the <tt>RegistrationState</tt> that this provider is
|
|
|
|
|
@ -128,6 +183,27 @@ else if(connection.isConnected() && connection.isAuthenticated())
|
|
|
|
|
return RegistrationState.UNREGISTERED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the certificate verification service impl.
|
|
|
|
|
* @return the CertificateVerification service.
|
|
|
|
|
*/
|
|
|
|
|
private CertificateVerificationService getCertificateVerificationService()
|
|
|
|
|
{
|
|
|
|
|
if(guiVerification == null)
|
|
|
|
|
{
|
|
|
|
|
ServiceReference guiVerifyReference
|
|
|
|
|
= JabberActivator.getBundleContext().getServiceReference(
|
|
|
|
|
CertificateVerificationService.class.getName());
|
|
|
|
|
if(guiVerifyReference != null)
|
|
|
|
|
guiVerification = (CertificateVerificationService)
|
|
|
|
|
JabberActivator.getBundleContext().getService(
|
|
|
|
|
guiVerifyReference);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return guiVerification;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Starts the registration process. Connection details such as
|
|
|
|
|
* registration server, user name/number are provided through the
|
|
|
|
|
@ -135,7 +211,7 @@ else if(connection.isConnected() && connection.isAuthenticated())
|
|
|
|
|
*
|
|
|
|
|
* @param authority the security authority that will be used for resolving
|
|
|
|
|
* any security challenges that may be returned during the
|
|
|
|
|
* registration or at any moment while wer're registered.
|
|
|
|
|
* registration or at any moment while we're registered.
|
|
|
|
|
* @throws OperationFailedException with the corresponding code it the
|
|
|
|
|
* registration fails for some reason (e.g. a networking error or an
|
|
|
|
|
* implementation problem).
|
|
|
|
|
@ -423,9 +499,70 @@ else if(globalProxyType.equals(
|
|
|
|
|
proxy
|
|
|
|
|
);
|
|
|
|
|
confConn.setReconnectionAllowed(false);
|
|
|
|
|
confConn.setExpiredCertificatesCheckEnabled(
|
|
|
|
|
additionalCertificateChecks);
|
|
|
|
|
confConn.setNotMatchingDomainCheckEnabled(
|
|
|
|
|
additionalCertificateChecks);
|
|
|
|
|
|
|
|
|
|
TrustManagerFactory tmFactory = null;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
|
|
|
|
keyStore.load(null, defaultPassword);
|
|
|
|
|
|
|
|
|
|
String keyStoreFile =
|
|
|
|
|
JabberActivator.getConfigurationService()
|
|
|
|
|
.getString(KEYSTORE_FILE_PROP);
|
|
|
|
|
|
|
|
|
|
if(keyStoreFile == null || keyStoreFile.length() == 0)
|
|
|
|
|
{
|
|
|
|
|
File f = JabberActivator.getFileAccessService()
|
|
|
|
|
.getPrivatePersistentFile("jssecacerts");
|
|
|
|
|
keyStoreFile = f.getCanonicalPath();
|
|
|
|
|
|
|
|
|
|
JabberActivator.getConfigurationService()
|
|
|
|
|
.setProperty(KEYSTORE_FILE_PROP, keyStoreFile);
|
|
|
|
|
|
|
|
|
|
keyStore.store(
|
|
|
|
|
new FileOutputStream(f), defaultPassword);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
File f = new File(keyStoreFile);
|
|
|
|
|
if(!f.exists())
|
|
|
|
|
{
|
|
|
|
|
// if for some reason file is missing, create it
|
|
|
|
|
// by saving the empty store
|
|
|
|
|
keyStore.store(
|
|
|
|
|
new FileOutputStream(f), defaultPassword);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keyStore.load(new FileInputStream(keyStoreFile), null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String algorithm = KeyManagerFactory.getDefaultAlgorithm();
|
|
|
|
|
tmFactory = TrustManagerFactory.getInstance(algorithm);
|
|
|
|
|
tmFactory.init(keyStore);
|
|
|
|
|
confConn.setKeystorePath(keyStoreFile);
|
|
|
|
|
} catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
logger.error("Cannot create key store", e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
connection = new XMPPConnection(confConn);
|
|
|
|
|
if(tmFactory != null)
|
|
|
|
|
{
|
|
|
|
|
connection.setCustomTrustManager(
|
|
|
|
|
new HostTrustManager(
|
|
|
|
|
(X509TrustManager)tmFactory.getTrustManagers()[0],
|
|
|
|
|
serverAddress,
|
|
|
|
|
Integer.parseInt(serverPort)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
connection.connect();
|
|
|
|
|
|
|
|
|
|
connection.addConnectionListener(
|
|
|
|
|
new JabberConnectionListener());
|
|
|
|
|
}
|
|
|
|
|
catch (XMPPException exc)
|
|
|
|
|
{
|
|
|
|
|
@ -442,8 +579,20 @@ else if(globalProxyType.equals(
|
|
|
|
|
, exc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
connection.addConnectionListener(
|
|
|
|
|
new JabberConnectionListener());
|
|
|
|
|
if(abortConnecting)
|
|
|
|
|
{
|
|
|
|
|
abortConnecting = false;
|
|
|
|
|
if(abortConnectingAndReconnect)
|
|
|
|
|
{
|
|
|
|
|
abortConnectingAndReconnect = false;
|
|
|
|
|
reregister(SecurityAuthority.CONNECTION_FAILED);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
connection.disconnect();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fireRegistrationStateChanged(
|
|
|
|
|
getRegistrationState()
|
|
|
|
|
@ -487,6 +636,21 @@ else if(globalProxyType.equals(
|
|
|
|
|
connection.addConnectionListener(
|
|
|
|
|
new JabberConnectionListener());
|
|
|
|
|
|
|
|
|
|
if(abortConnecting)
|
|
|
|
|
{
|
|
|
|
|
abortConnecting = false;
|
|
|
|
|
if(abortConnectingAndReconnect)
|
|
|
|
|
{
|
|
|
|
|
abortConnectingAndReconnect = false;
|
|
|
|
|
reregister(SecurityAuthority.CONNECTION_FAILED);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
connection.disconnect();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fireRegistrationStateChanged(
|
|
|
|
|
getRegistrationState()
|
|
|
|
|
, RegistrationState.REGISTERING
|
|
|
|
|
@ -898,6 +1062,11 @@ public void connectionClosedOnError(Exception exception)
|
|
|
|
|
"Connecting multiple times with the same resource");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} // Ignore certificate exceptions as we handle them elsewhere
|
|
|
|
|
else if(exception instanceof SSLHandshakeException &&
|
|
|
|
|
exception.getCause() instanceof CertificateException)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!reconnecting)
|
|
|
|
|
@ -1022,4 +1191,165 @@ String getFullJid(Contact contact)
|
|
|
|
|
|
|
|
|
|
return presence.getFrom();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The trust manager which asks the client whether to trust particular
|
|
|
|
|
* certificate which is not globally trusted.
|
|
|
|
|
*/
|
|
|
|
|
private class HostTrustManager
|
|
|
|
|
implements X509TrustManager
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* The address we connect to.
|
|
|
|
|
*/
|
|
|
|
|
String address;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The port we connect to.
|
|
|
|
|
*/
|
|
|
|
|
int port;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The default trust manager.
|
|
|
|
|
*/
|
|
|
|
|
private final X509TrustManager tm;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates the custom trust manager.
|
|
|
|
|
* @param tm the default trust manager.
|
|
|
|
|
* @param address the address we are connecting to.
|
|
|
|
|
* @param port the port.
|
|
|
|
|
*/
|
|
|
|
|
HostTrustManager(X509TrustManager tm, String address, int port)
|
|
|
|
|
{
|
|
|
|
|
this.tm = tm;
|
|
|
|
|
this.port = port;
|
|
|
|
|
this.address = address;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Not used.
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public X509Certificate[] getAcceptedIssuers() {
|
|
|
|
|
throw new UnsupportedOperationException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Not used.
|
|
|
|
|
* @param chain the cert chain.
|
|
|
|
|
* @param authType authentication type like: RSA.
|
|
|
|
|
* @throws CertificateException
|
|
|
|
|
*/
|
|
|
|
|
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
|
|
|
|
throws CertificateException
|
|
|
|
|
{
|
|
|
|
|
throw new UnsupportedOperationException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether a certificate is trusted, if not as user whether he
|
|
|
|
|
* trust it.
|
|
|
|
|
* @param chain the certificate chain.
|
|
|
|
|
* @param authType authentication type like: RSA.
|
|
|
|
|
* @throws CertificateException not trusted.
|
|
|
|
|
*/
|
|
|
|
|
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
|
|
|
|
throws CertificateException
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
tm.checkServerTrusted(chain, authType);
|
|
|
|
|
} catch (Throwable certificateException)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < chain.length; i++)
|
|
|
|
|
{
|
|
|
|
|
X509Certificate c = chain[i];
|
|
|
|
|
|
|
|
|
|
// check for temporaly allowed certs
|
|
|
|
|
if(temporalyAllowed.contains(c))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now check for permanent allow of certs
|
|
|
|
|
String alias = keyStore.getCertificateAlias(c);
|
|
|
|
|
if(alias != null)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CertificateVerificationService guiVerification =
|
|
|
|
|
getCertificateVerificationService();
|
|
|
|
|
|
|
|
|
|
if(guiVerification == null)
|
|
|
|
|
throw new CertificateException(certificateException.getMessage());
|
|
|
|
|
|
|
|
|
|
abortConnecting = true;
|
|
|
|
|
int result = guiVerification
|
|
|
|
|
.verificationNeeded(chain, address, port);
|
|
|
|
|
|
|
|
|
|
if(result == CertificateVerificationService.DO_NOT_TRUST)
|
|
|
|
|
{
|
|
|
|
|
fireRegistrationStateChanged(getRegistrationState(),
|
|
|
|
|
RegistrationState.CONNECTION_FAILED,
|
|
|
|
|
RegistrationStateChangeEvent.REASON_USER_REQUEST,
|
|
|
|
|
"Not trusted certificate");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if(result
|
|
|
|
|
== CertificateVerificationService.TRUST_THIS_SESSION_ONLY)
|
|
|
|
|
{
|
|
|
|
|
for (X509Certificate c : chain)
|
|
|
|
|
temporalyAllowed.add(c);
|
|
|
|
|
}
|
|
|
|
|
else if(result == CertificateVerificationService.TRUST_ALWAYS)
|
|
|
|
|
{
|
|
|
|
|
for (X509Certificate c : chain)
|
|
|
|
|
keyStore.setCertificateEntry(
|
|
|
|
|
String.valueOf(System.currentTimeMillis()), c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
String keyStoreFile = JabberActivator.getConfigurationService()
|
|
|
|
|
.getString(KEYSTORE_FILE_PROP);
|
|
|
|
|
keyStore.store(
|
|
|
|
|
new FileOutputStream(keyStoreFile), defaultPassword);
|
|
|
|
|
} catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
logger.error("Error saving keystore.", e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(abortConnecting)
|
|
|
|
|
{
|
|
|
|
|
abortConnectingAndReconnect = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// register.connect in new thread so we can release the
|
|
|
|
|
// current connecting thread, otherwise this blocks
|
|
|
|
|
// jabber
|
|
|
|
|
new Thread(new Runnable()
|
|
|
|
|
{
|
|
|
|
|
public void run()
|
|
|
|
|
{
|
|
|
|
|
reregister(SecurityAuthority.CONNECTION_FAILED);
|
|
|
|
|
}
|
|
|
|
|
}).start();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} catch (KeyStoreException e)
|
|
|
|
|
{
|
|
|
|
|
// something happend
|
|
|
|
|
logger.error("Error trying to " +
|
|
|
|
|
"show certificate to user", e);
|
|
|
|
|
|
|
|
|
|
throw new CertificateException(certificateException.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|