Skip to content

Commit 0288533

Browse files
authored
Add new Checksum Algorithms for data integrity validation (#161)
1 parent 973a97b commit 0288533

16 files changed

Lines changed: 487 additions & 29 deletions

File tree

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@
6363
<scope>provided</scope> <!-- lombok is not needed at runtime -->
6464
</dependency>
6565

66+
<dependency>
67+
<groupId>software.amazon.awssdk.crt</groupId>
68+
<artifactId>aws-crt</artifactId>
69+
<version>0.39.4</version>
70+
</dependency>
71+
72+
6673
<!-- Test Dependencies -->
6774
<dependency>
6875
<groupId>ch.qos.logback</groupId>

src/main/java/io/github/jpmorganchase/fusion/api/request/PartFetcher.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import static io.github.jpmorganchase.fusion.api.tools.ResponseChecker.checkResponseStatus;
44

5+
import io.github.jpmorganchase.fusion.FusionConfiguration;
56
import io.github.jpmorganchase.fusion.api.response.GetPartResponse;
67
import io.github.jpmorganchase.fusion.api.response.Head;
78
import io.github.jpmorganchase.fusion.api.stream.IntegrityCheckingInputStream;
9+
import io.github.jpmorganchase.fusion.digest.PartChecker;
810
import io.github.jpmorganchase.fusion.http.Client;
911
import io.github.jpmorganchase.fusion.http.HttpResponse;
1012
import io.github.jpmorganchase.fusion.oauth.provider.FusionTokenProvider;
@@ -28,6 +30,7 @@ public class PartFetcher {
2830

2931
Client client;
3032
FusionTokenProvider credentials;
33+
FusionConfiguration configuration;
3134

3235
/**
3336
* Makes a call to get the part corresponding to the part number in the {@link PartRequest}.
@@ -55,9 +58,15 @@ private IntegrityCheckingInputStream getIntegrityCheckingInputStream(
5558
return IntegrityCheckingInputStream.builder()
5659
.part(response.getBody())
5760
.checksum(head.getChecksum())
61+
.partChecker(
62+
PartChecker.builder().digestAlgo(getDigestAlgo(head)).build())
5863
.build();
5964
}
6065

66+
private String getDigestAlgo(Head head) {
67+
return head.getChecksumAlgorithm() != null ? head.getChecksumAlgorithm() : configuration.getDigestAlgorithm();
68+
}
69+
6170
private Head getHead(HttpResponse<InputStream> response, PartRequest pr) {
6271
if (pr.isHeadRequired()) {
6372
return Head.builder().fromHeaders(response.getHeaders()).build();

src/main/java/io/github/jpmorganchase/fusion/api/response/Head.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@
1818
@EqualsAndHashCode
1919
public class Head {
2020

21-
private static String CHECKSUM_HEADER = "x-jpmc-checksum-sha256";
21+
private static String CHECKSUM_HEADER = "x-jpmc-checksum";
22+
private static String CHECKSUM_SHA256_HEADER = "x-jpmc-checksum-sha256";
2223
private static String VERSION_HEADER = "x-jpmc-version-id";
2324
private static String PART_COUNT_HEADER = "x-jpmc-mp-parts-count";
2425
private static String CONTENT_LENGTH_HEADER = "Content-Length";
2526
private static String CONTENT_RANGE_HEADER = "Content-Range";
2627
private static String CHECKSUM_SEPARATOR = "-";
28+
private static String DEFAULT_ALGORITHM = "SHA-256";
29+
private static String CHECKSUM_ALGO_HEADER = "x-jpmc-checksum-algorithm";
2730

2831
private String version;
2932
private String checksum;
33+
private String checksumAlgorithm;
3034
private int partCount;
3135
private long contentLength;
3236
private ContentRange contentRange;
@@ -58,10 +62,11 @@ public Head build() {
5862
handlePartCountHeader();
5963
handleChecksumHeader();
6064
handleVersionHeader();
65+
handleChecksumAlgorithmHeader();
6166
handleContentLength();
6267
handleContentRangeHeader();
6368
}
64-
return new Head(version, checksum, partCount, contentLength, contentRange, isMultipart);
69+
return new Head(version, checksum, checksumAlgorithm, partCount, contentLength, contentRange, isMultipart);
6570
}
6671

6772
private void handleContentLength() {
@@ -88,6 +93,22 @@ private void handleChecksumHeader() {
8893
if (!values.isEmpty() && nonNull(values.get(0))) {
8994
this.checksum = values.get(0).split(CHECKSUM_SEPARATOR)[0];
9095
}
96+
} else {
97+
List<String> values = this.headers.get(CHECKSUM_SHA256_HEADER);
98+
if (nonNull(values) && !values.isEmpty() && nonNull(values.get(0))) {
99+
this.checksum = values.get(0).split(CHECKSUM_SEPARATOR)[0];
100+
}
101+
}
102+
}
103+
104+
private void handleChecksumAlgorithmHeader() {
105+
if (this.headers.containsKey(CHECKSUM_ALGO_HEADER)) {
106+
List<String> values = this.headers.get(CHECKSUM_ALGO_HEADER);
107+
if (!values.isEmpty() && nonNull(values.get(0))) {
108+
this.checksumAlgorithm = values.get(0);
109+
}
110+
} else {
111+
this.checksumAlgorithm = DEFAULT_ALGORITHM;
91112
}
92113
}
93114

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package io.github.jpmorganchase.fusion.digest;
22

3+
import io.github.jpmorganchase.fusion.digest.checksum.DigestProvider;
4+
import io.github.jpmorganchase.fusion.digest.checksum.DigestProviderService;
35
import java.io.IOException;
46
import java.security.MessageDigest;
5-
import java.security.NoSuchAlgorithmException;
6-
import java.util.Base64;
77
import java.util.Objects;
88
import lombok.Builder;
99
import lombok.Getter;
@@ -20,44 +20,47 @@ public class PartChecker {
2020

2121
MessageDigest digest;
2222

23-
String digestAlgo;
23+
private DigestProvider digestProvider;
24+
private String digestAlgo;
25+
private DigestProviderService digestProviderService;
2426

25-
public PartChecker(String digestAlgo) {
27+
public PartChecker(String digestAlgo, DigestProviderService digestProviderService) {
2628
this.digestAlgo = digestAlgo;
29+
this.digestProviderService = digestProviderService;
2730
}
2831

2932
public void update(int bytesRead) throws IOException {
30-
if (Objects.isNull(digest)) {
33+
if (Objects.isNull(digestProvider)) {
3134
init();
3235
}
33-
digest.update(Integer.valueOf(bytesRead).byteValue());
36+
digestProvider.update(Integer.valueOf(bytesRead).byteValue());
3437
}
3538

36-
public void verify(String checksum) throws IOException {
37-
String encodedDigest = Base64.getEncoder().encodeToString(digest.digest());
38-
if (!Objects.isNull(checksum) && !checksum.equals(encodedDigest)) {
39+
public void verify(String expectedChecksum) throws IOException {
40+
String calculatedChecksum = digestProvider.getDigest();
41+
42+
if (!Objects.equals(expectedChecksum, calculatedChecksum)) {
3943
log.error(
4044
"Corrupted stream encountered, failed to verify checksum [{}] against calculated checksum [{}]",
41-
checksum,
42-
encodedDigest);
45+
expectedChecksum,
46+
calculatedChecksum);
4347
throw new IOException("Corrupted stream, verification of checksum failed");
4448
}
4549
}
4650

4751
private void init() throws IOException {
48-
try {
49-
digest = MessageDigest.getInstance(digestAlgo);
50-
} catch (NoSuchAlgorithmException e) {
51-
throw new IOException("Invalid digest algorithm provided", e);
52-
}
52+
digestProvider = digestProviderService.getDigestProvider(digestAlgo);
5353
}
5454

5555
public static class PartCheckerBuilder {
56+
57+
DigestProviderService digestProviderService = new DigestProviderService();
58+
5659
public PartChecker build() {
5760
if (digestAlgo == null) {
5861
digestAlgo = DEFAULT_DIGEST_ALGO;
5962
}
60-
return new PartChecker(digestAlgo);
63+
return new PartChecker(digestAlgo, digestProviderService);
6164
}
6265
}
6366
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.github.jpmorganchase.fusion.digest.checksum;
2+
3+
import java.util.Base64;
4+
import java.util.zip.Checksum;
5+
import software.amazon.awssdk.crt.checksums.CRC32C;
6+
7+
public class CRC32CProvider implements DigestProvider {
8+
9+
private final Checksum checksum;
10+
11+
public CRC32CProvider() {
12+
checksum = new CRC32C();
13+
}
14+
15+
@Override
16+
public void update(byte singleByte) {
17+
checksum.update(singleByte);
18+
}
19+
20+
@Override
21+
public String getDigest() {
22+
long checksumValue = checksum.getValue();
23+
return Base64.getEncoder().encodeToString(longToBytes(checksumValue));
24+
}
25+
26+
private byte[] longToBytes(long value) {
27+
return new byte[] {(byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value};
28+
}
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.github.jpmorganchase.fusion.digest.checksum;
2+
3+
import java.util.Base64;
4+
import java.util.zip.Checksum;
5+
import software.amazon.awssdk.crt.checksums.CRC32;
6+
7+
public class CRC32Provider implements DigestProvider {
8+
9+
private final Checksum checksum;
10+
11+
public CRC32Provider() {
12+
checksum = new CRC32();
13+
}
14+
15+
@Override
16+
public void update(byte singleByte) {
17+
checksum.update(singleByte);
18+
}
19+
20+
@Override
21+
public String getDigest() {
22+
long checksumValue = checksum.getValue();
23+
24+
return Base64.getEncoder().encodeToString(longToBytes(checksumValue));
25+
}
26+
27+
private byte[] longToBytes(long value) {
28+
return new byte[] {(byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value};
29+
}
30+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.github.jpmorganchase.fusion.digest.checksum;
2+
3+
import java.util.Base64;
4+
import java.util.zip.Checksum;
5+
import software.amazon.awssdk.crt.checksums.CRC64NVME;
6+
7+
public class CRC64NVMEProvider implements DigestProvider {
8+
9+
private final Checksum checksum;
10+
11+
public CRC64NVMEProvider() {
12+
this.checksum = new CRC64NVME();
13+
}
14+
15+
@Override
16+
public void update(byte singleByte) {
17+
checksum.update(singleByte);
18+
}
19+
20+
@Override
21+
public String getDigest() {
22+
long checksumValue = checksum.getValue();
23+
return Base64.getEncoder().encodeToString(longToBytes(checksumValue));
24+
}
25+
26+
private byte[] longToBytes(long value) {
27+
return new byte[] {
28+
(byte) (value >>> 56),
29+
(byte) (value >>> 48),
30+
(byte) (value >>> 40),
31+
(byte) (value >>> 32),
32+
(byte) (value >>> 24),
33+
(byte) (value >>> 16),
34+
(byte) (value >>> 8),
35+
(byte) value
36+
};
37+
}
38+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.github.jpmorganchase.fusion.digest.checksum;
2+
3+
import java.io.IOException;
4+
5+
public interface DigestProvider {
6+
7+
void update(byte singleByte);
8+
9+
String getDigest() throws IOException;
10+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.github.jpmorganchase.fusion.digest.checksum;
2+
3+
import java.io.IOException;
4+
import java.security.NoSuchAlgorithmException;
5+
6+
public class DigestProviderService {
7+
8+
public DigestProvider getDigestProvider(String digestAlgo) throws IOException {
9+
try {
10+
switch (digestAlgo) {
11+
case "CRC32":
12+
return new CRC32Provider();
13+
case "CRC32C":
14+
return new CRC32CProvider();
15+
case "CRC64NVME":
16+
return new CRC64NVMEProvider();
17+
case "SHA-1":
18+
case "SHA-256":
19+
case "MD5":
20+
return new SHAProvider(digestAlgo);
21+
default:
22+
throw new IOException("Invalid digest algorithm provided");
23+
}
24+
} catch (NoSuchAlgorithmException e) {
25+
throw new IOException("Invalid digest algorithm provided", e);
26+
}
27+
}
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.github.jpmorganchase.fusion.digest.checksum;
2+
3+
import java.security.MessageDigest;
4+
import java.security.NoSuchAlgorithmException;
5+
import java.util.Base64;
6+
7+
public class SHAProvider implements DigestProvider {
8+
9+
private final MessageDigest digest;
10+
11+
public SHAProvider(String algorithm) throws NoSuchAlgorithmException {
12+
this.digest = MessageDigest.getInstance(algorithm);
13+
}
14+
15+
@Override
16+
public void update(byte singleByte) {
17+
digest.update(singleByte);
18+
}
19+
20+
@Override
21+
public String getDigest() {
22+
return Base64.getEncoder().encodeToString(digest.digest());
23+
}
24+
}

0 commit comments

Comments
 (0)