From b35a1102f7f511f6ca44307003be23c20b4fbabd Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Thu, 5 Oct 2023 20:08:29 -0700 Subject: [PATCH 1/3] 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. --- .../jsonwebtoken/impl/DefaultJwtBuilder.java | 9 ++-- .../jsonwebtoken/impl/DefaultJwtParser.java | 17 ++++---- .../java/io/jsonwebtoken/impl/Payload.java | 6 +-- .../AbstractCompressionAlgorithm.java | 3 +- .../impl/io/BytesInputStream.java | 43 +++++++++++++++++++ .../impl/io/DelegateStringDecoder.java | 3 +- .../java/io/jsonwebtoken/impl/io/Streams.java | 21 +++------ .../impl/security/AbstractJwk.java | 4 +- .../impl/security/GcmAesAeadAlgorithm.java | 3 +- .../impl/security/HmacAesAeadAlgorithm.java | 7 ++- .../impl/security/JcaTemplate.java | 4 +- .../impl/security/X509BuilderSupport.java | 4 +- .../groovy/io/jsonwebtoken/JwtsTest.groovy | 7 +-- .../groovy/io/jsonwebtoken/RFC7797Test.groovy | 19 ++++---- .../impl/DefaultMutableJweHeaderTest.groovy | 5 ++- .../impl/io/CountingInputStreamTest.groovy | 6 +-- .../impl/io/DelegateStringDecoderTest.groovy | 2 +- .../AbstractAsymmetricJwkBuilderTest.groovy | 9 ++-- .../AbstractSecureDigestAlgorithmTest.groovy | 7 +-- .../security/DefaultHashAlgorithmTest.groovy | 3 +- .../DefaultJwkParserBuilderTest.groovy | 5 ++- .../security/DefaultJwkThumbprintTest.groovy | 5 ++- .../security/EcSignatureAlgorithmTest.groovy | 22 +++++----- .../security/GcmAesAeadAlgorithmTest.groovy | 2 +- .../impl/security/HashAlgorithmsTest.groovy | 3 +- .../impl/security/JcaTemplateTest.groovy | 3 +- .../impl/security/JwkThumbprintsTest.groovy | 5 +-- .../security/EncryptionAlgorithmsTest.groovy | 4 +- 28 files changed, 135 insertions(+), 96 deletions(-) create mode 100644 impl/src/main/java/io/jsonwebtoken/impl/io/BytesInputStream.java diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java index ce9cb2c6f..b6b178c46 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java @@ -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; @@ -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."); @@ -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(); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 449dd871c..beb36f4bb 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -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; @@ -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; @@ -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; @@ -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(); @@ -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 @@ -378,7 +377,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe // =============== Header ================= final byte[] headerBytes = decode(base64UrlHeader, "protected header"); - Map m = deserialize(new ByteArrayInputStream(headerBytes), "protected header"); + Map m = deserialize(Streams.of(headerBytes), "protected header"); Header header; try { header = tokenized.createHeader(m); @@ -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: @@ -834,8 +833,8 @@ public Jws 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); @@ -874,7 +873,7 @@ public Jwe 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: diff --git a/impl/src/main/java/io/jsonwebtoken/impl/Payload.java b/impl/src/main/java/io/jsonwebtoken/impl/Payload.java index b6a9e187e..121a3ecb8 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/Payload.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/Payload.java @@ -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; @@ -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() { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java index 05d4e8b2a..1e6d6f0ba 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java @@ -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; @@ -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); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/BytesInputStream.java b/impl/src/main/java/io/jsonwebtoken/impl/io/BytesInputStream.java new file mode 100644 index 000000000..b5f2cb927 --- /dev/null +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/BytesInputStream.java @@ -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(); + } +} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/DelegateStringDecoder.java b/impl/src/main/java/io/jsonwebtoken/impl/io/DelegateStringDecoder.java index d57a7176c..f3bb2afa4 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/DelegateStringDecoder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/DelegateStringDecoder.java @@ -20,7 +20,6 @@ import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; -import java.io.ByteArrayInputStream; import java.io.InputStream; @SuppressWarnings("DeprecatedIsStillUsed") @@ -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); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/Streams.java b/impl/src/main/java/io/jsonwebtoken/impl/io/Streams.java index 15a81df03..cd10bffe1 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/Streams.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/Streams.java @@ -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; @@ -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) { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java index c3a5019a8..86bb6b3f0 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java @@ -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; @@ -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; @@ -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); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithm.java index ed3ee598f..86359e9cd 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithm.java @@ -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; @@ -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() { @Override diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java index 09887081d..970785f66 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java @@ -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; @@ -145,11 +144,11 @@ private byte[] sign(byte[] aad, byte[] iv, InputStream ciphertext, byte[] macKey Collection 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()); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java index 0affd2d31..5c19556c3 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java @@ -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; @@ -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; @@ -332,7 +332,7 @@ public X509Certificate generateX509Certificate(final byte[] x509DerBytes) { return fallback(CertificateFactory.class, new CheckedFunction() { @Override public X509Certificate apply(CertificateFactory cf) throws CertificateException { - InputStream is = new ByteArrayInputStream(x509DerBytes); + InputStream is = Streams.of(x509DerBytes); return (X509Certificate) cf.generateCertificate(is); } }); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java b/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java index f683a5bb7..f5984ac9a 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java @@ -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; @@ -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; @@ -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 request = new DefaultRequest<>(in, null, null); return alg.digest(request); } diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy index 8c9e88a61..5ded80389 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy @@ -18,6 +18,7 @@ package io.jsonwebtoken import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.impl.* import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.security.* @@ -141,7 +142,7 @@ class JwtsTest { def h = base64Url('{"alg":"HS256"}') def c = base64Url('{"sub":"joe","exp":"-42-"}') def data = Strings.utf8(("$h.$c" as String)) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) def request = new DefaultSecureRequest<>(payload, null, null, key) def result = Jwts.SIG.HS256.digest(request) def sig = Encoders.BASE64URL.encode(result) @@ -191,7 +192,7 @@ class JwtsTest { @Test void testContentStreamWithContentType() { String s = 'Hello JJWT' - InputStream content = new ByteArrayInputStream(Strings.utf8(s)) + InputStream content = Streams.of(Strings.utf8(s)) String cty = 'text/plain' String compact = Jwts.builder().content(content, cty).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) @@ -202,7 +203,7 @@ class JwtsTest { @Test void testContentStreamWithoutContentType() { String s = 'Hello JJWT' - InputStream content = new ByteArrayInputStream(Strings.utf8(s)) + InputStream content = Streams.of(Strings.utf8(s)) String compact = Jwts.builder().content(content).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertNull jwt.header.getContentType() diff --git a/impl/src/test/groovy/io/jsonwebtoken/RFC7797Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/RFC7797Test.groovy index 532d9e486..deb24973b 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/RFC7797Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/RFC7797Test.groovy @@ -17,6 +17,7 @@ package io.jsonwebtoken import io.jsonwebtoken.impl.DefaultJwsHeader import io.jsonwebtoken.impl.DefaultJwtParser +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.security.TestKeys @@ -69,8 +70,8 @@ class RFC7797Test { String s = Jwts.builder().signWith(key).content(content).encodePayload(false).compact() // But verify with 3 types of sources: string, byte array, and two different kinds of InputStreams: - InputStream asByteInputStream = new ByteArrayInputStream(content) - InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) + InputStream asByteInputStream = Streams.of(content) + InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content)) for (def payload : [content, asByteInputStream, asBufferedInputStream]) { def parser = Jwts.parser().verifyWith(key).build() @@ -107,8 +108,8 @@ class RFC7797Test { String s = Jwts.builder().signWith(key).content(content).encodePayload(false).compact() // But verify with 3 types of sources: string, byte array, and two different kinds of InputStreams: - InputStream asByteInputStream = new ByteArrayInputStream(content) - InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) + InputStream asByteInputStream = Streams.of(content) + InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content)) for (def payload : [content, asByteInputStream, asBufferedInputStream]) { def parser = Jwts.parser().verifyWith(key).build() @@ -128,13 +129,13 @@ class RFC7797Test { def key = TestKeys.HS256 byte[] content = Strings.utf8('$.02') // https://datatracker.ietf.org/doc/html/rfc7797#section-4.2 - InputStream contentStream = new ByteArrayInputStream(content) + InputStream contentStream = Streams.of(content) String s = Jwts.builder().signWith(key).content(contentStream).encodePayload(false).compact() // But verify with 3 types of sources: byte array, and two different kinds of InputStreams: - InputStream asByteInputStream = new ByteArrayInputStream(content) - InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) + InputStream asByteInputStream =Streams.of(content) + InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content)) for (def payload : [content, asByteInputStream, asBufferedInputStream]) { def parser = Jwts.parser().verifyWith(key).build() @@ -348,8 +349,8 @@ class RFC7797Test { .compact() // But verify with 3 types of sources: byte array, and two different kinds of InputStreams: - InputStream asByteInputStream = new ByteArrayInputStream(compressed) - InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(compressed)) + InputStream asByteInputStream = Streams.of(compressed) + InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(compressed)) for (def payload : [compressed, asByteInputStream, asBufferedInputStream]) { def parser = Jwts.parser().verifyWith(key).build() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy index ff6090617..76b647341 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy @@ -16,6 +16,7 @@ package io.jsonwebtoken.impl import io.jsonwebtoken.Jwts +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.security.DefaultHashAlgorithm import io.jsonwebtoken.impl.security.DefaultRequest @@ -269,7 +270,7 @@ class DefaultMutableJweHeaderTest { */ @Test void testX509Sha1Thumbprint() { - def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) + def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) def request = new DefaultRequest(payload, null, null) def x5t = DefaultHashAlgorithm.SHA1.digest(request) String encoded = Encoders.BASE64URL.encode(x5t) @@ -285,7 +286,7 @@ class DefaultMutableJweHeaderTest { */ @Test void testX509Sha256Thumbprint() { - def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) + def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) def request = new DefaultRequest(payload, null, null) def x5tS256 = Jwks.HASH.@SHA256.digest(request) String encoded = Encoders.BASE64URL.encode(x5tS256) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/CountingInputStreamTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/CountingInputStreamTest.groovy index ede080f14..a2af3669f 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/io/CountingInputStreamTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/io/CountingInputStreamTest.groovy @@ -25,7 +25,7 @@ class CountingInputStreamTest { @Test void readEmpty() { - def stream = new CountingInputStream(new ByteArrayInputStream(Bytes.EMPTY)) + def stream = new CountingInputStream(Streams.of(Bytes.EMPTY)) stream.read() assertEquals 0, stream.getCount() } @@ -34,7 +34,7 @@ class CountingInputStreamTest { void readSingle() { def single = (byte) 0x18 // any random byte is fine def data = new byte[1]; data[0] = single - def stream = new CountingInputStream(new ByteArrayInputStream(data)) + def stream = new CountingInputStream(Streams.of(data)) assertEquals single, stream.read() assertEquals 1, stream.getCount() } @@ -42,7 +42,7 @@ class CountingInputStreamTest { @Test void testSkip() { def data = Strings.utf8('hello world') - def stream = new CountingInputStream(new ByteArrayInputStream(data)) + def stream = new CountingInputStream(Streams.of(data)) stream.skip(6) assertEquals 6, stream.getCount() int w = ('w' as char) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/DelegateStringDecoderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/DelegateStringDecoderTest.groovy index 3d9bae5b1..44e8193c0 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/io/DelegateStringDecoderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/io/DelegateStringDecoderTest.groovy @@ -29,7 +29,7 @@ class DelegateStringDecoderTest { void decode() { def value = 'test' def bytes = Strings.utf8(value) - def ins = new ByteArrayInputStream(bytes) + def ins = Streams.of(bytes) def test = new Decoder() { @Override byte[] decode(CharSequence s) throws DecodingException { diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy index c2376ac65..514c9ccd7 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.security +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.* import org.junit.Test @@ -64,7 +65,7 @@ class AbstractAsymmetricJwkBuilderTest { @Test void testX509CertificateSha1Thumbprint() { - def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) + def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) Request request = new DefaultRequest(payload, null, null) def x5t = DefaultHashAlgorithm.SHA1.digest(request) def encoded = Encoders.BASE64URL.encode(x5t) @@ -75,7 +76,7 @@ class AbstractAsymmetricJwkBuilderTest { @Test void testX509CertificateSha1ThumbprintEnabled() { - def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) + def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) Request request = new DefaultRequest(payload, null, null) def x5t = DefaultHashAlgorithm.SHA1.digest(request) def encoded = Encoders.BASE64URL.encode(x5t) @@ -86,7 +87,7 @@ class AbstractAsymmetricJwkBuilderTest { @Test void testX509CertificateSha256Thumbprint() { - def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) + def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) Request request = new DefaultRequest(payload, null, null) def x5tS256 = Jwks.HASH.SHA256.digest(request) def encoded = Encoders.BASE64URL.encode(x5tS256) @@ -97,7 +98,7 @@ class AbstractAsymmetricJwkBuilderTest { @Test void testX509CertificateSha256ThumbprintEnabled() { - def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) + def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) Request request = new DefaultRequest(payload, null, null) def x5tS256 = Jwks.HASH.SHA256.digest(request) def encoded = Encoders.BASE64URL.encode(x5tS256) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy index c5ced6a24..166762c4d 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy @@ -16,6 +16,7 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.SecureRequest import io.jsonwebtoken.security.SignatureException @@ -37,7 +38,7 @@ class AbstractSecureDigestAlgorithmTest { Provider provider = Security.getProvider('BC') def pair = Jwts.SIG.RS256.keyPair().build() byte[] data = Strings.utf8('foo') - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) byte[] signature = Jwts.SIG.RS256.digest(new DefaultSecureRequest<>(payload, provider, null, pair.getPrivate())) payload.reset() assertTrue Jwts.SIG.RS256.verify(new DefaultVerifySecureDigestRequest(payload, provider, null, pair.getPublic(), signature)) @@ -54,7 +55,7 @@ class AbstractSecureDigestAlgorithmTest { } } try { - def payload = new ByteArrayInputStream(Strings.utf8('foo')) + def payload = Streams.of(Strings.utf8('foo')) alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate())) } catch (SignatureException e) { assertTrue e.getMessage().startsWith('Unable to compute test signature with JCA algorithm \'test\' using key {') @@ -74,7 +75,7 @@ class AbstractSecureDigestAlgorithmTest { } } def data = Strings.utf8('foo') - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) try { byte[] signature = alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate())) payload.reset() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy index 871d36559..d9e1435b3 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.security +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.Jwks @@ -29,7 +30,7 @@ class DefaultHashAlgorithmTest { @Test void testDigestAndVerify() { byte[] data = Strings.utf8('Hello World') - InputStream payload = new ByteArrayInputStream(data) + InputStream payload = Streams.of(data) for (HashAlgorithm alg : algs) { byte[] hash = alg.digest(new DefaultRequest<>(payload, null, null)) payload.reset() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy index 0d43218fb..89dfa8614 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy @@ -17,6 +17,7 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.CharSequenceReader import io.jsonwebtoken.impl.io.ConvertingParser +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.io.AbstractDeserializer import io.jsonwebtoken.io.DeserializationException import io.jsonwebtoken.io.Deserializer @@ -52,7 +53,7 @@ class DefaultJwkParserBuilderTest { @Test(expected = IllegalArgumentException) void parseNull() { - Jwks.parser().build().parse((CharSequence)null) + Jwks.parser().build().parse((CharSequence) null) } @Test(expected = IllegalArgumentException) @@ -133,7 +134,7 @@ class DefaultJwkParserBuilderTest { assertEquals jwk, parsed // InputStream parsing: - parsed = parser.parse(new ByteArrayInputStream(Strings.utf8(json))) + parsed = parser.parse(Streams.of(json)) assertEquals jwk, parsed } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy index 2c3049147..a6af05791 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.security +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.HashAlgorithm @@ -30,7 +31,7 @@ class DefaultJwkThumbprintTest { private static String content = "Hello World" private static HashAlgorithm alg = Jwks.HASH.SHA256 - private static byte[] digest = alg.digest(new DefaultRequest(new ByteArrayInputStream(Strings.utf8(content)), null, null)) + private static byte[] digest = alg.digest(new DefaultRequest(Streams.of(content), null, null)) private static String expectedToString = Encoders.BASE64URL.encode(digest) private static String expectedUriString = DefaultJwkThumbprint.URI_PREFIX + alg.getId() + ":" + expectedToString private static URI expectedUri = URI.create(expectedUriString) @@ -81,7 +82,7 @@ class DefaultJwkThumbprintTest { assertFalse thumbprint == new DefaultJwkThumbprint(digest, DefaultHashAlgorithm.SHA1) // same alg, different digest: - def payload = new ByteArrayInputStream(Strings.utf8('Hello World!')) + def payload = Streams.of(Strings.utf8('Hello World!')) byte[] digest2 = alg.digest(new DefaultRequest<>(payload, null, null)) assertFalse thumbprint == new DefaultJwkThumbprint(digest2, DefaultHashAlgorithm.SHA1) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy index a460f52e1..a4993bc27 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy @@ -169,7 +169,7 @@ class EcSignatureAlgorithmTest { @Test void testVerifyWithPrivateKey() { byte[] data = 'foo'.getBytes(StandardCharsets.UTF_8) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) algs().each { payload.reset() def pair = it.keyPair().build() @@ -194,7 +194,7 @@ class EcSignatureAlgorithmTest { BigInteger order = BigInteger.ONE ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new TestECField(), BigInteger.ONE, BigInteger.ONE), new ECPoint(BigInteger.ONE, BigInteger.ONE), order, 1) ECPublicKey pub = new TestECPublicKey(algorithm: 'EC', params: spec) - def request = new DefaultVerifySecureDigestRequest(new ByteArrayInputStream(new byte[1]), null, null, pub, new byte[1]) + def request = new DefaultVerifySecureDigestRequest(Streams.of(new byte[1]), null, null, pub, new byte[1]) try { it.verify(request) } catch (InvalidKeyException expected) { @@ -310,7 +310,7 @@ class EcSignatureAlgorithmTest { def signatureStart = token.lastIndexOf('.') def withoutSignature = token.substring(0, signatureStart) def data = Strings.ascii(withoutSignature); - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) def signature = Decoders.BASE64URL.decode(token.substring(signatureStart + 1)) assertTrue "Signature do not match that of other implementations", alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, pub, signature)) } @@ -329,7 +329,7 @@ class EcSignatureAlgorithmTest { assertTrue keypair.getPublic() instanceof ECPublicKey assertTrue keypair.getPrivate() instanceof ECPrivateKey def data = Strings.ascii(withoutSignature) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) def signature = alg.digest(new DefaultSecureRequest<>(payload, null, null, keypair.private)) payload.reset() assertTrue alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, signature)) @@ -473,7 +473,7 @@ class EcSignatureAlgorithmTest { def signature = token.substring(signatureStart + 1) def data = withoutSignature.getBytes(StandardCharsets.US_ASCII) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) def sigBytes = Decoders.BASE64URL.decode(signature) def request = new DefaultVerifySecureDigestRequest(payload, null, null, pub, sigBytes) assert alg.verify(request), "Signature do not match that of other implementations" @@ -495,7 +495,7 @@ class EcSignatureAlgorithmTest { signature.initSign(keypair.private) signature.update(data) def signed = signature.sign() - def request = new DefaultVerifySecureDigestRequest(new ByteArrayInputStream(data), null, null, keypair.public, signed) + def request = new DefaultVerifySecureDigestRequest(Streams.of(data), null, null, keypair.public, signed) try { alg.verify(request) fail() @@ -519,7 +519,7 @@ class EcSignatureAlgorithmTest { def keypair = alg.keyPair().build() def signature = Signature.getInstance(alg.jcaName as String) def data = Strings.ascii(withoutSignature) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) signature.initSign(keypair.private) signature.update(data) def signed = signature.sign() @@ -538,7 +538,7 @@ class EcSignatureAlgorithmTest { def alg = Jwts.SIG.ES256 def keypair = alg.keyPair().build() def data = Strings.ascii(withoutSignature) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, forgedSig) assertFalse alg.verify(request) } @@ -556,7 +556,7 @@ class EcSignatureAlgorithmTest { def alg = Jwts.SIG.ES256 def keypair = alg.keyPair().build() def data = Strings.ascii(withoutSignature) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, sig) assertFalse alg.verify(request) } @@ -574,7 +574,7 @@ class EcSignatureAlgorithmTest { def alg = Jwts.SIG.ES256 def keypair = alg.keyPair().build() def data = Strings.ascii(withoutSignature) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, sig) assertFalse alg.verify(request) } @@ -587,7 +587,7 @@ class EcSignatureAlgorithmTest { def alg = Jwts.SIG.ES256 def keypair = alg.keyPair().build() def data = Strings.ascii(withoutSignature) - def payload = new ByteArrayInputStream(data) + def payload = Streams.of(data) def invalidSignature = Decoders.BASE64URL.decode(invalidEncodedSignature) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, invalidSignature) assertFalse("Forged signature must not be considered valid.", alg.verify(request)) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy index b3ef1e0dd..04264341a 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy @@ -60,7 +60,7 @@ class GcmAesAeadAlgorithmTest { def alg = Jwts.ENC.A256GCM - def ins = new ByteArrayInputStream(P) + def ins = Streams.of(P) def aad = Streams.of(AAD) def out = new ByteArrayOutputStream(8192) def res = new DefaultAeadResult(out) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy index 71ad1526a..463ae739c 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.security +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.lang.Registry import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.Jwks @@ -80,7 +81,7 @@ class HashAlgorithmsTest { static DefaultRequest request(String msg) { byte[] data = msg.getBytes(StandardCharsets.UTF_8) - InputStream payload = new ByteArrayInputStream(data) + InputStream payload = Streams.of(data) return new DefaultRequest(payload, null, null) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy index 54f2a1a90..81190b0f5 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.security +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.lang.Classes @@ -347,7 +348,7 @@ class JcaTemplateTest { X509Certificate cert = template.withCertificateFactory(new CheckedFunction() { @Override X509Certificate apply(CertificateFactory certificateFactory) throws Exception { - (X509Certificate)certificateFactory.generateCertificate(new ByteArrayInputStream(expected.getEncoded())) + (X509Certificate) certificateFactory.generateCertificate(Streams.of(expected.getEncoded())) } }) assertEquals expected, cert diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy index 67fcd206b..008a24f55 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy @@ -16,13 +16,13 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.RfcTests +import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.JwkThumbprint import io.jsonwebtoken.security.Jwks import org.junit.Test import javax.crypto.SecretKey -import java.nio.charset.StandardCharsets import static io.jsonwebtoken.impl.security.DefaultHashAlgorithm.SHA1 import static org.junit.Assert.assertEquals @@ -32,8 +32,7 @@ class JwkThumbprintsTest { static final HashAlgorithm SHA256 = Jwks.HASH.@SHA256 static byte[] digest(String json, HashAlgorithm alg) { - def utf8Bytes = json.getBytes(StandardCharsets.UTF_8) - def payload = new ByteArrayInputStream(utf8Bytes); + def payload = Streams.of(json) def req = new DefaultRequest(payload, null, null) return alg.digest(req) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy index 24207c061..dabb50d82 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy @@ -121,7 +121,7 @@ class EncryptionAlgorithmsTest { assertEquals(ciphertextBytes.length, PLAINTEXT_BYTES.length) } - def ciphertext = new ByteArrayInputStream(ciphertextBytes) + def ciphertext = Streams.of(ciphertextBytes) out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(ciphertext, key, null, iv, tag) alg.decrypt(dreq, out) @@ -155,7 +155,7 @@ class EncryptionAlgorithmsTest { assertEquals(ciphertextBytes.length, PLAINTEXT_BYTES.length) } - def ciphertext = new ByteArrayInputStream(ciphertextBytes) + def ciphertext = Streams.of(ciphertextBytes) out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(ciphertext, key, aad, iv, tag) alg.decrypt(dreq, out) From d8860548256acbe035f7d6e5f331c287e7c6224a Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:11:10 -0700 Subject: [PATCH 2/3] 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). --- .../io/jsonwebtoken/impl/lang/AddOpens.java | 128 ------------------ .../impl/security/DefaultMacAlgorithm.java | 5 +- .../impl/security/KeysBridge.java | 52 ++++--- 3 files changed, 26 insertions(+), 159 deletions(-) delete mode 100644 impl/src/main/java/io/jsonwebtoken/impl/lang/AddOpens.java diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/AddOpens.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/AddOpens.java deleted file mode 100644 index 7e4edd05f..000000000 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/AddOpens.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2021 Stefan Zobel - * 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.lang; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * A utility class that allows to open arbitrary packages to the calling module - * at runtime, so it is a kind of dynamic device for "--add-opens" that could be - * used inside libraries instead of forcing the application to be run with - * command line parameters like "--add-opens java.base/java.util=ALL-UNNAMED" or - * having the "Add-Opens:" entries supplied in the application Jar manifest. - * Note that this still works in the Java 17 GA release, dated 2021-09-14 but it - * may break at any time in the future (theoretically even for a minor - * release!). - * - * @since 0.12.1, gratefully copied from https://github.com/stefan-zobel/wip/blob/b74e927edddf19a5dce7c8610835f620c0b6f557/src/main/java/misc/AddOpens.java - * under the terms of the Apache 2 open source license (same as the JJWT license). - */ -public final class AddOpens { - - // field offset of the override field (Warning: this may change at any time!) - private static final long OVERRIDE_OFFSET = 12; - private static final sun.misc.Unsafe U = getUnsafe(); - - private AddOpens() { - throw new AssertionError(); - } - - /** - * Open one or more packages in the given module to the current module. Example - * usage: - * - *
{@code
-     *  boolean success = AddOpens.open("java.base", "java.util", "java.net");
-     * }
- * - * @param moduleName the module you want to open - * @param packageNames packages in that module you want to be opened - * @return {@code true} if the open operation has succeeded for all packages, - * otherwise {@code false} - */ - public static boolean open(String moduleName, String... packageNames) { - // Use reflection so that this code can run on Java 8 - Class javaLangModule; - try { - javaLangModule = Class.forName("java.lang.Module"); - } catch (Throwable t) { - // we must be < Java 9 - return true; - } - try { - // the module we are currently running in (either named or unnamed) - Object thisModule = getCurrentModule(); - // find the module to open - Object targetModule = findModule(moduleName); - // get the method that is also used by "--add-opens" - Method m = javaLangModule.getDeclaredMethod("implAddOpens", String.class, javaLangModule); - // override language-level access checks - setAccessible(m); - // open given packages in the target module to this module - for (String package_ : packageNames) { - m.invoke(targetModule, package_, thisModule); - } - return true; - } catch (Throwable ignore) { - return false; - } - } - - private static Object findModule(String moduleName) { - // Use reflection so that this code can run on Java 8 - try { - Class moduleLayerClass = Class.forName("java.lang.ModuleLayer"); - Method bootMethod = moduleLayerClass.getDeclaredMethod("boot"); - Object bootLayer = bootMethod.invoke(null); - Method findModuleMethod = moduleLayerClass.getDeclaredMethod("findModule", String.class); - Object optionalModule = findModuleMethod.invoke(bootLayer, moduleName); - Class optionalClass = Class.forName("java.util.Optional"); - Method getMethod = optionalClass.getDeclaredMethod("get"); - return getMethod.invoke(optionalModule); - } catch (Throwable t) { - return null; - } - } - - private static Object getCurrentModule() { - // Use reflection so that this code can run on Java 8 - try { - Method m = Class.class.getDeclaredMethod("getModule"); - setAccessible(m); - return m.invoke(AddOpens.class); - } catch (Throwable t) { - return null; - } - } - - private static void setAccessible(Method method) { - if (U != null) { - U.putBoolean(method, OVERRIDE_OFFSET, true); - } - } - - private static sun.misc.Unsafe getUnsafe() { - try { - Field unsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - unsafe.setAccessible(true); - return (sun.misc.Unsafe) unsafe.get(null); - } catch (Throwable ignore) { - return null; - } - } -} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java index 58a7c0f6e..28ecdfb86 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java @@ -135,9 +135,8 @@ private void assertAlgorithmName(SecretKey key, boolean signing) { throw new InvalidKeyException(msg); } - // We can ignore PKCS11 key name assertions for two reasons: - // 1. HSM module key algorithm names don't always align with JCA standard algorithm names, and - // 2. Our KeysBridge.findBitLength implementation can extract the key length so we can still validate with that + // We can ignore PKCS11 key name assertions because HSM module key algorithm names don't always align with + // JCA standard algorithm names: boolean pkcs11Key = KeysBridge.isSunPkcs11GenericSecret(key); //assert key's jca name is valid if it's a JWA standard algorithm: diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java b/impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java index 980b01bf1..a3602619a 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java @@ -15,9 +15,7 @@ */ package io.jsonwebtoken.impl.security; -import io.jsonwebtoken.impl.lang.AddOpens; import io.jsonwebtoken.impl.lang.Bytes; -import io.jsonwebtoken.impl.lang.OptionalMethodInvoker; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; @@ -30,6 +28,8 @@ import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.interfaces.ECKey; +import java.security.interfaces.RSAKey; @SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation public final class KeysBridge { @@ -37,16 +37,6 @@ public final class KeysBridge { private static final String SUNPKCS11_GENERIC_SECRET_CLASSNAME = "sun.security.pkcs11.P11Key$P11SecretKey"; private static final String SUNPKCS11_GENERIC_SECRET_ALGNAME = "Generic Secret"; // https://github.com/openjdk/jdk/blob/4f90abaf17716493bad740dcef76d49f16d69379/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyStore.java#L1292 - private static final String SUN_KEYUTIL_CLASSNAME = "sun.security.util.KeyUtil"; - private static final OptionalMethodInvoker SUN_KEYSIZE = - new OptionalMethodInvoker<>(SUN_KEYUTIL_CLASSNAME, "getKeySize", Key.class, true); - private static final String SUN_KEYUTIL_ERR = "Unexpected " + SUN_KEYUTIL_CLASSNAME + " invocation error."; - - static { - // For reflective access to KeyUtil on >= JDK 9: - AddOpens.open("java.base", "sun.security.util"); - } - // prevent instantiation private KeysBridge() { } @@ -119,24 +109,30 @@ public static boolean isSunPkcs11GenericSecret(Key key) { */ public static int findBitLength(Key key) { - Integer retval = SUN_KEYSIZE.apply(key); - int bitlen = Assert.stateNotNull(retval, SUN_KEYUTIL_ERR); - - // SunPKCS11 SecretKey lengths are unfortunately reported in bytes, not bits - // per https://bugs.openjdk.org/browse/JDK-8163173 - // (they should be multiplying the PKCS11 CKA_VALUE_LEN value by 8 since their own - // sun.security.util.Length#getLength() JavaDoc states that values are intended to be in bits, not bytes) - // So we account for that here: - if (bitlen > 0 && isSunPkcs11GenericSecret(key)) { - bitlen *= Byte.SIZE; + int bitlen = -1; + + // try to parse the length from key specification + if (key instanceof SecretKey) { + SecretKey secretKey = (SecretKey) key; + if ("RAW".equals(secretKey.getFormat())) { + byte[] encoded = findEncoded(secretKey); + if (!Bytes.isEmpty(encoded)) { + bitlen = (int) Bytes.bitLength(encoded); + Bytes.clear(encoded); + } + } + } else if (key instanceof RSAKey) { + RSAKey rsaKey = (RSAKey) key; + bitlen = rsaKey.getModulus().bitLength(); + } else if (key instanceof ECKey) { + ECKey ecKey = (ECKey) key; + bitlen = ecKey.getParams().getOrder().bitLength(); + } else { + // We can check additional logic for EdwardsCurve even if the current JDK version doesn't support it: + EdwardsCurve curve = EdwardsCurve.findByKey(key); + if (curve != null) bitlen = curve.getKeyBitLength(); } - if (bitlen > 0) return bitlen; - - // We can check additional logic for EdwardsCurve even if the current JDK version doesn't support it: - EdwardsCurve curve = EdwardsCurve.findByKey(key); - if (curve != null) bitlen = curve.getKeyBitLength(); - return bitlen; } From 3a30ea101b3e64e7d886847b18799fd38a3fdd77 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:37:35 -0700 Subject: [PATCH 3/3] 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) --- pom.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c41f781db..0bd5e3bf9 100644 --- a/pom.xml +++ b/pom.xml @@ -133,7 +133,10 @@ --add-opens java.base/java.lang=ALL-UNNAMED, --add-opens java.desktop/java.beans=ALL-UNNAMED, - --add-opens java.base/java.lang.ref=ALL-UNNAMED + --add-opens java.base/java.lang.ref=ALL-UNNAMED, + + --add-opens java.base/sun.security.util=ALL-UNNAMED