<div dir="ltr"><div>AKA "1 out of 256 SSL/TLS handshakes fails with DHE cipher suites"</div><div><br></div><div>I reported this bug over a month of ago, but for some reason, it's not </div><div>yet visible at <a href="http://bugs.sun.com">bugs.sun.com</a>. I've included the bug report below just in </div>
<div>case. </div><div><br></div><div>It seems this commit from March 2012 inadvertently broke SSL/TLS DHE </div><div>cipher suites, causing the SSL/TLS handshake to fail approximately </div><div>1 out of 256 times: </div>
<div><br></div><div><a href="http://hg.openjdk.java.net/jdk7u/jdk7u-gate/jdk/rev/e574e475c8a6">http://hg.openjdk.java.net/jdk7u/jdk7u-gate/jdk/rev/e574e475c8a6</a></div><div><br></div><div>The commit was done to fix this bug:</div>
<div><br></div><div><a href="http://bugs.sun.com/view_bug.do?bug_id=7146728">http://bugs.sun.com/view_bug.do?bug_id=7146728</a></div><div><br></div><div>While generating a secret of the same length as modulus may be the right </div>
<div>choice generally speaking (and it's what e.g. IPsec uses), SSL/TLS uses </div><div>a different convention: leading zeroes must be stripped. </div><div><br></div><div>This is currently blocking us from updating our production systems to </div>
<div>Java 7, so although I have not contributed to OpenJDK before, I'd like </div><div>to submit a patch and a test case for this (I've signed the OCA </div><div>already). But before I do this, I'd like to check that the approach is </div>
<div>agreeable. </div><div><br></div><div>We have a separate "algorithm" value "TlsPremasterSecret", so </div><div>behavior for other cases could stay the same. Would a patch</div><div>like this:</div>
<div><br></div><div> } else if (algorithm.equals("TlsPremasterSecret")) {</div><div> // remove leading zero bytes per RFC 5246 Section 8.1.2</div><div> int i = 0;</div><div> while ((i < secret.length - 1) && (secret[i] == 0)) {</div>
<div> i++;</div><div> }</div><div> if (i == 0) {</div><div> return new SecretKeySpec(secret, "TlsPremasterSecret");</div><div> } else {</div><div> byte[] secret2 = new byte[secret.length - i];</div>
<div> System.arraycopy(secret, i, secret2, 0, secret2.length);</div><div> return new SecretKeySpec(secret2, "TlsPremasterSecret");</div><div> }</div><div> }</div><div><br></div><div>
Plus a test case (with fixed keys) that checks that leading zero is stripped </div><div>for TlsPremasterSecret and is not stripped otherwise, be sufficient?</div><div><br></div><div>Best regards,</div><div>Pasi</div><div>
<br></div><div>---snip---</div><div><br></div><div>Synopsis:</div><div>DHKeyAgreement calculates wrong TlsPremasterSecret 1 out of 256 times</div><div><br></div><div>Full OS version:</div><div>Tested on Windows 7 (Microsoft Windows [Version 6.1.7601]), but occurs in</div>
<div>e..g OpenJDK 7 as well.</div><div><br></div><div>Development Kit or Runtime version:</div><div>java version "1.7.0_17"</div><div>Java(TM) SE Runtime Environment (build 1.7.0_17-b02)</div><div>Java HotSpot(TM) Client VM (build 23.7-b01, mixed mode, sharing)</div>
<div><br></div><div>Description:</div><div>When performing Diffie-Hellman key agreement for SSL/TLS, the TLS</div><div>specification (RFC 5246) says that "Leading bytes of Z that contain all zero</div><div>bits are stripped before it is used as the pre_master_secret."</div>
<div><br></div><div>However, com.sun.crypto.provider.DHKeyAgreement.java does not strip leading</div><div>zero bytes. This causes approximately 1 out 256 SSL/TLS handshakes with</div><div>DH/DHE cipher suites to fail (when the leading byte happens, by chance, to</div>
<div>be zero).</div><div><br></div><div>Steps to Reproduce:</div><div>1. Start a simple JSSE socket server with -Djavax.net.debug=all.</div><div><br></div><div>2. Connect to the server with e.g. OpenSSL command line tool, ensuring that</div>
<div>DHE cipher suite gets selected (e.g. "openssl s_client -cipher</div><div>DHE-RSA-AES128-SHA -connect <a href="http://192.168.81.1:9999">192.168.81.1:9999</a>") repeatedly. Other SSL</div><div>clients can be used -- this is not an OpenSSL bug (see below).</div>
<div><br></div><div>3. Repeat the connection. After a couple of hundred successful connections,</div><div>the connection will fail with handshake_failure alert.</div><div><br></div><div>4. Examine the JSSE debug logs produced by the server: the failed connection</div>
<div>will have a PreMaster secret that begins with zero byte</div><div>(while all other connections have non-zero byte here). For example:</div><div><br></div><div>SESSION KEYGEN:</div><div>PreMaster Secret:</div><div>0000: 00 70 C5 7E 91 38 C8 DE ED 75 3D 76 8A B5 44 69 .p...8...u=v..Di</div>
<div>0010: E7 32 1C EE 80 77 50 C7 A9 51 24 2E E3 15 11 30 .2...wP..Q$....0</div><div>0020: 9D F6 9F BC 9D EB 5C 18 F7 A4 19 ED 1A AC 2E 0C ......\.........</div><div>0030: E3 18 C5 11 B1 80 07 7D B1 C6 70 A8 D7 EB CF DD ..........p.....</div>
<div>0040: 2D B5 1D BC 01 3E 28 2A 2B 5B 38 8F EB 20 F2 A2 -....>(*+[8.. ..</div><div>0050: 00 07 47 F7 87 B8 99 CB EF B4 13 04 C8 8B 82 FB ..G.............</div><div><br></div><div>Expected Result:</div><div>Expected result is that every connection succeed.</div>
<div><br></div><div>Actual Result:</div><div>Roughly one out of 256 connections fail.</div><div><br></div><div>Source code for an executable test case:</div><div><br></div><div>Java server:</div><div><br></div><div>import javax.net.ssl.SSLServerSocket;</div>
<div>import javax.net.ssl.SSLServerSocketFactory;</div><div>import javax.net.ssl.SSLSocket;</div><div><br></div><div>public class TestServer {</div><div> public static void main(String args[]) throws Exception {</div><div>
SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();</div><div> SSLServerSocket ss = (SSLServerSocket) ssf.createServerSocket(9999);</div><div> System.out.println("Listening on port 9999");</div>
<div> for (String cs : ss.getEnabledCipherSuites()) {</div><div> System.out.println(cs);</div><div> }</div><div> while (true) {</div><div> SSLSocket s = (SSLSocket) ss.accept();</div>
<div> System.out.println("Connected with "+s.getSession().getCipherSuite());</div><div> s.close();</div><div> }</div><div> }</div><div>}</div><div><br></div><div>Run as as follows:</div>
<div><br></div><div>keytool -storepass "password" -keypass "password" -genkey -keyalg RSA -keystore test_keystore.jks -dname CN=test</div><div>javac TestServer.java</div><div>java -Djavax.net.debug=all -Djavax.net.ssl.keyStore=./test_keystore.jks -Djavax.net.ssl.keyStorePassword=password TestServer</div>
<div><br></div><div>OpenSSL client:</div><div><br></div><div>set -e</div><div>while true; do</div><div> openssl s_client -cipher DHE-RSA-AES128-SHA -connect <a href="http://127.0.0.1:9999">127.0.0.1:9999</a> -quiet -no_ign_eof < /dev/null</div>
<div>done</div><div><br></div><div>Workaround:</div><div>Disable Diffie-Hellman cipher suites.</div><div><br></div><div>---snip---</div><div><br></div></div>