Skip to content

Commit ced1891

Browse files
committed
refactor: HMAC implementation linter issue fixed
1 parent 8bce401 commit ced1891

5 files changed

Lines changed: 318 additions & 161 deletions

File tree

src/main/java/io/apimatic/core/security/DigestCodec.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ public interface DigestCodec {
1919
* @return The decoded byte array.
2020
*/
2121
byte[] decode(String encoded);
22-
}
22+
}

src/main/java/io/apimatic/core/security/DigestCodecFactory.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,33 @@ private DigestCodecFactory() { } // Prevent instantiation
1111

1212
/**
1313
* Creates a Hex codec.
14+
* @return a DigestCodec for Hex encoding/decoding
1415
*/
1516
public static DigestCodec hex() {
1617
return new HexDigestCodec();
1718
}
1819

1920
/**
2021
* Creates a Base64 codec.
22+
* @return a DigestCodec for Base64 encoding/decoding
2123
*/
2224
public static DigestCodec base64() {
2325
return new Base64DigestCodec();
2426
}
2527

2628
/**
2729
* Creates a Base64Url codec.
30+
* @return a DigestCodec for Base64Url encoding/decoding
2831
*/
2932
public static DigestCodec base64Url() {
3033
return new Base64UrlDigestCodec();
3134
}
3235

