854 jdk17 visibility (#855)

* 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)
This commit is contained in:
lhazlewood 2023-10-05 22:13:57 -07:00 committed by GitHub
parent fad6e2737d
commit a7d3d3197c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 164 additions and 255 deletions

View File

@ -64,7 +64,6 @@ import io.jsonwebtoken.security.UnsupportedKeyException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -588,18 +587,18 @@ public class DefaultJwtBuilder implements JwtBuilder {
InputStream payloadStream = null; // not needed unless b64 is enabled InputStream payloadStream = null; // not needed unless b64 is enabled
if (this.encodePayload) { if (this.encodePayload) {
encodeAndWrite("JWS Payload", payload, jws); encodeAndWrite("JWS Payload", payload, jws);
signingInput = new ByteArrayInputStream(jws.toByteArray()); signingInput = Streams.of(jws.toByteArray());
} else { // b64 } else { // b64
// First, ensure we have the base64url header bytes + the SEPARATOR_CHAR byte: // 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, // 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: // so we ensure we have an input stream for that:
if (payload.isClaims() || payload.isCompressed()) { if (payload.isClaims() || payload.isCompressed()) {
ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192); ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192);
writeAndClose("JWS Unencoded Payload", payload, claimsOut); writeAndClose("JWS Unencoded Payload", payload, claimsOut);
payloadStream = new ByteArrayInputStream(claimsOut.toByteArray()); payloadStream = Streams.of(claimsOut.toByteArray());
} else { } else {
// No claims and not compressed, so just get the direct InputStream: // No claims and not compressed, so just get the direct InputStream:
payloadStream = Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null."); payloadStream = Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null.");
@ -698,7 +697,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
if (content.isClaims()) { if (content.isClaims()) {
ByteArrayOutputStream out = new ByteArrayOutputStream(4096); ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
writeAndClose("JWE Claims", content, out); writeAndClose("JWE Claims", content, out);
plaintext = new ByteArrayInputStream(out.toByteArray()); plaintext = Streams.of(out.toByteArray());
} else { } else {
plaintext = content.toInputStream(); plaintext = content.toInputStream();
} }

View File

@ -39,6 +39,7 @@ import io.jsonwebtoken.ProtectedHeader;
import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.io.AbstractParser; import io.jsonwebtoken.impl.io.AbstractParser;
import io.jsonwebtoken.impl.io.BytesInputStream;
import io.jsonwebtoken.impl.io.CharSequenceReader; import io.jsonwebtoken.impl.io.CharSequenceReader;
import io.jsonwebtoken.impl.io.JsonObjectDeserializer; import io.jsonwebtoken.impl.io.JsonObjectDeserializer;
import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.io.Streams;
@ -56,7 +57,6 @@ import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.DeserializationException; import io.jsonwebtoken.io.DeserializationException;
import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.DateFormats; import io.jsonwebtoken.lang.DateFormats;
import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Objects;
@ -74,7 +74,6 @@ import io.jsonwebtoken.security.WeakKeyException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
@ -315,7 +314,7 @@ public class DefaultJwtParser extends AbstractParser<Jwt<?, ?>> implements JwtPa
bb.rewind(); bb.rewind();
byte[] data = new byte[bb.remaining()]; byte[] data = new byte[bb.remaining()];
bb.get(data); bb.get(data);
verificationInput = new ByteArrayInputStream(data); verificationInput = Streams.of(data);
} else { // b64 extension } else { // b64 extension
ByteBuffer headerBuf = StandardCharsets.US_ASCII.encode(Strings.wrap(tokenized.getProtected())); ByteBuffer headerBuf = StandardCharsets.US_ASCII.encode(Strings.wrap(tokenized.getProtected()));
headerBuf.rewind(); headerBuf.rewind();
@ -325,7 +324,7 @@ public class DefaultJwtParser extends AbstractParser<Jwt<?, ?>> implements JwtPa
buf.rewind(); buf.rewind();
byte[] data = new byte[buf.remaining()]; byte[] data = new byte[buf.remaining()];
buf.get(data); buf.get(data);
InputStream prefixStream = new ByteArrayInputStream(data); InputStream prefixStream = Streams.of(data);
payloadStream = payload.toInputStream(); payloadStream = payload.toInputStream();
// We wrap the payloadStream here in an UncloseableInputStream to prevent the SequenceInputStream from // 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 // closing it since we'll need to rewind/reset it if decompression is enabled
@ -378,7 +377,7 @@ public class DefaultJwtParser extends AbstractParser<Jwt<?, ?>> implements JwtPa
// =============== Header ================= // =============== Header =================
final byte[] headerBytes = decode(base64UrlHeader, "protected 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; Header header;
try { try {
header = tokenized.createHeader(m); header = tokenized.createHeader(m);
@ -517,7 +516,7 @@ public class DefaultJwtParser extends AbstractParser<Jwt<?, ?>> implements JwtPa
ByteBuffer buf = StandardCharsets.US_ASCII.encode(Strings.wrap(base64UrlHeader)); ByteBuffer buf = StandardCharsets.US_ASCII.encode(Strings.wrap(base64UrlHeader));
final byte[] aadBytes = new byte[buf.remaining()]; final byte[] aadBytes = new byte[buf.remaining()];
buf.get(aadBytes); buf.get(aadBytes);
InputStream aad = new ByteArrayInputStream(aadBytes); InputStream aad = Streams.of(aadBytes);
base64Url = base64UrlDigest; base64Url = base64UrlDigest;
//guaranteed to be non-empty via the `alg` + digest check above: //guaranteed to be non-empty via the `alg` + digest check above:
@ -834,8 +833,8 @@ public class DefaultJwtParser extends AbstractParser<Jwt<?, ?>> implements JwtPa
} }
private static Payload payloadFor(InputStream in) { private static Payload payloadFor(InputStream in) {
if (in instanceof ByteArrayInputStream) { if (in instanceof BytesInputStream) {
byte[] data = Classes.getFieldValue(in, "buf", byte[].class); byte[] data = Streams.bytes(in, "Unable to obtain payload InputStream bytes.");
return new Payload(data, null); return new Payload(data, null);
} }
//if (in.markSupported()) in.mark(0); //if (in.markSupported()) in.mark(0);
@ -874,7 +873,7 @@ public class DefaultJwtParser extends AbstractParser<Jwt<?, ?>> implements JwtPa
protected byte[] decode(CharSequence base64UrlEncoded, String name) { protected byte[] decode(CharSequence base64UrlEncoded, String name) {
try { 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."); return Streams.bytes(decoding, "Unable to Base64Url-decode input.");
} catch (Throwable t) { } catch (Throwable t) {
// Don't disclose potentially-sensitive information per https://github.com/jwtk/jjwt/issues/824: // Don't disclose potentially-sensitive information per https://github.com/jwtk/jjwt/issues/824:

View File

@ -18,13 +18,13 @@ package io.jsonwebtoken.impl;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -68,10 +68,10 @@ class Payload {
} }
this.bytes = data; this.bytes = data;
if (in == null && !Bytes.isEmpty(this.bytes)) { if (in == null && !Bytes.isEmpty(this.bytes)) {
in = new ByteArrayInputStream(data); in = Streams.of(data);
} }
this.inputStreamEmpty = in == null; this.inputStreamEmpty = in == null;
this.inputStream = this.inputStreamEmpty ? new ByteArrayInputStream(Bytes.EMPTY) : in; this.inputStream = this.inputStreamEmpty ? Streams.of(Bytes.EMPTY) : in;
} }
boolean isClaims() { boolean isClaims() {

View File

@ -27,7 +27,6 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -152,7 +151,7 @@ public abstract class AbstractCompressionAlgorithm implements CompressionAlgorit
* @throws IOException if the decompression runs into an IO problem * @throws IOException if the decompression runs into an IO problem
*/ */
protected byte[] doDecompress(byte[] compressed) throws IOException { protected byte[] doDecompress(byte[] compressed) throws IOException {
InputStream is = new ByteArrayInputStream(compressed); InputStream is = Streams.of(compressed);
InputStream decompress = decompress(is); InputStream decompress = decompress(is);
byte[] buffer = new byte[512]; byte[] buffer = new byte[512];
ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length); ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length);

View File

@ -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();
}
}

View File

@ -20,7 +20,6 @@ import io.jsonwebtoken.io.DecodingException;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
@SuppressWarnings("DeprecatedIsStillUsed") @SuppressWarnings("DeprecatedIsStillUsed")
@ -38,7 +37,7 @@ public class DelegateStringDecoder implements Decoder<InputStream, InputStream>
try { try {
byte[] data = Streams.bytes(in, "Unable to Base64URL-decode input."); byte[] data = Streams.bytes(in, "Unable to Base64URL-decode input.");
data = delegate.decode(Strings.utf8(data)); data = delegate.decode(Strings.utf8(data));
return new ByteArrayInputStream(data); return Streams.of(data);
} catch (Throwable t) { } catch (Throwable t) {
String msg = "Unable to Base64Url-decode InputStream: " + t.getMessage(); String msg = "Unable to Base64Url-decode InputStream: " + t.getMessage();
throw new DecodingException(msg, t); throw new DecodingException(msg, t);

View File

@ -15,15 +15,12 @@
*/ */
package io.jsonwebtoken.impl.io; package io.jsonwebtoken.impl.io;
import io.jsonwebtoken.impl.lang.AddOpens;
import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Flushable; import java.io.Flushable;
import java.io.IOException; import java.io.IOException;
@ -43,30 +40,26 @@ public class Streams {
*/ */
public static final int EOF = -1; 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) { public static byte[] bytes(final InputStream in, String exmsg) {
if (in instanceof ByteArrayInputStream) { if (in instanceof BytesInputStream) {
return Classes.getFieldValue(in, "buf", byte[].class); return ((BytesInputStream) in).getBytes();
} }
// otherwise we have to copy over:
ByteArrayOutputStream out = new ByteArrayOutputStream(8192); ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
copy(in, out, new byte[8192], exmsg); copy(in, out, new byte[8192], exmsg);
return out.toByteArray(); return out.toByteArray();
} }
public static ByteArrayInputStream of(byte[] bytes) { public static InputStream of(byte[] bytes) {
return Bytes.isEmpty(bytes) ? new ByteArrayInputStream(Bytes.EMPTY) : new ByteArrayInputStream(bytes); return new BytesInputStream(bytes);
} }
public static ByteArrayInputStream of(CharSequence seq) { public static InputStream of(CharSequence seq) {
return of(Strings.utf8(seq)); return of(Strings.utf8(seq));
} }
public static Reader reader(byte[] bytes) { public static Reader reader(byte[] bytes) {
return reader(new ByteArrayInputStream(bytes)); return reader(Streams.of(bytes));
} }
public static Reader reader(InputStream in) { public static Reader reader(InputStream in) {

View File

@ -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 <a href="https://github.com/stefan-zobel/wip/blob/b74e927edddf19a5dce7c8610835f620c0b6f557/src/main/java/misc/AddOpens.java">https://github.com/stefan-zobel/wip/blob/b74e927edddf19a5dce7c8610835f620c0b6f557/src/main/java/misc/AddOpens.java</a>
* 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:
*
* <pre>{@code
* boolean success = AddOpens.open("java.base", "java.util", "java.net");
* }</pre>
*
* @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;
}
}
}

View File

@ -15,6 +15,7 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.Nameable; import io.jsonwebtoken.impl.lang.Nameable;
import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.ParameterReadable;
@ -31,7 +32,6 @@ import io.jsonwebtoken.security.JwkThumbprint;
import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.Jwks;
import io.jsonwebtoken.security.KeyOperation; import io.jsonwebtoken.security.KeyOperation;
import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.Key; import java.security.Key;
@ -152,7 +152,7 @@ public abstract class AbstractJwk<K extends Key> implements Jwk<K>, ParameterRea
String json = toThumbprintJson(); String json = toThumbprintJson();
Assert.hasText(json, "Canonical JWK Thumbprint JSON cannot be null or empty."); 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 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())); byte[] digest = alg.digest(new DefaultRequest<>(in, this.context.getProvider(), this.context.getRandom()));
return new DefaultJwkThumbprint(digest, alg); return new DefaultJwkThumbprint(digest, alg);
} }

View File

@ -135,9 +135,8 @@ final class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey,
throw new InvalidKeyException(msg); throw new InvalidKeyException(msg);
} }
// We can ignore PKCS11 key name assertions for two reasons: // We can ignore PKCS11 key name assertions because HSM module key algorithm names don't always align with
// 1. HSM module key algorithm names don't always align with JCA standard algorithm names, and // JCA standard algorithm names:
// 2. Our KeysBridge.findBitLength implementation can extract the key length so we can still validate with that
boolean pkcs11Key = KeysBridge.isSunPkcs11GenericSecret(key); boolean pkcs11Key = KeysBridge.isSunPkcs11GenericSecret(key);
//assert key's jca name is valid if it's a JWA standard algorithm: //assert key's jca name is valid if it's a JWA standard algorithm:

