Skip to content

Refactor Encoding util for easier replacement of Base64 encoding class #1121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 6, 2024
4 changes: 2 additions & 2 deletions src/main/java/io/nats/client/Connection.java
Original file line number Diff line number Diff line change
Expand Up @@ -548,8 +548,8 @@ enum Status {
/**
* Forces reconnect behavior. Stops the current connection including the reading and writing,
* copies already queued outgoing messages, and then begins the reconnect logic.
* @throws IOException
* @throws InterruptedException
* @throws IOException the force reconnected fails
* @throws InterruptedException if one is thrown, in order to propagate it up
*/
void forceReconnect() throws IOException, InterruptedException;

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/nats/client/ErrorListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ default void socketWriteTimeout(Connection conn) {}
* @param sub the JetStreamSubscription that this occurred on, if applicable
* @param pairs custom string pairs. I.E. "foo: ", fooObject, "bar-", barObject will be appended
* to the message like ", foo: <fooValue>, bar-<barValue>".
* @return
* @return the message
*/
default String supplyMessage(String label, Connection conn, Consumer consumer, Subscription sub, Object... pairs) {
StringBuilder sb = new StringBuilder(label == null ? "" : label);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/nats/client/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

import static io.nats.client.support.Encoding.base64UrlEncodeToString;
import static io.nats.client.support.Encoding.uriDecode;
import static io.nats.client.support.NatsConstants.*;
import static io.nats.client.support.SSLUtils.DEFAULT_TLS_ALGORITHM;
Expand Down Expand Up @@ -2367,7 +2368,7 @@ public CharBuffer buildProtocolConnectOptionsString(String serverURI, boolean in
nkey = new char[0];
}

String encodedSig = Base64.getUrlEncoder().withoutPadding().encodeToString(sig);
String encodedSig = base64UrlEncodeToString(sig);

appendOption(connectString, Options.OPTION_NKEY, nkey, true, true);
appendOption(connectString, Options.OPTION_SIG, encodedSig, true, true);
Expand Down
136 changes: 117 additions & 19 deletions src/main/java/io/nats/client/support/Encoding.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,32 @@
public abstract class Encoding {
private Encoding() {} /* ensures cannot be constructed */

/**
* base64 encode a byte array to a byte array
* @param input the input byte array to encode
* @return the encoded byte array
*/
public static byte[] base64BasicEncode(byte[] input) {
return Base64.getEncoder().encode(input);
}

/**
* base64 encode a byte array to a byte array
* @param input the input byte array to encode
* @return the encoded byte array
*/
public static String base64BasicEncodeToString(byte[] input) {
return Base64.getEncoder().encodeToString(input);
}

/**
* base64 url encode a byte array to a byte array
* @param input the input byte array to encode
* @return the encoded byte array
* @deprecated prefer base64UrlEncode
*/
@Deprecated
public static byte[] base64Encode(byte[] input) {
return Base64.getUrlEncoder().withoutPadding().encode(input);
public static String base64BasicEncodeToString(String input) {
return Base64.getEncoder()
.encodeToString(input.getBytes(StandardCharsets.UTF_8));
}

/**
Expand All @@ -43,21 +60,50 @@ public static byte[] base64UrlEncode(byte[] input) {
}

/**
* base64 url encode a byte array to a string
* base64 url encode a byte array to a byte array
* @param input the input byte array to encode
* @return the encoded string
* @return the encoded byte array
*/
public static String toBase64Url(byte[] input) {
return new String(base64UrlEncode(input));
public static String base64UrlEncodeToString(byte[] input) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(input);
}

/**
* base64 url encode a string to a string
* @param input the input string to encode
* @return the encoded string
* base64 url encode a byte array to a byte array
* @param input the input byte array to encode
* @return the encoded byte array
*/
public static String toBase64Url(String input) {
return new String(base64UrlEncode(input.getBytes(StandardCharsets.US_ASCII)));
public static String base64UrlEncodeToString(String input) {
return Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(input.getBytes(StandardCharsets.UTF_8));
}

/**
* base64 decode a byte array
* @param input the input byte array to decode
* @return the decoded byte array
*/
public static byte[] base64BasicDecode(byte[] input) {
return Base64.getDecoder().decode(input);
}

/**
* base64 decode a base64 encoded string
* @param input the input string to decode
* @return the decoded byte array
*/
public static byte[] base64BasicDecode(String input) {
return Base64.getDecoder().decode(input);
}

/**
* base64 decode a base64 encoded string
* @param input the input string to decode
* @return the decoded string
*/
public static String base64BasicDecodeToString(String input) {
return new String(Base64.getDecoder().decode(input));
}

/**
Expand All @@ -70,12 +116,21 @@ public static byte[] base64UrlDecode(byte[] input) {
}

/**
* get a string from a base64 url encoded byte array
* base64 url decode a base64 url encoded string
* @param input the input string to decode
* @return the decoded byte array
*/
public static byte[] base64UrlDecode(String input) {
return Base64.getUrlDecoder().decode(input);
}

/**
* base64 url decode a base64 url encoded string
* @param input the input string to decode
* @return the decoded string
*/
public static String fromBase64Url(String input) {
return new String(base64UrlDecode(input.getBytes(StandardCharsets.US_ASCII)));
public static String base64UrlDecodeToString(String input) {
return new String(Base64.getUrlDecoder().decode(input));
}

// http://en.wikipedia.org/wiki/Base_32
Expand Down Expand Up @@ -142,8 +197,8 @@ public static byte[] base32Decode(final char[] input) {
int next = 0;
int bitsLeft = 0;

for (int i = 0; i < input.length; i++) {
int lookup = input[i] - '0';
for (char value : input) {
int lookup = value - '0';

if (lookup < 0 || lookup >= BASE32_LOOKUP.length) {
continue;
Expand All @@ -170,7 +225,6 @@ public static String jsonDecode(String s) {
char nextChar = (x == len - 1) ? '\\' : s.charAt(x + 1);
switch (nextChar) {
case '\\':
ch = '\\';
break;
case 'b':
ch = '\b';
Expand Down Expand Up @@ -262,4 +316,48 @@ public static String uriDecode(String source) {
return source;
}
}

/**
* @deprecated Use {@link #base64UrlEncode(byte[])} instead.
* base64 url encode a byte array to a byte array
* @param input the input byte array to encode
* @return the encoded byte array
*/
@Deprecated
public static byte[] base64Encode(byte[] input) {
return base64UrlEncode(input);
}

/**
* @deprecated Use {@link #base64UrlEncodeToString(byte[])} instead.
* base64 url encode a byte array to a string
* @param input the input byte array to encode
* @return the encoded string
*/
@Deprecated
public static String toBase64Url(byte[] input) {
return base64UrlEncodeToString(input);
}

/**
* @deprecated Use {@link #base64UrlEncodeToString(String)} instead.
* base64 url encode a string to a string
* @param input the input string to encode
* @return the encoded string
*/
@Deprecated
public static String toBase64Url(String input) {
return base64UrlEncodeToString(input);
}

/**
* @deprecated Use {@link #base64UrlDecodeToString(String)} instead.
* get a string from a base64 url encoded byte array
* @param input the input string to decode
* @return the decoded string
*/
@Deprecated
public static String fromBase64Url(String input) {
return base64UrlDecodeToString(input);
}
}
3 changes: 2 additions & 1 deletion src/main/java/io/nats/client/support/JsonValueUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.*;
import java.util.function.Function;

import static io.nats.client.support.Encoding.base64BasicDecode;
import static io.nats.client.support.JsonValue.*;

/**
Expand Down Expand Up @@ -192,7 +193,7 @@ public static byte[] readBytes(JsonValue jsonValue, String key) {

public static byte[] readBase64(JsonValue jsonValue, String key) {
String b64 = readString(jsonValue, key);
return b64 == null ? null : Base64.getDecoder().decode(b64);
return b64 == null ? null : base64BasicDecode(b64);
}

public static Integer getInteger(JsonValue v) {
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/io/nats/client/support/JwtUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public abstract class JwtUtils {
private JwtUtils() {} /* ensures cannot be constructed */

private static final String ENCODED_CLAIM_HEADER =
toBase64Url("{\"typ\":\"JWT\", \"alg\":\"ed25519-nkey\"}");
base64UrlEncodeToString("{\"typ\":\"JWT\", \"alg\":\"ed25519-nkey\"}");

private static final long NO_LIMIT = -1;

Expand Down Expand Up @@ -251,11 +251,11 @@ public static String issueJWT(NKey signingKey, String publicUserKey, String name
claimJson = claim.toJson();

// all three components (header/body/signature) are base64url encoded
String encBody = toBase64Url(claimJson);
String encBody = base64UrlEncodeToString(claimJson);

// compute the signature off of header + body (. included on purpose)
byte[] sig = (ENCODED_CLAIM_HEADER + "." + encBody).getBytes(StandardCharsets.UTF_8);
String encSig = toBase64Url(signingKey.sign(sig));
String encSig = base64UrlEncodeToString(signingKey.sign(sig));

// append signature to header and body and return it
return ENCODED_CLAIM_HEADER + "." + encBody + "." + encSig;
Expand All @@ -267,7 +267,7 @@ public static String issueJWT(NKey signingKey, String publicUserKey, String name
* @return the claim body json
*/
public static String getClaimBody(String jwt) {
return fromBase64Url(jwt.split("\\.")[1]);
return base64UrlDecodeToString(jwt.split("\\.")[1]);
}

public static class UserClaim implements JsonSerializable {
Expand Down
6 changes: 2 additions & 4 deletions src/main/java/io/nats/client/support/NatsObjectStoreUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@

import io.nats.client.impl.Headers;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

import static io.nats.client.support.Encoding.base64BasicEncodeToString;
import static io.nats.client.support.NatsConstants.DOT;
import static io.nats.client.support.NatsJetStreamConstants.ROLLUP_HDR;
import static io.nats.client.support.NatsJetStreamConstants.ROLLUP_HDR_SUBJECT;
Expand Down Expand Up @@ -59,7 +57,7 @@ public static String toChunkPrefix(String bucketName) {
}

public static String encodeForSubject(String name) {
return Base64.getEncoder().encodeToString(name.getBytes(StandardCharsets.UTF_8));
return base64BasicEncodeToString(name);
}

public static Headers getMetaHeaders() {
Expand Down
7 changes: 3 additions & 4 deletions src/main/java/io/nats/client/support/WebSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

import static io.nats.client.support.Encoding.base64BasicEncodeToString;
import static java.nio.charset.StandardCharsets.UTF_8;

public class WebSocket extends Socket {
Expand Down Expand Up @@ -63,7 +63,7 @@ private static void handshake(Socket socket, String host, List<Consumer<HttpRequ
// been base64-encoded
byte[] keyBytes = new byte[16];
new SecureRandom().nextBytes(keyBytes);
String key = Base64.getEncoder().encodeToString(keyBytes);
String key = base64BasicEncodeToString(keyBytes);

request.getHeaders()
.add("Host", host)
Expand Down Expand Up @@ -130,8 +130,7 @@ private static void handshake(Socket socket, String host, List<Consumer<HttpRequ
}
sha1.update(key.getBytes(UTF_8));
sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(UTF_8));
String acceptKey = Base64.getEncoder().encodeToString(
sha1.digest());
String acceptKey = base64BasicEncodeToString(sha1.digest());
String gotAcceptKey = headers.get("sec-websocket-accept");
if (!acceptKey.equals(gotAcceptKey)) {
throw new IllegalStateException(
Expand Down
26 changes: 12 additions & 14 deletions src/test/java/io/nats/client/NKeyTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

import static io.nats.client.NKey.removePaddingAndClear;
import static io.nats.client.support.Encoding.base32Decode;
import static io.nats.client.support.Encoding.base32Encode;
import static io.nats.client.support.Encoding.*;
import static io.nats.client.utils.ResourceUtils.dataAsLines;
import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -519,12 +517,12 @@ public void testInterop() throws Exception {

assertEquals(fromSeed.getType(), NKey.Type.USER);

byte[] nonceData = Base64.getUrlDecoder().decode(nonce);
byte[] nonceSig = Base64.getUrlDecoder().decode(nonceEncodedSig);
byte[] nonceData = base64UrlDecode(nonce);
byte[] nonceSig = base64UrlDecode(nonceEncodedSig);
byte[] seedNonceSig = fromSeed.sign(nonceData);
String encodedSeedNonceSig = Base64.getUrlEncoder().withoutPadding().encodeToString(seedNonceSig);
String encodedSeedNonceSig = base64UrlEncodeToString(seedNonceSig);

assertTrue(Arrays.equals(seedNonceSig, nonceSig));
assertArrayEquals(seedNonceSig, nonceSig);
assertEquals(nonceEncodedSig, encodedSeedNonceSig);

assertTrue(fromSeed.verify(nonceData, nonceSig));
Expand All @@ -533,10 +531,10 @@ public void testInterop() throws Exception {
assertTrue(fromPublicKey.verify(nonceData, seedNonceSig));

byte[] seedSig = fromSeed.sign(data);
byte[] sig = Base64.getUrlDecoder().decode(encodedSig);
String encodedSeedSig = Base64.getUrlEncoder().withoutPadding().encodeToString(seedSig);
byte[] sig = base64UrlDecode(encodedSig);
String encodedSeedSig = base64UrlEncodeToString(seedSig);

assertTrue(Arrays.equals(seedSig, sig));
assertArrayEquals(seedSig, sig);
assertEquals(encodedSig, encodedSeedSig);

assertTrue(fromSeed.verify(data, sig));
Expand All @@ -545,13 +543,13 @@ public void testInterop() throws Exception {
assertTrue(fromPublicKey.verify(data, seedSig));

// Make sure generation is the same
assertTrue(Arrays.equals(fromSeed.getSeed(), seed));
assertTrue(Arrays.equals(fromSeed.getPublicKey(), publicKey));
assertTrue(Arrays.equals(fromSeed.getPrivateKey(), privateKey));
assertArrayEquals(fromSeed.getSeed(), seed);
assertArrayEquals(fromSeed.getPublicKey(), publicKey);
assertArrayEquals(fromSeed.getPrivateKey(), privateKey);

DecodedSeed decoded = NKey.decodeSeed(seed);
char[] encodedSeed = NKey.encodeSeed(NKey.Type.fromPrefix(decoded.prefix), decoded.bytes);
assertTrue(Arrays.equals(encodedSeed, seed));
assertArrayEquals(encodedSeed, seed);
}

@Test
Expand Down
Loading
Loading