36+
private static final int HEX_RADIX = 16;
37+
private static final int HEX_BYTE_MASK = 0xff;
38+
private static final int HEX_BYTE_LENGTH = 2;
39+
private static final int HEX_SHIFT = 4;
40+
3341
/**
3442
* Codec for Hex encoding/decoding.
3543
*/
@@ -38,21 +46,21 @@ private static class HexDigestCodec implements DigestCodec {
3846
public String encode(byte[] bytes) {
3947
StringBuilder sb = new StringBuilder();
4048
for (byte b : bytes) {
41-
sb.append(String.format("%02x", b & 0xff));
49+
sb.append(String.format("%02x", b & HEX_BYTE_MASK));
4250
}
4351
return sb.toString();
4452
}
4553

4654
@Override
4755
public byte[] decode(String encoded) {
4856
int len = encoded.length();
49-
if (len % 2 != 0) {
57+
if (len % HEX_BYTE_LENGTH != 0) {
5058
throw new IllegalArgumentException("Invalid hex string length.");
5159
}
52-
byte[] result = new byte[len / 2];
53-
for (int i = 0; i < len; i += 2) {
54-
result[i / 2] = (byte) ((Character.digit(encoded.charAt(i), 16) << 4)
55-
+ Character.digit(encoded.charAt(i + 1), 16));
60+
byte[] result = new byte[len / HEX_BYTE_LENGTH];
61+
for (int i = 0; i < len; i += HEX_BYTE_LENGTH) {
62+
result[i / HEX_BYTE_LENGTH] = (byte) ((Character.digit(encoded.charAt(i), HEX_RADIX) << HEX_SHIFT)
63+
+ Character.digit(encoded.charAt(i + 1), HEX_RADIX));
5664
}
5765
return result;
5866
}

src/main/java/io/apimatic/core/security/HmacSignatureVerifier.java

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ public class HmacSignatureVerifier implements SignatureVerifier {
5555
* @param signatureValueTemplate Template for signature format.
5656
*/
5757
public HmacSignatureVerifier(
58-
String secretKey,
59-
String signatureHeaderName,
60-
DigestCodec digestCodec,
61-
Function<Request, byte[]> requestBytesResolver,
62-
String algorithm,
63-
String signatureValueTemplate
58+
final String secretKey,
59+
final String signatureHeaderName,
60+
final DigestCodec digestCodec,
61+
final Function<Request, byte[]> requestBytesResolver,
62+
final String algorithm,
63+
final String signatureValueTemplate
6464
) {
6565

6666
if (secretKey == null || secretKey.trim().isEmpty()) {
@@ -73,7 +73,8 @@ public HmacSignatureVerifier(
7373
throw new IllegalArgumentException("Signature value template cannot be null or Empty.");
7474
}
7575
if (requestBytesResolver == null) {
76-
throw new IllegalArgumentException("Request signature template resolver function cannot be null.");
76+
throw new IllegalArgumentException(
77+
"Request signature template resolver function cannot be null.");
7778
}
7879
if (digestCodec == null) {
7980
throw new IllegalArgumentException("Digest encoding cannot be null.");
@@ -92,22 +93,30 @@ public HmacSignatureVerifier(
9293

9394
/**
9495
* Verifies the HMAC signature of the specified HTTP request.
96+
*
97+
* @param request The HTTP request to verify.
98+
* @return A CompletableFuture containing the verification result.
9599
*/
96100
@Override
97-
public CompletableFuture<VerificationResult> verifyAsync(Request request) {
101+
public CompletableFuture<VerificationResult> verifyAsync(final Request request) {
98102
return CompletableFuture.supplyAsync(() -> {
99103
try {
100104
String headerValue = request.getHeaders().asSimpleMap().entrySet().stream()
101-
.filter(e -> e.getKey() != null && e.getKey().equalsIgnoreCase(signatureHeaderName))
102-
.map(Map.Entry::getValue).findFirst().orElse(null);
105+
.filter(e -> e.getKey() != null
106+
&& e.getKey().equalsIgnoreCase(signatureHeaderName))
107+
.map(Map.Entry::getValue)
108+
.findFirst()
109+
.orElse(null);
103110

104111
if (headerValue == null) {
105-
return VerificationResult.failure("Signature header '" + signatureHeaderName + "' is missing.");
112+
return VerificationResult.failure(
113+
"Signature header '" + signatureHeaderName + "' is missing.");
106114
}
107115

108116
byte[] provided = extractSignature(headerValue);
109117
if (provided == null) {
110-
return VerificationResult.failure("Malformed signature header '" + signatureHeaderName + "'.");
118+
return VerificationResult.failure(
119+
"Malformed signature header '" + signatureHeaderName + "'.");
111120
}
112121

113122
byte[] message = requestBytesResolver.apply(request);
@@ -126,20 +135,24 @@ public CompletableFuture<VerificationResult> verifyAsync(Request request) {
126135
}
127136

128137
/**
129-
* Extracts the digest value from the signature header according to the template.
130-
* And decode the signature from the header value.
138+
* Extracts the digest value from the signature header according to the template
139+
* and decodes the signature from the header value.
140+
*
141+
* @param headerValue The value of the signature header.
142+
* @return The decoded signature as a byte array, or null if extraction fails.
131143
*/
132-
private byte[] extractSignature(String headerValue) {
144+
private byte[] extractSignature(final String headerValue) {
133145
try {
134146
int index = signatureValueTemplate.indexOf(SIGNATURE_VALUE_PLACEHOLDER);
135-
if (index < 0) return null;
147+
if (index < 0) { return null; }
136148

137149
String prefix = signatureValueTemplate.substring(0, index);
138-
String suffix = signatureValueTemplate.substring(index + SIGNATURE_VALUE_PLACEHOLDER.length());
150+
String suffix = signatureValueTemplate.substring(
151+
index + SIGNATURE_VALUE_PLACEHOLDER.length());
139152

140153
// find prefix anywhere (case-insensitive)
141154
int prefixAt = indexOfIgnoreCase(headerValue, prefix, 0);
142-
if (prefixAt < 0) return null;
155+
if (prefixAt < 0) { return null; }
143156

144157
int digestStart = prefixAt + prefix.length();
145158

@@ -149,14 +162,15 @@ private byte[] extractSignature(String headerValue) {
149162
digestEnd = headerValue.length();
150163
} else {
151164
digestEnd = indexOfIgnoreCase(headerValue, suffix, digestStart);
152-
if (digestEnd < 0) return null;
165+
if (digestEnd < 0) { return null; }
153166
}
154167

155-
if (digestEnd < digestStart) return null;
168+
if (digestEnd < digestStart) { return null; }
156169

157170
String digest = headerValue.substring(digestStart, digestEnd).trim();
158171
// strip Optional Quotes
159-
if (digest.length() >= 2 && digest.charAt(0) == '"' && digest.charAt(digest.length() - 1) == '"') {
172+
if (digest.length() >= 2 && digest.charAt(0) == '"'
173+
&& digest.charAt(digest.length() - 1) == '"') {
160174
digest = digest.substring(1, digest.length() - 1);
161175
}
162176

@@ -168,13 +182,25 @@ private byte[] extractSignature(String headerValue) {
168182
}
169183

170184
/**
171-
* Finds the index of the first case-insensitive occurrence of {@code needle} in {@code haystack} starting from {@code fromIndex}, or -1 if not found.
185+
* Finds the index of the first case-insensitive occurrence of {@code needle} in {@code haystack}
186+
* starting from {@code fromIndex}, or -1 if not found.
187+
*
188+
* @param haystack The string to search in.
189+
* @param needle The substring to search for.
190+
* @param fromIndex The index to start searching from.
191+
* @return The index of the first occurrence, or -1 if not found.
172192
*/
173-
private static int indexOfIgnoreCase(String haystack, String needle, int fromIndex) {
174-
if (needle.isEmpty()) return fromIndex;
193+
private static int indexOfIgnoreCase(
194+
final String haystack,
195+
final String needle,
196+
final int fromIndex
197+
) {
198+
if (needle.isEmpty()) { return fromIndex; }
175199
int max = haystack.length() - needle.length();
176200
for (int i = Math.max(0, fromIndex); i <= max; i++) {
177-
if (haystack.regionMatches(true, i, needle, 0, needle.length())) return i;
201+
if (haystack.regionMatches(true, i, needle, 0, needle.length())) {
202+
return i;
203+
}
178204
}
179205
return -1;
180206
}

src/test/java/apimatic/core/security/DigestCodecFactoryTest.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,31 @@
1414
@RunWith(MockitoJUnitRunner.class)
1515
public class DigestCodecFactoryTest {
1616

17+
/** Rule for expecting exceptions in tests. */
1718
@Rule
1819
public ExpectedException thrown = ExpectedException.none();
1920

21+
private static final byte HEX_BYTE_1 = 0x0A;
22+
private static final byte HEX_BYTE_2 = 0x1B;
23+
private static final byte HEX_BYTE_3 = (byte) 0xFF;
24+
25+
private static final byte[] BASE64_INPUT = {1, 2, 3, 4, 5};
26+
private static final byte BASE64_BYTE_1 = 1;
27+
private static final byte BASE64_BYTE_2 = 2;
28+
private static final byte BASE64_BYTE_3 = 3;
29+
private static final byte BASE64_BYTE_4 = 4;
30+
private static final byte BASE64_BYTE_5 = 5;
31+
32+
private static final byte BASE64URL_BYTE_1 = 10;
33+
private static final byte BASE64URL_BYTE_2 = 20;
34+
private static final byte BASE64URL_BYTE_3 = 30;
35+
private static final byte BASE64URL_BYTE_4 = 40;
36+
private static final byte BASE64URL_BYTE_5 = 50;
37+
2038
@Test
2139
public void testHexEncodeDecode() {
2240
DigestCodec codec = DigestCodecFactory.hex();
23-
byte[] input = {0x0A, 0x1B, (byte)0xFF};
41+
byte[] input = {HEX_BYTE_1, HEX_BYTE_2, HEX_BYTE_3};
2442
String encoded = codec.encode(input);
2543
assertEquals("0a1bff", encoded);
2644
assertArrayEquals(input, codec.decode(encoded));
@@ -52,7 +70,7 @@ public void testHexDecodeInvalidCharacter() {
5270
@Test
5371
public void testBase64EncodeDecode() {
5472
DigestCodec codec = DigestCodecFactory.base64();
55-
byte[] input = {1, 2, 3, 4, 5};
73+
byte[] input = {BASE64_BYTE_1, BASE64_BYTE_2, BASE64_BYTE_3, BASE64_BYTE_4, BASE64_BYTE_5};
5674
String encoded = codec.encode(input);
5775
assertEquals("AQIDBAU=", encoded);
5876
assertArrayEquals(input, codec.decode(encoded));
@@ -77,7 +95,7 @@ public void testBase64DecodeInvalid() {
7795
@Test
7896
public void testBase64UrlEncodeDecode() {
7997
DigestCodec codec = DigestCodecFactory.base64Url();
80-
byte[] input = {10, 20, 30, 40, 50};
98+
byte[] input = {BASE64URL_BYTE_1, BASE64URL_BYTE_2, BASE64URL_BYTE_3, BASE64URL_BYTE_4, BASE64URL_BYTE_5};
8199
String encoded = codec.encode(input);
82100
assertEquals("ChQeKDI", encoded); // without padding
83101
assertArrayEquals(input, codec.decode(encoded));

0 commit comments

Comments
 (0)