All work

 
38 of 38

DataSealer roundtrip encryption fails on Red Hat 9 OpenJDK in FIPS mode

Fixed

Description

When trying to run Shibboleth with OpenJDK in FIPS mode, we noticed that the {{DataSealer.testEncryption()}} was failing with “Round trip encryption/decryption test unsuccessful. Decrypted text did not match”, causing the IdP to fail to start. We traced this to a quirk of the FIPS crypto implementation filling the unused portion of the output buffer with null characters, which the {{String}} constructor was picking up and including in the {{decrypted}} String.

I’ve attached a small patch that fixes this, taking into account that the actual length of the output (as returned by {{Cipher.update()}} and {{Cipher.doFinal}}) may be smaller than the size returned by {{Cipher.getOutputSize}}.

Environment

None

Attachments

2

Details

Assignee

Reporter

Components

Fix versions

Affects versions

Created November 21, 2023 at 9:46 PM
Updated March 14, 2024 at 4:17 PM
Resolved November 27, 2023 at 2:23 PM

Activity

Scott Cantor 
November 27, 2023 at 2:10 PM

A nominal search for some of the relevant Cipher methods do not reveal any other (direct) usage by us across the IdP stack.

Scott Cantor 
November 22, 2023 at 6:49 PM

Appreciate all your help. We don’t do a lot of this anywhere else and this is exactly why, but we’ll search the code ourselves just in case after addressing this.

Paul Donohue 
November 22, 2023 at 6:27 PM
(edited)

(I’ve been working with Rich on this)

I’ve attached an improved patch (DataSealer.patch) that fixes another issue with the Shib calls to Cipher.

Shib was definitely using the Cipher API improperly. It looks to me like the FIPS implementation is functioning within the API guarantees, but feel free to verify.

Note: I have NOT checked the rest of the Shibboleth codebase for other instances of similar misuse of Cipher.

More specifically:

  • Cipher.getOutputSize() returns a number that is intended to be used for sizing the output buffer.
    However, per the API spec: “The actual output length … may be smaller than the length returned by this method.”
    Shib was effectively assuming that getOutputSize() returned the actual output length, and it was doing the wrong thing in cases where getOutputSize() did not return the actual output length.
    It looks like the non-FIPS implementation just happens to have getOutputSize() return the actual output length, but the FIPS implementation returns a number larger than the actual output length.

  • In case it isn’t clear what Shib was doing wrong in the case where getOutputSize() is too big:
    When converting a byte array to a String without specifying a length, the byte array’s length is used and null characters are included in the String. So, for example:
    byte[] b = new byte[2];
    b[0] = 'x';
    String s = new String(b, StandardCharsets.UTF_8);
    s.length(); // Returns 2
    "x".equals(s); // Returns false because “x” != “x\0”

  • So, the first fix is: Don’t assume the size of the output buffer is the size of the output data; Instead, track the actual output data size and pass it to anything that reads the output buffer.

  • The additional problem that I fixed in my improved patch is:
    The Cipher API supports either streaming (“multiple-part”) or all-at-once (“single-part”) encryption/decryption.
    Shib is not streaming, so it may use the all-at-once behavior … But it was instead using the streaming behavior … And it was using it wrong.
    If you are using the streaming functions, then you are supposed to call getOutputSize() and create a new byte array output buffer for every call to .update() or .doFinal(), and then concatenate the buffers. However, Shib was not doing that.
    The fix is to switch to using the all-at-once behavior (call the four-parameter doFinal()) instead of using the streaming behavior (calling update() followed by the two-parameter doFinal()).

Scott Cantor 
November 21, 2023 at 10:36 PM

Much appreciated. Seems like the code was probably wrong anyway, but I’ll dig into it, I want to make sure this isn’t a case of the FIPS code violating any API guarantees.