View File

@ -26,7 +26,6 @@ import io.jsonwebtoken.security.DecryptAeadRequest;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.SequenceInputStream; import java.io.SequenceInputStream;
@ -93,7 +92,7 @@ public class GcmAesAeadAlgorithm extends AesAlgorithm implements AeadAlgorithm {
final AlgorithmParameterSpec ivSpec = getIvSpec(iv); 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: //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[]>() { jca(req).withCipher(new CheckedFunction<Cipher, byte[]>() {
@Override @Override

View File

@ -31,7 +31,6 @@ import io.jsonwebtoken.security.SignatureException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -145,11 +144,11 @@ public class HmacAesAeadAlgorithm extends AesAlgorithm implements AeadAlgorithm
Collection<InputStream> streams = new ArrayList<>(4); Collection<InputStream> streams = new ArrayList<>(4);
if (!Bytes.isEmpty(aad)) { // must come first if it exists 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(ciphertext);
streams.add(new ByteArrayInputStream(AL)); streams.add(Streams.of(AL));
InputStream in = new SequenceInputStream(Collections.enumeration(streams)); InputStream in = new SequenceInputStream(Collections.enumeration(streams));
SecretKey key = new SecretKeySpec(macKeyBytes, SIGALG.getJcaName()); SecretKey key = new SecretKeySpec(macKeyBytes, SIGALG.getJcaName());

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.CheckedSupplier; import io.jsonwebtoken.impl.lang.CheckedSupplier;
@ -36,7 +37,6 @@ import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory; import javax.crypto.SecretKeyFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.security.AlgorithmParameters; import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
@ -332,7 +332,7 @@ public class JcaTemplate {
return fallback(CertificateFactory.class, new CheckedFunction<CertificateFactory, X509Certificate>() { return fallback(CertificateFactory.class, new CheckedFunction<CertificateFactory, X509Certificate>() {
@Override @Override
public X509Certificate apply(CertificateFactory cf) throws CertificateException { public X509Certificate apply(CertificateFactory cf) throws CertificateException {
InputStream is = new ByteArrayInputStream(x509DerBytes); InputStream is = Streams.of(x509DerBytes);
return (X509Certificate) cf.generateCertificate(is); return (X509Certificate) cf.generateCertificate(is);
} }
}); });

View File

@ -15,9 +15,7 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.AddOpens;
import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.OptionalMethodInvoker;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.InvalidKeyException;
@ -30,6 +28,8 @@ import javax.crypto.SecretKey;
import java.security.Key; import java.security.Key;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; 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 @SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation
public final class KeysBridge { 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_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 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<Key, Integer> 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 // prevent instantiation
private KeysBridge() { private KeysBridge() {
} }
@ -119,24 +109,30 @@ public final class KeysBridge {
*/ */
public static int findBitLength(Key key) { public static int findBitLength(Key key) {
Integer retval = SUN_KEYSIZE.apply(key); int bitlen = -1;
int bitlen = Assert.stateNotNull(retval, SUN_KEYUTIL_ERR);
// SunPKCS11 SecretKey lengths are unfortunately reported in bytes, not bits // try to parse the length from key specification
// per https://bugs.openjdk.org/browse/JDK-8163173 if (key instanceof SecretKey) {
// (they should be multiplying the PKCS11 CKA_VALUE_LEN value by 8 since their own SecretKey secretKey = (SecretKey) key;
// sun.security.util.Length#getLength() JavaDoc states that values are intended to be in bits, not bytes) if ("RAW".equals(secretKey.getFormat())) {
// So we account for that here: byte[] encoded = findEncoded(secretKey);
if (bitlen > 0 && isSunPkcs11GenericSecret(key)) { if (!Bytes.isEmpty(encoded)) {
bitlen *= Byte.SIZE; 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; return bitlen;
} }

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.ParameterMap; import io.jsonwebtoken.impl.ParameterMap;
import io.jsonwebtoken.impl.io.Streams;
import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.Functions; import io.jsonwebtoken.impl.lang.Functions;
@ -27,7 +28,6 @@ import io.jsonwebtoken.security.Jwks;
import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.Request;
import io.jsonwebtoken.security.X509Builder; import io.jsonwebtoken.security.X509Builder;
import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -99,7 +99,7 @@ public class X509BuilderSupport implements X509Builder<X509BuilderSupport> {
private byte[] computeThumbprint(final X509Certificate cert, HashAlgorithm alg) { private byte[] computeThumbprint(final X509Certificate cert, HashAlgorithm alg) {
byte[] encoded = GET_X509_BYTES.apply(cert); 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); Request<InputStream> request = new DefaultRequest<>(in, null, null);
return alg.digest(request); return alg.digest(request);
} }

View File

@ -18,6 +18,7 @@ package io.jsonwebtoken
import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.* import io.jsonwebtoken.impl.*
import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.lang.Services
import io.jsonwebtoken.impl.security.* import io.jsonwebtoken.impl.security.*
@ -141,7 +142,7 @@ class JwtsTest {
def h = base64Url('{"alg":"HS256"}') def h = base64Url('{"alg":"HS256"}')
def c = base64Url('{"sub":"joe","exp":"-42-"}') def c = base64Url('{"sub":"joe","exp":"-42-"}')
def data = Strings.utf8(("$h.$c" as String)) 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 request = new DefaultSecureRequest<>(payload, null, null, key)
def result = Jwts.SIG.HS256.digest(request) def result = Jwts.SIG.HS256.digest(request)
def sig = Encoders.BASE64URL.encode(result) def sig = Encoders.BASE64URL.encode(result)
@ -191,7 +192,7 @@ class JwtsTest {
@Test @Test
void testContentStreamWithContentType() { void testContentStreamWithContentType() {
String s = 'Hello JJWT' String s = 'Hello JJWT'
InputStream content = new ByteArrayInputStream(Strings.utf8(s)) InputStream content = Streams.of(Strings.utf8(s))
String cty = 'text/plain' String cty = 'text/plain'
String compact = Jwts.builder().content(content, cty).compact() String compact = Jwts.builder().content(content, cty).compact()
def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact)
@ -202,7 +203,7 @@ class JwtsTest {
@Test @Test
void testContentStreamWithoutContentType() { void testContentStreamWithoutContentType() {
String s = 'Hello JJWT' 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() String compact = Jwts.builder().content(content).compact()
def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact)
assertNull jwt.header.getContentType() assertNull jwt.header.getContentType()

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken
import io.jsonwebtoken.impl.DefaultJwsHeader import io.jsonwebtoken.impl.DefaultJwsHeader
import io.jsonwebtoken.impl.DefaultJwtParser import io.jsonwebtoken.impl.DefaultJwtParser
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.lang.Services
import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.impl.security.TestKeys
@ -69,8 +70,8 @@ class RFC7797Test {
String s = Jwts.builder().signWith(key).content(content).encodePayload(false).compact() 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: // But verify with 3 types of sources: string, byte array, and two different kinds of InputStreams:
InputStream asByteInputStream = new ByteArrayInputStream(content) InputStream asByteInputStream = Streams.of(content)
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) { for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).build() def parser = Jwts.parser().verifyWith(key).build()
@ -107,8 +108,8 @@ class RFC7797Test {
String s = Jwts.builder().signWith(key).content(content).encodePayload(false).compact() 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: // But verify with 3 types of sources: string, byte array, and two different kinds of InputStreams:
InputStream asByteInputStream = new ByteArrayInputStream(content) InputStream asByteInputStream = Streams.of(content)
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) { for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).build() def parser = Jwts.parser().verifyWith(key).build()
@ -128,13 +129,13 @@ class RFC7797Test {
def key = TestKeys.HS256 def key = TestKeys.HS256
byte[] content = Strings.utf8('$.02') // https://datatracker.ietf.org/doc/html/rfc7797#section-4.2 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() 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: // But verify with 3 types of sources: byte array, and two different kinds of InputStreams:
InputStream asByteInputStream = new ByteArrayInputStream(content) InputStream asByteInputStream =Streams.of(content)
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) { for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).build() def parser = Jwts.parser().verifyWith(key).build()
@ -348,8 +349,8 @@ class RFC7797Test {
.compact() .compact()
// But verify with 3 types of sources: byte array, and two different kinds of InputStreams: // But verify with 3 types of sources: byte array, and two different kinds of InputStreams:
InputStream asByteInputStream = new ByteArrayInputStream(compressed) InputStream asByteInputStream = Streams.of(compressed)
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(compressed)) InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(compressed))
for (def payload : [compressed, asByteInputStream, asBufferedInputStream]) { for (def payload : [compressed, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).build() def parser = Jwts.parser().verifyWith(key).build()

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken.impl package io.jsonwebtoken.impl
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.impl.security.DefaultHashAlgorithm import io.jsonwebtoken.impl.security.DefaultHashAlgorithm
import io.jsonwebtoken.impl.security.DefaultRequest import io.jsonwebtoken.impl.security.DefaultRequest
@ -269,7 +270,7 @@ class DefaultMutableJweHeaderTest {
*/ */
@Test @Test
void testX509Sha1Thumbprint() { 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 request = new DefaultRequest(payload, null, null)
def x5t = DefaultHashAlgorithm.SHA1.digest(request) def x5t = DefaultHashAlgorithm.SHA1.digest(request)
String encoded = Encoders.BASE64URL.encode(x5t) String encoded = Encoders.BASE64URL.encode(x5t)
@ -285,7 +286,7 @@ class DefaultMutableJweHeaderTest {
*/ */
@Test @Test
void testX509Sha256Thumbprint() { 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 request = new DefaultRequest(payload, null, null)
def x5tS256 = Jwks.HASH.@SHA256.digest(request) def x5tS256 = Jwks.HASH.@SHA256.digest(request)
String encoded = Encoders.BASE64URL.encode(x5tS256) String encoded = Encoders.BASE64URL.encode(x5tS256)

View File

@ -25,7 +25,7 @@ class CountingInputStreamTest {
@Test @Test
void readEmpty() { void readEmpty() {
def stream = new CountingInputStream(new ByteArrayInputStream(Bytes.EMPTY)) def stream = new CountingInputStream(Streams.of(Bytes.EMPTY))
stream.read() stream.read()
assertEquals 0, stream.getCount() assertEquals 0, stream.getCount()
} }
@ -34,7 +34,7 @@ class CountingInputStreamTest {
void readSingle() { void readSingle() {
def single = (byte) 0x18 // any random byte is fine def single = (byte) 0x18 // any random byte is fine
def data = new byte[1]; data[0] = single 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 single, stream.read()
assertEquals 1, stream.getCount() assertEquals 1, stream.getCount()
} }
@ -42,7 +42,7 @@ class CountingInputStreamTest {
@Test @Test
void testSkip() { void testSkip() {
def data = Strings.utf8('hello world') def data = Strings.utf8('hello world')
def stream = new CountingInputStream(new ByteArrayInputStream(data)) def stream = new CountingInputStream(Streams.of(data))
stream.skip(6) stream.skip(6)
assertEquals 6, stream.getCount() assertEquals 6, stream.getCount()
int w = ('w' as char) int w = ('w' as char)

View File

@ -29,7 +29,7 @@ class DelegateStringDecoderTest {
void decode() { void decode() {
def value = 'test' def value = 'test'
def bytes = Strings.utf8(value) def bytes = Strings.utf8(value)
def ins = new ByteArrayInputStream(bytes) def ins = Streams.of(bytes)
def test = new Decoder<CharSequence, byte[]>() { def test = new Decoder<CharSequence, byte[]>() {
@Override @Override
byte[] decode(CharSequence s) throws DecodingException { byte[] decode(CharSequence s) throws DecodingException {

View File

@ -15,6 +15,7 @@
*/ */
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.io.Encoders
import io.jsonwebtoken.security.* import io.jsonwebtoken.security.*
import org.junit.Test import org.junit.Test
@ -64,7 +65,7 @@ class AbstractAsymmetricJwkBuilderTest {
@Test @Test
void testX509CertificateSha1Thumbprint() { void testX509CertificateSha1Thumbprint() {
def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) def payload = Streams.of(TestKeys.RS256.cert.getEncoded())
Request<byte[]> request = new DefaultRequest(payload, null, null) Request<byte[]> request = new DefaultRequest(payload, null, null)
def x5t = DefaultHashAlgorithm.SHA1.digest(request) def x5t = DefaultHashAlgorithm.SHA1.digest(request)
def encoded = Encoders.BASE64URL.encode(x5t) def encoded = Encoders.BASE64URL.encode(x5t)
@ -75,7 +76,7 @@ class AbstractAsymmetricJwkBuilderTest {
@Test @Test
void testX509CertificateSha1ThumbprintEnabled() { void testX509CertificateSha1ThumbprintEnabled() {
def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) def payload = Streams.of(TestKeys.RS256.cert.getEncoded())
Request<byte[]> request = new DefaultRequest(payload, null, null) Request<byte[]> request = new DefaultRequest(payload, null, null)
def x5t = DefaultHashAlgorithm.SHA1.digest(request) def x5t = DefaultHashAlgorithm.SHA1.digest(request)
def encoded = Encoders.BASE64URL.encode(x5t) def encoded = Encoders.BASE64URL.encode(x5t)
@ -86,7 +87,7 @@ class AbstractAsymmetricJwkBuilderTest {
@Test @Test
void testX509CertificateSha256Thumbprint() { void testX509CertificateSha256Thumbprint() {
def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) def payload = Streams.of(TestKeys.RS256.cert.getEncoded())
Request<byte[]> request = new DefaultRequest(payload, null, null) Request<byte[]> request = new DefaultRequest(payload, null, null)
def x5tS256 = Jwks.HASH.SHA256.digest(request) def x5tS256 = Jwks.HASH.SHA256.digest(request)
def encoded = Encoders.BASE64URL.encode(x5tS256) def encoded = Encoders.BASE64URL.encode(x5tS256)
@ -97,7 +98,7 @@ class AbstractAsymmetricJwkBuilderTest {
@Test @Test
void testX509CertificateSha256ThumbprintEnabled() { void testX509CertificateSha256ThumbprintEnabled() {
def payload = new ByteArrayInputStream(TestKeys.RS256.cert.getEncoded()) def payload = Streams.of(TestKeys.RS256.cert.getEncoded())
Request<InputStream> request = new DefaultRequest(payload, null, null) Request<InputStream> request = new DefaultRequest(payload, null, null)
def x5tS256 = Jwks.HASH.SHA256.digest(request) def x5tS256 = Jwks.HASH.SHA256.digest(request)
def encoded = Encoders.BASE64URL.encode(x5tS256) def encoded = Encoders.BASE64URL.encode(x5tS256)

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.lang.Strings
import io.jsonwebtoken.security.SecureRequest import io.jsonwebtoken.security.SecureRequest
import io.jsonwebtoken.security.SignatureException import io.jsonwebtoken.security.SignatureException
@ -37,7 +38,7 @@ class AbstractSecureDigestAlgorithmTest {
Provider provider = Security.getProvider('BC') Provider provider = Security.getProvider('BC')
def pair = Jwts.SIG.RS256.keyPair().build() def pair = Jwts.SIG.RS256.keyPair().build()
byte[] data = Strings.utf8('foo') 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())) byte[] signature = Jwts.SIG.RS256.digest(new DefaultSecureRequest<>(payload, provider, null, pair.getPrivate()))
payload.reset() payload.reset()
assertTrue Jwts.SIG.RS256.verify(new DefaultVerifySecureDigestRequest<PublicKey>(payload, provider, null, pair.getPublic(), signature)) assertTrue Jwts.SIG.RS256.verify(new DefaultVerifySecureDigestRequest<PublicKey>(payload, provider, null, pair.getPublic(), signature))
@ -54,7 +55,7 @@ class AbstractSecureDigestAlgorithmTest {
} }
} }
try { try {
def payload = new ByteArrayInputStream(Strings.utf8('foo')) def payload = Streams.of(Strings.utf8('foo'))
alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate())) alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate()))
} catch (SignatureException e) { } catch (SignatureException e) {
assertTrue e.getMessage().startsWith('Unable to compute test signature with JCA algorithm \'test\' using key {') 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 data = Strings.utf8('foo')
def payload = new ByteArrayInputStream(data) def payload = Streams.of(data)
try { try {
byte[] signature = alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate())) byte[] signature = alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate()))
payload.reset() payload.reset()

View File

@ -15,6 +15,7 @@
*/ */
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.lang.Strings
import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.HashAlgorithm
import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.Jwks
@ -29,7 +30,7 @@ class DefaultHashAlgorithmTest {
@Test @Test
void testDigestAndVerify() { void testDigestAndVerify() {
byte[] data = Strings.utf8('Hello World') byte[] data = Strings.utf8('Hello World')
InputStream payload = new ByteArrayInputStream(data) InputStream payload = Streams.of(data)
for (HashAlgorithm alg : algs) { for (HashAlgorithm alg : algs) {
byte[] hash = alg.digest(new DefaultRequest<>(payload, null, null)) byte[] hash = alg.digest(new DefaultRequest<>(payload, null, null))
payload.reset() payload.reset()

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken.impl.security
import io.jsonwebtoken.impl.io.CharSequenceReader import io.jsonwebtoken.impl.io.CharSequenceReader
import io.jsonwebtoken.impl.io.ConvertingParser import io.jsonwebtoken.impl.io.ConvertingParser
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.io.AbstractDeserializer import io.jsonwebtoken.io.AbstractDeserializer
import io.jsonwebtoken.io.DeserializationException import io.jsonwebtoken.io.DeserializationException
import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.io.Deserializer
@ -52,7 +53,7 @@ class DefaultJwkParserBuilderTest {
@Test(expected = IllegalArgumentException) @Test(expected = IllegalArgumentException)
void parseNull() { void parseNull() {
Jwks.parser().build().parse((CharSequence)null) Jwks.parser().build().parse((CharSequence) null)
} }
@Test(expected = IllegalArgumentException) @Test(expected = IllegalArgumentException)
@ -133,7 +134,7 @@ class DefaultJwkParserBuilderTest {
assertEquals jwk, parsed assertEquals jwk, parsed
// InputStream parsing: // InputStream parsing:
parsed = parser.parse(new ByteArrayInputStream(Strings.utf8(json))) parsed = parser.parse(Streams.of(json))
assertEquals jwk, parsed assertEquals jwk, parsed
} }
} }

View File

@ -15,6 +15,7 @@
*/ */
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.io.Encoders
import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.lang.Strings
import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.HashAlgorithm
@ -30,7 +31,7 @@ class DefaultJwkThumbprintTest {
private static String content = "Hello World" private static String content = "Hello World"
private static HashAlgorithm alg = Jwks.HASH.SHA256 private static HashAlgorithm alg = Jwks.HASH.SHA256
private static byte[] digest = alg.digest(new DefaultRequest<InputStream>(new ByteArrayInputStream(Strings.utf8(content)), null, null)) private static byte[] digest = alg.digest(new DefaultRequest<InputStream>(Streams.of(content), null, null))
private static String expectedToString = Encoders.BASE64URL.encode(digest) private static String expectedToString = Encoders.BASE64URL.encode(digest)
private static String expectedUriString = DefaultJwkThumbprint.URI_PREFIX + alg.getId() + ":" + expectedToString private static String expectedUriString = DefaultJwkThumbprint.URI_PREFIX + alg.getId() + ":" + expectedToString
private static URI expectedUri = URI.create(expectedUriString) private static URI expectedUri = URI.create(expectedUriString)
@ -81,7 +82,7 @@ class DefaultJwkThumbprintTest {
assertFalse thumbprint == new DefaultJwkThumbprint(digest, DefaultHashAlgorithm.SHA1) assertFalse thumbprint == new DefaultJwkThumbprint(digest, DefaultHashAlgorithm.SHA1)
// same alg, different digest: // 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)) byte[] digest2 = alg.digest(new DefaultRequest<>(payload, null, null))
assertFalse thumbprint == new DefaultJwkThumbprint(digest2, DefaultHashAlgorithm.SHA1) assertFalse thumbprint == new DefaultJwkThumbprint(digest2, DefaultHashAlgorithm.SHA1)
} }

View File

@ -169,7 +169,7 @@ class EcSignatureAlgorithmTest {
@Test @Test
void testVerifyWithPrivateKey() { void testVerifyWithPrivateKey() {
byte[] data = 'foo'.getBytes(StandardCharsets.UTF_8) byte[] data = 'foo'.getBytes(StandardCharsets.UTF_8)
def payload = new ByteArrayInputStream(data) def payload = Streams.of(data)
algs().each { algs().each {
payload.reset() payload.reset()
def pair = it.keyPair().build() def pair = it.keyPair().build()
@ -194,7 +194,7 @@ class EcSignatureAlgorithmTest {
BigInteger order = BigInteger.ONE BigInteger order = BigInteger.ONE
ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new TestECField(), BigInteger.ONE, BigInteger.ONE), new ECPoint(BigInteger.ONE, BigInteger.ONE), order, 1) 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) 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 { try {
it.verify(request) it.verify(request)
} catch (InvalidKeyException expected) { } catch (InvalidKeyException expected) {
@ -310,7 +310,7 @@ class EcSignatureAlgorithmTest {
def signatureStart = token.lastIndexOf('.') def signatureStart = token.lastIndexOf('.')
def withoutSignature = token.substring(0, signatureStart) def withoutSignature = token.substring(0, signatureStart)
def data = Strings.ascii(withoutSignature); def data = Strings.ascii(withoutSignature);
def payload = new ByteArrayInputStream(data) def payload = Streams.of(data)
def signature = Decoders.BASE64URL.decode(token.substring(signatureStart + 1)) 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)) 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.getPublic() instanceof ECPublicKey
assertTrue keypair.getPrivate() instanceof ECPrivateKey assertTrue keypair.getPrivate() instanceof ECPrivateKey
def data = Strings.ascii(withoutSignature) 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)) def signature = alg.digest(new DefaultSecureRequest<>(payload, null, null, keypair.private))
payload.reset() payload.reset()
assertTrue alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, signature)) assertTrue alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, signature))
@ -473,7 +473,7 @@ class EcSignatureAlgorithmTest {
def signature = token.substring(signatureStart + 1) def signature = token.substring(signatureStart + 1)
def data = withoutSignature.getBytes(StandardCharsets.US_ASCII) def data = withoutSignature.getBytes(StandardCharsets.US_ASCII)
def payload = new ByteArrayInputStream(data) def payload = Streams.of(data)
def sigBytes = Decoders.BASE64URL.decode(signature) def sigBytes = Decoders.BASE64URL.decode(signature)
def request = new DefaultVerifySecureDigestRequest(payload, null, null, pub, sigBytes) def request = new DefaultVerifySecureDigestRequest(payload, null, null, pub, sigBytes)
assert alg.verify(request), "Signature do not match that of other implementations" assert alg.verify(request), "Signature do not match that of other implementations"
@ -495,7 +495,7 @@ class EcSignatureAlgorithmTest {
signature.initSign(keypair.private) signature.initSign(keypair.private)
signature.update(data) signature.update(data)
def signed = signature.sign() 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 { try {
alg.verify(request) alg.verify(request)
fail() fail()
@ -519,7 +519,7 @@ class EcSignatureAlgorithmTest {
def keypair = alg.keyPair().build() def keypair = alg.keyPair().build()
def signature = Signature.getInstance(alg.jcaName as String) def signature = Signature.getInstance(alg.jcaName as String)
def data = Strings.ascii(withoutSignature) def data = Strings.ascii(withoutSignature)
def payload = new ByteArrayInputStream(data) def payload = Streams.of(data)
signature.initSign(keypair.private) signature.initSign(keypair.private)
signature.update(data) signature.update(data)
def signed = signature.sign() def signed = signature.sign()
@ -538,7 +538,7 @@ class EcSignatureAlgorithmTest {
def alg = Jwts.SIG.ES256 def alg = Jwts.SIG.ES256
def keypair = alg.keyPair().build() def keypair = alg.keyPair().build()
def data = Strings.ascii(withoutSignature) 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) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, forgedSig)
assertFalse alg.verify(request) assertFalse alg.verify(request)
} }
@ -556,7 +556,7 @@ class EcSignatureAlgorithmTest {
def alg = Jwts.SIG.ES256 def alg = Jwts.SIG.ES256
def keypair = alg.keyPair().build() def keypair = alg.keyPair().build()
def data = Strings.ascii(withoutSignature) 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) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, sig)
assertFalse alg.verify(request) assertFalse alg.verify(request)
} }
@ -574,7 +574,7 @@ class EcSignatureAlgorithmTest {
def alg = Jwts.SIG.ES256 def alg = Jwts.SIG.ES256
def keypair = alg.keyPair().build() def keypair = alg.keyPair().build()
def data = Strings.ascii(withoutSignature) 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) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, sig)
assertFalse alg.verify(request) assertFalse alg.verify(request)
} }
@ -587,7 +587,7 @@ class EcSignatureAlgorithmTest {
def alg = Jwts.SIG.ES256 def alg = Jwts.SIG.ES256
def keypair = alg.keyPair().build() def keypair = alg.keyPair().build()
def data = Strings.ascii(withoutSignature) def data = Strings.ascii(withoutSignature)
def payload = new ByteArrayInputStream(data) def payload = Streams.of(data)
def invalidSignature = Decoders.BASE64URL.decode(invalidEncodedSignature) def invalidSignature = Decoders.BASE64URL.decode(invalidEncodedSignature)
def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, invalidSignature) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, invalidSignature)
assertFalse("Forged signature must not be considered valid.", alg.verify(request)) assertFalse("Forged signature must not be considered valid.", alg.verify(request))

View File

@ -60,7 +60,7 @@ class GcmAesAeadAlgorithmTest {
def alg = Jwts.ENC.A256GCM def alg = Jwts.ENC.A256GCM
def ins = new ByteArrayInputStream(P) def ins = Streams.of(P)
def aad = Streams.of(AAD) def aad = Streams.of(AAD)
def out = new ByteArrayOutputStream(8192) def out = new ByteArrayOutputStream(8192)
def res = new DefaultAeadResult(out) def res = new DefaultAeadResult(out)

View File

@ -15,6 +15,7 @@
*/ */
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.lang.Registry import io.jsonwebtoken.lang.Registry
import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.HashAlgorithm
import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.Jwks
@ -80,7 +81,7 @@ class HashAlgorithmsTest {
static DefaultRequest<InputStream> request(String msg) { static DefaultRequest<InputStream> request(String msg) {
byte[] data = msg.getBytes(StandardCharsets.UTF_8) byte[] data = msg.getBytes(StandardCharsets.UTF_8)
InputStream payload = new ByteArrayInputStream(data) InputStream payload = Streams.of(data)
return new DefaultRequest<InputStream>(payload, null, null) return new DefaultRequest<InputStream>(payload, null, null)
} }

View File

@ -15,6 +15,7 @@
*/ */
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.impl.lang.CheckedFunction
import io.jsonwebtoken.lang.Classes import io.jsonwebtoken.lang.Classes
@ -347,7 +348,7 @@ class JcaTemplateTest {
X509Certificate cert = template.withCertificateFactory(new CheckedFunction<CertificateFactory, X509Certificate>() { X509Certificate cert = template.withCertificateFactory(new CheckedFunction<CertificateFactory, X509Certificate>() {
@Override @Override
X509Certificate apply(CertificateFactory certificateFactory) throws Exception { X509Certificate apply(CertificateFactory certificateFactory) throws Exception {
(X509Certificate)certificateFactory.generateCertificate(new ByteArrayInputStream(expected.getEncoded())) (X509Certificate) certificateFactory.generateCertificate(Streams.of(expected.getEncoded()))
} }
}) })
assertEquals expected, cert assertEquals expected, cert

View File

@ -16,13 +16,13 @@
package io.jsonwebtoken.impl.security package io.jsonwebtoken.impl.security
import io.jsonwebtoken.impl.RfcTests import io.jsonwebtoken.impl.RfcTests
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.HashAlgorithm
import io.jsonwebtoken.security.JwkThumbprint import io.jsonwebtoken.security.JwkThumbprint
import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.Jwks
import org.junit.Test import org.junit.Test
import javax.crypto.SecretKey import javax.crypto.SecretKey
import java.nio.charset.StandardCharsets
import static io.jsonwebtoken.impl.security.DefaultHashAlgorithm.SHA1 import static io.jsonwebtoken.impl.security.DefaultHashAlgorithm.SHA1
import static org.junit.Assert.assertEquals import static org.junit.Assert.assertEquals
@ -32,8 +32,7 @@ class JwkThumbprintsTest {
static final HashAlgorithm SHA256 = Jwks.HASH.@SHA256 static final HashAlgorithm SHA256 = Jwks.HASH.@SHA256
static byte[] digest(String json, HashAlgorithm alg) { static byte[] digest(String json, HashAlgorithm alg) {
def utf8Bytes = json.getBytes(StandardCharsets.UTF_8) def payload = Streams.of(json)
def payload = new ByteArrayInputStream(utf8Bytes);
def req = new DefaultRequest(payload, null, null) def req = new DefaultRequest(payload, null, null)
return alg.digest(req) return alg.digest(req)
} }

View File

@ -121,7 +121,7 @@ class EncryptionAlgorithmsTest {
assertEquals(ciphertextBytes.length, PLAINTEXT_BYTES.length) assertEquals(ciphertextBytes.length, PLAINTEXT_BYTES.length)
} }
def ciphertext = new ByteArrayInputStream(ciphertextBytes) def ciphertext = Streams.of(ciphertextBytes)
out = new ByteArrayOutputStream(8192) out = new ByteArrayOutputStream(8192)
def dreq = new DefaultDecryptAeadRequest(ciphertext, key, null, iv, tag) def dreq = new DefaultDecryptAeadRequest(ciphertext, key, null, iv, tag)
alg.decrypt(dreq, out) alg.decrypt(dreq, out)
@ -155,7 +155,7 @@ class EncryptionAlgorithmsTest {
assertEquals(ciphertextBytes.length, PLAINTEXT_BYTES.length) assertEquals(ciphertextBytes.length, PLAINTEXT_BYTES.length)
} }
def ciphertext = new ByteArrayInputStream(ciphertextBytes) def ciphertext = Streams.of(ciphertextBytes)
out = new ByteArrayOutputStream(8192) out = new ByteArrayOutputStream(8192)
def dreq = new DefaultDecryptAeadRequest(ciphertext, key, aad, iv, tag) def dreq = new DefaultDecryptAeadRequest(ciphertext, key, aad, iv, tag)
alg.decrypt(dreq, out) alg.decrypt(dreq, out)

View File

@ -133,7 +133,10 @@
<test.addOpens> <test.addOpens>
--add-opens java.base/java.lang=ALL-UNNAMED, <!-- Needed by EasyMock/cglib --> --add-opens java.base/java.lang=ALL-UNNAMED, <!-- Needed by EasyMock/cglib -->
--add-opens java.desktop/java.beans=ALL-UNNAMED, <!-- Needed by EasyMock/cglib --> --add-opens java.desktop/java.beans=ALL-UNNAMED, <!-- Needed by EasyMock/cglib -->
--add-opens java.base/java.lang.ref=ALL-UNNAMED <!-- Needed by PowerMock --> --add-opens java.base/java.lang.ref=ALL-UNNAMED, <!-- Needed by PowerMock -->
<!-- needed by KeysImplTest.testKeyPairFor, KeysTest.testDeprecatedKeyPairFor, and
KeysTest.testKeyPairBuilder: -->
--add-opens java.base/sun.security.util=ALL-UNNAMED
</test.addOpens> </test.addOpens>
</properties> </properties>