From 8108adbc00c17c595b947ef25b3e6f2f9bb45b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Kil=C3=A5s?= Date: Sun, 13 Apr 2014 10:36:10 +0200 Subject: [PATCH] Logging of TLS protocols and cipher suites available during connection establishment and then the chosen ones. Add ability to access TLS cipher suite, protocol name and server certificate using an new OperationSet. Only implemented for XMPP so far. Also makes this information available in the call information frame. --- resources/languages/resources.properties | 6 + .../impl/gui/main/call/CallInfoFrame.java | 53 ++++++ .../gui/main/call/ViewCertificateFrame.java | 155 ++++++++++++++++++ .../jabber/OperationSetTLSJabberImpl.java | 75 +++++++++ .../ProtocolProviderServiceJabberImpl.java | 80 +++++++++ .../service/protocol/OperationSetTLS.java | 43 +++++ 6 files changed, 412 insertions(+) create mode 100644 src/net/java/sip/communicator/impl/gui/main/call/ViewCertificateFrame.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/OperationSetTLSJabberImpl.java create mode 100644 src/net/java/sip/communicator/service/protocol/OperationSetTLS.java 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(); +}