JDK-8036970 : Accessing Tomcat 8.0.3 via HTTPS doesn't work using TLS 1.2 GCM with ucrypto provider
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.crypto
  • Affected Version: 8
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: solaris_11
  • CPU: x86
  • Submitted: 2014-03-07
  • Updated: 2015-01-21
  • Resolved: 2014-04-02
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
8u40Fixed 9 b08Fixed
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b129)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b69, mixed mode)

java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

SunOS 7iv05-server-2 5.11 11.1 i86pc i386 i86pc

Trying to access a locally installed Tomcat 8.0.3 instance via HTTPS using a SSL certificate doesn't work with Java 8 build 129 and 132; the browser only shows an error message indicating a SSL connection error.

However, using the same Tomcat with Java 7u51 instead of Java 8 works.

See the discussion in the java.net forum:


REGRESSION.  Last worked in version 7u51

$ /usr/local/jdk/jdk1.7.0/bin/java -version
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) Server VM (build 24.51-b03, mixed mode)

$ /usr/local/jdk/jdk1.7.0/bin/java -d64 -version
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)

1. Create a certificate:

$ keytool -genkeypair -alias tomcat -keyalg RSA
$ keytool -certreq -keyalg RSA -alias tomcat -file tomcat.csr

2. Sign the request using my own CA:

$ openssl ca -out tomcat.cer -policy policy_anything -days 3650 -infiles tomcat.csr

3. Import the root CA certificate and the signed certificate into the Tomcat user's keytore:

$ keytool -import -alias root -trustcacerts -file <my-own-root-ca.crt>
$ keytool -import -alias tomcat -file tomcat.cer

4. Install Tomcat 8.0.3 and enable the SSL connector as described in the docs, i.e. enable the following connector in <tomcat_home>/conf/server.conf:

<Connector port="4443" protocol="HTTP/1.1" SSLEnabled="true"
    maxThreads="150" scheme="https" secure="true"
    clientAuth="false" sslProtocol="TLS"
    keystoreFile="${user.home}/.keystore" keystorePass="changeit" />

5. Start Tomcat and try to access its web page via HTTPS (http://localhost:8443)

The Tomcat welcome web page saying "If you're seeing this, you've successfully installed Tomcat. Congratulations!"
The web page can't be loaded because of an SSL connection error.

Firefox shows:

An error occurred during a connection to 7iv05-server-2.vkb.loc:8443.
SSL received a record with an incorrect Message Authentication Code.
(Error code: ssl_error_bad_mac_read)

Google Chrome says:

SSL connection error

The file catalina.out in Tomcat's log folder contains the following stack trace:

07-Mar-2014 12:13:31.338 SEVERE [http-nio-8443-exec-15] org.apache.coyote.http11.AbstractHttp11Processor.process Error processing request
 java.lang.IllegalStateException: Must use either different key or iv for GCM encryption
        at com.oracle.security.ucrypto.NativeGCMCipher.engineDoFinal(NativeGCMCipher.java:359)
        at javax.crypto.CipherSpi.bufferCrypt(CipherSpi.java:830)
        at javax.crypto.CipherSpi.engineDoFinal(CipherSpi.java:730)
        at javax.crypto.Cipher.doFinal(Cipher.java:2416)
        at sun.security.ssl.CipherBox.encrypt(CipherBox.java:396)
        at sun.security.ssl.EngineOutputRecord.write(EngineOutputRecord.java:300)
        at sun.security.ssl.EngineOutputRecord.write(EngineOutputRecord.java:225)
        at sun.security.ssl.EngineWriter.writeRecord(EngineWriter.java:186)
        at sun.security.ssl.SSLEngineImpl.writeRecord(SSLEngineImpl.java:1280)
        at sun.security.ssl.SSLEngineImpl.writeAppRecord(SSLEngineImpl.java:1251)
        at sun.security.ssl.SSLEngineImpl.wrap(SSLEngineImpl.java:1166)
        at javax.net.ssl.SSLEngine.wrap(SSLEngine.java:469)
        at org.apache.tomcat.util.net.SecureNioChannel.write(SecureNioChannel.java:498)
        at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
        at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:173)
        at org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:139)
        at org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:197)
        at org.apache.coyote.http11.InternalNioOutputBuffer.access$000(InternalNioOutputBuffer.java:41)
        at org.apache.coyote.http11.InternalNioOutputBuffer$SocketOutputBuffer.doWrite(InternalNioOutputBuffer.java:320)
        at org.apache.coyote.http11.filters.ChunkedOutputFilter.doWrite(ChunkedOutputFilter.java:118)
        at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:257)
        at org.apache.coyote.Response.doWrite(Response.java:523)
        at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:391)
        at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:426)
        at org.apache.catalina.connector.OutputBuffer.realWriteChars(OutputBuffer.java:474)
        at org.apache.tomcat.util.buf.CharChunk.flushBuffer(CharChunk.java:393)
        at org.apache.catalina.connector.OutputBuffer.close(OutputBuffer.java:276)
        at org.apache.catalina.connector.Response.finishResponse(Response.java:409)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:557)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1015)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:652)
        at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1575)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1533)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:744)

This bug can be reproduced always.

