diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index c255cfbe5..8ae72a74d 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -648,6 +648,12 @@ service.gui.callinfo.IS_CONFERENCE_FOCUS=Conference focus service.gui.callinfo.IS_DEFAULT_ENCRYPTED=Encryption enabled service.gui.callinfo.CALL_TRANSPORT=Signalling call transport service.gui.callinfo.CALL_DURATION=Call duration +service.gui.callinfo.TLS_PROTOCOL=TLS protocol +service.gui.callinfo.TLS_CIPHER_SUITE=TLS cipher suite +service.gui.callinfo.TLS_SERVER_CERTIFICATE_CHAIN=TLS server certificate chain +service.gui.callinfo.TLS_CERTIFICATE_CONTENT=The content of the TLS server \ +certificate is displayed below. +service.gui.callinfo.VIEW_CERTIFICATE=View certificate service.gui.callinfo.CODEC=Codec / Frequency service.gui.callinfo.NA=N.A. service.gui.callinfo.VIDEO_SIZE=Video size diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java b/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java index 3094622b7..b67b336d1 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java @@ -27,6 +27,8 @@ import org.jitsi.util.*; import com.explodingpixels.macwidgets.*; +import java.security.cert.*; +import javax.swing.event.*; /** * The frame displaying the statistical information for a telephony conference. @@ -69,6 +71,11 @@ public class CallInfoFrame */ private boolean hasCallInfo; + /** + * Dummy URL to indicate that the certificate should be displayed. + */ + private final String CERTIFICATE_URL = "http://viewCertificate"; + /** * Creates a new frame containing the statistical information for a specific * telephony conference. @@ -156,6 +163,31 @@ private JEditorPane createGeneralInfoPane() infoTextPane.setOpaque(false); infoTextPane.setEditable(false); infoTextPane.setContentType("text/html"); + infoTextPane.addHyperlinkListener(new HyperlinkListener() + { + public void hyperlinkUpdate(HyperlinkEvent e) + { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED + && e.getURL() != null + && CERTIFICATE_URL.equals(e.getURL().toString())) + { + List calls = callConference.getCalls(); + if (!calls.isEmpty()) + { + Call aCall = calls.get(0); + Certificate[] chain = aCall.getProtocolProvider() + .getOperationSet(OperationSetTLS.class) + .getServerCertificates(); + + ViewCertificateFrame certFrame = + new ViewCertificateFrame(chain, null, + resources.getI18NString( + "service.gui.callinfo.TLS_CERTIFICATE_CONTENT")); + certFrame.setVisible(true); + } + } + } + }); return infoTextPane; } @@ -235,6 +267,27 @@ private boolean constructCallInfo() resources.getI18NString("service.gui.callinfo.CALL_TRANSPORT"), preferredTransport.toString())); + final OperationSetTLS tlsDetails = aCall.getProtocolProvider() + .getOperationSet(OperationSetTLS.class); + if (tlsDetails != null) + { + stringBuffer.append(getLineString( + resources.getI18NString( + "service.gui.callinfo.TLS_PROTOCOL"), + tlsDetails.getProtocol())); + stringBuffer.append(getLineString( + resources.getI18NString( + "service.gui.callinfo.TLS_CIPHER_SUITE"), + tlsDetails.getCipherSuite())); + + stringBuffer.append("") + .append(resources.getI18NString( + "service.gui.callinfo.VIEW_CERTIFICATE")) + .append("
"); + } + constructCallPeersInfo(stringBuffer); stringBuffer.append("

"); diff --git a/src/net/java/sip/communicator/impl/gui/main/call/ViewCertificateFrame.java b/src/net/java/sip/communicator/impl/gui/main/call/ViewCertificateFrame.java new file mode 100644 index 000000000..529c83263 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/call/ViewCertificateFrame.java @@ -0,0 +1,155 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.gui.main.call; + +import java.awt.*; +import java.security.cert.*; +import javax.swing.*; +import net.java.sip.communicator.plugin.desktoputil.*; +import org.jitsi.service.resources.*; + +/** + * Frame for showing information about a certificate. + */ +public class ViewCertificateFrame extends SIPCommFrame { + + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * The resource service. + */ + private final ResourceManagementService R = DesktopUtilActivator.getResources(); + + /** + * The maximum width that we allow message dialogs to have. + */ + private static final int MAX_MSG_PANE_WIDTH = 600; + + /** + * The maximum height that we allow message dialogs to have. + */ + private static final int MAX_MSG_PANE_HEIGHT = 800; + + /** + * The certificate to show. + */ + Certificate cert; + + /** + * A text that describes why the verification failed. + */ + String message; + + /** + * The certificate panel. + */ + TransparentPanel certPanel; + + /** + * This dialog content pane. + */ + TransparentPanel contentPane; + + /** + * Creates the dialog. + * + * @param certs the certificates list + * @param title The title of the dialog; when null the resource + * service.gui.CERT_DIALOG_TITLE is loaded. + * @param message A text that describes why the verification failed. + */ + public ViewCertificateFrame(Certificate[] certs, + String title, String message) + { + super(false); + + setTitle(title != null ? title : + R.getI18NString("service.gui.CERT_DIALOG_TITLE")); + + // for now shows only the first certificate from the chain + this.cert = certs[0]; + this.message = message; + + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + + init(); + + setLocationRelativeTo(getParent()); + } + + /** + * Inits the dialog initial display. + */ + private void init() + { + this.getContentPane().setLayout(new BorderLayout()); + + contentPane = + new TransparentPanel(new BorderLayout(5, 5)); + + TransparentPanel northPanel = + new TransparentPanel(new BorderLayout(5, 5)); + northPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5)); + + JLabel imgLabel = new JLabel( + R.getImage("service.gui.icons.CERTIFICATE_WARNING")); + imgLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + northPanel.add(imgLabel, BorderLayout.WEST); + + StyledHTMLEditorPane descriptionPane = new StyledHTMLEditorPane(); + descriptionPane.setOpaque(false); + descriptionPane.setEditable(false); + descriptionPane.setContentType("text/html"); + descriptionPane.setText(message); + descriptionPane.setSize( + new Dimension(MAX_MSG_PANE_WIDTH, MAX_MSG_PANE_HEIGHT)); + int height = descriptionPane.getPreferredSize().height; + descriptionPane.setPreferredSize( + new Dimension(MAX_MSG_PANE_WIDTH, height)); + + northPanel.add(descriptionPane, BorderLayout.CENTER); + contentPane.add(northPanel, BorderLayout.NORTH); + + certPanel = new TransparentPanel(new BorderLayout()); + contentPane.add(certPanel, BorderLayout.CENTER); + + this.getContentPane().add(contentPane, BorderLayout.CENTER); + + Component certInfoPane; + if(cert instanceof X509Certificate) + { + certInfoPane = new X509CertificatePanel((X509Certificate)cert); + } + else + { + JTextArea textArea = new JTextArea(); + textArea.setOpaque(false); + textArea.setEditable(false); + textArea.setText(cert.toString()); + certInfoPane = textArea; + } + + final JScrollPane certScroll = new JScrollPane(certInfoPane); + certScroll.setPreferredSize(new Dimension(300, 600)); + certPanel.add(certScroll, BorderLayout.CENTER); + + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + certScroll.getVerticalScrollBar().setValue(0); + } + }); + setPreferredSize(null); + + pack(); + } + +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTLSJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTLSJabberImpl.java new file mode 100644 index 000000000..0b1e3bece --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTLSJabberImpl.java @@ -0,0 +1,75 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.security.cert.*; +import javax.net.ssl.*; + +import net.java.sip.communicator.service.protocol.*; + +/** + * An implementation of the OperationSetTLS for the Jabber protocol. + * + * @author Markus Kilås + */ +public class OperationSetTLSJabberImpl + implements OperationSetTLS +{ + private final ProtocolProviderServiceJabberImpl jabberService; + + public OperationSetTLSJabberImpl( + ProtocolProviderServiceJabberImpl jabberService) + { + this.jabberService = jabberService; + } + + public String getCipherSuite() { + final String result; + final SSLSocket socket = jabberService.getSSLSocket(); + if (socket == null) + { + result = null; + } + else + { + result = socket.getSession().getCipherSuite(); + } + return result; + } + + public String getProtocol() { + final String result; + final SSLSocket socket = jabberService.getSSLSocket(); + if (socket == null) + { + result = null; + } + else + { + result = socket.getSession().getProtocol(); + } + return result; + } + + public Certificate[] getServerCertificates() { + Certificate[] result = null; + final SSLSocket socket = jabberService.getSSLSocket(); + if (socket != null) + { + try + { + result = socket.getSession().getPeerCertificates(); + } + catch (SSLPeerUnverifiedException ignored) // NOPMD + { + // result will be null + } + } + return result; + } + +} 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 4e465c9cd..f9ca61ad1 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -1154,6 +1154,32 @@ private ConnectState connectAndLogin( { SSLContext sslContext = loginStrategy.createSslContext(cvs, getTrustManager(cvs, serviceName)); + + // log SSL/TLS algorithms and protocols + if (logger.isDebugEnabled()) + { + final StringBuilder buff = new StringBuilder(); + buff.append("Available TLS protocols and algorithms:\n"); + buff.append("Default protocols: "); + buff.append(Arrays.toString( + sslContext.getDefaultSSLParameters().getProtocols())); + buff.append("\n"); + buff.append("Supported protocols: "); + buff.append(Arrays.toString( + sslContext.getSupportedSSLParameters().getProtocols())); + buff.append("\n"); + buff.append("Default cipher suites: "); + buff.append(Arrays.toString( + sslContext.getDefaultSSLParameters() + .getCipherSuites())); + buff.append("\n"); + buff.append("Supported cipher suites: "); + buff.append(Arrays.toString( + sslContext.getSupportedSSLParameters() + .getCipherSuites())); + logger.debug(buff.toString()); + } + connection.setCustomSslContext(sslContext); } else if (tlsRequired) @@ -1220,6 +1246,35 @@ else if (tlsRequired) } else { + if (connection.getSocket() instanceof SSLSocket) + { + final SSLSocket sslSocket = (SSLSocket) connection.getSocket(); + StringBuilder buff = new StringBuilder(); + buff.append("Chosen TLS protocol and algorithm:\n") + .append("Protocol: ").append(sslSocket.getSession() + .getProtocol()).append("\n") + .append("Cipher suite: ").append(sslSocket.getSession() + .getCipherSuite()); + logger.info(buff.toString()); + + if (logger.isDebugEnabled()) + { + buff = new StringBuilder(); + buff.append("Server TLS certificate chain:\n"); + try + { + buff.append(Arrays.toString( + sslSocket.getSession().getPeerCertificates())); + } + catch (SSLPeerUnverifiedException ex) + { + buff.append(""); + } + logger.debug(buff.toString()); + } + } + connection.addConnectionListener(connectionListener); } @@ -1859,6 +1914,11 @@ protected void initialize(String screenname, new OperationSetUserSearchJabberImpl(this)); } + OperationSetTLS opsetTLS + = new OperationSetTLSJabberImpl(this); + addSupportedOperationSet(OperationSetTLS.class, + opsetTLS); + isInitialized = true; } } @@ -2867,4 +2927,24 @@ private static void loadJabberServiceClasses() logger.error("Error loading classes in smack", e); } } + + /** + * Return the SSL socket (if TLS used). + * @return The SSL socket or null if not used + */ + public SSLSocket getSSLSocket() + { + final SSLSocket result; + final Socket socket = connection.getSocket(); + if (socket instanceof SSLSocket) + { + result = (SSLSocket) socket; + } + else + { + result = null; + } + return result; + } + } diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetTLS.java b/src/net/java/sip/communicator/service/protocol/OperationSetTLS.java new file mode 100644 index 000000000..2a3e796a7 --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/OperationSetTLS.java @@ -0,0 +1,43 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol; + +import java.security.cert.*; + +/** + * An OperationSet that allows access to information about TLS used by + * the protocol provider. + * + * @author Markus Kilås + */ +public interface OperationSetTLS + extends OperationSet +{ + /** + * Returns the negotiated cipher suite + * + * @return The cipher suite name used for instance + * "TLS_RSA_WITH_AES_256_CBC_SHA" or null if TLS is not used. + */ + String getCipherSuite(); + + /** + * Returns the negotiated SSL/TLS protocol. + * + * @return The protocol name used for instance "TLSv1". + */ + String getProtocol(); + + /** + * Returns the TLS server certificate chain with the end entity certificate + * in the first position and the issuers following (if any returned by the + * server). + * + * @return The TLS server certificate chain. + */ + Certificate[] getServerCertificates(); +}