Skip to content

Commit

Permalink
Closes #854.
Browse files Browse the repository at this point in the history
- Replaced ByteArrayInputStream reflection with new BytesInputStream implementation. The reflection is what required --add-opens java.base/java.io=jjwt.api on JDK 17.
  • Loading branch information
lhazlewood committed Oct 6, 2023
1 parent fad6e27 commit b35a110
Show file tree
Hide file tree
Showing 28 changed files with 135 additions and 96 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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.jsonwebtoken.impl.security;

import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.Nameable;
import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.lang.ParameterReadable;
Expand All @@ -31,7 +32,6 @@
import io.jsonwebtoken.security.Jwks;
import io.jsonwebtoken.security.KeyOperation;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.Key;
Expand Down Expand Up @@ -152,7 +152,7 @@ public JwkThumbprint thumbprint(final HashAlgorithm alg) {
String json = toThumbprintJson();
Assert.hasText(json, "Canonical JWK Thumbprint JSON cannot be null or empty.");
byte[] bytes = json.getBytes(StandardCharsets.UTF_8); // https://www.rfc-editor.org/rfc/rfc7638#section-3 #2
InputStream in = new ByteArrayInputStream(bytes);
InputStream in = Streams.of(bytes);
byte[] digest = alg.digest(new DefaultRequest<>(in, this.context.getProvider(), this.context.getRandom()));
return new DefaultJwkThumbprint(digest, alg);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
Expand Down Expand Up @@ -93,7 +92,7 @@ public void decrypt(final DecryptAeadRequest req, final OutputStream out) throws
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);

//for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array:
final InputStream taggedCiphertext = new SequenceInputStream(ciphertext, new ByteArrayInputStream(tag));
final InputStream taggedCiphertext = new SequenceInputStream(ciphertext, Streams.of(tag));

jca(req).withCipher(new CheckedFunction<Cipher, byte[]>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import javax.crypto.Cipher;
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 @@ -145,11 +144,11 @@ private byte[] sign(byte[] aad, byte[] iv, InputStream ciphertext, byte[] macKey

Collection<InputStream> streams = new ArrayList<>(4);
if (!Bytes.isEmpty(aad)) { // must come first if it exists
streams.add(new ByteArrayInputStream(aad));
streams.add(Streams.of(aad));
}
streams.add(new ByteArrayInputStream(iv));
streams.add(Streams.of(iv));
streams.add(ciphertext);
streams.add(new ByteArrayInputStream(AL));
streams.add(Streams.of(AL));
InputStream in = new SequenceInputStream(Collections.enumeration(streams));

SecretKey key = new SecretKeySpec(macKeyBytes, SIGALG.getJcaName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.jsonwebtoken.impl.security;

import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.CheckedSupplier;
Expand All @@ -36,7 +37,6 @@
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
Expand Down Expand Up @@ -332,7 +332,7 @@ public X509Certificate generateX509Certificate(final byte[] x509DerBytes) {
return fallback(CertificateFactory.class, new CheckedFunction<CertificateFactory, X509Certificate>() {
@Override
public X509Certificate apply(CertificateFactory cf) throws CertificateException {
InputStream is = new ByteArrayInputStream(x509DerBytes);
InputStream is = Streams.of(x509DerBytes);
return (X509Certificate) cf.generateCertificate(is);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.jsonwebtoken.impl.security;

import io.jsonwebtoken.impl.ParameterMap;
import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.Functions;
Expand All @@ -27,7 +28,6 @@
import io.jsonwebtoken.security.Request;
import io.jsonwebtoken.security.X509Builder;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.security.cert.X509Certificate;
Expand Down Expand Up @@ -99,7 +99,7 @@ public X509BuilderSupport x509Sha256Thumbprint(boolean enable) {

private byte[] computeThumbprint(final X509Certificate cert, HashAlgorithm alg) {
byte[] encoded = GET_X509_BYTES.apply(cert);
InputStream in = new ByteArrayInputStream(encoded);
InputStream in = Streams.of(encoded);
Request<InputStream> request = new DefaultRequest<>(in, null, null);
return alg.digest(request);
}
Expand Down
Loading

0 comments on commit b35a110

Please sign in to comment.