Skip to content

Commit

Permalink
854 jdk17 visibility (#855)
Browse files Browse the repository at this point in the history
* Closes #854.

- Replaced `ByteArrayInputStream` reflection with new `BytesInputStream` implementation. The reflection is what required `--add-opens java.base/java.io=jjwt.api` on JDK 17+.
- Refactored `KeysBridge` to perform our own key length logic instead of delegating to `sun.security.util.KeyUtil`.  The reflection is what required `--add-opens java.base/sun.security.util=jjwt.api` on JDK 17+
- Removed `AddOpens.java` due to above refactoring (no longer needed).
- Returned a test-only `--add-opens` for `sun.security.util` for 3 test cases (added to `test.addOpens` maven property)
  • Loading branch information
lhazlewood committed Oct 6, 2023
1 parent fad6e27 commit a7d3d31
Show file tree
Hide file tree
Showing 32 changed files with 165 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
Expand Down Expand Up @@ -588,18 +587,18 @@ private String sign(final Payload payload, final Key key, final Provider provide
InputStream payloadStream = null; // not needed unless b64 is enabled
if (this.encodePayload) {
encodeAndWrite("JWS Payload", payload, jws);
signingInput = new ByteArrayInputStream(jws.toByteArray());
signingInput = Streams.of(jws.toByteArray());
} else { // b64

// First, ensure we have the base64url header bytes + the SEPARATOR_CHAR byte:
ByteArrayInputStream prefixStream = new ByteArrayInputStream(jws.toByteArray());
InputStream prefixStream = Streams.of(jws.toByteArray());

// Next, b64 extension requires the raw (non-encoded) payload to be included directly in the signing input,
// so we ensure we have an input stream for that:
if (payload.isClaims() || payload.isCompressed()) {
ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192);
writeAndClose("JWS Unencoded Payload", payload, claimsOut);
payloadStream = new ByteArrayInputStream(claimsOut.toByteArray());
payloadStream = Streams.of(claimsOut.toByteArray());
} else {
// No claims and not compressed, so just get the direct InputStream:
payloadStream = Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null.");
Expand Down Expand Up @@ -698,7 +697,7 @@ private String encrypt(final Payload content, final Key key, final Provider keyP
if (content.isClaims()) {
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
writeAndClose("JWE Claims", content, out);
plaintext = new ByteArrayInputStream(out.toByteArray());
plaintext = Streams.of(out.toByteArray());
} else {
plaintext = content.toInputStream();
}
Expand Down
17 changes: 8 additions & 9 deletions impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.io.AbstractParser;
import io.jsonwebtoken.impl.io.BytesInputStream;
import io.jsonwebtoken.impl.io.CharSequenceReader;
import io.jsonwebtoken.impl.io.JsonObjectDeserializer;
import io.jsonwebtoken.impl.io.Streams;
Expand All @@ -56,7 +57,6 @@
import io.jsonwebtoken.io.DeserializationException;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.DateFormats;
import io.jsonwebtoken.lang.Objects;
Expand All @@ -74,7 +74,6 @@

import javax.crypto.SecretKey;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.Reader;
Expand Down Expand Up @@ -315,7 +314,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
bb.rewind();
byte[] data = new byte[bb.remaining()];
bb.get(data);
verificationInput = new ByteArrayInputStream(data);
verificationInput = Streams.of(data);
} else { // b64 extension
ByteBuffer headerBuf = StandardCharsets.US_ASCII.encode(Strings.wrap(tokenized.getProtected()));
headerBuf.rewind();
Expand All @@ -325,7 +324,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
buf.rewind();
byte[] data = new byte[buf.remaining()];
buf.get(data);
InputStream prefixStream = new ByteArrayInputStream(data);
InputStream prefixStream = Streams.of(data);
payloadStream = payload.toInputStream();
// We wrap the payloadStream here in an UncloseableInputStream to prevent the SequenceInputStream from
// closing it since we'll need to rewind/reset it if decompression is enabled
Expand Down Expand Up @@ -378,7 +377,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe

// =============== Header =================
final byte[] headerBytes = decode(base64UrlHeader, "protected header");
Map<String, ?> m = deserialize(new ByteArrayInputStream(headerBytes), "protected header");
Map<String, ?> m = deserialize(Streams.of(headerBytes), "protected header");
Header header;
try {
header = tokenized.createHeader(m);
Expand Down Expand Up @@ -517,7 +516,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe
ByteBuffer buf = StandardCharsets.US_ASCII.encode(Strings.wrap(base64UrlHeader));
final byte[] aadBytes = new byte[buf.remaining()];
buf.get(aadBytes);
InputStream aad = new ByteArrayInputStream(aadBytes);
InputStream aad = Streams.of(aadBytes);

base64Url = base64UrlDigest;
//guaranteed to be non-empty via the `alg` + digest check above:
Expand Down Expand Up @@ -834,8 +833,8 @@ public Jws<byte[]> parseSignedContent(CharSequence jws, byte[] unencodedPayload)
}

private static Payload payloadFor(InputStream in) {
if (in instanceof ByteArrayInputStream) {
byte[] data = Classes.getFieldValue(in, "buf", byte[].class);
if (in instanceof BytesInputStream) {
byte[] data = Streams.bytes(in, "Unable to obtain payload InputStream bytes.");
return new Payload(data, null);
}
//if (in.markSupported()) in.mark(0);
Expand Down Expand Up @@ -874,7 +873,7 @@ public Jwe<Claims> parseEncryptedClaims(CharSequence compact) throws JwtExceptio

protected byte[] decode(CharSequence base64UrlEncoded, String name) {
try {
InputStream decoding = this.decoder.decode(new ByteArrayInputStream(Strings.utf8(base64UrlEncoded)));
InputStream decoding = this.decoder.decode(Streams.of(Strings.utf8(base64UrlEncoded)));
return Streams.bytes(decoding, "Unable to Base64Url-decode input.");
} catch (Throwable t) {
// Don't disclose potentially-sensitive information per https://github.com/jwtk/jjwt/issues/824:
Expand Down
6 changes: 3 additions & 3 deletions impl/src/main/java/io/jsonwebtoken/impl/Payload.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;

Expand Down Expand Up @@ -68,10 +68,10 @@ private Payload(Claims claims, CharSequence string, byte[] bytes, InputStream in
}
this.bytes = data;
if (in == null && !Bytes.isEmpty(this.bytes)) {
in = new ByteArrayInputStream(data);
in = Streams.of(data);
}
this.inputStreamEmpty = in == null;
this.inputStream = this.inputStreamEmpty ? new ByteArrayInputStream(Bytes.EMPTY) : in;
this.inputStream = this.inputStreamEmpty ? Streams.of(Bytes.EMPTY) : in;
}

boolean isClaims() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -152,7 +151,7 @@ public final byte[] decompress(byte[] compressed) {
* @throws IOException if the decompression runs into an IO problem
*/
protected byte[] doDecompress(byte[] compressed) throws IOException {
InputStream is = new ByteArrayInputStream(compressed);
InputStream is = Streams.of(compressed);
InputStream decompress = decompress(is);
byte[] buffer = new byte[512];
ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length);
Expand Down
43 changes: 43 additions & 0 deletions impl/src/main/java/io/jsonwebtoken/impl/io/BytesInputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright © 2023 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.io;

import io.jsonwebtoken.impl.lang.Bytes;

import java.io.ByteArrayInputStream;
import java.io.IOException;

/**
* Allows read access to the internal byte array, avoiding the need copy/extract to a
* {@link java.io.ByteArrayOutputStream}.
*
* @since JJWT_RELEASE_VERSION
*/
public final class BytesInputStream extends ByteArrayInputStream {

BytesInputStream(byte[] buf) {
super(Bytes.isEmpty(buf) ? Bytes.EMPTY : buf);
}

public byte[] getBytes() {
return this.buf;
}

@Override
public void close() throws IOException {
reset();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

@SuppressWarnings("DeprecatedIsStillUsed")
Expand All @@ -38,7 +37,7 @@ public InputStream decode(InputStream in) throws DecodingException {
try {
byte[] data = Streams.bytes(in, "Unable to Base64URL-decode input.");
data = delegate.decode(Strings.utf8(data));
return new ByteArrayInputStream(data);
return Streams.of(data);
} catch (Throwable t) {
String msg = "Unable to Base64Url-decode InputStream: " + t.getMessage();
throw new DecodingException(msg, t);
Expand Down
21 changes: 7 additions & 14 deletions impl/src/main/java/io/jsonwebtoken/impl/io/Streams.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@
*/
package io.jsonwebtoken.impl.io;

import io.jsonwebtoken.impl.lang.AddOpens;
import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Flushable;
import java.io.IOException;
Expand All @@ -43,30 +40,26 @@ public class Streams {
*/
public static final int EOF = -1;

static {
// For reflective access to ByteArrayInputStream via the 'bytes' static method on >= JDK 9:
AddOpens.open("java.base", "java.io");
}

public static byte[] bytes(final InputStream in, String exmsg) {
if (in instanceof ByteArrayInputStream) {
return Classes.getFieldValue(in, "buf", byte[].class);
if (in instanceof BytesInputStream) {
return ((BytesInputStream) in).getBytes();
}
// otherwise we have to copy over:
ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
copy(in, out, new byte[8192], exmsg);
return out.toByteArray();
}

public static ByteArrayInputStream of(byte[] bytes) {
return Bytes.isEmpty(bytes) ? new ByteArrayInputStream(Bytes.EMPTY) : new ByteArrayInputStream(bytes);
public static InputStream of(byte[] bytes) {
return new BytesInputStream(bytes);
}

public static ByteArrayInputStream of(CharSequence seq) {
public static InputStream of(CharSequence seq) {
return of(Strings.utf8(seq));
}

public static Reader reader(byte[] bytes) {
return reader(new ByteArrayInputStream(bytes));
return reader(Streams.of(bytes));
}

public static Reader reader(InputStream in) {
Expand Down

0 comments on commit a7d3d31

Please sign in to comment.