diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index 4db6e0ecf7..dc3fbbbc98 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -618,6 +618,9 @@ service.gui.callinfo.PEER_COUNT=Participant count 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.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.CALL_DURATION=Call duration service.gui.callinfo.CODEC=Codec / Frequency service.gui.callinfo.NA=N.A. 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 3094622b73..0b73e87937 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.io.*; +import java.security.cert.*; /** * The frame displaying the statistical information for a telephony conference. @@ -172,6 +174,64 @@ private String getLineString(String labelText, String infoText) { return "" + labelText + " : " + infoText + "
"; } + + /** + * Returns an HTML string containing the PEM encoded certificate chain. + * + * @param chain the certificate chain + * @return the newly constructed HTML string + */ + private String getPEMChain(Certificate[] chain) + { + final StringBuilder buff = new StringBuilder(); + buff.append("
");
+        if (chain != null)
+        {
+            for (Certificate cert : chain)
+            {
+                if (cert instanceof X509Certificate)
+                {
+                    X509Certificate x509 = (X509Certificate) cert;
+                    buff.append("Subject: ");
+                    buff.append(x509.getSubjectDN().getName()).append("\n");
+                    buff.append("Issuer: ");
+                    buff.append(x509.getIssuerDN().getName()).append("\n");
+                }
+                else
+                {
+                    buff.append("Unknown certificate type: ");
+                    buff.append(cert.getType());
+                }
+                try
+                {
+                    buff.append("-----BEGIN CERTIFICATE-----\n");
+                    byte[] encoded = Base64.encode(cert.getEncoded());
+                    ByteArrayOutputStream os = new ByteArrayOutputStream();
+                    for (int i = 0; i < encoded.length; i += 64)
+                    {
+                        if ((i + 64) < encoded.length)
+                        {
+                            os.write(encoded, i, 64);
+                            os.write('\n');
+                        }
+                        else
+                        {
+                            os.write(encoded, i, encoded.length - i);
+                        }
+                     }
+                    buff.append(os.toString()).append("\n");
+                    buff.append("-----END CERTIFICATE-----\n");
+                }
+                catch (CertificateEncodingException ex)
+                {
+                    buff.append("Unable to encode certificate: ");
+                    buff.append(ex.getLocalizedMessage());
+                }
+            }
+        }
+        buff.append("
"); + return buff.toString(); + } /** * Constructs the call info text. @@ -235,7 +295,29 @@ private boolean constructCallInfo() resources.getI18NString("service.gui.callinfo.CALL_TRANSPORT"), preferredTransport.toString())); + if (preferredTransport == TransportProtocol.TLS) + { + stringBuffer.append(getLineString( + resources.getI18NString( + "service.gui.callinfo.TLS_PROTOCOL"), + aCall.getProtocolProvider().getTLSProtocol())); + stringBuffer.append(getLineString( + resources.getI18NString( + "service.gui.callinfo.TLS_CIPHER_SUITE"), + aCall.getProtocolProvider().getTLSCipherSuite())); + } + constructCallPeersInfo(stringBuffer); + + if (preferredTransport == TransportProtocol.TLS) + { + stringBuffer.append("
\n"); + stringBuffer.append(getLineString( + resources.getI18NString( + "service.gui.callinfo.TLS_SERVER_CERTIFICATE_CHAIN"), + getPEMChain(aCall.getProtocolProvider() + .getTLSServerCertificates()))); + } stringBuffer.append("

"); 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 05f3d511f1..abaf85e259 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -637,6 +637,73 @@ public TransportProtocol getTransportProtocol() return TransportProtocol.UNKNOWN; } + /** + * Returns the negotiated cipher suite if TLS is used. + * + * @return The cipher suite name used for instance + * "TLS_RSA_WITH_AES_256_CBC_SHA" or null if TLS is not used. + */ + @Override + public String getTLSCipherSuite() + { + final String result; + if (connection.getSocket() instanceof SSLSocket) + { + final SSLSocket sslSocket = (SSLSocket) connection.getSocket(); + result = sslSocket.getSession().getCipherSuite(); + } + else + { + result = null; + } + return result; + } + + /** + * Returns the negotiated SSL/TLS protocol. + * + * @return The protocol name used for instance "TLSv1" or null if TLS + * (or SSL) is not used. + */ + @Override + public String getTLSProtocol() + { + final String result; + if (connection.getSocket() instanceof SSLSocket) + { + final SSLSocket sslSocket = (SSLSocket) connection.getSocket(); + result = sslSocket.getSession().getProtocol(); + } + else + { + result = null; + } + return result; + } + + /** + * 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 or null if TLS is not used. + */ + @Override + public java.security.cert.Certificate[] getTLSServerCertificates() + { + java.security.cert.Certificate[] result = null; + if (connection.getSocket() instanceof SSLSocket) + { + try { + final SSLSocket sslSocket = (SSLSocket) connection.getSocket(); + result = sslSocket.getSession().getPeerCertificates(); + } catch (SSLPeerUnverifiedException ignored) { + // result will be null + } + } + return result; + } + /** * Connects and logins to the server * @param authority SecurityAuthority @@ -1148,6 +1215,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) @@ -1214,6 +1307,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); } diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java b/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java index dac258e0f1..32733a72af 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java @@ -6,6 +6,7 @@ */ package net.java.sip.communicator.impl.protocol.mock; +import java.security.cert.*; import java.util.*; import net.java.sip.communicator.service.protocol.*; @@ -263,6 +264,45 @@ public TransportProtocol getTransportProtocol() return TransportProtocol.UNKNOWN; } + /** + * Returns the negotiated cipher suite if TLS is used. + * + * Note: This implementation always returns null as TLS is not used. + * + * @return The cipher suite name used for instance + * "TLS_RSA_WITH_AES_256_CBC_SHA" or null if TLS is not used. + */ + public String getTLSCipherSuite() + { + return null; + } + + /** + * Returns the negotiated SSL/TLS protocol. + * + * Note: This implementation always returns null as TLS is not used. + * + * @return The protocol name used for instance "TLSv1" or null if TLS + * (or SSL) is not used. + */ + public String getTLSProtocol() + { + return null; + } + + /** + * 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). + * + * Note: This implementation always returns null as TLS is not used. + * + * @return The TLS server certificate chain or null if TLS is not used. + */ + public Certificate[] getTLSServerCertificates() { + return null; + } + /** * Returns the AccountID that uniquely identifies the account represented by * this instance of the ProtocolProviderService. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java index 0d23dfc0ed..d0fb049469 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java @@ -11,6 +11,7 @@ import gov.nist.javax.sip.message.*; import java.net.*; +import java.security.cert.*; import java.text.*; import java.util.*; @@ -1714,6 +1715,27 @@ public TransportProtocol getTransportProtocol() sipRegistrarConnection.getTransport()); } + @Override + public String getTLSCipherSuite() + { + // TODO: Return the negotiated cipher suite if TLS is used + return null; + } + + @Override + public String getTLSProtocol() + { + // TODO: Return the negotiated SSL/TLS protocol if used + return null; + } + + @Override + public Certificate[] getTLSServerCertificates() + { + // TODO: Return the TLS server certificate chain if available + return null; + } + /** * Registers methodProcessor in the methorProcessors table * so that it would receives all messages in a transaction initiated by a diff --git a/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java b/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java index 429204afe9..c335f0f97f 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java @@ -6,6 +6,7 @@ */ package net.java.sip.communicator.service.protocol; +import java.security.cert.*; import java.util.*; import net.java.sip.communicator.service.protocol.event.*; @@ -300,6 +301,55 @@ public void clearRegistrationStateChangeListener() registrationListeners.clear(); } } + + /** + * Returns the negotiated cipher suite if TLS is used. + * + * Note: The default implementation always returns null. Implementors + * should override and provide an implementation for this method if TLS is + * used. + * + * @return The cipher suite name used for instance + * "TLS_RSA_WITH_AES_256_CBC_SHA" or null if TLS is not used. + */ + @Override + public String getTLSCipherSuite() + { + return null; + } + + /** + * Returns the negotiated SSL/TLS protocol. + * + * Note: The default implementation always returns null. Implementors + * should override and provide an implementation for this method if TLS is + * used. + * + * @return The protocol name used for instance "TLSv1" or null if TLS + * (or SSL) is not used. + */ + @Override + public String getTLSProtocol() + { + return null; + } + + /** + * 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). + * + * Note: The default implementation always returns null. Implementors + * should override and provide an implementation for this method if TLS is + * used. + * + * @return The TLS server certificate chain or null if TLS is not used. + */ + @Override + public Certificate[] getTLSServerCertificates() + { + return null; + } /** * A clear display for ProtocolProvider when its printed in logs. diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java index 5bd182ba08..89cb9abf61 100644 --- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java +++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java @@ -6,6 +6,7 @@ */ package net.java.sip.communicator.service.protocol; +import java.security.cert.*; import java.util.*; import net.java.sip.communicator.service.protocol.event.*; @@ -211,4 +212,29 @@ public void removeRegistrationStateChangeListener( * UNKNOWN. */ public TransportProtocol getTransportProtocol(); + + /** + * Returns the negotiated cipher suite if TLS is used. + * + * @return The cipher suite name used for instance + * "TLS_RSA_WITH_AES_256_CBC_SHA" or null if TLS is not used. + */ + public String getTLSCipherSuite(); + + /** + * Returns the negotiated SSL/TLS protocol. + * + * @return The protocol name used for instance "TLSv1" or null if TLS + * (or SSL) is not used. + */ + public String getTLSProtocol(); + + /** + * 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 or null if TLS is not used. + */ + public Certificate[] getTLSServerCertificates(); }