Use Java 7u51 instead of Java 8.

The affected test case could pass in nightly result: http://aurora.ru.oracle.com/functional/faces/RunDetails.xhtml?names=669066.CORELIBS-JDK-NIGHTLY-JTREG-15 The fix looks good

Change Fix version to 9, but will backport to 8u afterwards.

Release team: Approved for deferral. Pushed this to 8u20. If that isn't correct then please adjust it.

I would suggest using the same workaround wording for both GCM issues. Disable the GCM implementation from the OracleUcrypto provider by adding the "Cipher.AES/GCM/NoPadding" string to the disabledServices section in its provider configuration file, for example, "ucrypto-solaris.cfg". Disable the OracleUCrypto provider completely, and use the SunJCE GCM implementation instead. (Note well that this disables all services provided by OracleUcrypto.) statically: edit the >java-home</lib/security/java.security file dynamically: use the java.security.Security.removeProvider("OracleUcrypto") API

Need to skip re-checking key+iv uniqueness in case of ShortBufferException.

Good catch. You could probably copy/paste the workaround text from the ucrypto issue already in there: 8031431

I think it's worthwhile mentioning a lighter-weight workaround than removing OracleUcrypto provider altogether: It is also possible to just disable the GCM impl from OracleUcrypto provider by adding the "Cipher.AES/GCM/NoPadding" string to the disabledServices section in its provider configuration file, i.e. "ucrypto-solaris.cfg"

Here is a draft, Valerie/Xuelei may also want to review it: Area: Security Libs / javax.crypto Solaris Only Newer clients such as Mozilla's Firefox may try to negotiate TLS v1.2 connections using AEAD/GCM-based ciphers (e.g. TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256). JDK 8-based servers such as Tomcat 8.0.3 may run into a known issue in the OracleUcrypto provider, in which a previous GCM state could be retried: java.lang.IllegalStateException: Must use either different key or iv for GCM encryption Until this issue is fixed, the workaround is to use the SunJCE GCM implementation instead of OracleUcrypto's. You can disable the OracleUcrypto provider by editing the <java-home>/lib/security/java.security file or by using the java.security.Security.removeProvider("OracleUcrypto") API at runtime.

I have added the "release-note=yes" label. We should add something to the JDK 8 release notes documenting the issue and workaround. Brad or Valerie, can you add some suggested text for Joni to incorporate?

> The GCM mode implementation of Cipher should allow such cases. Just for clarity, there was never discussion to change the ShortBufferException behavior. Correct, it's the finally block on the way out of this method that got us into this situation. If the underlying super.engineDoFinal failed, the requireReinit flag would be set regardless of the fail mode.

Please consider the spec of Cipher.doFinal() during the fix. Cipher.doFinal() can throw ShortBufferException if the given output buffer is too small to hold the result. This spec allows to adjust the output buffer and call the Cipher.doFinal() again. The GCM mode implementation of Cipher should allow such cases. There is another explain from the different angle of the cause. NativeGCMCipher.java ================ protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { .... try { return super.engineDoFinal(in, inOfs, inLen, out, outOfs); } catch (UcryptoException ue) { .... } finally { requireReinit = encrypt; } Note that the engineDoFinal() (call to this object and super object) is nested. It means a call to Cipher.doFinal() may call NativeGCMCipher.engineDoFinal() more than once (for example, CipherSpi) for the same crypto encryption. For the first call, the requireReinit is set to true for encryption in the finally block. And the following calls run into problems accordingly. There is no nested engineDoFinal() in GCM implementation in SunJCE.

Bug exists in the Ucrypto provider, SunJCE is not affected. Workaround is to remove the Ucrypto provider from the Security properties until this bug can be fixed. GCM ciphersuites were added to JDK 8, so 7 is not affected. If the Cipher implementation does not override the ByteBuffer APIs, CipherSpi has a default implementation that uses byte arrays. If there are no backing arrays available, it chunks the data from the ByteBuffers into 4096 byte chunks and uses local buffers as temp variables. These variables are then passed to the byte array Cipher implementations. It tries to write the final chunk via doFinal(), but then finds it needs more than 4096 bytes (+16 for tag), and then retries the doFinal() operation with a larger output buffer size. The SunJCE does expected bounds checking, and does not do any operation unless the output destination size is big enough. The Ucrypto provider tries the operation before checking the output size and sets the reinitRequired flag on the way out. There are a couple ways to solve this, have discussed with the RE.

8-defer-request justification: This bug should be deferred as it is not a show-stopper for jdk8. This unexpected exception only happens when OracleUcrypto provider is used and thus it only applies to certain version of Solaris releases. In addition, users can work around this bug by disabling OracleUcrypto provider and use SunJCE provider instead.

The different exception on Solaris 11, e.g. com.oracle.security.ucrypto.UcryptoException: CRYPTO_DATA_LEN_RANGE, is due to a known bug. https://bugs.openjdk.java.net/browse/JDK-8031431 AFAIK, this bug only exists in S11 and later releases. If you can find a S10u release which supports GCM, then you won't run into this bug.

I use S11u1 machine and cannot reproduce this. Not all Solaris 10 machines have Ucrypto GCM support, so it can only be determined at runtime.