diff --git a/lib/installer-exclude/smack.jar b/lib/installer-exclude/smack.jar index 7acf9e106..e82c2f9c1 100644 Binary files a/lib/installer-exclude/smack.jar and b/lib/installer-exclude/smack.jar differ diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java b/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java index 21f8bb1e5..4083aaa8f 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java @@ -9,6 +9,7 @@ import java.util.*; import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.fileaccess.*; import net.java.sip.communicator.service.gui.*; //import net.java.sip.communicator.service.media.*; import net.java.sip.communicator.service.protocol.*; @@ -42,6 +43,11 @@ public class JabberActivator */ private static ConfigurationService configurationService = null; + /** + * File access service. + */ + private static FileAccessService fileService = null; + /** * Media service. */ @@ -122,6 +128,26 @@ public static ConfigurationService getConfigurationService() return configurationService; } + /** + * Returns a reference to a FileAccessService implementation currently + * registered in the bundle context or null if no such implementation was + * found. + * + * @return a currently valid implementation of the FileAccessService. + */ + public static FileAccessService getFileAccessService() + { + if(fileService == null) + { + ServiceReference fileReference + = bundleContext.getServiceReference( + FileAccessService.class.getName()); + fileService + = (FileAccessService) bundleContext.getService(fileReference); + } + return fileService; + } + /** * Returns a reference to the bundle context that we were started with. * @return a reference to the BundleContext instance that we were started diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 5e00a8121..67a055f93 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -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 temporalyAllowed = + new ArrayList(); + /** * Returns the state of the registration of this protocol provider * @return the RegistrationState 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()); + } + } + } + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf index 7e32c7a02..dfa3a86fd 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf @@ -24,6 +24,7 @@ Import-Package: org.osgi.framework, org.jivesoftware.smackx.filetransfer, org.jivesoftware.smackx.provider, net.java.sip.communicator.service.configuration, + net.java.sip.communicator.service.fileaccess, net.java.sip.communicator.service.resources, net.java.sip.communicator.util, net.java.sip.communicator.service.configuration.event, @@ -36,5 +37,6 @@ Import-Package: org.osgi.framework, org.xmlpull.v1, org.xmlpull.mxp1, javax.xml.parsers, + javax.net.ssl, javax.security.sasl, javax.security.auth.callback