Skip to content

Commit

Permalink
Refactor Encoding util for easier replacement of Base64 encoding class (
Browse files Browse the repository at this point in the history
  • Loading branch information
scottf authored May 6, 2024
1 parent 1907a43 commit 481191a
Show file tree
Hide file tree
Showing 24 changed files with 304 additions and 100 deletions.
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

0 comments on commit 481191a

Please sign in to comment.